AV/EDR 免杀逃避技术汇总

这篇文章重在分享bybass的方法,循序渐进分为12种方案。

并给出了详细的参考项目和一些案例,这些都是经过实战对抗检测的。

目录

1.shellcode加密

2.降低程序的熵值

逃离本地沙箱

导入表混淆

禁用 ETW

避免“恶意API调用模式”

直接调用系统函数,规避“系统调用hook”

删除或覆盖ntdll里面的hook

欺骗线程调用堆栈(hook sleep)

beacon/shellcode 内存加密(读写权限)

反射加载

不使用RWX内存


更多内容可以关注公众号:TIPFactory情报工厂   获取更多其他对抗技术

AV/EDR 免杀逃避技术汇总_第1张图片

 

1.shellcode加密

从一个基本但重要的方法开始,静态 shellcode 混淆。在编写loader的时候一般利用XOR 或 RC4 加密算法,因为易于实现并且不会留下大量加载程序执行的加密IOC。比如用于混淆 shellcode 静态签名的 AES 加密会在二进制文件的导入地址表中留下线索, AES 解密函数(例如 CryptDecrypt、CryptHashData、CryptDeriveKey 等)以及被各家C端产品标记了。

AV/EDR 免杀逃避技术汇总_第2张图片

 

2.降低程序的熵值

任何 AV/EDR检测未知二进制时都会考虑二进制熵。加密 shellcode文件的熵相当高,这就表明二进制文件中的代码部分被混淆或者加密处理了。有几种方法可以减少二进制的熵:

  1. 资源添加一些熵值比较低的文件,例如图片。
  2. 添加字符串,如英文字典或一些无关紧要的字符串输出。比如:C:\Program Files\Google\Chrome\Application\100.0.4896.88\chrome.dll
  3. 一个更优雅的解决方案是设计和实现一种算法,将 shellcode 混淆(编码/加密)成英文单词(低熵)。后续会单独出一篇方案。

3.逃离本地沙箱

很多 EDR会在本地沙箱中运行二进制文件以检查其行为,但是都是几秒因为考虑用户体验。我们可以通过延迟执行 shellcode 来逃避这个限制。你可以随机计算一个大素数延迟执行或者其他操作。

4.导入表混淆

EDR都会有自己的一套API灰名单。比如 VirtualAlloc 、 VirtualProtect 、 WriteProcessMemory 、 CreateRemoteThread 、 SetThreadContext 等,可以从https://github.com/Mr-Un1k0d3r/EDRs/blob/main/EDRs.m获取一些参考。在大多数情况下,我们使用直接系统调用来绕过“可疑 WINAPI 调用”的两个 EDR  hook(参考第 7 节),但对于不太可疑的 API 调用,我们添加 WINAPI 调用的函数签名,在 ntdll.dll 中获取 WINAPI 的地址,然后创建指向该地址的函数指针(隐式调用):

typedef BOOL (WINAPI * pVirtualProtect)(LPVOID lpAddress, SIZE_T dwSize, DWORD
flNewProtect, PDWORD lpflOldProtect);
pVirtualProtect fnVirtualProtect;
unsigned char sVirtualProtect[] = {
'V','i','r','t','u','a','l','P','r','o','t','e','c','t', 0x0 };
unsigned char sKernel32[] = { 'k','e','r','n','e','l','3','2','.','d','l','l', 0x0 };
fnVirtualProtect = (pVirtualProtect) GetProcAddress(GetModuleHandle((LPCSTR)
sKernel32), (LPCSTR)sVirtualProtect);
// call VirtualProtect
fnVirtualProtect(address, dwSize, PAGE_READWRITE, &oldProt);

         这其实并没有什么大用,单纯抹掉IAT调用,对于一些win api hook无能为力,但是也可以增加静态提取关键字符检测的成本。

5.禁用 ETW

现代 EDR 广泛利用 Windows 事件跟踪 (ETW),特别是 Microsoft Defender for Endpoint(以前称为 Microsoft ATP)。 ETW 允许对进程功能, WINAPI 调用等行为进行广泛的检测和跟踪。 ETW 大量组件在内核中,主要用于为系统调用和其他内核操作注册回调,但也包含一个用户态组件,参考之前对ProcessMon的攻击文章。由于 ntdll.dll 是加载到我们的二进制进程中的 DLL,我们完全可以控制这个 DLL,从而控制 ETW 功能。用户空间中有很多不同的 ETW 绕过方法,最常见的一种是修补函数 EtwEventWrite,该函数被调用以写入/记录 ETW 事件。我们在 ntdll.dll 中获取它的地址,并将它的第一条指令替换为返回 0 (SUCCESS) 的指令,核心代码如下:

void disableETW(void) {
// return 0
unsigned char patch[] = { 0x48, 0x33, 0xc0, 0xc3}; // xor rax, rax; ret
ULONG oldprotect = 0;
size_t size = sizeof(patch);
HANDLE hCurrentProc = GetCurrentProcess();
unsigned char sEtwEventWrite[] = {
'E','t','w','E','v','e','n','t','W','r','i','t','e', 0x0 };
void *pEventWrite = GetProcAddress(GetModuleHandle((LPCSTR) sNtdll), (LPCSTR)
sEtwEventWrite);
NtProtectVirtualMemory(hCurrentProc, &pEventWrite, (PSIZE_T) &size,
PAGE_READWRITE, &oldprotect);
memcpy(pEventWrite, patch, size / sizeof(patch[0]));
NtProtectVirtualMemory(hCurrentProc, &pEventWrite, (PSIZE_T) &size,
oldprotect, &oldprotect);
FlushInstructionCache(hCurrentProc, pEventWrite, size);
}

6.避免“恶意API调用模式”

大多数行为检测最终都是基于检测恶意模式。其中一种模式是特定 WINAPI 在短时间内的调用顺序。第 4 节中简要提到的可疑 WINAPI 调用通常用于执行 shellcode,因此受到严格监控。然而,这些调用也用于一般正常的活动(VirtualAlloc、WriteProcess、CreateThread 模式可以看成恶性:内存分配和写入 shellcode),因此 EDR挑战是区分良性和恶意调用。 可以参考此前Bypassing EDR Real-Time Injection Detection Logic - RedBluePurple的博客。分为两步:

  1. 与其分配一大块内存并注入shellcode ,不如分配小的连续块,例如<64KB 内存并将它们标记为 NO_ACCESS 。然后以类似的块大小将 shellcode 写入分配的内存页面。
  2. 在上述每个操作之间引入延迟。这将增加执行 shellcode 所需的时间,但也会使“连续执行模式”变得不那么突出。

该技术难点在找到连续内存页面中容纳整个 shellcode 的内存位置。可以参考GitHub - xuanxuan0/DripLoader: Evasive shellcode loader for bypassing event-based injection detection (PoC) 项目。

7.直接调用系统函数,规避“系统调用hook”

         直接系统调用可以参考Red Team Tactics: Combining Direct System Calls and sRDI to bypass AV/EDR | Outflank这边文章,写的很详细。

简而言之,直接系统调用是直接对内核系统调用等效的 WINAPI 调用。我们不调用 ntdll.dll VirtualAlloc,而是调用它在 Windows 内核中定义的内核等效 NtAlocateVirtualMemory。这就避免了EDR在用户层对ntdll的检测。

         为了能直接调用系统函数,我们从 ntdll.dll 中获取我们要调用的系统函数 syscall ID,使用函数签名将函数参数的正确顺序和类型推送到堆栈,然后调用 syscall < id>指令。有几个工具可以为参考,SysWhispers2 和 SysWhisper3 。当然这样会存在两个问题:

  1. 静态检测会被查到,有一篇很棒的博客可以参考https://klezvirus.github.io/RedTeaming/AV_Evasion/NoSysWhisper/
  2. 系统调用返回地址不在ntdll中,这是一个很明显的线索,可以通过在ntdll中查找syscall指令地址,通过ntdll中的syscall调用来避免检测

8.删除或覆盖ntdll里面的hook

         另一个在 ntdll.dll 中逃避 EDR hook的好方法是用来自 ntdll.dll 的新副本覆盖默认加载(并由 EDR hook)的 ntdll.dll。 ntdll.dll 是任何 Windows 进程加载的第一个 DLL。如果我们的代码运行之后在内存中加载一个新的 ntdll.dll 副本,那些EDR hook就会被覆盖。 RefleXXion 是一个 C++ 库

(https://github.com/hlldz/RefleXXion,https://www.mdsec.co.uk/2022/01/edr-parallel-asis-through-analysis/ ),使用直接系统调用 NtOpenSection 和 NtMapViewOfSection 来获取 \KnownDlls\ntdll.dll(预加载的 DLL 的注册表路径)中干净 ntdll.dll 的句柄。然后它会覆盖已经加载 ntdll.dll 的 .TEXT 部分,从而清除 EDR hook。

9.欺骗线程调用堆栈(hook sleep)

         后面介绍的两种bypass是针对内存中的shellcode,大部分内存中shellcode处在等到CC指令的状态,这很容易被EDR内存扫描到。当注入程序处于休眠状态时,它的线程返回地址指向我们驻留在内存中的 shellcode。通过检查可疑进程中线程的返回地址,可以很容易地识别出我们的植入的 shellcode。我们可以通过hook sleep的方法来实现返回地址和shellcode的关联。

https://twitter.com/mariuszbit

https://github.com/mgeeky/ThreadStackSpoofer

上面两个参考程序对beacon的这种hook方式很有用,也被大量引用在动态bypass杀软上。

可以用一个例子看一下,上面是默认线程调用堆栈图示,下面是返回sleep上的调用堆栈:

10.beacon/shellcode 内存加密(读写权限)

我们可以注册一个自己的VEH,并写一个hook sleep函数,然后在调用mysleep的时候更改shllcode内存区域权限。但需要shellcode执行的时候抛出一个异常,由veh在线程上下文恢复,将shellcode权限改回 RX,参考:

https://github.com/mgeeky/ShellcodeFluctuation

11.反射加载

反射加载的方式已经有点普遍,cs自带了反射dll加载方式,推荐另一个项目:

https://github.com/boku7/BokuLoader,它有以下特点:

  1. 限制GetProcAddress的调用
  2. 包含了AMSI和ETW bypass
  3. 只用系统直接调用
  4. 仅使用 RW 或 RX ,并且没有 RWX (EXECUTE_READWRITE) 权限
  5. 内存中删除dll 的头

相比较之前cs和msf使用的GitHub - stephenfewer/ReflectiveDLLInjection: Reflective DLL injection is a library injection technique in which the concept of reflective programming is employed to perform the loading of a library from memory into a host process.这个更加的“红队”。

12.不使用RWX内存

在cs配置文件中不适用RWX权限内存空间:

AV/EDR 免杀逃避技术汇总_第3张图片

 

你可能感兴趣的:(EDR,免杀,windows,windows)