
一种高级内存规避技术,在 RW/NoAccess 与 RX 之间波动切换 shellcode 的内存保护,并对其内容进行加密/解密。
A PoC implementation for an another in-memory evasion technique that cyclically encrypts and decrypts shellcode's contents to then make it fluctuate between RW (or NoAccess) and RX memory protection.
When our shellcode resides in RW or NoAccess memory pages, scanners such as Moneta or pe-sieve will be unable to track it down and dump it for further analysis.
在发布 ThreadStackSpoofer 之后,我收到了一些关于以下 README 要点的提问:
在 Beacon 睡眠前,将其内存页保护从 RX/RWX 改为 RW,并加密其内容(这样可以规避诸如 Moneta 或 pe-sieve 之类的扫描器)
在此之前,我曾相当确信社区已经知道如何加密/解密其载荷并翻转内存保护,以简单规避那些寻找异常可执行区域的内存扫描器。 事实证明并非如此,因此我决定发布这个未武器化的 PoC,以记录又一种规避策略,并为社区提供可用的示例实现。
这个 PoC 演示了一种相当简单的技术,红队社区早已熟知(所以我其实并没有带来什么新东西),希望借此揭开某些商业框架在演示针对上述两款内存扫描器的规避能力时所展现的“魔法”背后的秘密。
以下是对波动到 RW 时的对比(另一种选择是波动到 PAGE_NOACCESS——见下文描述):

该实现连同我的 ThreadStackSpoofer,为攻防安全社区带来了可与商业 C2 产品功能比肩的示例实现,使我们在红队工具上也能不落下风。💪
该程序执行自注入 shellcode(大致通过经典的 VirtualAlloc + memcpy + CreateThread)。
当 shellcode 运行(此实现专门针对 Cobalt Strike Beacon 植入体)时,将会挂钩一个 Windows 函数,以拦截 Beacon 进入睡眠的时机 kernel32!Sleep。
每当被挂钩的 MySleep 函数被调用时,它会定位其内存分配边界,将保护属性翻转为 RW,并对其中存储的所有字节进行 xor32 加密。
在等待了预期的时间后,当 shellcode 返回到我们的 MySleep 处理程序时,我们会解密 shellcode 的数据,并将保护属性翻转回 RX。
PAGE_READWRITE 的工作方式如下kernel32!Sleep,使其指向我们的回调函数。VirtualAlloc + memcpy + CreateThread 注入并启动 shellcode。与我们在 ThreadStackSpoofer 中所做的相反,这里我们不在 ntdll 中挂钩任何东西来启动 shellcode,而是从我们自己的函数跳转到它。这样做的目的是避免在内存中留下指向被修改的 ntdll 内存的简单 IOC。MySleep 回调就会被调用。RWkernel32!Sleep,以避免在内存中留下表明 Sleep 已被 trampoline(内联挂钩)的简单 IOC。::Sleep,让 Beacon 在等待后续通信时保持睡眠。RX,然后重新挂钩 kernel32!Sleep,以确保拦截后续的睡眠。PAGE_NOACCESS 的工作方式如下kernel32!Sleep,使其指向我们的回调函数。VirtualAlloc + memcpy + CreateThread 注入并启动 shellcode ...MySleep 回调就会被调用。PAGE_NOACCESSkernel32!Sleep,以避免在内存中留下表明 Sleep 已被 trampoline(内联挂钩)的简单 IOC。::Sleep,让 Beacon 在等待后续通信时保持睡眠。kernel32!Sleep,以确保拦截后续的睡眠。RX,shellcode 随即恢复执行。这项技术并不是全新的,也不是我自己发明的。它仅仅是一个展示概念及其实际应用的实现,旨在让我们攻防安全社区赶上商业 C2 框架所提供的功能。
实际上,几年前我通过 Josh Lospinoso 在他出色的 Gargoyle 中的工作,第一次接触到了翻转 shellcode 内存保护这一想法。
更多背景资料:
Gargoyle 通过利用调用 VirtualProtect 的 ROP 序列,将自感知、自波动 shellcode 的概念又向前推进了一步。
不过,尽管这项技术令人印象深刻,但要将其与 Cobalt Strike 的 Beacon 结合使用同样困难,因为你必须杀死其线程,并在内存中不断重新初始化 Beacon。
这远非完美,但既然我们已经立足于自己的自注入加载器进程,就可以对 shellcode 运行的环境为所欲为,想怎么隐藏它就怎么隐藏它。这项技术(以及之前的 ThreadStackSpoofer)展示了以这种方式运行 shellcode 的优势。
波动到 PAGE_NOACCESS 的实现灵感来自于 ORCA666 在他的 https://github.com/ORCA666/0x41 注入器中展示的工作。
他证明了:
该实现包含了这一想法,可通过 <fluctuate> 中的选项 2 启用。
也请务必查看他的其他项目。
工具 ShellcodeFluctuation 接受三个参数:第一个是 shellcode 的路径,第二个是我们功能的修饰符。```
Usage: ShellcodeFluctuation.exe
:
-1 - Read shellcode but dont inject it. Run in an infinite loop.
0 - Inject the shellcode but don't hook kernel32!Sleep and don't encrypt anything
1 - Inject shellcode and start fluctuating its memory with standard PAGE_READWRITE.
2 - Inject shellcode and start fluctuating its memory with ORCA666's PAGE_NOACCESS.
### Moneta(看似)误报```
C:\> ShellcodeFluctuation.exe beacon64.bin -1
So firstly we'll see what Moneta64 scanner thinks about process that does nothing dodgy and simply resorts to run an infinite loop:

As we can see there's some 误报 (at least how I consider it) allegedly detecting Mismatching PEB module / Phantom image.
The memory boundaries point at the ShellcodeFluctuate.exe module itself and could indicate that this module however being of MEM_IMAGE type, is not linked in process' PEB - which is unusual and sounds rather odd.
The reason for this IOC is not known to me and I didn't attempt to understand it better, yet it isn't something we should be concerned about really.
If anyone knows what's the reason for this detection, I'd be very curious to hear! Please do reach out.
C:> ShellcodeFluctuation.exe beacon64.bin 0
第二个用例展示了在我们进程中运行的 Beacon 的内存 IOC,该 Beacon 不使用任何定制的 `Artifact Kits`、`User-Defined Reflective Loaders`(例如我的 [`ElusiveMice`](https://github.com/mgeeky/ElusiveMice)),也不执行任何会破坏我们结果的初始操作。

我们可以看到 `Moneta64` 正确识别出指向我们 shellcode 所在位置的 `Abnormal private executable memory`。
这是一个非常强的内存 IOC,会暴露我们的 shellcode,使其可能被自动化扫描器转储和分析。这可不太好。
### 使用 RW 保护的加密 Beacon```
C:\> ShellcodeFluctuation.exe beacon64.bin 1
现在来看第三个用例,从本实现的角度来看最有趣,即 波动 Beacon。

除了第一个 IOC(在某种程度上被认为是 误报)之外,我们还看到了一个新的 IOC,表明 kernel32.dll 内存被修改了。
然而,这次没有 Abnormal private executable memory IOC。我们的波动(反复加密/解密以及内存保护切换是激活的)。
另外需要说明的是,pe-sieve 在使用 /data 3 选项时也会检测到植入的 PE(除非指定该选项,否则不会进行检测):

我目前的假设是,PE-Sieve 检测到的特征与 Moneta 相同(在下面的 kernel32.dll 中的修改代码 中描述)——即 PE 映射的模块具有非空的工作集,这显然是某种代码注入的明显事实。 这被标记为 Implanted PE / Implanted。如果是这样,结论与 Moneta 的观察类似。我认为从检测的角度来看,我们不必太在意这个 IOC。
目前,除了挂钩 kernel32!Sleep 之外,我想不到更好的办法来中途拦截 shellcode 的执行(这里说的是 Cobalt Strike)。因此,我们必然会留下这类 IOC。
但是嘿,与文件系统上(C:\Windows\System32\kernel32.dll)的文件相比,仍然没有一个字节不同,也没有任何函数被挂钩,这是怎么回事?😉
C:> ShellcodeFluctuation.exe beacon64.bin 2

这实际上会导致 shellcode 在 `RX` 和 `NA` 页面之间波动。
目前我不确定切换到 `PAGE_NOACCESS` 而不是 `PAGE_READWRITE` 有什么好处。
### kernel32.dll 中被修改的代码
那么,那个被修改的 `kernel32` IOC 又是怎么回事呢?
现在,让我们试着追查这个 IOC 的根源,看看这里到底是怎么回事。
首先,我们会转储所提到的内存区域——即 `kernel32.dll` 的 `.text`(代码)节。为此,我们使用 `ProcessHacker`,以利用公开已知且稳定的工具: