微信视频号视频加密逆向分析与解密方案

在数字化内容传播时代,视频平台为了保护版权内容,通常会对视频进行加密处理。本文将深入分析微信视频号的视频加密机制,通过逆向工程技术揭示其加密原理,并提供可行的解密方案。这不仅是一次技术探索,更是对现代网络安全和逆向工程领域发展趋势的观察。

🎯 项目背景

在日常使用微信视频号时,我们可能会遇到需要保存重要视频的场景,比如:

  • 📚 学习资料保存:保存有价值的教程视频
  • 📰 资讯内容备份:保存重要的新闻资讯视频
  • 🎨 创作素材收集:收集优秀的创意视频作为参考
  • 📖 个人收藏整理:整理个人喜爱的视频内容

然而,微信视频号为了保护内容版权,对视频进行了加密处理,导致通过常规方式下载的视频无法直接播放。

🔍 问题发现

在研究微信视频号下载方案时,我们发现:

  1. 使用 WeChatVideoDownloader 等工具通过代理获取视频地址
  2. 下载的视频文件无法正常播放
  3. 通过十六进制分析发现文件头格式异常
1
2
3
4
5
6
7
# 加密视频文件头分析
00000000: 75a2 b80f 5db2 528b af76 c5f0 9407 a7e9 u...].R..v......
00000010: 4c31 99a8 60ef a5de c64e ce1e 3ab1 6e74 L1..`....N..:.nt

# 正常 MP4 文件头对比
00000000: 0000 0020 6674 7970 6973 6f6d 0000 0200 ... ftypisom....
00000010: 6973 6f6d 6973 6f32 6176 6331 6d70 3431 isomiso2avc1mp41

重要声明:本文仅供学习和研究目的使用,不鼓励、不支持任何形式的非法行为。请在使用时遵守相关法律法规。

🛠️ 准备工作

在正式开始逆向分析之前,我们需要启用微信开发者工具以便调试。

启用开发者工具

由于微信默认不会启用开发者工具,我们需要对微信的动态链接库进行修改:

  1. 找到 xweb-enable-inspect 启动选项
  2. 修改 branch 指令
  3. 使该启动选项所在的分支永远执行

完成修改后,我们就能在微信视频号中打开开发者工具进行调试。

启用开发者工具效果

🔍 逆向分析

JavaScript 初步分析

首先打开一个视频,通过开发者工具我们可以看到很多网络请求。其中带有 stodownload 的就是下载的视频文件,但这些视频链接下载下来的内容是加密的。

视频下载请求

通过分析发现,解密操作发生在文件下载完成后。查看请求发起位置,我们追踪到 worker_release.js 中的 g.send() 方法。

请求发起位置

熟悉 JavaScript XMLHttpRequest 的开发者会发现,这正是发送请求的标准方法。向上查找,我们找到了返回值处理逻辑。

返回值处理

解密函数分析

通过分析,我们发现解密函数就是函数 M,参数分别为数据和 startIndex(文件的第几个字节):

1
2
3
4
5
// 解密函数 M
function M(data, startIndex) {
// 将数据和 decryptor_array 进行异或
// 如果 startIndex 大于 decryptor_array 的长度,则不进行异或
}

函数 M 的逻辑非常简单:

  1. 将数据和 decryptor_array 进行异或运算
  2. 如果当前的 startIndex 大于 decryptor_array 的长度,则不进行异或,保持原有数据

解密函数

通过断点调试,我们发现 decryptor_array 的长度是一个常量:2^17 = 131072 字节。

decryptor_array 长度

加密策略推断

decryptor_array 的恒定长度可以推断出:

  1. 视频加密只作用于文件的前 131072 字节(128KB)
  2. 这样的加密策略是合理的,避免对整个视频进行加密解密时消耗过多资源
  3. 对于同一视频,decryptor_array 是一致的
  4. 不同的视频文件对应不同的 decryptor_array

经过搜索,我们发现 decryptor_array 的赋值仅在 wasm_isaac_generate 函数中进行。

wasm_isaac_generate 函数

wasm_isaac_generate 函数在代码中只被 wasm_video_decode.js 调用。

wasm_video_decode.js 中,wasm_isaac_generate 作为一个汇编函数,可以在 WebAssembly 中通过 _emscripten_asm_const_int 接口被调用。

WebAssembly 调用

WebAssembly 接口

🧩 WebAssembly 深入分析

WASM 文件处理

下载 wasm_video_decode.wasm 后,我们使用 wabt 工具将其转换为 .o 文件,以便在反编译软件中进行分析:

1
2
3
4
5
6
7
8
# 使用 wabt 工具转换 wasm 文件
./path/to/wasm2c wasm_video_decode.wasm -o wasm_video_decode.c

cp /path/to/wasm-rt-impl.c .
cp /path/to/wasm-rt-impl.h .
cp /path/to/wasm-rt.h .

gcc -c wasm_video_decode.c -o wasm_video_decode.o

将生成的二进制文件 wasm_video_decode.o 拖入反编译软件,搜索 _emscripten_asm_const_int 的调用。我们发现 wasm_isaac_generate 在函数 f378 处被调用。

WASM 函数调用

解密器分析

通过断点和调用栈的检查,我们发现 worker_release.js 中的 decryptor.generate() 最终触发了 wasm_isaac_generate 的调用。

仔细分析揭示出 decryptor 也是 WebAssembly 环境中的一个对象,即 WxIsaac64

WxIsaac64 对象

经过研究,我们了解到 Isaac64 实际上是一个随机数生成算法。

Isaac64 算法

解密流程推断

基于以上分析,我们可以合理推测解密流程:

graph TD
    A[获取视频seed] --> B[初始化Isaac64算法]
    B --> C[调用decryptor.generate()]
    C --> D[WASM生成随机数]
    D --> E[写回JavaScript赋值给decryptor_array]
    E --> F[下载加密视频]
    F --> G[前128KB数据与decryptor_array异或]
    G --> H[视频解密完成]
  1. decryptor 使用视频对应的 seed 进行初始化
  2. JavaScript 调用 decryptor.generate(),指示 wasm 在其内存中生成 2^17 即 131072 个随机数
  3. wasm 生成随机数后,通过 wasm_isaac_generate 将这些随机数写回 JavaScript,赋值给 decryptor_array

现在,我们知道了 decryptor_array 的来源,接下来的问题是确定初始化 Isaac64 算法的 seed 的来源。

WebAssembly 进一步分析
下载wasm_video_decode.wasm后,我们使用wabt工具将其转换为.o文件,以便在反编译软件中进行分析。

./path/to/wasm2c wasm_video_decode.wasm -o wasm_video_decode.c
cp /path/to/wasm-rt-impl.c .
cp /path/to/wasm-rt-impl.h .
cp /path/to/wasm-rt.h .
gcc -c wasm_video_decode.c -o wasm_video_decode.o
完成这些步骤后,我们得到一个二进制文件wasm_video_decode.o。将此文件拖入反编译软件,搜索_emscripten_asm_const_int的调用。我们发现wasm_isaac_generate在函数f378处被调用。

image-20251010100723223

进一步通过断点和调用栈的检查,我们发现worker_release.js中的decryptor.generate()最终触发了wasm_isaac_generate的调用。

仔细分析揭示出 decryptor 也是 WebAssembly 环境中的一个对象,即WxIsaac64。

image-20251010100729774

经过研究,我们了解到 Isaac64 实际上是一个随机数生成算法。

image-20251010100734366

因此,我们可以合理推测:

decryptor 使用视频对应的 seed 进行初始化。
JavaScript 调用decryptor.generate(),指示 wasm 在其内存中生成 2^17 即 131072 个随机数。
wasm 生成随机数后,通过wasm_isaac_generate将这些随机数写回 JavaScript,赋值给decryptor_array。
现在,我们知道了decryptor_array的来源,接下来的问题是确定初始化 Isaac64 算法的 seed 的来源。

Tracebackbackbackbackback

🔎 Seed 追踪

接下来就是不停地打断点,查看调用栈,直到找到 seed 最早出现的地方。

调用栈分析1

调用栈分析2

调用栈分析3

简单来说,调用顺序为:

1
2
3
FinderGetCommentDetail(objectid) 
→ objectDesc.media.decodeKey
→ seed

💉 WeixinJSBridge 注入

那么 FinderGetCommentDetail 又是通过什么获取到信息的呢?继续追踪调用,我们发现 FinderGetCommentDetail 最后使用了 window.WeixinJSBridge.invoke 来获取数据。

WeixinJSBridge 调用

window.WeixinJSBridge???那接下来就要逆向微信的通信协议了。我才懒得逆向这玩意。

立刻启动后备隐藏能源,发动注入模式!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
(function () {
function wrapper(name, origin) {
console.log(`injecting ${name}`);
return function() {
let cmdName = arguments[0];
console.log(`${name}("${cmdName}", ...) => args: `);
console.log(arguments[1])
if (arguments.length == 3) {
let original_callback = arguments[2];
arguments[2] = async function () {
console.log(`${name}("${cmdName}", ...) => callback result (length: ${arguments.length}):`);
if (arguments.length == 1) {
console.log(arguments[0]);
} else {
console.log(arguments);
}
return await original_callback.apply(this, arguments);
}
}
let result = origin.apply(this, arguments);
return result;
}
}
window.WeixinJSBridge.invoke = wrapper("WeixinJSBridge.invoke", window.WeixinJSBridge.invoke);
})()

注入结果非常好,我们获得了需要的所有数据!

注入结果1

注入结果2

📌 总结

通过以上分析,我们得出了解密微信视频号视频的完整流程:

  1. 通过 FinderGetCommentDetail 获取视频的 decode_key(即 seed)、url、title 等信息
  2. 通过 seed 生成 decryptor_array
  3. 通过 url 下载加密后的视频文件
  4. 将视频的前 128KB 加密段数据和 decryptor_array 做异或运算即可完成解密

🛠️ 视频下载器实现方案

由于获取 seed 需要逆向微信协议,我不想在逆向这个协议上花费太多时间。

既然 WeChatVideoDownloader 已经使用代理获取视频地址,我们可以进一步使用中间人攻击来获取视频链接及对应的 decode_key

只需将注入 WeixinJSBridge.invoke 的代码插入到某个 JS 文件中,当微信客户端请求视频链接时,就把获取到的视频链接发送到本地服务器。

这样不仅解决了 seed 和链接的问题,连视频标题也能获取到。

最后,下载完视频后,通过 seed 生成解密序列并对视频进行解密。

📝 写在后面

回顾这次的逆向工程过程,我们可以看到 WebAssembly 在现代网络安全和逆向工程领域扮演着越来越重要的角色。随着 WebAssembly 的普及,JavaScript 逆向逐渐演变为 WebAssembly 逆向。这不仅提高了代码的执行效率,同时也给逆向工程带来了更多的挑战和机遇。

相关链接

  • 微信 v3.9.8.15
  • wasm_video_decode.wasm v1.2.46
  • worker_release.js v1.2.46
  • wasm_video_decode.js v1.2.46
  • wasm_video_decode_fallback.js v1.2.46

免责声明:本博客中提供的信息和过程仅供学习和研究目的使用。博主不鼓励、不支持并强烈反对任何形式的非法行为,包括但不限于未经授权的数据访问、破解或逆向工程。博客内容的使用应遵守相关法律法规以及道德规范。

读者在使用本博客中的信息时,应自行承担相应的风险和责任。博主不对由于使用、引用或依赖本博客中信息而产生的任何形式的损害或损失负责。此外,博主对于博客内容的准确性、完整性或适用性不作任何明示或暗示的保证。

请读者在使用本博客中的技术和信息时,始终保持合法、负责任的态度,尊重知识产权和隐私权。如果您不确定您的行为是否合法,或者您的行为可能会侵犯他人的权利,请在行动前咨询专业法律意见。

📚 相关资源


通过本次逆向分析,我们不仅解决了微信视频号视频解密的问题,更深入了解了现代Web应用中WebAssembly的使用方式。这为今后处理类似的技术挑战提供了宝贵的经验和思路。


微信视频号视频加密逆向分析与解密方案
https://miku2024.top/posts/微信视频号视频加密逆向/
作者
KB
发布于
2025年10月3日
许可协议