bypass 行业最优质的EDR

admin 2023年5月12日08:46:54评论124 views字数 4763阅读15分52秒阅读模式

现在当我和我的红队朋友谈及进程注入的话题时,回答通常是“是的……但是……”。因为检测的风险超过了在宿主进程中“寄生”的需要。典型的进程注入技术过于突出,而且注入往往与恶意活动有关。有时,我喜欢培养这种“反病毒软件bypass”的爱好,并使用恶意 shellcode 来对抗当今最好的端点保护来实现进程注入。

因此,在这篇文章中,我将介绍可以使用哪些绕过技术组合来实现零检测或零告警的进程注入。

1.自定义版本GetProcAddress()

WINAPI许多规避技术依赖于函数的使用GetProcAddress()来获取函数的虚拟内存地址例如,获取函数内存地址Nt*执行直接系统调用的典型方法(如结合直接系统调用和 sRDI 绕过 AV/EDR中https://outflank.nl/blog/2019/06/19/red-team-tactics-combining-direct-system-calls-and-srdi-to-bypass-av-edr/首次演示的那样)是:

GetProcAddress(GetModuleHandle(L"ntdll.dll"), L"NtWriteProcessMemory");

上面一行中的所有内容都是用于检测的签名。"NtWriteProcessMemory"字符串和的组合"ntdll.dll"是可疑的,尤其是作为GetModuleHandle和的参数GetProcAddress两者都用于解析函数的内存位置,旨在绕过导出的(和潜在的)API 函数的使用。如今,GetProcAddress这是一项受到严密监控的功能。

为了绕过这两种检测技术,我们可以使用我们自己的实现,GetProcAddress()该实现使用进程的PEB结构来获取可执行文件中(导出的)函数的内存地址。其中PEB包含有关进程、加载模块 (DLL)、函数等的大量信息,我们可以自己读取这些信息以获取它们的内存位置。获取 的内存位置,向下ntdll.dll移动到给我们一个我们可以直接调用的函数内存地址列表有多种实现方式,大多数都归结为相同的原理。PEBAddressOfNamesWINAPI

我们将使用前面描述的方法混淆的字符串。你还可以考虑使用 API 调用的哈希值进行查找https://www.ired.team/offensive-security/defense-evasion/windows-api-hashing-in-malware

2.使用硬件断点的系统调用

这种绕过钩子的相对较新的绕过技术我首先在@EthicalChaos的帖子In-Process Patchless AMSI Bypass中发现。他的帖子概述了如何使用硬件断点和矢量异常处理程序 (VEH) 绕过 EDR 挂钩,从而避免对(恶意活动的指示器)ntdll.dll进行内存修补。ntdll.dll该技术相当简单:

  1. 注册一个VEH来处理断点触发的异常。VEH 在引发异常的线程中处理,并且 VEH 可以访问相应的线程上下文(包括所有寄存器)。
  2. 在要拦截执行的内存地址上设置断点,即WINAPI NtWriteProcessMemory. 设置DR7寄存器会导致操作系统调用已注册的 VEH。
    GetThreadContext(myThread, &ctx);   /* get thread context */
    ctx.Dr0 = (UINT64)&bp_addr;         /* address you want to break on */
    ctx.Dr7 |= (1 << 0);                /* set first bit in DR7 */
    ctx.Dr7 &= ~(1 << 16);              /* clear 16 an 17th bit */
    ctx.Dr7 &= ~(1 << 17);
    SetThreadContext(myThread, &ctx)    /* set the thread context, putting the breakpoint in place */
  3. 然后可以从 VEH 接管控制流,并绕过 EDR 挂钩。

@Dec0ne创建了HWSyscalls,其中实现了上述步骤。在 VEH 中,它使用HalosGate在检测到挂钩时解析系统调用号 (SSN) WINAPI(例如,指令中地址后的下一条指令JMP)。作为一个很好的补充,HWSyscalls 会将RIP(指令指针)指向syscall; ret中的一条指令ntdll.dll,使返回地址 ( RAX) 指向ntdll.dll内存,而不是直接来自加载程序的可执行内存(直接系统调用的指示)。

HWSyscalls 是一个易于集成的模块。我们将在加载器中使用它。

3.无线程注入

下一个新技术,也是这个加载器真正的明星,是@EthicalChaos无线程进程注入这种技术只需要VirtualAlloc, WriteProcessMemory (和 VirtualProtect并且避免使用NtCreateThread(因此我认为是“无线程”)。最后一次调用的失败打破了典型的进程注入检测组合。它是这样的:

  1. 在远程进程中找到一个足够大的内存位置(“memory hole”或“code cave”),可以容纳我们生成的shellcode和trampoline
  2. 将shellcode加stub写入code cave。存根将用作trampoline
  3. JMP在常用ntdll功能(例如)之后添加指令NtOpen
  4. 等待一个合法的线程调用NtOpen,按照JMP指令执行我们的shellcode。
  5. trampoline重定向控制流回到合法NtOpen指令,以继续进程执行并避免崩溃。

ThreadlessInject存储库https://github.com/CCob/ThreadlessInject中提供了更多详细信息

4. 绕过常见的恶意模式

这实际上只是重复先前解释的相同技术。其中一项关键检测技术是VirtualAllocand WriteProcessMemory(或 Nt equivalents)调用约 300KB 内存(植入shellcode )。将这些内存操作分块可以绕过DripLoader在两年前引入的检测。所以我们的加载器中也使用这种技术。

bypass 行业最优质的EDR

加载程序执行流程

5. 休眠绕过

在大部分时间里,植入程度将处于休眠状态,等待下一次 C2 连接。一旦我们成功执行了植入shellcode,在休眠时将其隐藏在内存中是规避 EDR 的关键。已经有一些新的休眠绕过实现,但没有太多的公开,所以让我们扩展一下这个话题。

大多数现在休眠绕过实施都是基于Austin Hudson的 FOLIAGE 技术。其中之一,5piderEkko可能是当今使用最广泛的实现。

Ekko(类似于 FOLIAGE,但使用排队计时器而不是排队APC)使用线程池将休眠混淆任务托管给工作线程。工作线程处理主线程(信标所在的位置)的休眠混淆,并在植入程序继续执行时提醒主线程。具体为以下步骤:

  1. 创建一个新的Event和一个来TimerQueue对混淆操作进行排队。
     hEvent      = CreateEventW( 0, 0, 0, 0 );
     hTimerQueue = CreateTimerQueue();
  2. 使用创建当前(主)线程的快照RtlCaptureContext并将其保存在&CtxThreadWaitForSingleObject调用只是等待RtlCaptureContext完成保存快照)。
     if ( CreateTimerQueueTimer( &hNewTimer, hTimerQueue, RtlCaptureContext, &CtxThread, 0, 0, WT_EXECUTEINTIMERTHREAD ) ) {
            WaitForSingleObject( hEvent, 0x32 );
  3. 然后 Ekko 定义了 6 个不同的上下文结构,每个结构都包含要执行的混淆操作:
     memcpy( &RopProtRW, &CtxThread, sizeof( CONTEXT ) ); // 1. Set memory protection to RW
     memcpy( &RopMemEnc, &CtxThread, sizeof( CONTEXT ) ); // 2. Encrypt memory image, multi-byte RC4 without needing memory allocations
     memcpy( &RopDelay,  &CtxThread, sizeof( CONTEXT ) ); // 3. Delay (sleep) for specified amount of time, using WaitForSingleObject on something that does not become alertable
     memcpy( &RopMemDec, &CtxThread, sizeof( CONTEXT ) ); // 4. Decrypt the memory image
     memcpy( &RopProtRX, &CtxThread, sizeof( CONTEXT ) ); // 5. Set memory protection to RX
     memcpy( &RopSetEvt, &CtxThread, sizeof( CONTEXT ) ); // 6. Call SetEvent to alert our main thread that the worker thread is finished.
  4. 将上述所有调用排队到线程池中,供工作线程执行并在完成时提醒主线程:
     CreateTimerQueueTimer( &hNewTimer, hTimerQueue, NtContinue, &RopProtRW, 100, 0, WT_EXECUTEINTIMERTHREAD );
     CreateTimerQueueTimer( &hNewTimer, hTimerQueue, NtContinue, &RopMemEnc, 200, 0, WT_EXECUTEINTIMERTHREAD );
     CreateTimerQueueTimer( &hNewTimer, hTimerQueue, NtContinue, &RopDelay,  300, 0, WT_EXECUTEINTIMERTHREAD );
     CreateTimerQueueTimer( &hNewTimer, hTimerQueue, NtContinue, &RopMemDec, 400, 0, WT_EXECUTEINTIMERTHREAD );
     CreateTimerQueueTimer( &hNewTimer, hTimerQueue, NtContinue, &RopProtRX, 500, 0, WT_EXECUTEINTIMERTHREAD );
     CreateTimerQueueTimer( &hNewTimer, hTimerQueue, NtContinue, &RopSetEvt, 600, 0, WT_EXECUTEINTIMERTHREAD );

存扫描不需钩子或其他RWX

Ekko 在 Cobalt Strike 4.7+ sleepmask 套件中实现,我建议启用它。此外,可以考虑添加ETW 和 AMSI 的无补丁绕过

对于注入 PoC,我们将使用Kyle Avery的 Cobalt Strike 反射加载器AceLdr,它为我们实现了上述内容。此外,它还会在我们休眠时通过使用NtSetContextThread“namazso 的 x64 返回地址欺骗器”(强烈推荐他的 DEF CON 30 演讲内容)指向随机的其他线程上下文来欺骗返回地址。

bypass 行业最优质的EDR

在 Microsoft Defender for Endpoint 上进行进程注入,检测为 0(没有截图= =)。

就这样,绕过(至少一个)行业领先的 EDR 产品的检测。而且全部在纯 C 里.exe,没有其它花哨的语言、运行时、可以使用晦涩的文件扩展名或任何东西。只需“双击即可”。

原文始发于微信公众号(军机故阁):bypass 行业最优质的EDR

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年5月12日08:46:54
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   bypass 行业最优质的EDRhttps://cn-sec.com/archives/1728474.html

发表评论

匿名网友 填写信息