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]」,那就是了。你双击进去,看到的不是IL指令,而是一堆十六进制字节。

注意:Native Code不是不能逆向,只是门槛高。你需要用IDA Pro或Ghidra加载对应的机器码,然后手动分析。我建议先试试能不能找到IL版本的替代方法——有时候程序会同时保留IL和Native两个版本,只是调用逻辑藏得深。

三、.NET脱壳:撕掉保护层

加壳是.NET保护最常用的手段。壳的作用是把原始的程序集加密或压缩,运行时再解密到内存中执行。你直接看文件,看到的只是壳的代码,真正的逻辑被藏起来了。

常见的.NET壳有:

  • ConfuserEx:开源,功能强大,支持控制流混淆、常量加密、反调试
  • SmartAssembly:商业壳,带字符串加密和资源保护
  • DNGuard:国产壳,Native Code + IL混合,脱壳难度较高
  • Obfuscar:轻量级混淆器,不算严格意义上的壳

脱壳的核心思路

说白了就一句话:找到原始程序集在内存中的位置,然后dump出来

具体怎么做?我一般分三步走:

  1. 定位OEP(原始入口点):壳执行完后,会跳转到原始代码。找到这个跳转,你就找到了原始程序集的起点。
  2. Dump内存:用Process Hacker或dnSpy的dump功能,把内存中的程序集保存到文件。
  3. 修复元数据:dump出来的程序集可能不完整,需要用dnLibMegaDumper修复。
// 用dnSpy脱壳的典型步骤
1. 用dnSpy加载加壳后的程序集
2. 运行程序,触发壳的解密逻辑
3. 在dnSpy中找到「模块」窗口,右键点击目标模块
4. 选择「保存模块」-> 保存为未混淆的版本
5. 如果失败,尝试用MegaDumper在进程运行时dump

个人经验:我曾经遇到一个ConfuserEx加壳的程序,壳里还嵌了反调试器检测。我开了dnSpy,它直接退出。后来我用x64dbg附加进程,在壳的解密循环里下断点,等它解密完再dump。嗯,折腾了整整一个下午。

四、知识体系总览

下面这张图是我自己整理的,把今天讲的三个知识点串起来了。你对照着看,思路会更清晰。

.NET逆向实战 强名称绕过 sn -Vr 跳过验证 sn -k / -R 重新签名 Patch二次校验逻辑 Native Code生成 NGen / IL2Native / 混合 dnSpy识别 [Native Code] IDA Pro / Ghidra 分析 .NET脱壳 定位OEP → Dump内存 dnSpy / MegaDumper dnLib 修复元数据 三者常连环出现:强名称 → Native Code → 加壳 逐个击破,才能拿到完整的原始逻辑

五、实战中的连环坑

最后聊点实际的。我遇到过最典型的一个场景是这样的:

一个商业软件,用ConfuserEx加壳,壳里嵌了强名称校验,核心算法用Native Code编译。我一开始只想着脱壳,结果dump出来的程序集签名不对,加载失败。我重新签名后,程序倒是能跑了,但核心算法还是Native Code,根本看不懂。

后来我换了个思路:先用sn -Vr跳过强名称校验,然后用dnSpy的「模块保存」功能脱壳,最后用IDA加载Native Code部分,配合动态调试才把算法还原出来。

血的教训:别指望一步到位。逆向工程是层层剥洋葱的过程,每一层都有对应的工具和思路。你卡住了,就退一步想想——是不是顺序搞反了?是不是漏掉了某个保护?

嗯,今天就到这里。这三块内容你消化完,.NET逆向的基本功就算扎实了。剩下的就是多练,多踩坑,多总结。


公众号:蓝海资料掘金营,微信deep3321