本文记录了 WeFlow 项目中实现朋友圈图片/视频/实况照片解密的完整过程——从硬搬 DLL 函数来调用,到用纯 TypeScript 独立实现。如果你尝试直接访问朋友圈图片的 CDN 地址(形如
f3eK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6E0L8i4y4F1M7#2)9J5k6i4q4H3K9h3y4Q4x3X3g2U0L8W2)9J5c8Y4y4F1M7#2)9J5c8W2)9J5k6g2)9J5k6g2)9J5k6b7`.`.) ,你会发现下载下来的文件根本不是正常的 JPEG 或 PNG——它是 加密过的。微信服务器在返回媒体数据时,会在 HTTP 响应头里带上一个字段:这个 x-Enc: 1 就是在告诉客户端:"这个数据是加密的,你得解密后才能用。" 而解密需要一个 key(密钥),存储在朋友圈动态的 XML 数据里。那加密用的是什么算法?图片、视频、实况照片的加密方式一样吗?这就是本文要逐一解答的问题。在谈解密之前,首先要搞清楚:图片/视频的 URL、鉴权令牌和密钥从哪来,怎么请求?朋友圈的数据存储在微信本地的 WCDB 数据库 (~\xwechat_files\wxid***\db_storage\sns\sns.db) 中。每条动态都有一段 XML 格式的原始数据,里面包含了媒体信息。不同类型的动态,XML 结构有所不同:普通图片动态:视频动态(Type 15):实况照片(Live Photo):数据库中存的 URL 不能直接用,需要做几个处理:微信用不同的域名/路径来分发图片和视频:代码上用一个简单的判断:微信的 CDN 会检查 User-Agent,普通浏览器 UA 会被拒绝:图片请求可以加 Accept-Encoding: gzip, deflate, br (CDN 可能对图片做 gzip 压缩返回)。但视频请求不要加,视频流通常不压缩,加上可能导致异常。作为一个逆向小白,我最开始的思路非常朴素:既然微信客户端自己能解密,那我直接调用它的解密函数不就行了?用 IDA Pro 打开 Weixin.dll(约 170MB),基地址 0x180000000。第一步:搜字符串。 在 IDA 的字符串窗口里搜 x-Enc ,找到它位于地址 0x1885282ac 。第二步:交叉引用。 右键 → "跳转到交叉引用",发现有 9 个函数引用了这个字符串。其中 sub_1845B0BC0 是 HTTP 响应头解析函数:第三步:顺藤摸瓜。 在收包处理函数 sub_1845B4360 里,找到检查这个标志的代码:就这样,通过 字符串搜索 → 交叉引用 → 追踪调用链,定位到了真正的解密函数:sub_182674280
,位于 DLL 偏移 0x2674280。IDA 反编译后,这个函数其实很短(约 0x134 字节),核心逻辑如下:核心就是:用 seed 初始化一个状态机,持续产生 8 字节密钥流,然后逐字节 XOR。 标准的流密码结构。既然找到了函数地址,最简单的做法是直接调用它。用 koffi(Node.js 的 FFI 库)加载Weixin.dll :深入看 sub_1845E3860(状态机初始化函数),一进函数就看到一组硬编码常量和特征操作:0x9e3779b97f4a7c15 是黄金比例 φ 的 64 位定点表示。搜索这个常量加上那组位移量
9, 9, 23, 15, 14, 20, 17, 14,直接命中 —— 这是 ISAAC-64。ISAAC 全称 Indirection, Shift, Accumulate, Add, and Count,是密码学家 Bob Jenkins 在 1996 年发明的 CSPRNG(密码学安全伪随机数生成器)。ISAAC-64 是它的 64 位版本。核心循环 sub_1845E3430 里最有特征的一段:对比ISAAC原始 C 实现的位移量完全一致。在 sub_182674280 的 XOR 循环中:每个 64 位密钥块在 XOR 之前会被转成大端序(Big-Endian)。这个细节如果搞错,解密出来就是乱码。理解了算法后,就可以彻底摆脱 DLL,用纯 TypeScript 重写。完整实现只有约 130 行代码,核心分三部分:1. 初始化 init()2. 生成随机数 isaac64()3. 生成大端序密钥流图片的解密最简单——整个文件每个字节都被 XOR 加密:WCDB 数据库
│ 解析 XML → 拿到 url, token, key
▼
修正 URL (http→https, /150→/0, 拼 token)
│
▼
HTTPS GET (UA: MicroMessenger Client, Accept-Encoding: gzip/br)
│
▼
解压 gzip/br → 拿到加密的原始数据
│
▼
检查响应头 x-Enc: 1 → 需要解密
│
▼
ISAAC-64(media.key) → 生成与图片等长的密钥流
│
▼
encrypted[i] ⊕ keystream[i] → 解密后的 JPEG/PNG/WebP
│
▼
验证文件头魔数 (FF D8 FF = JPEG, 89 50 4E 47 = PNG, etc.)核心代码:视频解密和图片有三个关键差异。图片的密钥在 media[].key字段里,但视频的密钥藏在整条动态 XML 的 <enc key="..." /> 标签中,需要正则提取:视频文件动辄几十 MB,微信不可能对整个文件做流式 XOR。只加密头部就够了——MP4 的关键元数据(ftyp、moov atom)都在前面,头部损坏就无法播放:图片小,可以在内存里拼接。视频要先流式写入临时文件,下载完再读出来解密:另外视频请求不要设置 Accept-Encoding: gzip,视频流不走压缩。实况照片本质上是一张静态图片 + 一段短视频的组合,分别用各自的逻辑处理。核心解密算法完全一样——都是 ISAAC-64 生成密钥流然后 XOR。区别只在于密钥来源、加密范围和下载方式。BigInt 在 JavaScript 中性能不太好,解密大视频时会比较慢。所以项目中做了三层实现:WASM 版本通过 Emscripten 编译,在 Node.js 中用 vm.createContext沙箱加载,暴露 WxIsaac64类调用。本人对于逆向基本是一窍不通 所以本文由我和claude opus 4.6在cc的研究基础上共同完成 感谢阅读到这里本文基于 WeFlow 项目的实际开发过程撰写,逆向分析使用 IDA Pro 对 Weixin.dll 进行。x-Enc: 1
---
来源: 看雪论坛
原文链接: https://bbs.kanxue.com/thread-290047.htm