资讯动态

NoFuserEx 反混淆实战:针对 .NET FuserEx 加固的定向脱壳与还原

发布时间:2026/10/20 14:58:16 来源:尧图企业网站定制
简介NoFuserEx 反混淆工具面向 .NET 程序逆向与安全分析场景专门用于清理 Fuser 等混淆器生成的类名、方法调用及控制流干扰还原出更易读的代码结构适合安全测试工程师、恶意样本分析人员以及有一定 .NET 基础的程序员。压缩包共 10 个文件以 exe 主程序为核心配合 dnlib 程序集解析库、pdb 调试符号、xml 接口文档与 config 配置各文件职责清晰既支持直接运行反混淆流程也便于在调试时追踪符号转换和依赖解析细节整体体积仅 1.76MB轻量便于携带。目前已有 356 人学习使用。通过实际操作这套工具读者能直观感受 Fuser 混淆特征掌握程序集符号还原、流程梳理等关键思路并将相同方法应用到其他 .NET 混淆样本的分析中去对日常安全研究和恶意代码排查具有较强的参考价值。1. NoFuserEx 反混淆工具照着 FuserEx 实现逆向出来的专用脱壳机NoFuserEx 是一款专门对付 .NET 程序集 FuserEx 加固的反混淆工具它不是通用脱壳器而是围绕 FuserEx 的混淆模板逐层拆出来的定向工具。典型场景是拿到的 exe 或 dll 被 FuserEx 处理过dnSpy 里方法名全是乱码逻辑被塞进巨大的 switch 分发器字符串字面量全部消失手工分析无从下手。跑一遍 NoFuserEx 后方法名、字符串、控制流会被还原到接近原始 C# 代码的形态。适合恶意样本分析、老程序集功能还原、授权校验逻辑研究这三类从业者。前提是你至少会用 dnSpy 打开程序集、能看懂 IL 基础指令。2. 先看清对手FuserEx 的混淆特征与 NoFuserEx 的破解思路2.1 在 dnSpy 里识别 FuserEx 的六类保护FuserEx 是一套开源 .NET 混淆器它的加固组合基本固定为六类。第一类是字符串加密所有字面量被抽离真正使用字符串的代码位置替换成对解密函数的调用解密函数每次运行时从资源或静态字段里恢复原文。第二类是控制流混淆普通 if/for/while 被拍平成一个大 switch每个 basic block 前面插入计算下一个状态的指令序列看起来像无意义运算加 switch 跳转。第三类是符号重命名方法名和字段名改成 Unicode 乱码或者同名重复字符dnSpy 的搜索功能基本报废。第四类是垃圾代码注入方法体内塞入不可达分支和无效计算目的是干扰人工单步。第五类是反调试与反篡改程序集启动时会校验自身哈希附加调试器就退出。第六类是常量加密整数常量被加密存储使用前用恢复函数还原。在 dnSpy 里识别这些特征有固定的套路。字符串 stub 型方法通常只有三四条 IL 指令加载参数、调用解密器、返回方法名随机但方法体极短。控制流混淆最明显整个方法看不到直接的 compare 和 branch全是 ldsfld/ldloc 加 switch代码行数比原始版本膨胀五到十倍。垃圾代码则表现为永远取不到的立即数和多余的 add/sub 组合。识别这些特征的意义在于你没搞清楚对面用的是哪种保护之前任何反混淆参数都是蒙着打的。2.2 为什么 NoFuserEx 选择定向拆弹而不是通用扫描通用反混淆插件走的是启发式扫描路线靠方法体形态猜测这里像混淆代码遇到模板会变的混淆器就很容易翻车。FuserEx 每次迭代都会调整生成代码的模板启发式规则一版跟不上就会把正常方法误判成混淆代码或者解密逻辑执行一半抛异常产出又脏又不能用。NoFuserEx 的处理思路完全不同它维护一个针对 FuserEx 模板的规则库先判断这段代码是 FuserEx 哪个模块生成的再套对应的恢复逻辑。规则库是特征驱动而不是形态猜测所以误报率低得多。这个设计带来的实际差异我用一个样本说明。同一个被 FuserEx 处理过的程序集通用插件跑完后字符串还是乱码、switch 还在NoFuserEx 跑完后字符串还原成明文switch 分发器被折叠回普通分支。代价是它只认 FuserEx 的模板换一个混淆器就只能干瞪眼。所以选型上我的建议很直接如果你手上的样本确认是 FuserEx 加固直接用 NoFuserEx 效率最高如果样本来源混杂、不确定混淆器类型先用 DetectItEasy 之类的工具做类型识别再决定。保护类型dnSpy 里的表现NoFuserEx 的处理方式字符串加密方法体只有调用解密器并返回识别 stub批量还原并回写明文控制流混淆大 switch 加状态变量符号执行状态计算折叠回原分支符号重命名Unicode 乱码方法名按调用关系重命名可读名称垃圾代码不可达分支、无效运算活性分析后删除死代码反调试/反篡改启动时自我校验剥离校验并修正元数据常量加密整数全是加密值还原常量表并替换引用还要注意一点规则库是跟着 FuserEx 版本走的。你手里的混淆器版本新、NoFuserEx 规则库旧识别就会失败。这时先跑一遍检测模式输出里如果有大量 unknown 标记说明规则库需要更新或者样本不是标准模板生成的属于混合加固得先处理外层再回来处理 FuserEx。3. 跑通第一遍NoFuserEx 的安装、命令行参数与典型用法3.1 运行环境与准备工作NoFuserEx 以命令行工具形式分发主程序依赖 .NET 运行时资源包通常包含主程序文件、规则库文件和一个用于运行时解密字符串的辅助程序集。我一般把整个解压目录放在纯英文路径下比如 D:\tools\NoFuserEx不要放进带空格和中文的路径。原因是后续处理大量样本时你会写批处理脚本中文路径在部分 Windows 编码环境下会让文件路径解析出错属于能提前躲掉就别硬踩的坑。验证环境是否就绪直接跑一次版本输出NoFuserEx.exe -v正常会输出程序版本号和规则库版本号规则库通常以日期格式命名方便你判断新旧。如果提示缺少运行时先去装对应版本的 .NET Desktop Runtime装完再回来。这一步卡住的多数原因是电脑只装了 .NET Framework 而没装新版 .NET 运行时两者的关系在 Windows 上经常被搞混装错版本也会报同样的错。3.2 命令行参数说明NoFuserEx 的参数设计比较直白核心参数如下表。日常分析最常用的是 -i、-o、-d 三个其余保持默认即可。参数作用默认值备注-i输入程序集路径无必填支持 exe/dll-o输出程序集路径输入路径加 _clean 后缀手动指定避免覆盖原文件-d启用的反混淆模块strings,ctrlflow,junk模块间用逗号分隔-m控制流还原最大递归深度5深度越大耗时越长-k是否保留解密 stubfalse调试规则时建议开 true-p自定义规则库路径内置规则库规则库更新时用--platform目标平台autoauto/x86/x64/anycpu-v输出版本信息无诊断用-d 参数需要特别说明它控制的是这一轮做哪些还原。字符串解密和控制流还原是最常用的两个模块垃圾代码清理默认开启但影响不大。如果你只想要快速的字符串明文来做关键词搜索可以只开 strings速度会快很多如果要做完整功能还原再开全量。参数 -m 只在开 ctrlflow 时生效递归深度调高会显著增加耗时但某些复杂方法深度不够时还原不干净需要按样本实际情况反复试。3.3 第一次完整反混淆命令与产物检查拿一个被 FuserEx 处理过的样本做完整还原我常用的命令是这样NoFuserEx.exe ^ -i C:\samples\target.exe ^ -o C:\samples\target_clean.exe ^ -d strings,ctrlflow,junk,names ^ -m 5 ^ --platform auto逐段解释一下。第一行指定输入样本第二行指定输出路径显式写输出路径是为了防止工具自动覆盖原样本原始样本一旦被覆盖就没有后悔药了。第三行的 -d 开启了四个模块strings 做字符串还原ctrlflow 做控制流折叠junk 做垃圾代码删除names 做可读重命名。第四行的 -m 5 表示控制流还原时递归分析状态计算的最大深度是 5对普通方法足够遇到特别复杂的混淆方法可以调到 8但耗时翻倍。第五行的 --platform auto 让工具自己读程序集平台标识避免人为指定错平台导致还原后程序集没法正常加载。跑完之后别急着关命令行看日志里的统计信息。重点关注两个数字还原了多少个字符串常量、折叠了多少个 switch 分发器。如果字符串还原数是 0说明规则没有命中这个样本可能不是 FuserEx 标准模板。然后用 dnSpy 打开输出文件检查一个已知有字符串的方法和原始版本对比。我习惯先看入口方法原始版本里是 stub 加 switch还原后应该能看到直接的字符串赋值和 if/else 分支。这一步确认通过再继续往下分析。提示第一次跑全量模块之前建议先用 -d strings 单独跑一遍输出到一个临时文件确认字符串解密正常后再叠加 ctrlflow。这样定位问题时能快速排除到底是哪个模块弄坏了产物。4. 核心反混淆流程从字符串解密到控制流还原的完整链路4.1 保护类型判定规则库如何命中混淆代码NoFuserEx 的第一步不是直接还原而是对整个程序集做一轮扫描按规则库的模板匹配每个类型和方法。规则库里每个规则描述的是 FuserEx 某类混淆代码的 IL 特征比如字符串 stub 的规则是方法体指令数小于 6、存在对标记为解密器的方法的调用、返回值类型为 string。命中规则后工具会把这些位置记录成待处理项并标注所属模块类型。这一步相当于给整个程序集做了一次体检体检报告在日志里按类型汇总。用 Mono.Cecil 写一个简化版的特征扫描大致是这个思路using Mono.Cecil; using Mono.Cecil.Cil; var asm AssemblyDefinition.ReadAssembly(args[0]); int stubCount 0; foreach (var type in asm.MainModule.Types) { foreach (var method in type.Methods) { if (!method.HasBody || method.Body.Instructions.Count 6) continue; foreach (var ins in method.Body.Instructions) { if (ins.OpCode OpCodes.Call ins.Operand is MethodReference callee callee.ReturnType.FullName System.String) { stubCount; break; } } } } Console.WriteLine($candidate string stubs: {stubCount});这段代码的过滤条件是方法体指令数少于 6 且有调用 string 返回值方法的 call 指令。指令数上限是经验值FuserEx 生成的字符串 stub 普遍很短如果方法体超过 6 条指令还命中 call很可能是正常代码而非混淆 stub。真正工具的实现比这复杂得多会验证 callee 的声明类型是否符合解密器特征还会检查调用约定不是只看返回类型。扫描通过后待处理项进入下一步的还原队列。4.2 字符串解密静态模拟与运行时调用的取舍字符串还原有两条主流路线。静态模拟是在不加载程序集的情况下用解释器执行解密方法的 IL把加密数据喂进去算出明文。优点是干净、不触发反调试缺点是遇到依赖外部状态的解密器时会算错。运行时调用则是在受控的 .NET 进程里加载程序集直接调用解密方法拿结果优点是准确缺点是可能触发程序集里的反调试逻辑。NoFuserEx 默认的策略是混合模式先尝试静态模拟模拟失败的方法标记出来再用运行时调用补一轮。我一般建议保持默认不要把运行时调用整个关掉。实际分析中遇到过一种典型情况某个字符串解密方法读取了一个静态字段静态字段的值在类型初始化器里被其他函数改过静态模拟算出来全是乱码运行时调用一次就全对了。这属于黑匣子行为不实际跑一遍很难提前判断。4.3 控制流还原从 switch 分发器回到普通分支控制流混淆的还原是整个流程里技术含量最高的一步。FuserEx 的混淆方法通常长这样一个入口加载代码、一个状态变量、一个巨大 switch每个 case 里是这个 basic block 的实际逻辑block 最后一段代码计算下一个状态并跳回 switch。还原思路是符号执行状态变量从入口开始跟踪状态变量的所有赋值路径把状态值到 case 块的映射整理出来再按顺序重构分支结构。做法上NoFuserEx 对每个命中 ctrlflow 规则的方法做一次模拟执行。模拟器只跟踪状态变量和少数几个关键值不完整执行所有指令遇到 call 指令直接跳过。得到状态转移表之后把 switch 折叠回条件跳转。有个细节值得注意原始代码里有些分支是故意不可达的还原时保留这些分支会让方法体仍然很难看所以工具会结合活性分析把不可达 case 直接剪掉。剪掉的前提是确认不可达判断错了就会丢代码这也是后面排查章节会讲到的常见翻车点。4.4 元数据重建还原后最后一步不能省字符串还原和控制流折叠都会改变方法的指令原有元数据的引用关系随之失效。NoFuserEx 最后会调用 Mono.Cecil 重新构建程序集元数据重算方法体大小、修正分支目标、更新异常处理块的偏移、刷新 token 表。这步如果做不干净输出程序集会直接报 BadImageFormatException。判断元数据重建是否成功的快捷方式是用 ilspycmd 把输出文件反编译成 C# 源码能正常出代码且没有格式错误基本就说明元数据是健康的ilspycmd -p C:\samples\target_clean.exe C:\samples\target_clean.cs如果反编译时抛异常多半是元数据重建出了问题而不是前面的还原逻辑错了。这时回到原样本重新跑把 -k 设为 true 保留解密 stub看问题是否消失用来缩小排查范围。5. 常见问题与避坑输出损坏与还原错误的两类高频翻车点5.1 程序集输出异常打不开、报格式错误现象一NoFuserEx 跑完没有任何报错但输出文件双击启动直接崩溃dnSpy 打开提示 BadImageFormatException。原因是元数据重建环节没处理干净字符串还原后引用的 token 和新的方法体大小不匹配。解决先确认你是否开了 -k true 保留解密 stub。保留 stub 时工具不会重写解密调用点只追加结果程序集结构变化小很多元数据问题会消失。如果保留 stub 后能打开再逐轮关闭模块定位是哪个还原步骤弄坏了结构。现象二输出文件能打开但运行时提示强名称签名验证失败。原因是原程序集带强名称签名混淆器在签名后修改了代码反混淆再次修改代码后签名自然失效。解决这属于预期行为不是工具 bug。分析场景下直接跳过验证即可需要交付时用重新签名工具对输出文件重新签名或者修改程序集属性去掉强名称再重新处理。现象三日志显示处理结束但生成的文件大小几乎没变。原因通常是规则库没有命中任何保护模块工具做了空跑。解决先用 -v 看规则库版本再用 DetectItEasy 确认样本确实是 FuserEx 加固如果确认是但没命中多半是混淆器版本太新等规则库更新或者把样本的相关方法导出出来供规则库补充。不要反复调参数硬试浪费时间也得不到好结果。5.2 还原结果错误乱码、丢代码、switch 没折叠现象四字符串解密后得到的结果是乱码但没有报错日志里能看到 static-sim fail 的标记。原因是该解密方法依赖静态状态静态模拟没能拿到正确输入。解决把失败的方法加入运行时调用白名单重新跑一遍。这种问题排查起来很费时间我的做法是第一次就跑完整的混合模式不要贪图速度只开静态模拟。现象五控制流还原后方法体变短了但和原始逻辑对比发现少了真实代码。原因是不可达分支判断过于激进把还活着的 case 当垃圾剪掉了。解决把 -d 里的 ctrlflow 模块单独拆出来重跑同时把 -m 的递归深度从 5 调到 3降低模拟器的探索范围减少误判。如果深度调低后逻辑恢复说明原始深度下模拟器吃进了过多无关路径。这是 ctrlflow 模块最常见的坑原则是先还原正确再追求美观。注意还原产物只要出现过一次丢代码就别拿它当最终分析基线。宁可退回原始样本重新处理也不要试图在损坏的产物上继续分析后面所有结论都会不靠谱。6. 进阶用法批量处理、与 dnSpy 联动和三层结果验证6.1 批量处理与还原结果验证实际分析很少只处理一个样本NoFuserEx 支持目录级批量处理配合批处理脚本可以一次跑完整个样本集。常见做法是写一个 for 循环遍历目录下的 exe 和 dll逐个处理并输出日志for %%f in (C:\samples\*.exe C:\samples\*.dll) do ( NoFuserEx.exe -i %%f -o %%f_clean.exe -d strings,ctrlflow,junk ^ C:\samples\process.log 21 )这段脚本会把每个样本的处理结果追加到同一个日志文件跑完后用 findstr 统计每条日志里的还原统计行自动筛选还原率异常的样本。批量跑之前我强烈建议先拿两个已知样本验证命令参数确认输出正确再放开全量否则一批样本全被跑坏重来成本很高。还原完对不对不能只看能不能打开。我一般按三个层面验证第一层是结构验证用 ilspycmd 把输出反编译成 C#确认无语法错误、异常处理块完整第二层是行为对比把原样本和还原样本分别在沙箱里跑一遍对比启动参数、网络请求、文件读写路径这些可观测行为是否一致第三层是逻辑抽查挑两三个关键方法人工比对还原前后 IL确认 switch 折叠后的分支和原逻辑等价。三层都过了我才会把还原产物当作分析基线。6.2 与 dnSpy 联动还原产物当对照基线单个方法还原后看不懂时我的习惯是把 NoFuserEx 的还原结果作为 dnSpy 的对照基线左边窗口打开原始样本右边打开还原样本同步滚动到同一个方法用 dnSpy 的 IL 视图对照看。原始样本里是乱码字符串和 switch还原样本里是明文和分支两边一对照混淆器做了什么手脚就一目了然。需要保存还原后程序集时确保输出路径不要覆盖原样本。我吃过一次亏当时为了让 dnSpy 直接加载输出文件把输出路径写到了原样本同目录同名原样本被覆盖后才想起后面还要对照只能重新去备份目录里拿回副本。从那以后我每次批量处理前都强制把输出目录单独建一个文件夹原样本目录一律只读。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑