美文网首页
起点QDSign AegisSign逆向

起点QDSign AegisSign逆向

作者: ever_hu | 来源:发表于2022-01-12 19:57 被阅读0次

起点QDSign AegisSign逆向

Java层

image-20220110104610878

搜索QDSign

image-20220109202434032 image-20220109202541940

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

image-20220109202646464

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

image-20220109203013433

a.c.signParams

image-20220109203107627

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
image-20220110104346265 image-20220110104446824 image-20220110104536455

其中a.c.sign的返回值就是QDSigna.c.s的返回值是AegisSign

QDSign

so层

由于apk支持64位指令集,手机也支持64位指令集,所以下面分析的是arm64-v8a的so。

image-20220110013526222

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

image-20220110013718920

跳转看看

image-20220110013745906

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

image-20220110013921131

看看sub_1084

image-20220110014044053

可以看到是调用了DES_ede3_cbc_encrypt

上网搜一搜,发现是OpenSSL里的算法。

image-20220110014239111
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。

image-20220110020122587

根据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))
image-20220110104709180

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

接下来分析它

image-20220110104931904 image-20220110104946673

看看sub_17D0

image-20220110105037388

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

image-20220110105315093
完整实现
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)
image-20220110160017429

AegisSign

so层

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

image-20220110152921931

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

image-20220110153049846

进入看看

image-20220110153210964

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

image-20220110153349397

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

image-20220110153507469

同样是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(){

        }
    })
}
image-20220110153706844

尝试拿去解密

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)
image-20220110154026094

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

分析src的来源

image-20220110154237981 image-20220110154456781

看看sub_157C

image-20220110154554143

调用了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);
        }
    })
}
image-20220110155008791

收获颇丰,不仅找到了DES_ede3_cbc_encrypt的key的生成方式,还找到了3d893aaa8fea88cf4cf8f9c10bc658ce的生成方式。可以看到先对请求参数做个MD5,然后取每个byte的低位,然后逆序,再和07.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)
image-20220110155724386

相关文章

网友评论

      本文标题:起点QDSign AegisSign逆向

      本文链接:https://www.haomeiwen.com/subject/ewxgcrtx.html