分而治之是一种编程中常用的算法,通过将复杂的问题分解为许多更简单的子问题来解决它。我们可以将这种方法以不同的目标应用于进攻性安全:迷惑 EDR,使其无法跟踪我们的活动,从而阻止它们触发任何警报。这类似于最近在现实攻击中几乎任何钓鱼活动中都能看到的情况:长感染链,逐步运行多个文件(例如 .url -> .one -> .js -> .bat -> .dll),而不是直接运行最终载荷。每个被运行的文件都执行一个简单的任务(下载另一个文件、对注册表进行任何更改、在目录之间移动文件或更改其名称/扩展名等),单独来看很难将这些任务标记为恶意,以此来为最终执行准备环境。
我决定测试这个简单的想法,但将其应用到了不同的场景,即远程进程注入。本仓库中的代码并不新鲜,恰恰相反,它可能是向远程进程注入 shellcode 最常见、最直接的方式之一:使用 NtOpenProcess、NtAllocateVirtualMemory、NtWriteVirtualMemory、NtProtectVirtualMemory 和 NtCreateThreadEx。唯一的区别是,在每次调用这些 API 之后,我都会使用 NtCreateUserProcess 对进程进行 fork。由于 fork 出的进程会从 RIP + 1 继续执行,并且内存完全从父进程复制而来,因此我们可以使用 5 个不同的进程执行远程进程注入,只需确保后续 API 调用所需的任何句柄都能被正确继承即可。
这样,我们将 shellcode 注入过程拆分为更简单的任务,并在各自独立的上下文(进程)中运行每个任务。
我已经针对当今三种最常见的 EDR 测试了这个 PoC:MDE、CrowdStrike 和 SentinelOne。结果不言自明:在运行没有 fork 的 PoC 时,3 个 EDR 中有 2 个触发了远程进程注入警报;相反,在我引入 fork 机制后,它们都没有发出任何警报。
当然,即使使用 fork 机制,我们仍然可以在原始遥测数据中看到与进程创建、线程创建以及所有跨进程行为相对应的事件,但这些似乎不足以让 EDR 将该活动标记为恶意,恰好证明了本 PoC 的观点。通过将恶意行为拆分为更简单的任务,并让每个任务从不同的进程中运行,我们可以迷惑并阻止 EDR 发出任何警报。
同样的结果也可以通过其他方式实现,我只是使用 fork 机制来简化代码并减少跨进程活动。
如果你想亲自测试,请编译带有和不带有 fork() 函数调用的代码,然后在装有目标 EDR 的环境中运行这两个载荷。
由于我们使用 LITCRYPT 插件来混淆字符串字面量(仅适用于 Dinvoke_rs 代码),因此编译代码前需要设置环境变量 LITCRYPT_ENCRYPT_KEY:
C:\Users\User\Desktop\Split> set LITCRYPT_ENCRYPT_KEY="yoursupersecretkey"
之后,只需编译代码并运行工具:
C:\Users\User\Desktop\Split> cargo build --release
C:\Users\User\Desktop\Split\target\release> split.exe -h
这种技术本身并不足以绕过 EDR;如果你的代码完全没有 OPSEC 考量,你很可能无论如何都会被抓住。这不是一颗银弹,只是你可以添加到工具中的另一层规避手段。尽管如此,本仓库中的代码完全没有 OPSEC 考量,原因包括但不限于以下几点:
另一方面,我只针对上述 EDR 测试过这种方法,不知道其他 EDR 是否也能被绕过。你可以测试一下,然后告诉我结果如何 ;)