Tiktok版本: 33.2.5 (24年1月25日)
1、前言
通过对Tiktok抓包发现TikTok的所有http请求的头部增加了参数签名(x-argus、x-goron、x-khronos、x-ladon):
2、获取关键函数
直接将apk文件用jadx工具反编译,搜索关键字x-argus等,未搜索到任何关键字:
使用frida hook java层 Header对象的构造方法,发现有监控到https请求头设置,但是始终没有发现签名字段
var Header = Java.use("X.2jn");
Header.$init.implementation = function(key, val) {
console.log(key + " : " + val);
this.$init(key, val);
}
接着尝试hook interceptor拦截器,打印出请求头部,结果输出的请求头部中还是没有签名字段
//添加intercept
var Request = Java.use("X.LGn");
Request.LIZJ.implementation = function(intercept){
var newObj = Java.cast(intercept, Java.use("java.lang.Object"));
//console.log(newObj.getClass());
var new_cls = newObj.getClass();
var class_name = new_cls.getName();
if(!mmp[class_name]) {
mmp[class_name] = 1;
var hook_class = Java.use(class_name);
hook_class.intercept.implementation = function(tt){
var res = this.intercept(tt);
/* var rq = tt.request();
console.log(rq.getUrl()); */
var request = getFieldVal(res, "LIZ");
//console.log(getFieldVal(request, "LIZ"));
//console.log(getFieldVal(request, "LIZJ"));
//console.log(getFieldVal(request, "LJI"));
var headers = getFieldVal(request, "LIZLLL");
//console.log(headers);
//console.log("----------------------------------------------------");
return res;
}
}
this.LIZJ(intercept);
}
Java层找了一遍始终找不到签名关键字符串,那很大可能是在native层设置,在native层在 https请求发出时进行设置。TikTok使用 cronet网络库 。将libsscronet拖IDA反编译,搜索 x-gorfon 关键字,确实找到了:
结合 cronet源码,找到Native层设置请求Header函数:
void HttpRequestHeaders::SetHeader(const base::StringPiece& key,
const base::StringPiece& value) {
DCHECK(HttpUtil::IsValidHeaderName(key.as_string()));
// TODO(ricea): Revert this. See crbug.com/627398.
CHECK(HttpUtil::IsValidHeaderValue(value.as_string()));
HeaderVector::iterator it = FindHeader(key);
if (it != headers_.end())
it->value.assign(value.data(), value.size());
else
headers_.push_back(HeaderKeyValuePair(key, value));
}
定位到该libsscronet中偏移为:0x2C44BC,HOOK该地址并打印出参数:
var SetHeaders = lib.base.add(0x2C44BC);
Interceptor.attach(SetHeaders, {
onEnter: function (args) {
var key = args[1].readPointer().readUtf8String(); //第一个属性是字符串的地址, 第二个属性长度
var val = args[2].readPointer().readUtf8String();
console.log(key + " : " + val);
},
onLeave: function (retval) {
}
});
这次全部头部都可以看到了,完美!
然后打印设置参数签名字段时的调用栈:
var SetHeaders = lib.base.add(0x2C44BC);
Interceptor.attach(SetHeaders, {
onEnter: function (args) {
var key = args[1].readPointer().readUtf8String(); //第一个属性是字符串的地址, 第二个属性长度
var val = args[2].readPointer().readUtf8String();
if(key.indexOf("X-Gorgon") !== -1) {
console.log(key + " : " + val);
console.log('SetHeaders called from:\n' + Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n') + '\n');
console.log("---------------------------end----------------------------------");
}
},
onLeave: function (retval) {
}
});
有两处调用,分别是 0x3a662c 和 0x2c4b78:
分别对两处进行反编译分析,0x3a662c 更像是tiktok定制的设置相关header处,关键代码反编译:
v128是从某个函数调用返回的结果,对该结果分割处理就得到了参数签名字段,因此用frida hook这里,然后输出调用时的两个参数查看
直接对0x3A658C地址进行HOOK,并打印出x0和x1寄存器的值,同时打印出X23寄存器的值:
var test = lib.base.add(0x3A658C);
Interceptor.attach(test, {
onEnter: function (args) {
console.log(args[0].readUtf8String());