正道:现代红队基础免杀心法:内存扫描与主动防御

文摘   2024-11-03 16:55   中国香港  

本文章的目标受众是困惑的初入免杀/红队武器开发人员。

最近当一些人们讨论免杀时,我看到他们只关心shell加载,使用像msf、Cobalt Strike、Winos 这样的框架等等,像他们这样的人确实喜欢认为免杀AV/EDR只是加载Shellcode代码,然后看到可能会得到一个信标或其他东西,然后就完事大吉了,其它什么都不做。

其实这很不现实对不对,在获取立足点之后,通常来说,你必须采取进一定的步骤才能实现你的目标,你需要收集信息,你需要横向移动,你需要提取凭证。如果你就是很喜欢欣赏一个上线的画面之外,其它什么都不做的话,那你当我没说。也可以退出这篇文章了。


内存扫描

绕过静态分析上线的最简单方法之一是加密Shellcode在执行阶段对其进行解密并执行,这是使典型的特征码查杀失效的策略、在github上有多个开源项目(Veil、SGN、Hyperion、PE-Crypter 等)可以简单高效的做到这一点。

但这完全不够,许多情况下,尽管上线,有一个传入的 Beacon 连接。但在发出单个命令/模块后,就会立即被检测到并被杀死。

比如使用Metasploit Meterpreter Shellcode,上线后当使用 shell/execute 等命令时,Windows Defender 仍会终止 Meterpreter 会话,这是为什么呢?

为了弄清楚这一点,先快速介绍一下大多数现代 AV 引擎使用的分析方法:

静态分析 - 涉及扫描磁盘上文件的内容,主要依靠一组已知的恶意签名。虽然这对已知恶意软件有效,但静态签名通常很容易被绕过,这意味着新的恶意软件会被遗漏。这种技术的一个较新的变体是基于机器学习的文件分类,它本质上是将静态特征与已知的良好和不良配置文件进行比较以检测异常文件。

进程内存/运行时分析 – 与静态分析类似,但分析的是运行进程内存而不是磁盘上的文件。这对攻击者来说更具挑战性,因为混淆内存中的代码会更加困难,因为其执行和现成的有效负载很容易被检测到。

其中内存扫描还值得一提的是,如何触发扫描:

文件读/写 ——每当创建或修改新文件时,都可能触发 AV 启动对文件的扫描。

定期 - AV 将定期扫描系统,每日或每周或每时,这可能涉及系统上的所有文件或仅部分文件。此概念也适用于扫描正在运行的进程的内存。

可疑行为 - AV 通常会监视可疑行为(通常是 API 调用)并使用它来触发扫描,这也可能是本地文件或进程内存。

考虑到我们的 Meterpreter 会话仅在使用 shell/execute 时被终止,因此很可能是该行为触发了扫描。查看Metasploit 源代码会发现当执行execute 命令时Meterpreter 使用 CreateProcess API 来启动新进程

那CreateProcess 是否被挂钩了呢?当通过windbg检查 CreateProcess 的参数及其周围的代码,没有发现挂钩。调试和单步执行代码也没有发现任何用户空间钩子的影子

这表明Defender 是在内核部分才设计了通知 EDR 进行主动扫描的设计并将 Meterpreter 反连 Shell 被断连/杀除、完成了行为上的主动防御查杀。可能采取了内核回调或者ETW-TI来记录调用可能可疑的 API 函数,并在调用特定 API 时触发对进程内存的扫描。验证这一假设,可以编写一些自定义代码来调用可能可疑的 API 函数

VOID detectMe() {
std::vector<BOOL(*)()>* funcs = new std::vector<BOOL(*)()>();

funcs->push_back(virtualAllocEx);
funcs->push_back(loadLibrary);
funcs->push_back(createRemoteThread);
funcs->push_back(openProcess);
funcs->push_back(writeProcessMemory);
funcs->push_back(openProcessToken);
funcs->push_back(openProcess2);
funcs->push_back(createRemoteThreadSuspended);
funcs->push_back(createEvent);
funcs->push_back(duplicateHandle);
funcs->push_back(createProcess);

for (int i = 0; i < funcs->size(); i++) {
printf("[!] Executing func at index %d ", i);

if (!funcs->at(i)()) {
printf(" Failed, %d", GetLastError());
}

Sleep(7000);
printf(" Passed OK!\n");
}
}

有趣的是,大多数测试函数都不会触发扫描事件,只有 CreateProcess 和 CreateRemoteThread 才会触发扫描。

这是很有道理的,因为许多测试的 API 都是经常使用的,如果每次调用其中一个时都触发扫描,Windows Defender 就会不断扫描,可能会影响系统性能。

其次,根据我们学习过汇编语言的常识,标准英特尔内存寻址公式:32位系统下单个进程内存有 32GB(64位则为 128 TB)→ 扫描十个程序要 1280 TB?所以它肯定也不会随时处于定期内存扫描→ 因为成本昂贵。

So,现代化的AV/EDR 为产品化、快速内存特征码匹配做出了必要性的取舍。通常,AV 不会扫描进程的整个虚拟内存空间,而是查找特定的内存页面属性或权限,例如 MEM_PRIVATE 或 RWX权限。

综合各方因素,Reverse Shell 被查杀的行为原因归结为:CreateProcess() 请求发起自 RWX Memory 而非正常 PE Image 导入表的call行为。API 调用(主动行为)在关系链上(Process-Chain 或 Image-Chain)的合法性是最重要的依据。


那如何绕过?简单!在 Defender 介入扫描时将我们的 Shellcode (CreateProcess发起者)变为不可扫描不就好了嘛 :

  1. 在Shellcode加载器里挂钩 CreateProcess() → MyCreateProcess()

  2. 当从 RWX Shellcode 发起 CreateProcess() 请求时会将执行权转移至挂钩的 MyCreateProcess()函数上

  3. MyCreateProcess函数负责将 RWX Shellcode 的记忆体属性更改为 PAGE_NOACCESS

  4. MyCreateProcess函数接续调用原始的CreateProcess() 函数 → 进到 Windows 内核 ntoskrnl!ZwCreateUserProcess

  5. Windows 系统内核发出通知 Defender EDR 介入扫描 RWX Shellcode内存对比特征判断是否为恶意 Payload

  6. 但我们的 RWX Shellcode 现在内存属性为 PAGE_NOACCESS,所以 EDR 放弃扫描。👍

  7. Windows 内核认为「Defender EDR 说这块内存不存在」所以清白无虞,OK,放行 CreateProcess() 请求

  8. Bypass!


主动防御:原生系统行为、误判、还是攻击?

如何记录不该发起的行为来源?

  1. CreateProcess 用法是否恶意比如用于RevShell

  2. CreateProcess 用法上是否滥用诸如 Hollowing 注入技巧

  3. GetProcAddress 用于 ReflectiveLoader 技巧的反射式加载PE映像

  4. CreateThread 是否用于执行 RWX 新内存片段、而非标准加载的PE映像内存

国内安全软件有自己的一套规则,这并非公开的,只能通过逆向或一些内存搜索,例如某数字软件的白加黑规则:

命令执行:


国外安全软件的话,大多数会采用线程调用栈和线程状态分析:

EDR 可以选择检查启动函数或 API 调用的进程,并分析调用堆栈中是否存在可疑情况:

当进程调用某个 Windows 函数时,可以找出导致此次调用的父函数。这称为调用堆栈。

调用堆栈是导致程序计数器当前位置的函数调用链。调用栈上的顶部函数是当前函数,下一个函数是调用当前函数的函数,依此类推。

通过调用栈可以很好地确定线程的意图,例如调用 WinHTTP 的线程调用栈:

进程启动的通用线程调用栈:

如何伪造 Stack Frame 使其看起来像是原生系统回调自然而然发起恶意发起行为?参考项目:

https://github.com/WithSecureLabs/CallStackSpoofer

其它好的项目:

  • ThreadStackSpoofer

  • CallStackSpoofer

  • AceLdr

  • CallStackMasker

  • Unwinder

  • TitanLd

滥用系统回调欺骗伪造发起来源:

红队武器开发系列:利用 Windows 线程池 API 和 I/O 完成回调来实现代理 DLL 加载


最后,现代免杀攻防的技术细节远不止这一些,如果你觉得学免杀需要学的知识太多了,还不如直接送外卖。

感谢你的收看,有什么问题留言欢迎讨论。如果这篇文章阅读量还不错的话,下一篇文章我们来讨论如何编写理想化完美的shellcode加载器以及编写加载器时的OPSEC错误。



知识星球

知识星球除了和其他星球一样分享技巧、代码、工具和内推资源外,其特色在于提供不厌其烦的答疑服务。


信 安 考 证




需要考以下各类安全证书的可以联系我,价格优惠、组团更便宜,还送【老鑫安全识星球1年!

CISP、PTE、PTS、DSG、IRE、IRS、NISP、PMP、CCSK、CISSP、ISO27001...


老鑫安全
真正的大师永远都怀着一颗学徒的心,专注于渗透测试,红蓝对抗,漏洞挖掘等安全技术培训 B站:老鑫安全 知识星球:老鑫安全 官网论坛:https://www.laoxinsec.com