*在2019年深圳上班的时候 那时候还是个Java 码农 接触了一下 Xposed.时隔多年 忘记差不多了 用frida先来练练手 新公司又让我研究微信视频号获取个人的视频主页标题列表 *
确定微信版本
- 不同版本微信hook点不一样。
预想实现方式
- 用Xposed去请求注册一个中转服务 然后脚本请求中转服务
- 实现:可以所有号都去处理业务 也可以用一部分号去做查询请求
- 难点:Xp会污染手机环境 微信检测点太多
- 用单独服务器hook微信去搭建一个中转 然后脚本请求
- 实现:利用微信其他端(pc) 协议或者hook 专门分部分号去做服务
- 难点:pc协议是否稳定 pchook 逆向
服务设计思路
服务应该满足 组 端 点 可以用sekiro治理
组:不同业务 方便拓展 例如以后需要视频号 或者其他等业务 防止污染
端:不同的客户端处理在某一个组下只处理同一种业务 方便查询管理
点:针对于端去调用某个函数 例如 以后同一个业务下 不同的函数查询
难点
- 多进程共享数据
当前解决方案:利用系统广播来传递数据
- 微信hook位置
整个项目 最最最麻烦的东西 需要不断调试 分析
预想期望
搜索获取用户信息
入参 账号名
返回 类json 或者 protobuf (会有唯一账号id)
获取用户主页视频
入参 账号id
返回 类json 或者 protobuf 主页视频信息 标题 视频id 等等
实战分析
打开视频号主 主页 查询 activity :adb shell dumpsys activity top
com.tencent.mm/.plugin.finder.feed.ui.FinderProfileUI
但是我并没有找到数据请求的地方 这个应该是有个中转的activity 上面这个 应该就是个显示页
搜索一番发现了Monitor这个工具 可以跟踪执行路径
利用GDA或者Jadx跟踪到这个类 发现这应该就是一个数据组装的构造函数
继续网下翻 可以看到 里面还有数据解析方法
所以这个类 我们可以猜到应该就是数据组包和请求的一个类~ 我们写脚本验证一下
//请求的数据
let NetSceneFinderUserPage = Java.use("com.tencent.mm.plugin.finder.cgi.eg");
NetSceneFinderUserPage["$init"].overload('java.lang.String', 'long', 'com.tencent.mm.cg.b', 'int', 'com.tencent.mm.protocal.protobuf.chn', 'int', 'long', 'boolean', 'java.lang.String', 'long', 'java.lang.Integer', 'java.lang.Long', 'java.lang.String', 'boolean', 'boolean', 'java.lang.Long', 'int', 'kotlin.g.b.k').implementation = function (str, j, c14425b, i, chnVar, i2, j2, z, str2, j3, num, l, str3, z2, z3, l2, i3, c100738k) {
console.log('$init is called' + ', ' + 'str: ' + str + ', ' + 'j: ' + j + ', ' + 'c14425b: ' + c14425b + ', ' + 'i: ' + i + ', ' + 'chnVar: ' + chnVar + ', ' + 'i2: ' + i2 + ', ' + 'j2: ' + j2 + ', ' + 'z: ' + z + ', ' + 'str2: ' + str2 + ', ' + 'j3: ' + j3 + ', ' + 'num: ' + num + ', ' + 'l: ' + l + ', ' + 'str3: ' + str3 + ', ' + 'z2: ' + z2 + ', ' + 'z3: ' + z3 + ', ' + 'l2: ' + l2 + ', ' + 'i3: ' + i3 + ', ' + 'c100738k: ' + c100738k);
let ret = this.$new(str, j, c14425b, i, chnVar, i2, j2, z, str2, j3, num, l, str3, z2, z3, l2, i3, c100738k);
console.log('$init ret value is ' + ret);
return ret;
};
//响应的数据
let FinderPreloadTransform = Java.use("com.tencent.mm.plugin.finder.preload.g");
FinderPreloadTransform["a"].overload('com.tencent.mm.protocal.protobuf.cgh', 'java.util.List', 'int').implementation = function (cghVar, list, i) {
console.log('a is called' + ', ' + 'cghVar: ' + cghVar + ', ' + 'list: ' + list + ', ' + 'i: ' + i);
let ret = this.a(cghVar, list, i);
console.log('a ret value is ' + ret);
return ret;
};
可以看到每次请求确实会走这里 而且第一个参数就是视频号的用户名:v2_060000231003b20faec8c5e********************@finder
解析响应的list里面也会包含标题 id等一堆信息 这样就拿到了我需要的标题 id 等信息了
接下来就去找找怎么执行这个请求
寻找构造函数的交叉引用 有很多
我们打印一下堆栈 看一下执行流程 (为什么是打印堆栈而不是选择交叉引用 因为这个点浪费了我很多 时间 就是它的类很多都是抽象方法 往上手动跟踪的话 会找到未实现的方法!)
可以看到我们开始没有定位到的UI界面 继续跟进去
在onCreate 函数中 他会获取 FinderProfileFeedLoader 对象 继承 BaseFinderFeedLoader
接下来我的思路就变成了 构造 FinderProfileFeedLoader 对象 然后调用requestRefresh() 方法
构造方法也很简单 第一个参数是枚举类型 第二个是用户名 第三个是chn对象 后面是boolean
hook输出一下参数打印 如下
开始构造执行请求
调用函数 执行刷新 完美~
后面发现视频多 需要翻页这么执行就不行了 同上继续跟踪
这么简单 翻页?执行一下就行了
发现还是第一页 原来是它第二个构造参数变成了 获取视频 id
可以填充data 或者 自己hook参数 改需要翻页的视频id就行了~ 感兴趣自己研究一下
待续…