TikTok抓包协议分析:So层逆向
前言
免责声明: 本文内容仅供学习交流使用,请勿用于其他用途,如因使用本文内容而产生任何问题,与作者无关,如果本文侵害贵司权益,请联系
[email protected]
下架处理第一章搞定了,Tk的上网环境,但是还不能抓包,因为Tk系的协议不太一样用的是QUIC协议
那我们今天的目标是绕过这个协议,让他降级,实现我们成功抓包的目的(还有会番外篇非降级抓包,可以小小期待一下)
文章尽量还原每一步,尽可能写的详细,如果还是有问题可以加我微信沟通
Alive0501
环境
手机
小米8
版本
33.4.3
准备
抓包失败
开了抓包以后APP直接是网络无连接了,并且抓到的请求也是不正常的,下面我们就开始定位
日志分析
可以从日志分析入手,看看有没有什么异常的报错
使用
adb logcat
命令查看先用 adb logcat -c 请一下日志
执行 adb logcat 命令
然后打开tiktok app
出现发生错误这个页面后停止 adb locat 命令
把文件保存到本地进行分析
定位(整个日志中,只有此处有发送网络请求而且失败,所以一眼定位这里)
日志里找到有一个请求URL的位置并且有一个
|Exception in CronetUrlRequest
像是网络错误的请求所以分析一下这个位置
jadx反编译
拿上面的这个
Exception in CronetUrlRequest
去jdax里进行搜索上面搜索到三个位置,后两个是同一个函数,所以hook一下这两个地方,打印下堆栈
直接右键复制为Frida片段就可以了
Java.perform(function () { let g = Java.use("org.chromium.g"); g["LIZ"].implementation = function (i, i2, str) { console.log(`g.LIZ is called: i=${i}, i2=${i2}, str=${str}`); let result = this["LIZ"](i, i2, str); console.log(`g.LIZ result=${result}`); console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new())); return result; }; let CronetUrlRequest = Java.use("com.ttnet.org.chromium.net.impl.CronetUrlRequest"); CronetUrlRequest["onError"].implementation = function (i, i2, i3, str, j) { console.log(`CronetUrlRequest.onError is called: i=${i}, i2=${i2}, i3=${i3}, str=${str}, j=${j}`); this["onError"](i, i2, i3, str, j); console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new())); }; })
打印结果
上面图片可以看到所有的打印都是走的
CronetUrlRequest.onError
这个函数,他是一个Native层的方法 那下面来分析一下So层
定位So
直接把app文件 重命名一下
.apk
改为.zip
直接找到
tiktok-33-4-3.zip\lib\arm64-v8a
这里面有很多so文件,怎么定位到我们需要的so文件那我这里使用grep进行搜索
grep的作用就是搜索到我们要定位的字符串在那个So文件里
grep的安装可以看这个
https://blog.csdn.net/qq_29752857/article/details/140169107
把.apk后缀改为.zip
压解到一个文件夹
cd 到解压文件夹的
\lib\arm64-v8a
目录 打开cmd执行命令
grep -r "CronetUrlRequest" *
显示在
libsscronet.so
这个So文件中
分析So
从
\lib\arm64-v8a
下面找到libsscronet.so
这个文件丢到ida64中按Shift F12 字符串搜索
CronetUrlRequest
点击
com/ttnet/org/chromium/net/impl/CronetUrlRequest
进入
sub_190028+8↓o
然后按tab键Hook下这个位置
190028
这里为了方便注释Hook的代码,拆分开写的
主函数
function hook_dlopen(module_name, fun) { var android_dlopen_ext = Module.findExportByName(null, "android_dlopen_ext"); if (android_dlopen_ext) { Interceptor.attach(android_dlopen_ext, { onEnter: function (args) { pass }, onLeave: function (retval) { passa } }); } } function main() { hook_dlopen("libsscronet.so") } setImmediate(main)
注释
Hook
android_dlopen_ext
监控.so
加载判断是不是我们要hook的"libsscronet.so"文件
onEnter
onEnter: function (args) { var pathptr = args[0]; // 读取第一个参数,它是一个指向动态库路径的指针 if (pathptr) { // 确保指针有效,避免 `null` 访问错误 this.path = pathptr.readCString(); // 将指针解析成字符串,得到动态库的路径 if (this.path.indexOf(module_name) >= 0) { // 判断路径是否包含目标库名 this.canhook = true; // 发现目标库,标记 `this.canhook = true`,表示可以 hook } } }
注释
onEnter
在目标函数(android_dlopen_ext
)执行前触发如果路径是否包含目标库名,把canhook设置为True
onLeave
onLeave: function (retval) { if (this.canhook) { // 只有在 onEnter 里确认目标库被加载了,才会执行 hook 逻辑 let base_libsscronet = Module.findBaseAddress("libsscronet.so"); // 获取 libsscronet.so 的基地址 let sub_190028 = base_libsscronet.add(0x190028); // 计算 sub_190028 函数的地址(基地址 + 偏移 0x190028) console.log("hook", sub_190028); // 打印目标函数的地址,便于调试 Interceptor.attach(sub_190028, { // Hook 目标函数 onEnter(args) { console.log("sub_190028 called, return address:", DebugSymbol.fromAddress(this.returnAddress)); // `this.returnAddress` 是调用 `sub_190028` 的上层函数的返回地址 // `DebugSymbol.fromAddress` 解析该地址,尝试获取调用 `sub_190028` 的函数名 } }); } }
注释
this.returnAddress
返回的是调用者的地址DebugSymbol.fromAddress
是为了把地址转为函数名
打印结果
按G跳转到
0x18fae8
偏移量在V16上
点进
sub_18209C
这个函数.cc
文件是 C++ 源代码文件,它的作用与.cpp
文件相同,通常用于存放 C++ 代码
跟进
sub_182194
看到这块信息Hook一下
sub_20E814
可以看到第二个是一个String ,所以hook代码要修改一下把第二个参数String也打印
onLeave: function (retval) { if (this.canhook) { let base_libsscronet = Module.findBaseAddress("libsscronet.so"); let sub_20E814 = base_libsscronet.add(0x20E814); console.log("hook", sub_20E814); Interceptor.attach(sub_20E814, { onEnter(args) { console.log("sub_20E814 called, return address:",args[1].readCString()), DebugSymbol.fromAddress(this.returnAddress); } }); } }
输出结果
这个地方明显不是,app已经出现连接失败了,后面过了一会才有的打印,说明这个打印已经晚了,如果是我们想要的位置,他应该在检测中进行打印
上面的输出其实并没有什么有效信息
分析引用
那这样我们线索已经断了,先按X看一下这个函数都是有谁引用了 看看有没有线索
看了半天感觉也没啥有用的东西,那下面我们整理一下已经有信息,来猜测一下,后续怎么进行
那目前我们根据已经有信息,看到代码里有用
../../net/tt_net/ipc/ipc_channel_reader.cc"
这样的方式去写的那可以猜测,我们真正需要Hook的那个函数也有可能是这样加载的,可以字符串搜索一下
../../
看到有几个目录
net
base
components
等,我们是为了解决抓包问题,那很有可能是在net目录下(network),那来搜索一下../../net
还是比较多的,这个时候可以有选择的看一下,跟网络请求相关的
quic
ssl
等最后定位到是
../../net/socket/ssl_client_socket_impl.cc
这个文件
然后点进来看一下
hook下
20E7EC
看到有一个叫
HandleVerifyResult
比较像,我们跳进去看一下0x3174dc
如果打印不出
HandleVerifyResult
可以换个手机试下
然后向上滑找到函数看一下是谁在引用, 因为怀疑这一段是 错误处理 所以先看一下谁调用他
这里有两个点那个都可以,最后都是一个位置,进来之后继续找到函数看一下引用
接着点进去,进去以后看到很多个函数都点开看一下,最后定位到
sub_4913E8()
上面我们可以搜一下
SSL_CTX_set_custom_verify
这是什么意思第三个参数是一个回调函数,这里他的返回值1表示成功,0表示失败,那我们手动把他Hook为0,让他不支持quic协议,实现降级
就能成功绕过校验了~!
function hook_dlopen(module_name, fun) { var android_dlopen_ext = Module.findExportByName(null, "android_dlopen_ext"); if (android_dlopen_ext) { Interceptor.attach(android_dlopen_ext, { onEnter: function (args) { var pathptr = args[0]; if (pathptr) { this.path = (pathptr).readCString(); if (this.path.indexOf(module_name) >= 0) { this.canhook = true; } } }, onLeave: function (retval) { if (this.canhook) { let verifyadd = Module.getExportByName("libsscronet.so","SSL_CTX_set_custom_verify"); Interceptor.attach(verifyadd,{ onEnter(args){ Interceptor.attach(args[2],{ onLeave(retval){ retval.replace(0x0); } }) } }) } } }); } }
成功搞定
就可以抓包并且上TikTok了
星球
想跟大家介绍一下我们从年前就开始筹备的星球,最近也要正式上线了!
目前星球已有内容
加入星球你可以收获
TikTok 从0到1的保姆级逆向内容
wasm 纯算从零基础 - 大厂平台案例
jsvmp 超详细讲解文章,及问题答疑
不定时答疑直播课!
一对一简历指导,及面试经验技巧!
平等友好的交流圈子,内部交流群 !