驾考宝典sign逆向分析
环境
app 6.9.8
Java层
抓包

jadx搜索"sign"
,只有一个

查找用例

结果不是很多,但是看起来都不太像,还有几个是调用baidu
,tencent
的包,应该是有接入什么sdk或者其他的。换个关键词,搜索"_r"

aw.a -> a

aw.a -> N

cn.mucang.android.core.utils.aa -> ao

jadx不太给力啊。。换个工具,GDA打开

cn.mucang.android.core.jni.Riddle -> s

最后调用了native函数,对应的so是libtnpn.so
frida hook一下
function hook_s(){
var Riddle = Java.use("cn.mucang.android.core.jni.Riddle");
Riddle.s.implementation = function(str0, str1) {
console.log("s-str0=", str0);
console.log("s-str1=", str1);
var ret = this.s(str0, str1);
console.log("s-ret=", JSON.stringify(ret));
return ret;
}
}
Java.perform(function() {
hook_s();
})

第一个参数str0
很容易构造,但是第二个str1
就不太理解了,它好像不是生成随机的,因为有些请求的str1
是相等的,jadx搜索*#06#

好家伙,这样推断的话,应该是每个接口有对应的str1
?
so层
ida打开,搜索Java

说明是静态注册的,打开Java_cn_mucang_android_core_jni_Riddle_s

看看j_j_GetSigningVersion



所以,当str1
包含*#06#
时,会调用j_j_SignUrl1
,否则调用j_j_SignUrl0
。
看看j_j_SignUrl1



上面的代码算是通俗易懂了,先把str2
的解码实现一下
import base64
def decode(s):
s1 = base64.b64decode(s)
s2 = bytes(map(lambda x: (x-42) ^ 0x2A, s1))
return s2

然后可以看到,它还调用了j_j_SignUrl0
方法



简单的将a1
,a2
拼接一下,然后做个md5,a3
是结果

然后是这一部分,是把a2
转为uint8后求和,然后会调用sub_E9470



函数的实现看起来花里胡哨的,里面有个__clz
函数,搜了下,是计算前导零的个数
__clz:
Count Leading Zeros ,计算前导零指令;
指令编码格式
__clz指令返回操作数二进制编码中第一个1前0的个数。如果操作数为0,则指令返回32;如果操作数二进制编码第31位为1,指令返回0。功能:假如一个数为0x1FFF FFFF,则转换为2进制为 (0001 1111 1111 1111 1111 1111 1111 1111),
则 __clz(0x1FFF FFFF) 的值为3;
还有这篇文章指出,该函数的作用就是实现除法。
然后就很容易脑补出v10
就是余数,就是这么自然~

最后就是拼接md5结果和余数了,当余数为0时,直接返回md5,此时byte数组长度为16;当余数不为0时,拼接md5结果和余数再返回,此时byte数组长度为17。这也解释了为什么sign的长度有时是32,有时又是34。
代码实现
import base64
import hashlib
def decode(s):
s1 = base64.b64decode(s)
s2 = bytes(map(lambda x: (x-42) ^ 0x2A, s1))
return s2
def calc_sign1(url, key):
key0 = decode(key)
sign = calc_sign0(url, key0)
v9 = sum(key)
_, v10 = divmod(v9, 0x13)
if v10:
sign = sign + f'{v10:02x}'
return sign
def calc_sign0(url, key):
sign = hashlib.md5(url + key).hexdigest()
return sign
def calc_sign(url, key):
if isinstance(url, str):
url = url.encode()
if isinstance(key, str):
key = key.encode()
if key.startswith(b'*#06#'):
sign = calc_sign1(url, key[5:])
else:
sign = calc_sign0(url, key)
return sign

其他
每个API对应的key是什么,可以用frida hook打印一下,请自行处理,因为我只是想分享一下逆向的思路,并不是要去采集什么数据。
代码仅供把玩。
网友评论