Powered by ACTIVECYBER, LLC
Powered by ACTIVECYBER, LLC
CORSAIR is considered one of the world’s leading providers for high-performance PC peripherals and components. It offers a complete range of products to equip gamers and content creators, including keyboards, mice, headsets, capture cards, studio controllers, etc. While researching the interface software that is CORSAIR iCUE v3.23.66 (the latest at the time of research), we’ve found that said software installs a driver that will allow low privileged users to map an arbitrary physical memory which leads to local privilege escalation.
We start off by examining the device permissions and can see the device driver is writeable by Everyone group, meaning any low privileged process can interact with said driver by sending IOCTL codes.
Next, we reverse engineer the device driver to see what IOCTL codes it exposes and if there are any interesting code paths we can hit. The following is the CorsairLLAccess64.sys driver routine for device I/O control requests.
Once we located the routine that handles IOCTL control codes, we searched for interesting code paths and ultimately found IOCTL control code 0x225374 which is the sum of (0x225348 + 0x10 + 0x1C) as shown below.
Investigating this branch, we notice a call to IoGetRequestorProcessId() function that returns the unique 32-bit process ID for the call thread in EAX.
Followed by a call to the problematic function, that is FUN_1400013e0 which takes six arguments as an input. With the help of dynamic analysis tools, we communicate with the driver via DeviceIoControl() function call and then we use the comments section to record a few arguments used by said function that were interesting to us.
Now entering this function call, we noticed there are a few checks that we need to fail, otherwise we will end up with 0xc0000023 status code (i.e. STATUS_BUFFER_TOO_SMALL). In essence, we need to make sure the input buffer size is equal to or greater than 0xC and the output buffer size is equal to or greater than 0x8 in DeviceIoControl() function call.
Once the above conditions are met, we reach MmMapIoSpace() function call which maps a given physical address range to nonpaged system space where we control the PhysicalAddress and NumberOfBytes parameters via the input buffer parameter in DeviceIoControl() function call.
Followed by a number of function calls that make sure to properly map the physical memory pages onto the calling process virtual address space. Please note the description below was taken from MSDN documentation.
In summary, we can map an arbitrary physical memory from low privileged processes which effectively leads to elevation of privileges. In our proof-of-concept exploit, we located the EPROCESS structures (i.e. the kernel’s representation of a process object) of the LSASS process which runs in SYSTEM context and the PowerShell process within the mapped physical memory range and ultimately performed a token stealing attack to achieve privilege escalation.
Please note that the above exploit was based heavily on prior work done by Ruben Boonen in the Razer driver bug found here. At that point, we sent the vulnerability details to the vendor and a patch was shared with us in version 3.25.54. Running the proof-of-concept exploit against the patched driver shows we’re no longer able to obtain a handle to the driver from a non-privileged process and, moreover, we receive random error codes every time we run it.
Inspecting the patched driver, we quickly notice the addition of SeQueryInformationToken() and ExFreePoolWithTag() function calls.
Following SeQueryInformationToken() references, we land in function FUN_1400010d0 as shown below where there are two calls to said function that presumably check whether the calling process token is elevated.
Let’s break on the first call inside WinDbg to confirm our assumptions by inspecting the R8 register (i.e. *TokenInformation parameter) which should receive a pointer to a location that contains the address of a buffer that holds the requested information, that is TOKEN_ELEVATION structure (offset 0x14 in Token_Information_Class enumeration type).
The TOKEN_ELEVATION structure has one DWORD member called TokenIsElevated indicating whether a given token has elevated privileges.
Now running the proof-of-concept exploit from a non-elevated process returns zero value, whereas running it from an elevated process return nonzero value indicating that the token has elevated privileges.
It’s worth mentioning in the case of a non-elevated process, we reach the below section where a number of operations are performed to obfuscate 0xc0000022 status code (i.e. STATUS_ACCESS_DENIED) which explains the random error codes we got earlier.
In essence, when requesting a handle for the patched driver, this check is performed to limit access only to elevated processes (high-integrity level), meaning only those with administrative rights have access to the driver which effectively nullifies the reported vulnerability. Lastly, feel free to reach out to firstname.lastname@example.org if you have any questions. Also, see the link here for a complete list of ACTIVELabs advisories.
ACTIVELabs was created in 2018 to hunt and research undiscovered vulnerabilities, report them to vendors via responsible disclosure programs, publish advisories, develop and validate new patches, and to share this information for the advancement of the cybersecurity community. ACTIVELabs was established with the mission of securing our ever-growing client base, partnerships, and the technology community as a whole.
We are actively providing the community with verified findings and research that leads to the creation of new Common Vulnerabilities and Exposures (CVEs) and updates to the National Vulnerability Database (NVD). For a full listing of all of our Advisories, visit our GitHub page here.