A more powerful technique for bypassing the certificate requirement is by exploiting a digitally signed but vulnerable driver. A driver that can be loaded onto the system since it’s signed, but also includes a security vulnerability that allows for exploitation from user-mode. Capcom.sys is a perfect example of this, the driver is digitally signed, but it also includes a vulnerability in which it executes user-mode provided pointers directly into kernel-mode. This type of vulnerability is commonly abused for privilege escalation, but in the case of post-exploitation and loading rootkits, it can be used to disable KMCS on the system directly (using older techniques like the Turla technique by setting
ntoskrnl!g_CiEnable to false or by mapping a driver (rootkit) directly into kernel space.
Popular tools such as https://github.com/hfiref0x/DSEFix do just this, it abuses a vulnerable VirtualBox driver to overwrite the global system variable
ntoskrnl!g_CiEnable which disables DSE, in this specific instance, this technique is now obsolete, but the concept remains.
After loading the Capcom.sys driver onto a system (using a tool like OSR’s DriverLoader or using the SCM) it creates a device name called
\Device\Htsysm72FB which is accessible by anyone on the system, permissions wise, should be restricted to only members of the Administrators group. Luckily, from an exploitation perspective, the driver is accessible to anyone with full read/write/delete/all access permissions.
Using WinDBG to view the loaded modules on the system shows how Capcom.sys was loaded from a non-standard directory, this is typically a good indicator to look out for when performing system forensics during an incidence response session, in this case, the driver was loaded for vulnerability research purposes, but in a non-research context, looking for drivers that load from strange or temporary directories is something to look out for.
Looking at the exports for the driver show fairly standard functions, the functions being exported are mainly related to driver device creation and creating symbolic links to expose to user-mode, this is a good sign when analyzing potentially vulnerable drivers, you want to ensure it includes some aspect of IO functionality so you can actually communicate and exploit it.
Drivers typically include a function called DriverEntry as their main, first function of execution. In Capcoms case,
RtlInitUnicodeString is called to initialize the driver name,
v5 is the same device name seen earlier in DeviceTree
Htsysm72FB, this comes from a deobfuscation function which the driver uses to deobfuscate an obfuscated instance of
Htsysm72FB, for a driver, this behavior is slightly suspicious. The,
IoCreateDevice is called to create the device object.
This is followed up with a second call to
RtlInitUnicodeString to initialize
\\DosDevices\Htsysm72FB. And finally, IoCreateSymbolicLink is called to create a symbolic link between the two names, this symbolic link is then exposed to user-mode so a user application can open a handle to the driver. This symbolic link creation is followed up by the established
MajorFunction table, within this table are the various IRP routines that the driver supports (IRP_MJ_DEVICE_CONTROL, etc). First, The IRP routines of
IRP_MJ_CLOSE are set with the function
DispatchCreateClose, this function is responsible for allowing a handle to be opened and closed with the driver. After this, the IRP routine
IRP_MJ_DEVICE_CONTROL is set with the function
DispatchDeviceConto, this is one of the most important functions since it’s responsible for handling the inbound IO communications with the driver, including IRP’s/IOCTLs that are sent from user-mode. Finally, the driver’s unload function is defined, so if the driver is prompted to be unloaded from the system, it can do so.
Drivers have the ability to merge their DispatchCreate DispatchClose routines into a single routine, typically this is referred to as DispatchCreateClose. To establish their existence on a system when a create request is issued from user-mode
CreateFileA for example when opening a handle to the driver, the driver will include a create routine. The same logic applies to a close request.
For Capcom.sys, it includes a combined create and close routine called DispatchCreateClose. Within the function, first, a pointer to the current I/O stack location in the IRP is obtained through
Irp->Tail.Overlay.CurrentStackLocation. Then the
Status member of the I/O status block is set to STATUS_SUCESS. A check is performed to see if the
MajorFunction member from
_IO_STACK_LOCATION is equal to 2, if the check fails, the
Status member is set to
0xC0000002 which indicates a
Otherwise, the function calls
IofCompleteRequest to complete the IRP request and return the IRP to the I/O Manager. And finally, the
Status member which was previously set to STATUS_SUCCESS is returned from the function.
The IRP_MJ_DEVICE_CONTROL routine points to the
DispatchDeviceControl function, this is the function that is responsible for handling inbound requests from a user-mode application.
The function starts by setting the IRP’s current stack location to a variable, setting the
Status member, and defining the input and output buffer variables along with the IOCTL control code variable, these are all set using members from
IrpStackCurrentLocation->Parameters. Now, the function defines a set of requirements for input and output buffers if the user-mode client wants to successfully communicate with the driver.
0xAA012044should be used within
DeviceIoControlalong with a required input and output buffer size of 4
0xAA013044should be used within
DeviceIoControlalong with a required input buffer size of 8 and an output buffer size of 4
Now the user provided input buffer is passed through the
ExecuteUserSpaceCode function, which leads to the “vulnerability” within this driver.
ExecuteUserSpaceCode provides more information about what happens with the inbound data buffers passed by the user.
inputBufferis reassigned to a new variable (user_space_passed_data.
DisableSmepis called with the value of
old_cr4_valuebeing passed to it, this function is responsible for disabling SMEP temporarily
Now, we can see the execution flow we need to hit when writing our exploit, to have the provided input buffer executed in kernel-mode (with the function call at capcom.sys+573) we need to go through passing our input buffer to the driver via a call to DeviceIoControl, which will be handled by the DispatchDeviceControl function, if the proper IOCTL is passed (0xAA013044 since we are exploiting this driver on a 64-bit version of Windows), the user-provided input buffer will eventually reach
capcom.sys+613 which passes the input buffer to be executed (as seen above in the ExecuteUserSpaceCode pseudocode).
CreateFileAfor establishing a handle to the driver’s file object that it creates when calling
CreateFileA("\\\\.\\Htsysm72FB", 0, 0, NULL, OPEN_EXISTING, 0, NULL)
DeviceIoControlis used by providing the handle returned from
CreateFileAto send the driver an IOCTL, input, and output buffer, the buffer sizes
0xAA013044as the IOCTL code.
DeviceIoControl(hDevice, 0xAA013044, &inputBuffer, 8, &outputBuffer, 4, &sizeReturn, NULL)