用途
.NET 会编译为带完整元数据的 IL,因此未混淆的程序集反编译后接近原始 C#。这使分析中真正的问题不是"怎么读",而是"有什么在挡路"——打包器、混淆器、运行时加载器,或把托管代码藏进本机宿主中的发布模式。
适用时机:file 报告为 PE 且二进制包含 CLR 头或 mscoree.dll;ILSpy 或 dnSpy 能打开程序集但名称被改写或方法体为空;分析 .NET 恶意软件、加载器或红队工具;在没有源码的情况下审查 Windows 桌面或服务应用;从 PowerShell 或 C# 内存加载器恢复逻辑。
不适用:Unity IL2CPP(使用 reversing-unity-il2cpp,C# 已编译为本机代码);Xamarin/MAUI 移动应用(先用 reversing-xamarin-maui 提取容器,再回到本技能处理 DLL);疑似恶意软件在未隔离前(先用 analyzing-malware);Go 或 Rust 二进制(使用对应技能)。
确认是否为托管程序集
Linux 上可用 file target.exe(报告 "Mono/.Net assembly"),或 rg -a -o 'BSJB|mscoree\.dll|System\.Private\.CoreLib' target.exe | head。Windows 上可用 ildasm:ildasm /text target.exe | head。可选头数据目录 14 中存在 CLR 头是真正的判据。
根据发布模式决定处理方式:
- 引用 mscorlib/System.Private.CoreLib 的托管 PE(框架依赖)——直接反编译
- 内嵌托管包的本机 PE(单文件发布)——先提取包
- 完全没有 IL 元数据的本机 PE(NativeAOT)——不属于 .NET 逆向,使用 analyzing-binaries
- 无入口点的 .dll(库)——反编译,在别处查找调用方
单文件发布时,托管程序集作为包追加在末尾,可用包提取器提取,或在清单头处切割:ilspycmd --list-bundle target.exe 2>/dev/null || binwalk -Me target.exe。
反编译
ilspycmd target.dll -o ./decompiled -p(-p 写出项目结构)。图形界面可用 dnSpyEx(维护中的分支)、ILSpy、dotPeek。dnSpyEx 还能调试和编辑程序集,因此是默认选择。可用 rg -n 'https?://|ConnectionString|ApiKey|password|Convert\.FromBase64String' ./decompiled | head -40 搜索敏感信息。阅读顺序:入口点,然后任何以产品命名的类型,再是任何引用网络、加密或进程 API 的内容。跳过生成类型(<>c__DisplayClass、<Module>),除非在跟踪特定闭包。
先识别混淆器再对抗它
先命名工具再处理成本低,否则成本高。可用 detect-it-easy target.exe 或 DIE 的命令行 diec;查找 ConfuserEx、.NET Reactor、SmartAssembly、Eazfuscator、Babel、Dotfuscator、Agile.NET、Obfuscar。也可用 rg -a -o 'ConfuserEx|Reactor|SmartAssembly|Eazfuscator|Babel|DotNetPatcher' target.exe。
de4dot 可自动处理经典混淆器:de4dot target.exe -o cleaned.exe,检测失败时用 de4dot -p cr target.exe 强制指定 profile。针对 ConfuserEx 应使用维护中的解包分支,因为 de4dot 内置支持早于后续 ConfuserEx 版本。
各混淆层的外观与处理:
- 名称改写:Class1.method_3、unicode/不可见名称——属表面问题,阅读时重命名即可,de4dot 可恢复部分
- 字符串加密:到处是 <Module>.Decrypt(12345)——运行解密器(de4dot),或动态调用该方法
- 控制流平坦化:基于状态变量的巨大 switch——用 de4dot 的 CFG 清理,或动态阅读
- 代理/委托调用:每次调用都经过辅助方法——用 de4dot 去虚拟化
- 防篡改/反调试:在 dnSpy 调试器下失败——打补丁绕过检查,或在解密后转储
- 虚拟化(Agile.NET、KoiVM):没有可识别的 IL——用去虚拟化工具(KoiVM 用 OldRod)或动态分析
- 本机桩包裹 IL:托管代码在运行时解密——从内存转储,这是可靠答案
从内存转储是通用逃生通道。无论什么打包器,CLR 最终必须持有真实 IL 才能执行。让它加载,然后转储:pe-sieve /pid <pid> /dmode 3(MegaDumper / ExtremeDumper / pe-sieve 可从运行进程中转储已加载的托管模块),然后反编译转储。这与本机恶意软件打包的"让它自己解包"模式相同;运行任何敌意样本前请参考 analyzing-malware 的隔离措施。
调试与打补丁
dnSpyEx 可在反编译的 C# 中设置断点、检查局部变量、编辑方法体并保存修改后的程序集。这非常强大——可以更改许可证检查或验证结果并立即重跑。
打补丁流程:在 dnSpyEx 中编辑 IL 或反编译的 C#,保存程序集,重新运行。保存时强名称签名会失效——要么移除强名称要求,要么如果应用会校验自身哈希,则挂钩该检查。对于应用自身会校验的已签名程序集,打补丁通常不是正确工具,应在运行时挂钩。
.NET 恶意软件要点
.NET 被大量用于加载器和商品化 RAT,其痕迹很独特:
- Assembly.Load(byte[])——在内存中解密的第二阶段。找到解密例程,离线运行它,分析得到的程序集。这是最常见的 .NET 恶意软件模式
- Activator.CreateInstance 加反射来隐藏真实调用图
- 修补 AmsiScanBuffer / EtwEventWrite——搜索其前置的字节模式或 GetProcAddress 调用
- P/Invoke 声明([DllImport])暴露本机能力:VirtualAlloc、CreateRemoteThread、SetWindowsHookEx
- 资源和附属程序集承载加密载荷——始终枚举资源节
可用 rg -n 'DllImport|Assembly\.Load|CreateInstance|FromBase64String|RijndaelManaged|AmsiScanBuffer' ./decompiled | head -30 和 ilspycmd --list-resources target.dll。提取出的第二阶段回到本技能处理;能力与 IOC 输出交给 analyzing-malware,检测内容交给 engineering-detections。
应拒绝的借口
"名称无意义,太混淆了。" 名称改写是最弱的一层。结构、字符串和 P/Invoke 都完好。
"de4dot 失败了,所以无法去混淆。" 先识别混淆器并使用其专用解包器,或从内存转储。
"这是本机 EXE,所以不是 .NET。" 检查单文件发布,以及包裹托管代码的本机桩。
"字符串已加密。" 解密器就在程序集里。调用它。
"我反编译了加载器,这就是恶意软件。" 加载器只是第一阶段。找到 Assembly.Load 并恢复它加载的内容。
"我会手动阅读平坦化的控制流。" 运行清理,或在 dnSpyEx 中调试并观察真实路径。
参考
analyzing-malware——隔离、能力模型与 IOC analyzing-binaries——NativeAOT 与本机桩分析 reversing-unity-il2cpp、reversing-xamarin-maui——其他 .NET 容器 engineering-detections——把恢复出的行为转化为规则 dnSpyEx、ILSpy/ilspycmd、de4dot、Detect It Easy、pe-sieve、OldRod