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