app:车智赢-v3.13.0

接口:https://dealercloudapi.che168.com/tradercloud/sealed/login/login.ashx

解析流程:

该app有root检测,但是只检测无对应措施,也无双向验证,可以正常抓包请求,经过请求发现,_sign,udid,pwd三个参数有做加密,同样使用jadx进行反编译

image-20230519212606790

直接搜索接口地址,直接进去查看,在本页搜索LOGIN_URL看一下有谁调用,loginByPassword方法创建了一个post请求,视乎直接对密码进行md5加密,我们直接查看一下encodeMD5方法

image-20230519212802200

image-20230519212907691

image-20230519213001843

image-20230519213200397

直接手动写个md5加密进行验证,发现和抓包到的加密密码一致

1
2
3
4
5
6
7
import hashlib

obj = hashlib.md5()
obj.update("123456".encode('utf-8'))
res = obj.hexdigest()
print(res)
# e10adc3949ba59abbe56e057f20f883e

也可以再hook一下loginByPassword和loginByPassword方法,看一下是否和我们的结果一样,结果一致

image-20230519213857443

直接搜索_sign,结果不多,可以都点进去看一下,这里直接进到这个key_sign里面去

image-20230519214253860

发现这里定义了请求头里面的一些参数,我们直接搜KEY_SIGN看下有谁调用了,getRequestParams方法这里视乎对sign加密前的参数做了一些处理,我们看一下signByType方法

image-20230519214408535

image-20230519214553870

我们直接hook一下signByType方法看一下,发现加入的参数就是请求体,再看一下signByType的逻辑,就是循环请求体,然后拼接键值对,再开头和结尾加一个KEY_V2,这个参数固定是W@oC!AH_6Ew1f6%8,所以我们得先解决uuid是什么

image-20230519214715033

image-20230519215325529

接下来搜索”udid” 结果不多,我们直接看第二个,再看下getUDID方法,发现好像是一个3des加密,encode3Des方法传入两个参数,一个是Context还未知,一个是sb,我们对sb参数简写一下,REPORT_VAL_SEPARATOR是”|”固定,

image-20230519215900147

image-20230519215930693

image-20230519220006480

1
sb = getDeviceId(context) + "|" + System.nanoTime()  + "|" + userDeviceId

我们也可以hook一下encode3Des方法,看一下传入的参数是什么样的,然后我们再去追究一下sb的参数内容是什么

image-20230519220852460

image-20230519220908552

回到getUDID方法处,先看下getDeviceId(context)是什么,进去之后发现getDeviceId反编译未成功,我们重新把app拖进去反编译一下,getUDI方法总体逻辑没变,只是里面的一些参数名称变了getDeviceId方法变成了getIMEI方法,这里不用管,反编译问题,我们进去看getIMEI是什么

image-20230519224721143

进去之后发现调用了getIMEIbyAndroidIDandUUID方法,返回的是一个随机uuid,这个可以直接用str(uuid.uuid4())方法生成

image-20230519224952693

第二个参数System.nanoTime(),百度了一下这个方法,有点类似于开机到现在系统的时间差,比如你开机的时间错是11111,现在的时间是11222,那么System.nanoTime()就是两者之差,我们也可以直接用random再两个范围内随机获取

1
random.randint(5136066335773,7136066335773)

第三个参数getDeviceId,点进去看一下,发现有一个saveDeviceId和一个getDeviceId方法,我们猜测这个id应该跟系统有关,可能是先存了,在获取,所以我们可以直接hook saveDeviceId这个方法

image-20230519225721399

hook之前需要先清空缓存,毕竟先做了存储,发现这个id跟https://dealercloudapi.che168.com/tradercloud/v100/push/regdevice.ashx

这个接口的返回数据一样,猜测是这个接口对deviceid做了处理,我们可以先将deviceid固定,到后面测试的时候,将其切换成空试试,搜了一圈,这个玩意视乎和一个叫做个推id的东西有关

image-20230519230711425

接下来我们看encode3Des方法,明文已知,iv值是固定值appapich,现在就差加密的key,我们直接看getDesKey方法,发现最终调用的是native的方法,对应的so文件名称是native-lib

image-20230519231803505

image-20230519232935884

image-20230519233015996

我们直接hook getDesKey方法,看下会返回什么,多请求几次发现值始终是固定的,这下key也知道了appapiche168comappapiche168comap

image-20230519234840426

我们再hook一下encode3Des方法,然后加加密参数放到在线网站里面进行加密尝试,发现结果相同,说明是标准的3des加密 我们直接改写python代码

image-20230519235733987

image-20230519235813462

经过测试之后,也是发现deviceid是可以为空的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import uuid
import random
import base64
import hashlib
import requests
from Crypto.Cipher import DES3


def des3(data_string):
print(data_string)
BS = 8
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)
print(pad)
# 3DES的MODE_CBC模式下只有前24位有意义
key = b'appapiche168comappapiche168comap'[0:24]
iv = b'appapich'

plaintext = pad(data_string).encode("utf-8")
print(plaintext)
# 使用MODE_CBC创建cipher
cipher = DES3.new(key, DES3.MODE_CBC, iv)
result = cipher.encrypt(plaintext)
return base64.b64encode(result).decode('utf-8')


def md5(data_string):
obj = hashlib.md5()

obj.update(data_string.encode('utf-8'))

return obj.hexdigest()


def run():
username = "18877765431"
passwrod = "123456"

imei = str(uuid.uuid4())
nano_time = random.randint(5136066335773, 7136066335773)
device_id = "" # 290001
udid = des3(f"{imei}|{nano_time}|{device_id}")

data = "W@oC!AH_6Ew1f6%8"
data_dict = {
"_appid": "atc.android",
"appversion": "3.4.0",
"channelid": "csy",
"pwd": md5(passwrod),
"udid": udid,
"username": username
}

result = "".join(["{}{}".format(key, data_dict[key]) for key in sorted(data_dict.keys())])
un_sign_string = f"{data}{result}{data}"
sign = md5(un_sign_string).upper()
data_dict['_sign'] = sign
print(md5(passwrod))
res = requests.post(
url="https://dealercloudapi.che168.com/tradercloud/sealed/login/login.ashx",
data=data_dict
)

print(res.text)


if __name__ == '__main__':
run()