61、.NET逆向(三):强名称绕过、Native Code生成、.NET脱壳
好,咱们继续聊.NET逆向。前两章我们把基础打牢了,今天要啃的这三块——强名称绕过、Native Code生成、.NET脱壳——说实话,是真正区分「脚本小子」和「实战工程师」的分水岭。
我个人习惯把这三件事放在一起讲,因为它们在实际项目中往往是连环出现的。你想想看,一个商业软件,大概率会同时用上强名称签名、加壳保护,甚至把核心逻辑编译成Native Code。你只搞定其中一个,程序照样跑不起来。
一、强名称绕过:别被签名吓住
强名称(Strong Name)是.NET程序集的一种数字签名机制。它的本意是防止程序被篡改——你改了IL代码,签名校验就不通过,CLR直接拒绝加载。
但说实话,强名称的「防篡改」能力在逆向面前,其实挺脆弱的。
核心原理:强名称校验发生在程序集加载时,CLR会检查公钥令牌(Public Key Token)是否匹配。但注意——它只检查签名是否有效,并不检查「这个签名是不是原始作者的」。换句话说,你可以用自己的密钥重新签名。
我在项目中遇到过好几次这种情况:目标程序有强名称,但没做其他保护。我改了IL代码后,程序直接报错「强名称验证失败」。怎么办?
绕过方法一:移除强名称引用
最简单粗暴的方式。用sn -Vr命令可以跳过指定程序集的强名称验证。但注意,这需要管理员权限,而且只对当前机器有效。
sn -Vr *,12345678 // 跳过公钥令牌为12345678的所有程序集
sn -Vr MyAssembly.dll // 跳过指定程序集
绕过方法二:重新签名
更通用的做法。你改完IL后,用sn -k生成一个新密钥,然后用sn -R重新签名。这样程序就能正常加载了。
sn -k newkey.snk
sn -R MyAssembly.dll newkey.snk
避坑指南:我曾经遇到过一个程序,它不光有强名称,还在代码里硬编码了公钥令牌做二次校验。这种情况光重新签名没用,你得把代码里的校验逻辑也patch掉。嗯,这就是为什么我说「强名称脆弱但不可轻视」。
二、Native Code生成:从IL到机器码
有些.NET程序会把核心逻辑编译成Native Code(机器码),而不是IL。为什么要这么做?两个原因:一是性能,二是防逆向。IL太容易被反编译了,Native Code至少能挡住大部分新手。
常见的Native Code生成方式有两种:
| 方式 | 工具/技术 | 特点 |
|---|---|---|
| NGen | 原生映像生成器 | 生成Native Image,但IL仍然存在 |
| IL2Native | CoreRT / NativeAOT | 完全编译为机器码,无IL |
| 混合模式 | ConfuserEx等加壳工具 | 部分方法编译为Native,其余保留IL |
我个人最头疼的是第三种——混合模式。你反编译一看,大部分代码都是IL,但关键函数(比如License校验)被编译成了Native Code。你改不了IL,也看不懂机器码,卡在那里。
如何识别Native Code?
用dnSpy打开程序集,如果某个方法显示为「
注意:Native Code不是不能逆向,只是门槛高。你需要用IDA Pro或Ghidra加载对应的机器码,然后手动分析。我建议先试试能不能找到IL版本的替代方法——有时候程序会同时保留IL和Native两个版本,只是调用逻辑藏得深。
三、.NET脱壳:撕掉保护层
加壳是.NET保护最常用的手段。壳的作用是把原始的程序集加密或压缩,运行时再解密到内存中执行。你直接看文件,看到的只是壳的代码,真正的逻辑被藏起来了。
常见的.NET壳有:
- ConfuserEx:开源,功能强大,支持控制流混淆、常量加密、反调试
- SmartAssembly:商业壳,带字符串加密和资源保护
- DNGuard:国产壳,Native Code + IL混合,脱壳难度较高
- Obfuscar:轻量级混淆器,不算严格意义上的壳
脱壳的核心思路
说白了就一句话:找到原始程序集在内存中的位置,然后dump出来。
具体怎么做?我一般分三步走:
- 定位OEP(原始入口点):壳执行完后,会跳转到原始代码。找到这个跳转,你就找到了原始程序集的起点。
- Dump内存:用Process Hacker或dnSpy的dump功能,把内存中的程序集保存到文件。
- 修复元数据:dump出来的程序集可能不完整,需要用
dnLib或MegaDumper修复。
// 用dnSpy脱壳的典型步骤
1. 用dnSpy加载加壳后的程序集
2. 运行程序,触发壳的解密逻辑
3. 在dnSpy中找到「模块」窗口,右键点击目标模块
4. 选择「保存模块」-> 保存为未混淆的版本
5. 如果失败,尝试用MegaDumper在进程运行时dump
个人经验:我曾经遇到一个ConfuserEx加壳的程序,壳里还嵌了反调试器检测。我开了dnSpy,它直接退出。后来我用x64dbg附加进程,在壳的解密循环里下断点,等它解密完再dump。嗯,折腾了整整一个下午。
四、知识体系总览
下面这张图是我自己整理的,把今天讲的三个知识点串起来了。你对照着看,思路会更清晰。
五、实战中的连环坑
最后聊点实际的。我遇到过最典型的一个场景是这样的:
一个商业软件,用ConfuserEx加壳,壳里嵌了强名称校验,核心算法用Native Code编译。我一开始只想着脱壳,结果dump出来的程序集签名不对,加载失败。我重新签名后,程序倒是能跑了,但核心算法还是Native Code,根本看不懂。
后来我换了个思路:先用sn -Vr跳过强名称校验,然后用dnSpy的「模块保存」功能脱壳,最后用IDA加载Native Code部分,配合动态调试才把算法还原出来。
血的教训:别指望一步到位。逆向工程是层层剥洋葱的过程,每一层都有对应的工具和思路。你卡住了,就退一步想想——是不是顺序搞反了?是不是漏掉了某个保护?
嗯,今天就到这里。这三块内容你消化完,.NET逆向的基本功就算扎实了。剩下的就是多练,多踩坑,多总结。
公众号:蓝海资料掘金营,微信deep3321