本帖最后由 baoshan685 于 2026-7-10 11:35 编辑
场景:微软应用商店软件逆向。目标:去除应用广告 + 解锁会员功能。
目标:××.exe(××公司截图软件,MSIX 包,.NET 4.5.1 WPF 应用,Microsoft Store 签名)
结果:通过 Mono.Cecil IL Patch + MSIX Sideload 部署,成功解锁全部 VIP 功能。
耗时:约 3 小时(含多次失败和工具调研)
引子、关于fable5的好奇心
前阵子fable5不是被封禁了吗,于是我就想,他的强网络安全能力有多强呢,能让国家维度去禁掉?
然后我在应用市场下了个付费软件,丢给 Claude code去搞。
![]()
对,你没看错,是我让 AI 去搞。
不是让 AI 帮我搞,是让 AI 自己搞。
整个活儿大概花了三个小时,不到20次对话,大概2800wtoken,就完成了。
中间踩了一堆坑。最后的结局,会员全开,广告消失,工具栏所有锁着的按钮全亮起来。
讲下大概流程(感觉AI能力稍微逆天,所以只讲技术细节,不讲提示词工程了)。
一、阶段一:失败的 WPeGPT 自动化(识破目标真相)
起手即翻车
按惯例调用 wpegpt-analyzer skill 启动 IDA + WPeGPT 分析 ××.exe。结果:[!] WPeServer did not start within 240s.
排查过程(依次踩坑)
| WPeServer 启动失败 | IDA 9.4 许可证无效 | 用户手动修复 | | 仍失败 | WindowsApps 沙箱限制 | 复制到 %TEMP% | | 仍失败 | Python 3.14 与 IDA PySide 不兼容 | idapyswitch 切到 Python 3.11 | | 仍失败 | Python 3.11 缺 openai/httpx | pip install --break-system-packages | | 仍失败 | IDA -A 模式自动退出 | 编写 IDAPython 包装脚本 | | 终于启动 | idaapi.get_inf_structure 在 IDA 9.4 已移除 | 改用 ida_ida.inf_* 系列 | | Controller 退出码 -1 | subprocess.PIPE 缓冲 + bash SIGINT | 改用 Python 内部 Tee |
关键收获
WPeGPT 终于跑完,结论却毫无价值:
该二进制文件的自动化静态分析未能提取出任何有效特征(0个字符串,0个被分析的函数)...伪装成截图工具且采用重度加壳隐藏所有代码意图...
实际上 IDA 把它识别为 Microsoft.Net assembly。所有 2279 个"函数"都是 .NET metadata 桩,IDA 的反汇编对 .NET 完全无效。模型误判为"加壳恶意软件"。
教训:先看 PE header 的 COM Descriptor(Data Directory 14),是 .NET assembly 就直接换工具。
二、阶段二:纯 Python 解析 .NET 元数据(被一个 0-based tag 坑了 2 小时)
由于系统没有 dnSpy/ILSpy/ildasm,决定用 Python 自己解析 CLI metadata。
ECMA-335 标准的小坑
按 ECMA-335 II.24.2.6 实现了 coded index 解码:def decode_coded_idx(value, tables_list, tag_bits):
mask = (1 << tag_bits) - 1
tag = value & mask
row = value >> tag_bits
if tag == 0 or tag > len(tables_list): return None, row
return tables_list[tag - 1], row # ← 1-based 假设
问题:MemberRef 0x006B get_IsVip 的 Class 字段 raw value=0x0B69 解码出 tag=1, row=365,但 TypeDef 表只有 356 行 → 越界。
排查两小时后顿悟:ECMA MemberRefParent 实际是 0-based tag(不是 1-based)。修正:if tag >= len(tables_list): return None, row
return tables_list[tag], row # 0-based
解析成果
成功提取完整 metadata:
- ××.Models.dll → User 类:包含 IsVip、IsPermanentVip、VipType、IsTrialVip 等属性
- ××.exe:15 处方法调用 User.IsVip(含 MainWindow.ShowLimitToast、CaptureBase.ToggleWatermark/LongScreenShot/ScreenRecorder 等)
- ××.AppBase.Account.dll → AccountManager:含 get_IsLogin、get_LoginState、get_User
三、阶段三:IL 字节级 Patch(最惨烈的一次翻车)
完美设计的 6 个 Patch
精确定位每个方法的 IL 字节偏移,设计最小化 patch:
| 方法 | 文件偏移 | 原 IL | 新 IL | | User.get_IsVip | 0x120F | 02 7b d1 00 00 04 2a | 17 2a 00 00 00 00 00 | | User.get_IsPermanentVip | 0x1244 | (59B) | 17 2a + 57 nop | | User.get_IsTrialVip | 0x144F | 02 7b df 00 00 04 2a | 16 2a 00 00 00 00 00 | | User.get_VipType | 0x11E7 | 02 7b d0 00 00 04 2a | 1f 0d 2a 00 00 00 00 | | AccountManager.get_LoginState | 0x27F | 02 7b 07 00 00 04 2a | 18 2a 00 00 00 00 00 | | AccountManager.get_IsLogin | 0x300 | (21B) | 17 2a + 19 nop |
字节完全合法:ldc.i4.x; ret; nop... 栈平衡、max_stack=8(tiny header 默认)、长度匹配。
部署的极限拉扯
| 方案 | 失败原因 | | 直接替换 WindowsApps DLL | WindowsApps WRP(Windows Resource Protection)内核级保护,ACL 修改无效 | | MSIX Sideload(首次) | -PreserveApplicationData 不支持 Store 包 | | Sideload(v2) | 0x80073CFF - Store 源保护 | | Sideload(v3) | 0x80080204 - Publisher CN 格式不符 X.500 | | Sideload(v4) | 0x80073CFF - 复制目录带了 AppxSignature.p7x | | Sideload(最终版) | 删除 AppxSignature.p7x + AppxBlockMap.xml,改 Identity Name + Publisher → 成功 |
最终成功路径# 1. 复制 MSIX 安装目录到普通位置(绕过 WRP)
Copy-Item -Recurse "C:\...\WindowsApps\××..." "C:\...\msix_mod"
# 2. 删除 Store 标记文件
Remove-Item "AppxSignature.p7x", "AppxBlockMap.xml", "AppxMetadata"
# 3. 修改 Identity Name + Publisher(断开 Store 血缘)
Identity Name="××.VIPMod.112366801E824"
Publisher="CN=VIPModSideload, O=CTF" # 注意逗号后必须空格
# 4. 启用 sideload 注册表
HKLM:\...\AppModelUnlock\AllowAllTrustedApps = 1
# 5. 注册(不需要签名)
Add-AppxPackage -Register AppxManifest.xml
部署成功 ≠ Patch 生效
应用启动后,"开通会员"提示仍在、登录按钮仍在、点击工具栏无反应。
用 PowerShell 反射测试:IsVip = ERROR: 程序运行时检测到无效的程序 (InvalidProgramException)
根因:.NET Framework 4.x 的 CLR IL verifier 拒绝手动修改的字节流。即使 IL 看起来完全合法,CLR 内部还会校验 metadata 一致性、stack 计算等更深层的属性,手工 patch 无法保持这些不变量。
四、阶段四:Mono.Cecil 救场(完美收官)
下载 Mono.Cecil(nuget 702KB,秒下):Add-Type -Path "Mono.Cecil.dll"
Add-Type -TypeDefinition @'
using Mono.Cecil;
using Mono.Cecil.Cil;
public static class CecilPatcher {
public static void PatchMethod(TypeDefinition type, string methodName,
Tuple<OpCode, int?>[] instrs) {
foreach (var m in type.Methods) {
if (m.Name != methodName) continue;
m.Body.Instructions.Clear();
m.Body.Variables.Clear();
m.Body.ExceptionHandlers.Clear();
var il = m.Body.GetILProcessor();
foreach (var ins in instrs) {
if (ins.Item2.HasValue && ins.Item1 == OpCodes.Ldc_I4_S)
il.Emit(ins.Item1, (sbyte)ins.Item2.Value);
else if (ins.Item2.HasValue)
il.Emit(ins.Item1, ins.Item2.Value);
else
il.Emit(ins.Item1);
}
m.Body.MaxStackSize = 1;
m.Body.InitLocals = false;
}
}
}
'@ -ReferencedAssemblies @("Mono.Cecil.dll", "System.Core.dll")
关键差异
| 维度 | 手动字节 Patch | Mono.Cecil | | IL 字节 | 合法 | 合法 | | Metadata 一致性 | 破坏 | 自动重算 | | Method body RVA | 不变 | 重新对齐 | | Stack size 计算 | 手动 | 自动 | | StrongName 重算 | N/A | 自动 | | CLR Verifier | 拒绝 | 通过 |
反射验证
[Asm] 纯文本查看 复制代码 用 PowerShell 反射加载 Patch 后的 DLL,实例化 User 类后逐一读取属性:IsVip = True,IsPermanentVip = True,VipType = 13(任意非零值),IsTrialVip = True。全部返回预期值,Patch 生效。
部署后应用启动,所有功能解锁。
![]()
![]()
五、技术总结与 takeaway
1. MSIX 应用 patch 的可行路径 ┌──────────────────────────────────┐
│ .NET MSIX 应用(Store 签名) │
└──────────────────────────────────┘
↓ 修改文件
┌──────────────────────────────────┐
│ WRP / Code Integrity 内核拒绝 │ ← 直接替换:失败
└──────────────────────────────────┘
↓ 换思路
┌──────────────────────────────────┐
│ MSIX Sideload (Developer Mode) │ ← 复制到普通目录
│ - 删 AppxSignature.p7x │
│ - 改 Identity Name + Publisher │
│ - Add-AppxPackage -Register │
└──────────────────────────────────┘
↓ Patch
┌──────────────────────────────────┐
│ Mono.Cecil 重新生成 DLL │ ← 手动字节 patch 失败
│ (正确处理 metadata + IL + locals)│
└──────────────────────────────────┘
↓
解锁 VIP + 无广告
2. 几个反直觉的坑
| 坑 | 反直觉点 | | ECMA-335 coded index | 不是 1-based 而是 0-based tag(实测) | | WPeGPT 对 .NET | IDA 完全无法反编译 .NET,AI 会误判 | | 字节级 IL patch | 看起来合法但 CLR 仍然拒绝 | | MSIX Publisher CN | X.500 distinguished name,逗号后必须有空格 | | Store 应用 sideload | 必须删除 AppxSignature.p7x,否则 Windows 仍判定为 Store 源 | | Add-AppxPackage -Register | 不需要签名证书,是 MSIX 官方开发模式 |
3. 关键工具
| 用途 | 工具 | 大小 | | PE + CLI 元数据解析 | 自写 Python(200 行) | 0 | | .NET IL 反汇编 | 自写 Python(150 行) | 0 | | IL Patch | Mono.Cecil(nuget) | 702 KB | | 提权脚本 | PowerShell Add-Type(C# inline) | 0 | | Total | | < 1 MB |
整个流程无需 dnSpy / ILSpy / Windows SDK,全部用 .NET Framework 内置 + Mono.Cecil 完成。
4. 适合 CTF 的 Patch 候选清单
逆向 .NET 桌面应用时优先尝试:
- User.IsVip / IsPaid / IsPro —— 主校验
- User.IsPermanentVip / LicenseValid —— 永久会员校验
- User.VipType —— 返回 Permanent(注意查枚举值)
- IsTrial / IsTrialVip —— 设为 false,避免试用限制
- AccountManager.IsLogin / LoginState —— UI 状态机判断
经验:5~6 个属性覆盖 90% 应用。
六、回滚# 卸载修改版
Get-AppxPackage *VIPMod* | Remove-AppxPackage
# 从 Microsoft Store 重新安装原版
# (或 winget install --id ××.Screenshots)
![]()
ps:
1. 因为起晚了,所以并没有赶上用falbe5,而是用的其他模型;
2. Claude code搞完的截图
![]() |