某某语音auth值生成分析
字数 953 2025-08-22 12:22:54
某某语音Auth值生成分析教学文档
1. 分析背景
在某某语音应用的网络请求中,发现一个关键的校验参数auth,本文档将详细分析该参数的生成过程。
2. 初步定位
2.1 反编译定位
- 使用jadx反编译APK
- 搜索关键字"auth"定位到相关代码位置
- 调用链如下:
com.dyheart.lib.dylog.network.RequestInterceptor.a(Request request, String str) → com.dyheart.lib.dylog.network.RequestInterceptor.a(Context context, String str, Map<String, String> map, Map<String, String> map2, String str2) → com.douyu.lib.http.MakeUrlClient.b(Context context, String str, String[] strArr, String[] strArr2, String[] strArr3, String[] strArr4, int i, int i2) → com.douyu.lib.http.MakeUrlClient.b(Context context, String str, String[] strArr, String[] strArr2, String[] strArr3, String[] strArr4, int i, int i2) → com.douyu.lib.http.JniMakeUrl.native_newmakeUrl
2.2 JNI函数定位
native_newmakeUrl是一个JNI函数- 在
makeurl3.3.0.so库中未找到导出函数名,说明是动态注册 - 需要Hook
RegisterNatives函数来定位
3. Hook RegisterNatives
3.1 Hook脚本
function hook_RegisterNatives() {
var symbols = Module.enumerateSymbolsSync("libart.so");
var addrRegisterNatives = null;
for (var i = 0; i < symbols.length; i++) {
var symbol = symbols[i];
if (symbol.name.indexOf("art") >= 0 &&
symbol.name.indexOf("JNI") >= 0 &&
symbol.name.indexOf("RegisterNatives") >= 0 &&
symbol.name.indexOf("CheckJNI") < 0) {
addrRegisterNatives = symbol.address;
console.log("RegisterNatives is at ", symbol.address, symbol.name);
}
}
if (addrRegisterNatives != null) {
Interceptor.attach(addrRegisterNatives, {
onEnter: function(args) {
console.log("[RegisterNatives] method_count:", args[3]);
var env = args[0];
var java_class = args[1];
var class_name = Java.vm.tryGetEnv().getClassName(java_class);
var methods_ptr = ptr(args[2]);
var method_count = parseInt(args[3]);
for (var i = 0; i < method_count; i++) {
var name_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3));
var sig_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize));
var fnPtr_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize * 2));
var name = Memory.readCString(name_ptr);
var sig = Memory.readCString(sig_ptr);
var find_module = Process.findModuleByAddress(fnPtr_ptr);
console.log("[RegisterNatives] java_class:", class_name, "name:", name, "sig:", sig,
"fnPtr:", fnPtr_ptr, "module_name:", find_module.name,
"module_base:", find_module.base, "offset:", ptr(fnPtr_ptr).sub(find_module.base));
}
}
});
}
}
setImmediate(hook_RegisterNatives);
3.2 反调试绕过
- 发现存在Frida反调试(特征检测和进程附加)
- 使用Florida(去除部分特征的server)绕过
- 成功打印出
native_makeUrl的地址:0x6168
4. 分析native_newmakeUrl
4.1 Hook native_newmakeUrl
Java.perform(() => {
var JniMakeUrlClass = Java.use("com.douyu.lib.http.JniMakeUrl");
if (JniMakeUrlClass) {
JniMakeUrlClass.native_newmakeUrl.implementation = function(context, str, strArr, strArr2, strArr3, strArr4, i, i2) {
console.log("Context 参数: ", context);
console.log("字符串参数: ", str);
console.log("字符串数组参数(长度: " + (strArr && strArr.length || 0) + "):");
if (strArr && Array.isArray(strArr)) {
for (var j = 0; j < strArr.length; j++) {
console.log(" 元素 " + j + ": " + strArr[j]);
}
}
console.log("字符串数组参数2(长度: " + (strArr2 && strArr2.length || 0) + "):");
if (strArr2 && Array.isArray(strArr2)) {
for (var k = 0; k < strArr2.length; k++) {
console.log(" 元素 " + k + ": " + strArr2[k]);
}
}
console.log("字符串数组参数3(长度: " + (strArr3 && strArr3.length || 0) + "):");
if (strArr3 && Array.isArray(strArr3)) {
for (var l = 0; l < strArr3.length; l++) {
console.log(" 元素 " + l + ": " + strArr3[k]);
}
}
console.log("字符串数组参数4(长度: " + (strArr4 && strArr4.length || 0) + "):");
if (strArr4 && Array.isArray(strArr4)) {
for (var m = 0; m < strArr4.length; m++) {
console.log(" 元素 " + m + ": " + strArr4[m]);
}
}
console.log("整数参数1: ", i);
console.log("整数参数2: ", i2);
var retval = this.native_newmakeUrl(context, str, strArr, strArr2, strArr3, strArr4, i, i2);
console.log("函数返回值: ", retval);
return retval;
};
} else {
console.log("未能找到 com.douyu.lib.http.JniMakeUrl 类");
}
});
4.2 参数分析
- 多数参数是固定值
- 关键变化参数:字符串数组2的元素3,是一个时间戳(
System.currentTimeMillis()/1000)
5. Auth生成过程分析
5.1 关键函数sub_10898
v33被sub_10898赋值- Hook
sub_10898:
function hook_sub_10898(){
var addr = Module.findBaseAddress("libmakeurl3.3.0.so");
console.log("libmakeurl3.3.0.so base address: " + addr);
var funcAddr = addr.add(0x10898);
console.log("makeurl3.3.0.so makeurl address: " + funcAddr);
Interceptor.attach(funcAddr, {
onEnter: function(args) {
this.args8 = args[8];
console.log("args0: " + args[0]);
console.log("args1: " + args[1].toInt32());
console.log("args2: " + args[2]);
console.log("args3: " + args[3]);
console.log("args4: " + Memory.readUtf8String(args[4]));
console.log("args5: " + (args[5].readPointer()));
console.log("args6: " + args[6].toInt32());
console.log("args7: " + args[7].toInt32());
console.log("args8: " + hexdump(args[8]));
},
onLeave: function(retval){
console.log("retval:" + Memory.readUtf8String(retval));
console.log("args8:" + hexdump(this.args8));
}
})
}
5.2 生成逻辑
sub_10898的返回值是a6和a9拼接而成a9是固定字符串:"vOXGo3ad7hLHyTw4Zgu2blCjEBQDcx6z"a6是通过MD5计算得到(sub_CC34)
5.3 MD5计算部分
sub_CC34函数实现MD5算法- 输入参数包括时间戳等关键信息
6. Auth生成总结
- 获取当前时间戳(秒级)
- 将时间戳与其他固定参数组合
- 对组合后的字符串进行MD5计算
- 将MD5结果与固定字符串"vOXGo3ad7hLHyTw4Zgu2blCjEBQDcx6z"拼接
- 最终拼接结果即为
auth值
7. 完整调用链
获取时间戳(System.currentTimeMillis()/1000)
→ 传递到RequestInterceptor
→ 传递到MakeUrlClient
→ 调用JniMakeUrl.native_newmakeUrl
→ 调用libmakeurl3.3.0.so中的sub_10898
→ 调用MD5计算(sub_CC34)
→ 拼接MD5结果与固定字符串
→ 返回auth值
8. 关键点总结
- 使用Frida Hook动态注册的JNI函数
- 绕过Frida反调试(使用Florida)
- 逆向分析so库中的关键函数
- 识别MD5算法实现
- 确定auth由MD5结果与固定字符串拼接而成