前言
微信的通信协议没有使用传统的https,而是采用 mmtls 和 quic 协议结合的方案(可能),导致常用的抓包方案完全无效。因此我们考虑使用逆向 hook 的方式,对微信视频号的数据进行获取。
Frida 是目前几乎最好跨平台hook工具,深受广大牢友的喜爱。因此我们考虑用这个工具来进行 hook 。
提前声明,以下操作有被封号的风险,各位看官可以尽量用小号操作。
准备
- 准备一个解锁了 bootloader 、刷了 TWRP 并安装了 Magisk 的手机。(当然,也有无需root权限的方法,但是用起来会不方便,还是建议 root )
- 准备好 adb 环境。
- 参考 FRIDA 安装 frida 用于 hook。并最好把官网的 Tutorials 看下。
- 准备 Pycharm 作为开发环境。
- 准备好 wechat.apk 。
- 参考 JADX 安装好 jadx 用于代码静态分析。
思路
Frida 爬虫的思路如下:
- 利用 adb 的 dumpsys 工具定位到我们关心的 Activity 页。
- 利用 jadx 的静态分析工具在代码中定位到解密后的后的数据对象。
- 利用 frida 的 hook 能力重写数据对象的构造、拷贝等关键方法,提取出入参出参等。
- 将提取到的数据序列化成json,并持久化。
流程
启动 frida-server
首先需要在 Magisk 商店中找到 MagiskFrida 插件。这个插件会在手机启动时以高权限启动 frida-server 服务器用于后续注入 hook 代码。
如果一不小心 frida-server 跪了,只需要重启手机即可。
定位Activity
首先我们需要大概了解我们关注的页面的一些信息,方便我们后续定位代码。因此我们先打开感兴趣的页面(我这里是微信视频号的 tab),并执行 dumpsys 命令。
这样我们知道了,这个页面对应的是 FinderHomeUI 这个 activity。
定位数据对象
打开 jadx-gui ,定位到 FinderHomeUI 这个类。(可能loading一段时间,如果报OOM,则需要 通过 $ mdfind jadx-gui
找到启动脚本,并修改 JVM 参数)。
简单的四下观望,就可以找一个叫 FeedData 的类,也找到类这个类的一个类似 Builder 模式的静态内部类。
看起来这个 i 方法大概就是构造 FeedData 这个类的方法了,因此我们可以考虑下 hook 这个 i 方法。
提取重要参数
找到了上面的 com.tencent.mm.plugin.finder.storage.FeedData$a
对象,我们就可以简单编写一个hook脚本看看。
# -*- coding: UTF-8 -*-
import frida
import sys
def on_message(message, data):
print(message)
jscode = """
Java.perform(function () {
var a = Java.use('com.tencent.mm.plugin.finder.storage.FeedData$a');
var i = a.i;
i.implementation = function (finderItem) {
var res = i.call(this, finderItem);
try{
send(JSON.stringify(res))
}catch (e){
console.log('Error: ' e);//自己的逻辑要做好catch防止脚本有问题导致app崩溃。
}
return res;//注意保证函数输出不变。
};
});
"""
process = frida.get_usb_device().attach('com.tencent.mm')
script = process.create_script(jscode)
script.on('message', on_message)
print('[*] Running Hook')
script.load()
sys.stdin.read()
执行这个脚本,并滑动一下可以看到如下输入:
显然,这里的 payload 并没有正确的被序列化,因此我们需要再做一个用序列化工具。
数据序列化输出
由于安卓自带的 org.json.JSONObject 不支持json序列化,而 Javascript 的方法也无法序列化 java 对象。因此我们需要自己引入一个java包用于序列化,这里我选用无脑的fastjson。
- 首先需要下载fastjson的jar包,我在本地的maven仓库中找到了:
/Users/myths/.gradle/caches/modules-2/files-2.1/com.alibaba/fastjson/1.2.69/6cb063f1d527ff65bdbb9ea74888a5ffc3f92197/fastjson-1.2.69.jar
。 - 然后利用 adb 的 build-tools 中的 dx 工具将 jar 包重新打包成 dex 包:
$ /Users/myths/Library/Android/sdk/build-tools/26.0.2/dx --dex --output=fastjson.dex fastjson.jar
。 - 将上述生成的 dex 包 push 到手机中,例如
/data/local/tmp/fastjson.dex
下。 - 更改下脚本,再滑动下页面:
# -*- coding: UTF-8 -*-
import frida
import sys
def on_message(message, data):
print(message['payload'])
jscode = """
Java.perform(function () {
var a = Java.use('com.tencent.mm.plugin.finder.storage.FeedData$a');
Java.openClassFile('/data/local/tmp/fastjson.dex').load();
var JSONObject = Java.use('com.alibaba.fastjson.JSONObject')
var i = a.i;
i.implementation = function (finderItem) {
var res = i.call(this, finderItem);
try{
send(JSONObject.toJSONString(res));
}catch (e){
console.log('Error: ' e);
}
return res;
};
});
"""
process = frida.get_usb_device().attach('com.tencent.mm')
script = process.create_script(jscode)
script.on('message', on_message)
print('[*] Running Hook')
script.load()
sys.stdin.read()
得到的 json 如下:
代码语言:javascript复制{
"commentCount": 62,
"description": "人心换人心,谁都有底线!更多情感内容点击关注@疗伤情感 #晏子情感",
"expectId": -4877419130498574272,
"feedId": -4877419130498574272,
"hasBgmInfo": false,
"id": -4877419130498574272,
"likeCount": 2951,
"liveId": 0,
"liveStatus": 0,
"localId": 0,
"longVideo": false,
"mediaList": [
{
"NMD": false,
"NMq": 0,
"NMt": false,
"NMv": 28000,
"NMw": "http://wxapp.tc.qq.com/251/20350/stodownload?encfilekey=RBfjicXSHKCOONJnTbRmmlD8cOQPXE48ibNoPibrzxbICjN0mdDNRj71nM2TJfVYGhIxX0WCMf74biaftFqLW2zGtdmXTJ8wibeesB7n8Ntyu5uOic8136mUibM44fnge8amjDu6BLmlSE59icicdjIrI7KtpP8FxXibG6LwzicQMlmaVaAicD0&adaptivelytrans=0&bizid=1023&dotrans=0&hy=SH&idx=1&m=5190cada35800678ed0b1f917a2a32b5",
"NMx": "&token=x5Y29zUxcibDjE9JYkmdS0shbZ7djRmsC99U0UibtNT1u9hlAxSyM01icQZXHkptzicv",
"bitrate": 0,
"coverUrl": "http://wxapp.tc.qq.com/251/20304/stodownload?filekey=30350201010421301f020200fb040253480410d8869ba74f63ba490ac4069f994280f202030180d8040d00000004627466730000000131&storeid=323032313034303531303235343030303064316634353761356264386134653730353566363430303030303066623030303034663530&adaptivelytrans=0&bizid=1023&dotrans=0&hy=SH&m=d8869ba74f63ba490ac4069f994280f2",
"decodeKey": "2065249527",
"fileSize": 18284035,
"full_bitrate": 0,
"full_file_size": 0,
"full_height": 0,
"full_width": 0,
"height": 1264,
"hot_flag": 0,
"includeUnKnownField": false,
"md5sum": "f9f9eceb-6982-4e4d-bfa6-8db01fa9b522",
"mediaId": "a6b5fac7d510c532a4376934a31015a4",
"mediaType": 4,
"spec": [
{
"MZJ": 3141665,
"Nbp": 637,
"efu": "xV0",
"includeUnKnownField": true,
"vKg": "h264"
},
{
"MZJ": 1646534,
"Nbp": 345,
"efu": "xV2",
"includeUnKnownField": true,
"vKg": "h265"
},
{
"MZJ": 1037647,
"Nbp": 226,
"efu": "xV4",
"includeUnKnownField": true,
"vKg": "h265"
},
{
"MZJ": 472808,
"Nbp": 109,
"efu": "xV8",
"includeUnKnownField": true,
"vKg": "h265"
},
{
"MZJ": 418280,
"Nbp": 97,
"efu": "xV9",
"includeUnKnownField": true,
"vKg": "h264"
},
{
"MZJ": 295747,
"Nbp": 66,
"efu": "xV10",
"includeUnKnownField": true,
"vKg": "h265"
}
],
"thumbUrl": "http://wxapp.tc.qq.com/251/20304/stodownload?encfilekey=RBfjicXSHKCOONJnTbRmmlD8cOQPXE48ib0TrgC9GMRrlchGCNdXCyD2Pu6YbIWudBh6BngIDXS3M8Y18doicwuaXmAiblJJG5s2ib1XR3KMredUlbax6ZQvhQ77Ntoekw0O4VfpbFuHrhjIyEDd78AKe5GGybePkVA1jP75HyKHEyNc&adaptivelytrans=0&bizid=1023&dotrans=0&hy=SH&idx=1&m=d8869ba74f63ba490ac4069f994280f2",
"thumb_url_token": "&token=x5Y29zUxcibCadRELU5qibEtIicbNZqQqzxGicvUUexAbqribwZDAdDicOa5koiawnrKUtV",
"url": "http://wxapp.tc.qq.com/251/20302/stodownload?encfilekey=G83YYE2iciaib491UK8yGibLXOdhNpDoPpG748uNIa5DNuyuonSofEYDt1yf8eDoibNty4U8UXvSG2micv4HaEUcErdibfTOiaKKalN8FrUibibrfVqnPOh8sZFWl5oDALZajdFJsTg7Sqd4bPIyWib5CehDW4NbxzzdLUpoDvYBVjDkfp9C7Q&adaptivelytrans=0&bizid=1023&dotrans=906&hy=SH&idx=1&m=6b3f33e50bfc1c5c5f6f6c14e06b7d03&scene=0",
"url_token": "&token=AxricY7RBHdWhPYjkduXw4angAXxhu8UMIxGebhCliaYtTT7dCtwIxRibXyGodLxZxcPJYzq9CN5dU",
"videoDuration": 28,
"width": 1080
}
],
"mediaType": 4,
"nickName": "疗伤情感",
"onlineNum": 0,
"rvFeedList": [],
"sessionBuffer": "eyJzZXNzaW9uX2lkIjoic2lkXzIzNjY4ODU5NDVfMTYxNzc4MDIwOTk3NzYzNl8xNDk3MjAwODg4IiwicmVjb21tZW5kX3R5cGUiOjMsInJlY29tbWVuZF9zeXN0ZW0iOjIsInJlY29tbWVuZF93b3JkaW5nIjoiIiwiY3VyX2xpa2VfY291bnQiOjI5NTEsImN1cl9jb21tZW50X2NvdW50Ijo2MiwicmVjYWxsX3R5cGVzIjpbMTAxMTM1XSwiZGVsaXZlcnlfc2NlbmUiOjEzLCJkZWxpdmVyeV90aW1lIjoxNjE3NzgwMjEwLCJzZXRfY29uZGl0aW9uX2ZsYWciOjIsInRvdGFsX2ZyaWVuZF9saWtlX2NvdW50IjowLCJuZXdfZnJpZW5kX2xpa2VfY291bnQiOjAsInJlY2FsbF9pbmRleCI6WzBdLCJ0YWdfaWQiOiIwOyIsInJlcXVlc3RfaWQiOjE2MTc3ODAyMDg4MTc5MTMsIm1lZGlhX3R5cGUiOjQsInZpZF9sZW4iOjI4LCJjcmVhdGVfdGltZSI6MTYxNzU4OTU4NiwidGFiX3R5cGUiOjQsInJlY2FsbF9pbmZvIjpbeyJyZWNhbGxfdHlwZSI6MTAxMTM1LCJyZWNhbGxfc2NvcmUiOjAuNzI2MjMyOTQ1OTE5LCJyZWNhbGxfaW5kZXgiOjAsInJlcG9ydF9pbmZvIjoiNDA2XzEwMzVfMF8wXzEifV0sInJhbmtfc2NvcmUiOjEwNC40NzExNDU2Mywic2VjcmV0ZV9kYXRhIjoiQmdBQUFmMmliZTJFMXIrRFRHc2gwbzB6RGJVbnpvTkUwZDZncjliOVdKdyttakM2NkhnM3VXY0phOTg9IiwidGFiX3Nlc3Npb25faWQiOjE2MTc3ODAyMDg5MjE3ODQsImZyaWVuZF9saWtlZF9saXN0IjoiIiwiZGV2aWNlX3R5cGVfaWQiOjIsImRldmljZV9wbGF0Zm9ybSI6IlJlZG1pIDYiLCJkb3dubG9hZF9zcGVlZF9rYnBzIjoxMzE1OTUsIm5ldF90eXBlIjoxLCJ2aWRlb19pZCI6MCwiaXNfY2hpbGQiOnRydWUsInBhcmVudF9tZWRpYV90eXBlIjowLCJwYXJlbnRfaWQiOjAsImZlZWRfcG9zIjoyLCJwdWxsX3R5cGUiOjEsInBhZ2VfbnVtIjowLCJjbGllbnRfcmVwb3J0X2J1ZmYiOiJ7XCJzZXNzaW9uSWRcIjpcIjE0M18xNjE3NzEwNzYyNTY4IyQyXzE2MTc3MTA3NjEyNjgjXCJ9IiwiaXNfbGl2ZV9mZWVkIjowLCJpc19saXZlX2ZpbmRlcnVzZXIiOjAsImV4dF9mbGFnIjowLCJjb21tZW50X3NjZW5lIjoyMCwib2JqZWN0X2lkIjoxMzU2OTMyNDk0MzIxMDk3NzM0NCwiZmluZGVyX3VpbiI6MTMxMDQ4MDgwNjQ2MDA0ODYsInBvaW5hbWUiOiIiLCJjaXR5IjoiIiwiZ2VvaGFzaCI6MzM3NzY5OTcyMDUyNzg3Mn0=",
"timestamps": 1617780210295,
"urlValidDuration": 172800,
"userName": "v2_060000231003b20faec8cae38f1dc5d5ce00e432b077b11d4f3ce9c011535e0fff9bba95dcc6@finder"
}
这里要小心,过长的 long 在转 json 的时候可能会丢失精度,如果发现这种情况要特殊处理下,把 long 转成 string 。
数据处理
视频问题
检查了下数据,发现通过 url url_token 拼接的视频流虽然能下载,但是下载下来是加密后的,无法直接播放。
后来发现 url thumb_url_token 是可以播放的,高兴了一段时间。但是4月26号左右微信好像做了什么操作,导致这个渠道不能播放了。经过简单分析后发现视频解码的流程是放在native方法中,一时半会难以破解,那就只能想办法下载缓存了。
(当然,这些加密算法在安全组的同学面前都不算问题,三下五除二就发现原来是某个比较小众的流式加密算法