为什么内存扫描挡不住Voidgate?从加密Shellcode到逐条解密执行的原理全景
【免费下载链接】VoidgateA technique that can be used to bypass AV/EDR memory scanners. This can be used to hide well-known and detected shellcodes (such as msfvenom) by performing on-the-fly decryption of individual encrypted assembly instructions, thus rendering memory scanners useless for that specific memory page.项目地址: https://gitcode.com/gh_mirrors/vo/Voidgate
Voidgate 是一种针对 Windows 平台的内存扫描绕过技术:它通过将已知的 Shellcode(如 msfvenom 生成的载荷)整体 XOR 加密后驻留在内存中,再借助硬件断点(HWBP)与向量异常处理程序(VEH)实现"执行到哪条指令才解密哪条指令"的逐条解密执行机制,让 AV/EDR 的内存扫描器在那一页内存里永远只能看到一堆密文。对于想理解"单步执行 + 异常驱动解密"这类反检测思路的安全学习者来说,这是一个麻雀虽小、五脏俱全的典型样本。
一、先搞懂问题:内存扫描为什么"一抓一个准"
传统的恶意载荷在内存中展开后,会有一段明文的可执行字节码。AV/EDR 的内存扫描器做的事情很简单——定期遍历进程的内存页,把每一段内容拿去和已知 Shellcode 特征(YARA 规则、哈希、相似度)做比对。
这就带来一个尴尬的现实:
- 🔍特征比对只看静态字节:只要明文完整躺在内存里,哪怕你只执行过 1%,扫描器照样能匹配上;
- 📦msfvenom 等工具生成的载荷被"背板":这些载荷在攻防圈流传极广,其字节特征早已被各大厂商收录;
- ⏱️扫描周期短:内存驻留时间越长,被扫到的概率越高。
一句话总结:传统做法是"整段解密 → 整段驻留明文",而内存扫描的前提恰好就是"整段明文"。要破局,就必须打破这个前提。
二、Voidgate 的核心思路:内存里永远只有一条明文指令
Voidgate 的答案非常巧妙:永远不要同时存在两条明文指令。
它的运作模型可以概括为:
- 加密后的 Shellcode 整体存放在一块
PAGE_EXECUTE_READWRITE内存区域中,前后填充 NOP 字节作为"海绵"区域; - 程序启动时,Shellcode 的入口地址被设置为硬件断点(Hardware Breakpoint);
- 程序安装一个向量异常处理程序(VEH),充当"软件调试器";
- 每执行完一条指令,CPU 触发单步异常 → VEH 介入 →解密当前指令、重新加密上一条刚用过的指令→ 继续执行。
在任意一个瞬间,整个内存页中只有一条 15 字节以内的明文汇编指令(x64 单条指令的最大长度)。对于内存扫描器来说,这一页内容看起来就是一串无意义的密文加一段 NOP——没有任何可匹配的特征。
💡 打个比方:传统做法像把整本密码本摊开在桌上,Voidgate 则像只翻开当前要读的那一行,读完立刻合上,再翻下一行。
三、原理拆解:硬件断点 + VEH 的组合拳
这套机制由三个 Windows 底层机制配合完成,源码集中在 Voidgate.cpp 与 main.cpp 中。
3.1 硬件断点(HWBP):逐条执行的"发令枪"
硬件断点利用 CPU 的调试寄存器(Dr0~Dr3)实现,当 RIP 命中断点地址时,硬件直接产生异常——它不需要修改任何内存字节,对扫描器完全不可见(软断点改写内存为0xCC,反而容易暴露)。
Voidgate 在SetHardwareBreakpoint()中将 Dr0 指向 Shellcode 入口,并配置 Dr7 启用全局断点。这是整个"单步循环"的第一推动力。
3.2 VEH:解密器的真正载体
AddVectoredExceptionHandler()安装的 VEH 回调VehDecryptHeapAsm()是整个项目的灵魂,它每次被调用时做四件事:
- 🧹清理:清除 Dr0 上的入口断点(只在入口需要一次);
- 🔓解密:读取当前 RIP 指向的地址,用 XOR 密钥解密接下来的 16 字节(覆盖 x64 最长指令长度);
- 🔒回填加密:把上一条解密过的指令(地址记录在全局变量
last_decrypted_asm中)重新加密,恢复密文状态; - ▶️续跑:设置 EFlags 中的TRAP 标志,保证下一条指令执行完时再次触发单步异常。
其中 XOR 密钥的起点位置由GetXorKeyIndexForAsm()计算——用"当前指令地址与载荷基址的差值对密钥长度取模",这样无需记录每条指令的位置,VEH 就能随时算出该用密钥的哪一段去加解密。
3.3 一个容易被忽略的细节:边界检查
VEH 里有一处边界判断:如果当前 RIP 不在载荷的上下界之内(比如 Shellcode 调用了 kernel32.dll 的函数),就直接放行、不做任何加解密。这保证了解密器只"看守"自己的内存页,不干扰系统函数的正常执行。
四、完整执行流程:从加密 Shellcode 到逐条解密执行
把上面三个部件串起来,Voidgate 的完整生命周期如下:
| 阶段 | 动作 | 关键点 |
|---|---|---|
| ① 准备 | XOR 加密工具对 Shellcode 做整体加密,输出字节数组 | 密钥在加密端与执行端必须一致 |
| ② 分配 | VirtualAlloc申请 RWX 内存,前后填充0x90(NOP) | 加密载荷拷入中间区域 |
| ③ 布防 | 入口地址设置硬件断点,安装 VEH | 此时内存中全是密文 |
| ④ 点火 | 主线程跳转到载荷入口 | HWBP 触发,VEH 接管 |
| ⑤ 循环 | 每条指令:解密当前 → 加密上一条 → 置 TRAP 标志 | 内存中恒为"单条明文" |
| ⑥ 收尾 | 载荷执行完毕,释放内存 | 全程无整段明文驻留 |
整个过程中,执行速度会明显下降——每条指令背后 VEH 至少要额外执行 300 条左右的汇编指令来完成加解密与恢复现场。一个以体积优先优化的 msfvenom 载荷,跑完可能要 15 秒以上,这是"用速度换隐蔽"的典型代价。
五、POC 复现:四步跑通加密Shellcode逐条解密
⚠️ 本项目仅供学术研究与教学使用,请仅在自有环境中测试。
克隆仓库:
git clone https://gitcode.com/gh_mirrors/vo/VoidgateStep 1 · 生成载荷:用 msfvenom 生成 x64 反弹 Shell 并输出为原始二进制文件;
Step 2 · XOR 加密:用仓库自带的加密工具处理该文件,它会按密钥循环异或并直接打印出 C 字节数组;
Step 3 · 填入项目:将打印出的字节数组替换 main.cpp 中的payload变量,并确认加密端与执行端的密钥(key)完全一致;
Step 4 · 监听并运行:在接收端起好监听,然后编译并运行 Voidgate.exe,观察载荷被逐条解密执行、监听端成功收到连接的过程(见上方演示动图)。
📌 提示:由于逐条解密开销大,运行后请耐心等待十几秒到几十秒,这不是程序卡死。
六、局限与边界:哪些场景下 Voidgate 会失效
了解局限与理解原理同样重要,项目 README 中明确列出了三大限制:
- ⚡ 速度惩罚:每条指令都要过一遍 VEH,指令越多、循环越密的载荷,执行越慢;
- 🧵 单线程假设:如果载荷通过
NtCreateThread等新线程执行且入口在 Shellcode 内部,新线程没有安装 HWBP,VEH 不会为它工作(作者标记为进行中); - 📄 载荷内嵌数据不能自引用:若 Shellcode 把自己内部的字符串/数值当作数据(通过固定偏移引用),这段数据会一直处于加密状态而无法被正确读取。用汇编指令(如
push立即数)压栈传参则不受影响。
此外,它不适合反射加载器等复杂载荷,最理想的定位是:作为初始访问(Initial Access)的小型第一阶段的载荷,或用于从 C2 服务器拉取后续载荷的"引导器"。
七、项目结构与核心文件速览
项目结构非常精简,适合通读源码:
- 📁 voidgate-master/Voidgate/ —— 核心工程
- Voidgate.h:常量定义(填充长度、x64 最大指令长度 15 字节)与函数声明
- Voidgate.cpp:VEH 解密回调、硬件断点设置、XOR 密钥索引计算
- main.cpp:内存分配、NOP 填充、载荷植入与执行入口
- 📁 voidgate-master/XorEncryptPayload/ —— 配套 XOR 加密工具
- XorEncryptPayload.cpp:读取二进制载荷,循环异或后输出 C 数组
- 🖼️ poc.gif —— 完整 POC 演示动图
- 📄 README.md —— 原理说明、限制与使用说明
总结
Voidgate 用三个"教科书级"的底层机制——硬件断点、VEH、TRAP 标志——组合出了一套优雅的内存扫描对抗方案:XOR 加密 + 逐条解密执行,让目标内存页在任何被扫描的瞬间都只暴露一条明文指令。它的价值不仅在于"绕过"本身,更在于展示了一种思维方式:当防御方依赖"静态字节特征"时,攻击方可以把明文的生命周期压缩到单条指令的长度。
对于安全学习者,这个不到千行代码的小项目是一份极佳的学习材料——读懂它,你就同时理解了调试器工作原理、Windows 异常分发机制和内存扫描对抗的基本面。
【免费下载链接】VoidgateA technique that can be used to bypass AV/EDR memory scanners. This can be used to hide well-known and detected shellcodes (such as msfvenom) by performing on-the-fly decryption of individual encrypted assembly instructions, thus rendering memory scanners useless for that specific memory page.项目地址: https://gitcode.com/gh_mirrors/vo/Voidgate
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考