ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

为什么内存扫描挡不住Voidgate?从加密Shellcode到逐条解密执行的原理全景

为什么内存扫描挡不住Voidgate?从加密Shellcode到逐条解密执行的原理全景 为什么内存扫描挡不住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/VoidgateVoidgate 是一种针对 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 内存前后填充0x90NOP加密载荷拷入中间区域③ 布防入口地址设置硬件断点安装 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 内部新线程没有安装 HWBPVEH 不会为它工作作者标记为进行中 载荷内嵌数据不能自引用若 Shellcode 把自己内部的字符串/数值当作数据通过固定偏移引用这段数据会一直处于加密状态而无法被正确读取。用汇编指令如push立即数压栈传参则不受影响。此外它不适合反射加载器等复杂载荷最理想的定位是作为初始访问Initial Access的小型第一阶段的载荷或用于从 C2 服务器拉取后续载荷的引导器。七、项目结构与核心文件速览项目结构非常精简适合通读源码 voidgate-master/Voidgate/ —— 核心工程Voidgate.h常量定义填充长度、x64 最大指令长度 15 字节与函数声明Voidgate.cppVEH 解密回调、硬件断点设置、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),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进