Windows照片应用擦除功能分析
0.前言
某天想去除图片浮水印,找了挺多方案,无意中发现最新的Windows照片应用居然带了AI擦除修复功能哇!天助我也。使用了一下发现效果挺哇塞的哦。出于好奇分析它一波!
1.先测试此功能是网络模型还是本地模型实现的
经过断网分析后发现是本地功能,先对程序目录进行定位。然后按大小排序一下,很明显发现了onnxruntime.dll,也看到了mager.onnxe、seg.onnxe

然后搜索一下这个模型名字、顺便问一下老师傅(GPT)。大概也能知道这两个模型是什么咯。一个是抠图扣背景的,一个是咱们需要的擦除模型。

2.尝试解析onnxe模型与分析调用来源
有一个网站它可以在线分析模型有多少层,大概是什么样子嘟。模型X光照射!
正常把可以识别的模型丢进去后可以可视化得出当前模型的一些图和参数。
恰巧这个模型不能被识别,说明加密了或者非标准onnx呗。
先瞅一眼二进制再说!

赶脚有点不太对劲,应该是加密或者混淆了,不像正常的onnx,正常的onnx应该包含很多protobuf的序列化数据。
现在继续分析是谁加载调用了onnxruntime.dll从而分析加载过程和调用代码。
还有一些小意外,加载原来不是根目录的onnxruntime


很明显了东西都在一起!还有NPU相关的。
先抓一下调用链,确定一下整个大概流程是什么样的。

微软这一类的东西应该都不会加壳。直接基于调用链ida静态分析就完了!可以使用ida-pro-mcp搭配任意模型都可以分析出来调用链。

3.IDA 追密钥 — 从 seg 模型下手
思路很直接:DLL 加载模型文件 → 肯定要解密 → 解密就离不开密钥 → 密钥要么硬编码在 DLL 里要么从某处读取。既然没加壳没混淆,直接 IDA 往里冲。
先拿 seg.onnxe 开刀,因为它小(22MB),而且它是抠图的模型,逻辑应该比擦除模型简单。
第一个关键入口是 segapi.dll。用 IDA 打开后搜 CreateSession 或者 OrtCreateSession 之类的 onnxruntime API 调用,很快锁定了 sub_1800032B0 这个函数——它负责注册模型密钥:
- 注册
"key" → 指向 "Microsoft_2023" 字符串指针
- 注册
"long_key" → 指向另一个 4096 字节的数据块
有趣的是 seg 模型只用到了 "key" 这一个键,对应的值就是短短 14 个字节:Microsoft_2023。
这就是说 seg 模型的加密方案是 14 字节 ASCII 字符串的循环 XOR——不是 AES,就是普通的异或混淆。
具体在 DLL 里的位置:
| 内容 |
IDA 地址 |
文件偏移 |
"Microsoft_2023" |
0x180083EA8 |
0x82EA8 |
注意 segapi.dll 用了 PE section 映射。.rdata 段的 VA 基址是 0x180075000,文件偏移是 0x74000,所以 IDA 地址减 VA 再加文件偏移才能定位到文件。
4.验证 seg 解密 — 一击即中
有了密钥,解密就是一行 Python:
key = b"Microsoft_2023"
decrypted = bytes(enc[i] ^ key[i % 14] for i in range(len(enc)))
跑完把结果丢进 onnx.load() — 过了!IR=6,1094 个节点,输入 (1,3,512,512),输出 (1,512,512)。丢 Netron 也能正常可视化了:
| 属性 |
值 |
| 输入 |
input_image (1×3×512×512 float32) |
| 输出 |
output (1×512×512 float32) |
| 节点数 |
1094 |
| IR 版本 |
6 |
| 框架 |
PyTorch 2.0.1 |
| 大小 |
22 MB |
seg 模型解密搞定,接下来攻 mager。
5.mager 模型 — 密钥更深一层的藏法
magerapi.dll 里的密钥管理架构和 segapi 几乎一样——同一个 sub_180002BC0(对应 segapi 的 sub_1800032B0)负责注册密钥表。但 mager 多了一层:
"key" → "Microsoft_2023"(14 字节,和 seg 一样的前缀)
"long_key" → 4096 字节的大块密钥数据
开头我也以为 mager 的密钥是前缀 + long_key 拼接起来(14+10+4096),还算了半天偏移。直到手工 XOR 了一轮发现不对——解密出来的文件头对不上 ONNX 的protobuf 08 魔数。
后来仔细核对才发现:**mager 根本不用 "Microsoft_2023" 前缀!**它只用 "long_key" 那个 4096 字节的数据块。那个 "key" → "Microsoft_2023" 的映射是给 seg 模型用的,mager 的 InitModelKeyTable 里虽然也注册了 "key",但实际加载模型时只取了 "long_key"。
正确位置:
| 内容 |
IDA 地址 |
文件偏移 |
| 4096B 密钥数据 |
0x18007AAC0 |
0x7AAC0 |
之前写的 0x7BAC0 是个 typo,PE section 映射算偏了。正确的文件偏移是 VA 减 .rdata 基址再加 raw offset:0x18007AAC0 - 0x180075000 + 0x74000 = 0x7AAC0。
用这 4096 字节去 XOR mager.onnxe(295MB),出来的结果 onnx.load() 直接过:
| 属性 |
值 |
| 输入 |
img_lr(1×3×256×256) + mask_lr(1×1×256×256) + img_hr(1×3×512×512) + mask_hr(1×1×512×512) |
| 输出 |
output (1×3×512×512) |
| 节点数 |
15284 |
| IR 版本 |
8 |
| 框架 |
PyTorch 1.13.0 |
| 大小 |
288 MB |
6.两个模型的密钥总结
| 模型 |
密钥 |
长度 |
来源 |
| Seg |
Microsoft_2023 |
14 字节 |
segapi.dll 0x82EA8 |
| Mager |
4096 字节二进制块 |
4096 字节 |
magerapi.dll 0x7AAC0 |
加密算法都一样:静态循环 XOR。plaintext[i] = ciphertext[i] XOR key[i % key_len]。
XOR 属于混淆而非密码学加密,安全强度不高。但对于本地模型保护来说,能挡住大部分直接拷贝文件的人就算达到目的了。
7.跑一下推理验证
解密完不跑一下怎么行。用 onnxruntime CPU 模式加载两个模型测试:
- Seg 推理:输入 512×512 图片,输出同尺寸 mask,一张约 200ms
- Mager 推理:需要 4 个输入(高/低分辨率图像 + 高/低分辨率 mask),输出擦除结果,一张约 2500ms
Seg 模型配合视频逐帧处理,CPU 下约 4 FPS(1280×720)。如果装上 CUDA 版 onnxruntime 配合更好的 GPU,预计 40-80 FPS,实时抠图够用了。
期间踩了两个坑:
- 边缘撕裂:mask 从 512×512 放大回原图时,双线性插值 + 高斯羽化比硬二值化效果好得多。后来还加了时域 EMA 平滑(0.35 权重)消除帧间闪烁,效果立竿见影
- 音频丢失:OpenCV 的 VideoWriter 不写音频轨道,用 ffmpeg 从源文件合并回来就行,脚本里已经自动处理了
8.顺便看了一眼飞书的模型
飞书安装在 %LOCALAPPDATA%\Feishu\app\ 下,也有不少 AI 模型:
| 目录 |
引擎 |
用途 |
vesdk\model\ |
ByteNN (私有) |
视频编辑:场景识别、抠图、皮肤分割、人脸追踪 |
RTCSDK_RES\models\ |
ByteNN (基于 TVM) |
实时音视频:14 个音频降噪/回声消除/声纹模型 |
RTCSDK_RES\model\ |
ByteNN (基于 TVM) |
视频特效:实时抠图 (831KB)、人脸关键点 (868KB) |
有意思的是飞书用的是 Apache TVM 作为底层推理引擎(VolcEngineRTC.dll 里导出了 tvm::runtime::GraphRuntime、tvm::runtime::ModuleNode 等全套 TVM 的 C++ 类),外面包了一层 ByteDance 自研的 ByteNN 容器格式(BM 魔数)。体积极致压缩 — 抠图模型 831KB vs 我们的 seg 模型 22MB,差了 27 倍。
不过这 .model 格式不是 ONNX,解析起来需要逆向 BM 容器结构,先记一笔,以后再挖。
9.万一加密很强怎么办 — HOOK runtime 直接拦截明文
前面我们拆解的方案建立在 XOR 混淆的基础上,密钥直接硬编码在 DLL 里。万一碰到 AES-256-GCM 等强加密的模型——密钥通过白盒加密保护或者服务端动态下发——直接逆向提取密钥的工作量就会大很多。
这时候换个思路:不跟密钥死磕,等程序自己解密完了,在它喂给推理引擎之前截胡。
核心逻辑是这样的:
加密模型文件 → [程序内部解密] → 明文字节流 → OrtCreateSession → ONNX Runtime 推理
↑
在这里 HOOK 拦截!
ONNX Runtime 加载模型有两个关键 API:
| API |
参数 |
说明 |
OrtCreateSession |
文件路径 |
从磁盘加载 .onnx 文件 |
OrtCreateSessionFromArray |
内存字节 + 长度 |
直接从内存加载(解密后走这个) |
程序如果先解密到内存再调 OrtCreateSessionFromArray,那这个函数的参数里就已经是明文模型了。Hook 它,直接把 model_data 指针指向的内存 dump 出来,就是一个完整可用的 .onnx 文件。
Windows 上的实操方案 — MinHook
MinHook 是个轻量级 inline hook 库,几行代码就能劫持任意 DLL 导出函数。思路:
typedef OrtStatus*(*PFN_OrtCreateSessionFromArray)(
const OrtEnv*, const void* model_data, size_t model_data_length,
const OrtSessionOptions*, OrtSession** out);
PFN_OrtCreateSessionFromArray OriginalFunc = NULL;
OrtStatus* Hooked_OrtCreateSessionFromArray(
const OrtEnv* env, const void* model_data, size_t model_data_length,
const OrtSessionOptions* options, OrtSession** out)
{
FILE* f = fopen("dump.onnx", "wb");
fwrite(model_data, 1, model_data_length, f);
fclose(f);
return OriginalFunc(env, model_data, model_data_length, options, out);
}
MH_Initialize();
MH_CreateHookApi(L"onnxruntime.dll", "OrtCreateSessionFromArray",
Hooked_OrtCreateSessionFromArray, (void**)&OriginalFunc);
MH_EnableHook(MH_ALL_HOOKS);
把这个逻辑编译成 DLL,注入到目标进程(照片应用或任何加载 onnxruntime 的程序),触发一次 AI 功能,dump.onnx 就到手了。
更极端的情况 — 程序用自定义推理引擎
如果模型不是 ONNX 格式,而是像飞书那种 ByteNN 私有格式,OrtCreateSession 这条路就走不通了。这时候需要:
- IDA 里定位程序内部的
CreateSession 或 LoadModel 函数
- 找到它接收解密后模型数据的那一层
- 在那一层 hook/dump
本质上思路一样:追到"解密完成、推理开始"这个临界点,在那里截断。
总结对比三种提取路线
| 场景 |
方法 |
难度 |
| XOR 混淆 / 无加密 |
直接提取密钥 XOR 解密(我们这次的方案) |
⭐ |
| 对称加密但用 onnxruntime |
Hook OrtCreateSessionFromArray dump 明文 |
⭐⭐ |
| 对称加密 + 自定义引擎 |
动态调试 + IDA 静态分析 + hook 内部 LoadModel |
⭐⭐⭐⭐ |
这次照片应用的两个模型刚好是最简单的情况。但方法论是通用的——找不到钥匙就别撬锁,等人把门打开了再跟进去。
最后结果展示
速度还是很快的,一张人物图纯CPU模式下200ms不到


10.总结
整个分析过程下来,几个比较有意思的点:
- 加密为混淆而非密码学保护 — seg 用 14 字节 ASCII 字符串、mager 用 4096 字节二进制块做循环 XOR。挡得住直接拷贝文件的人,挡不住逆向分析,但对本地模型保护来说也够用了。
- 飞书的模型工程化更成熟 — 用 Apache TVM + 自定义容器格式 + 网络按需下载 + 版本管理,明显是一套考虑了端侧部署完整生命周期的推理架构。
- 本地 AI 正在变成桌面应用的标配 — Windows 照片的擦除、Windows 系统级 OCR、飞书的实时抠图降噪,都在往本地迁移。轻量级 ONNX/TVM 模型会越来越多地嵌入到日常软件里。
- DLL 分析中发现了不少其他模型命令 — 应该是微软内部一套完整的本地 AI 库,不止擦除、OCR这些功能。
公众号 觉得文章不错的兄弟们关注一波!谢谢啦
