文章总结: 该文档详细分析了一套基于ModuleStomping技术的免杀方案,通过将Shellcode藏入微软签名DLL的.text段,并利用WriteProcessMemory等API规避行为检测。方案包含Shellcode的三层伪装(指令替换、XOR加密、IPv4地址伪装)和Loader的隐蔽设计,实现了对主流杀软的绕过。文档提供了技术原理、版本对比及操作流程,对红队和防御方均有参考价值。 综合评分: 85 文章分类: 免杀,红队,内网渗透,安全工具,实战经验
把 Shellcode 藏进微软签名 DLL:一套绕过主流杀软的完整免杀方案是如何工作的
网络安全启蒙
2026年8月10日 11:50 陕西
在小说阅读器读本章
去阅读
一个 DLL 分离加载,一次 Module Stomping,一段伪装成 IPv4 地址的 Shellcode。当沙箱还在找
VirtualProtect(RX)的调用痕迹时,恶意代码已经在msimg32.dll的.text段里跑了起来。
一、杀软和 Loader,在玩一场永不停歇的捉迷藏
在红队行动和渗透测试中,Shellcode Loader 是最后一百米的关键——前面的漏洞利用、Payload 生成都成功之后,最终要有一个”运输工具”把恶意代码投递到目标机器的内存里并跑起来。
而杀软和 EDR 盯的,恰恰就是这个运输工具的行为特征。
一个”教科书式”的 Loader 通常长这样:
VirtualAlloc(RW) → memcpy → VirtualProtect(RX) → CreateThread
翻译成人话:先申请一块可写内存,把 Shellcode 拷进去,然后把这页内存改成可执行,最后起一个线程跳到上面执行。
这套流程在今天的主流杀软面前,基本等于在大喊”看我!我在执行恶意代码!”——VirtualAlloc 申请的内存突然变成 RX(可读可执行),同时一个新的线程从一个非模块地址开始执行,这些行为组合在一起,在 EDR 的行为树上就会亮起红灯。
于是高级 Loader 开始想各种办法绕。Module Stomping(模块践踏)就是其中一种——不自己申请内存,而是找一块已经存在的、合法签名的 DLL 的代码段,直接覆写它的内容,然后跳上去执行。
GitHub 作者 bluechips-zhao 在他的 av-evasion-skills 项目中,把这种思路做到了一个相当完整的程度:从 Shellcode 的静态混淆、到 Loader 的行为隐蔽、再到沙箱特征的精细化消除,形成了一个从头到尾的免杀流水线。
仓库地址:
https://github.com/bluechips-zhao/av-evasion-skills版本 v4.1 | 134 Star | 2026 年 7 月发布
二、Shellcode 的三层伪装:从二进制到”看起来完全无害”
这套方案的第一步,是在 Shellcode 被投递之前就做好静态混淆。作者提供了三个 Python 脚本,分别对应三个处理阶段。
第一层:同义指令替换(shellcode-patch.py)
杀软的静态引擎会对 Shellcode 做特征码匹配。一段 Cobalt Strike 生成的 HTTPS Beacon,在各大杀软的规则库里都有对应的指纹。
同义指令替换的思路很巧妙:在不改变代码逻辑的前提下,把一些指令替换成功能相同但字节不同的形式。比如把 test rax, rax(48 85 C0)替换成 or rax, rax(48 09 C0)——两者都会设置零标志位,语义完全等效,但字节序列却不同了。
实现上采用了保守策略:只替换已知安全的等价模式,不改变指令长度,不破坏后续指令的偏移。这保证了替换后的代码不会崩溃,同时改变了静态扫描器可能匹配到的特征字节。
第二层:XOR 加密(shellcode-encrypt.py)
Patch 完成后,Shellcode 进入加密阶段。脚本用 XOR 做一个逐字节异或,密钥可以是任意长度的自定义字符串。
XOR 加密后的数据有一个重要特性:它与随机数据在统计上是不可区分的。安全引擎面对一堆随机字节,没有明确的解密锚点、没有已知的加密算法标识位,就很难判断”这是加密的恶意代码”还是”这只是一堆无意义的二进制数据”。
第三层:IPv4 地址伪装(shellcode-obfuscate-ipv4.py)
这是整个混淆链条里最优雅的一步。
加密后的 Shellcode 被按照每 4 字节一组拆分,每一组的 4 个字节直接映射为一个 IPv4 地址:
原始: DE AD BE EF
结果: "222.173.190.239"
最终产生的是一段这样的 C 代码:
char* ipv4_array[] = {
"222.173.190.239", "104.65.108.108", "111.87.111.114",
"108.100.33.0", ...
};
在静态分析看来,这不过是一个 IPv4 地址数组——像是网络配置代码、白名单、或者其他合法用途的数据。谁会想到它是一个完整的 C2 通信 Payload?
而且更微妙的是,IPv4 地址格式天然没有字节序问题。如果你用 0xDEADBEEF 这样的十六进制常量,在大端序和小端序系统上会有不同的解析。IPv4 字符串是”所见即所得”——"222.173.190.239" 在 x64 和 ARM64 上都会被解析为完全相同的 4 个字节。
三、Loader:行为层面的免杀设计
Shellcode 的静态防御只是前半程。真正见功力的,是 Loader 在运行时如何避免触发 EDR 的行为检测。
3.1 执行内存从哪来:Module Stomping
传统 Loader 用 VirtualAlloc 申请内存来放 Shellcode。这个 API 本身不是恶意的——正常的 JIT 编译器、脚本引擎也会频繁使用——但 VirtualAlloc + 后续的 VirtualProtect(RX) + 非模块地址执行,三个特征组合在一起,就构成了一个经典的恶意行为模式。
Module Stomping 换了一个完全不同的思路:不用新内存,用的是微软签名 DLL 自带的 .text 段。
Loader 启动后先加载一个合法的 Windows DLL——首选是 msimg32.dll,一个旧式图形库,体积极小(约 6KB),加载后就很少被调用。然后解析这个 DLL 的 PE 结构,找到 .text 段(代码段)的地址和大小,直接把解密后的 Shellcode 写进去。
从防守方的视角看:执行代码的地址是 msimg32.dll(微软数字签名,合法模块),栈回溯看到的是 msimg32.dll!text——和正常运行的程序没有任何区别。
3.2 写入方式:WriteProcessMemory 替代 VirtualProtect
覆盖只读的代码段需要先修改内存保护属性。在 v4.0 版本中,作者用的是传统方法:
VirtualProtect(.text → PAGE_READWRITE) → memcpy → VirtualProtect(.text → PAGE_EXECUTE_READ)
这个操作在沙箱中被标记为”代码生成/载荷解密执行“——因为页面权限从 RW 翻转为 RX 是典型的恶意代码准备行为。
v4.1 版本的改进堪称精妙:用 WriteProcessMemory 代替 VirtualProtect + memcpy。
WriteProcessMemory 在内核态内部临时切换目标页的保护属性,写完后再自动恢复原属性。整个过程在用户态看来只是一个写内存操作——没有显式的 VirtualProtect 调用,没有权限翻转的痕迹。沙箱不会因为它标记出一个独立的可疑事件。
写完后,Loader 再调用 FlushInstructionCache 刷新 CPU 指令缓存,保证新写入的代码能被正确执行。这个 API 本身在很多合法场景下也会用到(JIT 编译、补丁热加载等),单独出现不构成危险信号。
3.3 执行方式:最简单的直接调用
Shellcode 写入到位后,最后一个关键问题是:怎么跳上去执行?
很多 Loader 在这里用 CreateThread、QueueUserAPC、CreateFiber 或者回调 API(EnumSystemLocalesW 等)。这些 API 本身是合法的,但它们出现在”刚刚修改了代码段的内存”之后,就构成了行为链上的一环。
更关键的是,CreateFiber 在内部会创建一个带有 PAGE_GUARD 属性的内存页用于栈保护。PAGE_GUARD 是调试器和逆向工程工具常用的标志,沙箱会将其标记为”反逆向工程”特征。
v4.1 的解决方案出奇地简单:
ShellcodeEntry entry = (ShellcodeEntry)textAddr;
entry(); // 就是直接调用
把 .text 段的地址强转成函数指针,直接 () 调用。没有 Fiber、没有回调、没有新线程——就是一个 C 语言里再正常不过的函数调用。对于所有 API 监控工具来说,这不过是一个程序在执行 msimg32.dll 里的一个函数。
3.4 辅助设计:刻意保持低调
除了核心加载链之外,Loader 还有几个精心的设计细节:
解密缓冲用 HeapAlloc 而不是 VirtualAlloc。 正常的应用程序在大多数时候都使用堆分配(malloc / HeapAlloc),只有少数特殊场景(大块内存映射、JIT 等)才用到 VirtualAlloc。使用堆分配让 Loader 的内存申请行为与普通程序完全一致。
手动解析 IPv4,不用 sscanf。sscanf 来自 CRT 库,会增加导入表。更重要的是,手动编写逐字符解析逻辑减少了库函数调用的特征痕迹,同时也更灵活。
DLL 分离加载。 Loader 本身编译后是一个干净的 EXE——不含任何 Shellcode 数据、不含密钥、不含可疑字符串。真实的 Payload(IPv4 数组 + 密钥)放在一个独立的 helper.dll 里。Loader 启动后先动态加载 DLL,取到数据,立即释放——DLL 只在内存里存在了几秒钟。
这种分离设计意味着:如果杀软扫描 EXE 文件,看到的是一个只依赖 KERNEL32.dll 和 msvcrt.dll 的无害程序。如果它扫描 DLL,看到的是一个导出了三个无害函数(GetPayloadData、GetPayloadSize、GetEncryptionKey)的数据载体。单独看谁都不觉得有问题。
DLL 回退链。msimg32.dll 的 .text 段只有约 6KB。如果 Shellcode 比这个段大,Stomping 就会失败。Loader 内置了回退链:
msimg32.dll → dcomp.dll → dwmapi.dll
每个 DLL 加载后都先检查 .text 段大小。不足就释放后试下一个。这条链路既保证了兼容性,又不会因为重复尝试可疑 DLL 而触发告警——这三个 DLL 都是正常的系统组件。
四、v4.0 → v4.1:一堂真实的沙箱对抗课
这个项目特别有价值的地方,是作者在 README 里详细记录了 v4.0 被沙箱检出的两个具体特征,以及 v4.1 是如何应对的。这对理解现代 EDR 的行为检测原理非常有帮助。
问题一:代码生成行为特征
| | v4.0 | v4.1 |
| — | — | — |
| 写入方式 | VirtualProtect(RW) → memcpy → VirtualProtect(RX) | WriteProcessMemory (内核态内部切换权限) |
| 沙箱视角 | “页面权限从 RW 翻转为 RX → 可疑” | “一次正常的跨进程写入(只不过是写给自己)” |
VirtualProtect 两次调用,尤其是最终翻转为 PAGE_EXECUTE_READ,在 EDR 规则里是典型的”代码生成/载荷解密并执行”信号。WriteProcessMemory 绕过了这个问题——保护属性的修改在内核态完成,用户态的日志里看不到。
问题二:反逆向工程特征
| | v4.0 | v4.1 |
| — | — | — |
| 执行方式 | CreateFiber + EnumSystemLocalesW 回调 | (*shellcodeAddr)() 直接函数指针调用 |
| 沙箱视角 | “Fiber 创建了 PAGE_GUARD 页 → 反逆向” | “一个普通的函数调用” |
CreateFiber 在内部会为纤程的栈创建一个 PAGE_GUARD 保护页——当栈增长触碰这个页时触发异常,由系统处理栈扩展。这本是一种正常的系统机制,但因为调试器也大量使用 PAGE_GUARD,沙箱将其与”反逆向工程”关联了起来。
v4.1 完全放弃了 Fiber 和所有回调 API,回归最基本的函数调用,一次性消除了这个误报来源。
五、如果我是防守方:这套方案的检测难点在哪
理解了 SKRoot 的技术架构之后(哦不,这是 av-evasion-skills),从防御视角审视也很有价值。
静态维度:Loader 编译后不含任何可疑字符串、密钥或 Shellcode 数据。Payload DLL 中的 IPv4 数组从字节特征上无法与正常的网络地址列表区分。三个 Python 脚本是通用的数据处理工具,本身不包含恶意代码。
行为维度:没有 VirtualAlloc、没有显式 VirtualProtect(RX)、没有 CreateThread、没有 Fiber、没有注册表操作、没有文件系统写入。整个流程只有:LoadLibrary(加载合法 DLL)、GetProcAddress(获取函数地址)、HeapAlloc(堆分配)、WriteProcessMemory(往自己的另一个模块写入)、函数指针调用。
这些操作在任何一个普通的 Windows 程序中都会发生。单个看都没有问题,组合在一起也没有形成 EDR 习惯识别的”恶意模式”。
运行时痕迹:Shellcode 执行时所在的地址属于微软签名的系统 DLL。栈回溯中看不到任何非模块地址。HeapAlloc 的缓冲区在 Shellcode 启动前就已经被 SecureZeroMemory 清空并释放了。
现实中的检测思路:更可能生效的方式是盯 FlushInstructionCache + 同一进程中 LoadLibrary 了不常用的 DLL(如 msimg32.dll)的组合——但这种规则会产生大量误报,成本很高。或者从网络层入手,在 Shellcode 真正执行后从流量侧发现 C2 通信。
六、动手搭建:从零生成一个免杀 Loader
以下是一个简化的操作流程,只展示技术原理。
环境准备
- • Windows 10/11 x64
- • Python 3.8+
- • MinGW-w64 GCC
- • 一份 64 位原始 Shellcode(合法的渗透测试或 CTF 场景)
Shellcode 处理流水线
cd scripts/
# 1. 同义指令替换,破坏静态特征码
python shellcode-patch.py shellcode_raw.bin
# 2. XOR 加密(密钥自定,不可为空)
python shellcode-encrypt.py shellcode_patched.bin "Your-Key-Here"
# 3. IPv4 地址伪装
python shellcode-obfuscate-ipv4.py shellcode_encrypted.bin
三步走完,你会得到一个 shellcode_obfuscated_ipv4.c,里面是一行行看起来人畜无害的 IPv4 地址。
组装并编译
将 IPv4 数组和密钥填入 payload_dll.c 中对应的位置,然后编译:
# 编译 Payload DLL
gcc -shared -o helper.dll payload_dll.c -s
# 编译 Loader(GUI 模式,无控制台窗口)
gcc -mwindows -o loader.exe loader_v4.c -s
loader.exe 和 helper.dll 同目录放置即可。执行 loader.exe,整个过程静默完成——没有窗口、没有提示、没有任何用户可见的痕迹。
编译参数中,-mwindows 将程序标记为 GUI 应用,让它启动时不带控制台窗口。-s 去除符号表,减小文件体积的同时也让逆向分析更困难。
七、不是银弹:写在方案的限制上
任何免杀方案都有生命周期。在记录优点之外也值得关注局限性。
时效性窗口。一旦沙箱厂商更新规则——比如将 WriteProcessMemory 对系统 DLL 代码段的写入标记为可疑——这套方案的静态免杀效果就会立刻下降。作者自己在 README 里也直白地写了:免杀技术会随杀软更新而失效,需定期更新策略。
对现代 EDR 的行为模型。本文讨论的方案主要对抗的是基于规则和特征的传统杀软和轻量级沙箱。顶级的 EDR 产品(CrowdStrike、SentinelOne 等)使用更复杂的行为建模,它们不一定依赖单个 API 调用来判断,而是对进程的整体行为树做图分析。这种粒度下,LoadLibrary(msimg32.dll) + WriteProcessMemory + FlushInstructionCache 的组合可能仍然是一个异常信号。
Payload 的下一步行为。Loader 解决的问题是把 Shellcode 安全地”送进去”。但 Shellcode 一旦开始执行(C2 通信、进程注入、凭证窃取等),它的行为仍然可能被运行时监控捕获。免杀 Loader + 普通 Shellcode ≠ 全程无感。
模块大小限制。msimg32.dll 的 .text 段就那么大(约 6KB)。虽然回退链提供了更大的 DLL,但如果 Shellcode 超过几百 KB,Module Stomping 就会变得非常困难。这是一个物理限制。
八、写在最后
bluechips-zhao 的 av-evasion-skills 是一个值得认真研究的技术样本。不仅因为它提供了从 Shellcode 混淆到 Loader 执行的全套工具链,更因为 v4.0 → v4.1 的演进过程真实地记录了一次对抗升级——从被检测到被检出,到识别原因,到找到绕过方案。
这种迭代速度,在杀软引擎更新周期越来越短、AI 驱动检测越来越强的今天,是一种生存必需。
有一个细节让我印象很深:v4.1 去掉了 Fiber、去掉了回调 API、去掉了显式 VirtualProtect,最终剩下的核心执行路径只有三行 C 代码——一个 HeapAlloc、一个 WriteProcessMemory、一个函数指针调用。最干净的方案往往是最少技巧的方案。
这不是”免杀技术的终结版”。这是一个时刻——在这个时刻,防御方的规则还没追上来,而攻击方已经换了路径。
下一次更新,也许就是 v4.2 了。
项目信息
- • 项目地址:
https://github.com/bluechips-zhao/av-evasion-skills- • 作者:bluechips-zhao
- • 版本:v4.1(2026-07-07)
- • 语言:C(Loader)+ Python(脚本)
- • 适用环境:Windows 10/11 x64
本文仅做技术原理分析。本工具仅应用于授权的安全测试、CTF 比赛和渗透评估,严禁用于任何非法用途。
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:网络安全启蒙 《把 Shellcode 藏进微软签名 DLL:一套绕过主流杀软的完整免杀方案是如何工作的》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。











评论