起点QDSign AegisSign逆向
Java层

搜索QDSign


跳转到com.qidian.QDReader.component.network.d.a

com.qidian.QDReader.component.network.d.d

a.c.signParams

objection hook验证一下
android hooking watch class_method a.c.signNew --dump-args --dump-return
android hooking watch class_method a.c.s --dump-args --dump-return
android hooking watch class_method a.c.sign --dump-args --dump-return



其中a.c.sign
的返回值就是QDSign
,a.c.s
的返回值是AegisSign
QDSign
so层
由于apk支持64位指令集,手机也支持64位指令集,所以下面分析的是arm64-v8a
的so。

打开libc-lib.so
,函数窗口搜索jni

跳转看看

跳转并修改a1
类型为JNIEnv *a1

看看sub_1084

可以看到是调用了DES_ede3_cbc_encrypt
上网搜一搜,发现是OpenSSL里的算法。

void DES_ede3_cbc_encrypt(const unsigned char *input,unsigned char *output,
long length,
DES_key_schedule *ks1,DES_key_schedule *ks2,
DES_key_schedule *ks3,DES_cblock *ivec,int enc);
所以v51, v50, v49
就是3个key,v46
是iv。
通过分析代码得知,v12就是3个key。

根据Java层的入参,a3
等于1,所以v11
等于0x3246387B63uLL >> 8 = 0x3246387B
,由于v12
的类型是char*
数据,所以v12[0]
等于0x3246387B & 0xff = 0x7B
,其他同理。
v46等于0x3736353433323130LL
,由于大小端的原因,所以iv等于"01234567"
。
代码尝试解密一下数据
import base64
from Crypto.Cipher import DES3
v44 = 0x362C746855 >> 8
v43 = 0x6C4A365B73 >> 8
v42 = 0x776D7D6B6A >> 8
v41 = 0x59572A5D25 >> 8
v40 = 0x61715B3475 >> 8
v39 = 0x585457765A >> 8
v38 = 0x2A275F6350 >> 8
v37 = 0x664D424573 >> 8
v36 = 0x7B5232713B >> 8
v35 = 0x7D26524B23 >> 8
v34 = 0x5B2C74685F >> 8
v33 = 0x536775296F >> 8
v32 = 0x522A565266 >> 8
v31 = 0x734A2C2C40 >> 8
v3 = 0x592C74686E >> 8
v4 = 0x656775295D >> 8
v5 = 0x714D424575 >> 8
v6 = 0x5552327168 >> 8
v7 = 0x7453556721 >> 8
v8 = 0x6B7923596E >> 8
v9 = 0x6B4D566465 >> 8
v10 = 0x6D43593168 >> 8
v11 = 0x3246387B63 >> 8
v30 = 0x79556D3967 >> 8
v12 = [0] * 24
v12[0] = v11
v12[1] = v10
v12[2] = v9
v12[9] = v30
v12[3] = v8
v12[4] = v7
v12[5] = v6
v12[10] = v31
v12[6] = v5
v12[7] = v4
v12[8] = v3
v12[11] = v32
v12[12] = v33
v12[13] = v34
v12[14] = v35
v12[15] = v36
v12[16] = v37
v12[17] = v38
v12[18] = v39
v12[19] = v40
v12[20] = v41
v12[21] = v42
v12[22] = v43
v12[23] = v44
key = bytes(x&0xff for x in v12)
print(key)
data = base64.b64decode('R7TCs6Tou2X528j+NblfBlkZKrDI6v4lL54ep1+q8M9Ne1vBxZaXEJmxFxpu qzl/sF8jizgbsoW/2mnH4Y1Id7TgNL80BUZy7x4lBzsWt7EIC48y0OA1xvrI UWEBy5jPl5HiNUfq5zAJg/g4FEoPzED2FYRy4GZh3f8m0JpwR3s=')
cryptor = DES3.new(key, DES3.MODE_CBC, b'01234567')
print(cryptor.decrypt(data))

解密成功,说明key和iv是对的,通过多个请求对比,发现aaddd0efa5721b0f655ebe9b22182bb7
不是固定的。
接下来分析它


看看sub_17D0

看起来是对a4
做了个MD5,根据Java层的传参规则,知道a4
就是第二个参数objid=0&objtype=-1&position=meiridaodu
,cyberchef上测一下

完整实现
key = b'{1dYgqE)h9,R)hKqEcv4]k[h'
def calc_qdsign(ts, params, did='', version='7.9.178'):
if isinstance(params, (dict, list, tuple)):
if hasattr(params, 'items'):
params = params.items()
params = '&'.join(f'{k}={v}' for k,v in sorted(params)).lower()
print(params)
sign0 = hashlib.md5(params.encode()).hexdigest()
data = f'Rv1rPTnczce|{ts}|0|{did}|1|{version}|0|{sign0}|f189adc92b816b3e9da29ea304d4a7e4'.encode()
print(data)
data = pad(data, 8)
cryptor = DES3.new(key, DES3.MODE_CBC, b'01234567')
data2 = cryptor.encrypt(data)
qdsign = base64.b64encode(data2)
return qdsign
def test_qdsign():
ts = '1641782588576'
params = {
'objId': '0',
'objType': '-1',
'position': 'meiridaodu'
}
# params = 'objid=0&objtype=-1&position=meiridaodu'
did = '352531082124700'
qdsign = calc_qdsign(ts, params, did)
print(qdsign)

AegisSign
so层
前面已经分析出a.c.s
就是加密函数

接下来继续往so层分析。打开libsos.so
,搜索jni

进入看看

函数名和参数都能对上,进入doSign
并将a1
改为JNIEnv *a1
。

sub_126C
应该就是最终加密的地方,进入看看

同样是DES_ede3_cbc_encrypt
加密,hook一下设置key的函数
function dump(name, addr, length) {
console.log("=============== " + name + " ====================");
console.log(hexdump(addr, {length: length||32}));
console.log("=================================================");
}
function hook_sos_key() {
Interceptor.attach(Module.findExportByName("libsos.so", "DES_set_key_unchecked"), {
onEnter: function(args) {
dump("sos-key", args[0], 8);
},
onLeave: function(){
}
})
}

尝试拿去解密
key1 = b'b135a3f3'
key2 = b'ec5d642c'
key3 = b'8b279aa4'
key = key1 + key2 + key3
iv = bytes.fromhex('30 31 32 33 34 35 36 37')
data = base64.b64decode('xXCTSy36cxHzm0nEtK+237OVtqQW7fy/M1PD5nS8OCWZt8juDSBqXOSPLHJKIhVWANLQhHEHe0gUBQ67U0p7XtabLlZ+UCPVExDFF59BMEB5y7TbP9NzDvEaMjSt0GIPNvfHIhBMpUc+Nat/A1A+IP1+cfkx2EX5W0CDmcXuYW4=')
cryptor = DES3.new(key, DES3.MODE_CBC, iv=iv)
ret = cryptor.decrypt(data)
print(ret)

成功解密。暂时不清楚key是否是固定的,先来看看原始数据的构造,比较多个请求,发现3d893aaa8fea88cf4cf8f9c10bc658ce
是不固定的。
分析src
的来源


看看sub_157C

调用了MD5,hook一下
function hook_sos_md5() {
Interceptor.attach(Module.findExportByName("libsos.so","MD5"), {
onEnter: function(args) {
this.arg2 = args[2];
dump("sos-md5-arg0", args[0], parseInt(args[1]));
},
onLeave: function(){
dump("sos-md5-ret-arg2", this.arg2, 16);
}
})
}

收获颇丰,不仅找到了DES_ede3_cbc_encrypt
的key的生成方式,还找到了3d893aaa8fea88cf4cf8f9c10bc658ce
的生成方式。可以看到先对请求参数做个MD5,然后取每个byte的低位,然后逆序,再和0
和7.9.178
进行拼接,最后再做个MD5就行了。
完整实现
def calc_aegissign(ts, params, did='', version='7.9.178'):
if isinstance(params, (dict, list, tuple)):
if hasattr(params, 'items'):
params = params.items()
params = '&'.join(f'{k}={v}' for k,v in sorted(params)).lower()
sign0 = hashlib.md5((did + '0').encode()).hexdigest()
print('sign0:', sign0)
key = sign0[8:].encode()
print('key:', key)
sign1 = hashlib.md5(params.encode()).hexdigest()
print('sign1:', sign1)
data1 = ''.join((sign1[-1:0:-2], '0', version)).encode()
print(data1)
sign1 = hashlib.md5(data1).hexdigest()
data2 = f'b4dc46c5b13|{ts}|0|{did}|1|{version}|0|{sign1}|f189adc92b816b3e9da29ea304d4a7e4'.encode()
print(data2)
data2 = pad(data2, 8)
cryptor = DES3.new(key, DES3.MODE_CBC, b'01234567')
data3 = cryptor.encrypt(data2)
aegissign = base64.b64encode(data3)
return aegissign
def test_aegissign():
ts = '1641782588583'
params = 'objid=0&objtype=-1&position=meiridaodu'
did = '352531082124700'
aegissign = calc_aegissign(ts, params, did)
print(aegissign)

网友评论