app:4.74.5

接口:https://app.dewu.com/sns-itr/v1/reply/content-reply-list

解析流程

该app不走系统代理,所以需要sockes转发,而且有frida反调试,需要删除libmsaoaidsec.so文件

正常抓包,找到接口,发现请求参数有个newSign 和请求头中有个X-Auth-Token需要获取到

image-20230520181438531

image-20230520181508545

直接搜索newSign关键字,发现它进入得是一个拦截器,最终是m37139c方法对newSign参数做了处理,我们看一下m37139c方法

image-20230520182014047

看上去好像是一个aes加密,传入了一个map参数,我们可以hook一下,传入了哪些参数

image-20230520182127326

image-20230520185602902

传入得字典也是请求头中得其它参数,我们在回过头来看一下m37139c方法做了哪些处理,传入得map后又添加了下面几个参数,然后字符串拼接,直接hook encode方法就可以得到拼接后的字符串,其它的几个参数都可以固定,但是uuid需要我们去获取

1
2
3
4
5
6
7
8
v=4.84.0
timestamp=1660649735904
uuid=0d9686a78e2aa975 -> ?
platform=android
loginToken=
最后得参数,直接hook encode方法就可以得到
anchorReplyId0contentId85347120contentType0lastIdlimit20loginTokenplatformandroidsourcetimestamp1660649735904uuid0d9686a78e2aa975v4.84.0

发现只有获取,而没有生成uuid,我们可以先不管,先搜索X-Auth-Token

image-20230520190657162

image-20230520191051064

发现在这里出现了uuid的生成,我们进去看一下,发现uuid可能是一个设备id,这个id我们可以直接伪造

image-20230520191131362

image-20230520191224375

1
2
3
4
5
6
def create_android_id():
data_list = []
for i in range(1, 9):
part = "".join(random.sample("0123456789ABCDEF", 2))
data_list.append(part)
return "".join(data_list).lower()

我们再看一下encode方法,发现最终会调用一个叫encodeByte的native方法,并且传入一个字节数组,这个字节数组就是我们上面讲到的sb字符数组转成的字节数组,直接hook这个方法

image-20230520191434729

image-20230520191906246

找到了最终的加密位置,但由于是在native层进行加密,我们还需要获取到对应的so文件,在jadx中也可以看到对应的so文件叫做JNIEncrypt

image-20230520192113713

我们用ida打开对应的so文件,去寻找encode方法传入的第一参数,经过确认,第29行的 AES_128_ECB_PKCS5Padding_Encrypt应该就是传入的对应方法,也就是对应的AES加密,那我们直接用frida hook native层的加密

image-20230520192420758

image-20230520192656597

image-20230520192728122

想要hook native层的方法,需要指定对应so文件和对应的方法,并且需要设置对应的进入和出去方法,发现传入的第一个参数,就是我们上面拼接的参数,第二个参数始终固定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Java.perform(function () {

var addr_func = Module.findExportByName("libJNIEncrypt.so", "AES_128_ECB_PKCS5Padding_Encrypt");


Interceptor.attach(addr_func, {

onEnter: function(args){
console.log("-------------执行函数-------------");

console.log("-------------参数 1-------------");
console.log(args[0].readUtf8String())

console.log("-------------参数 2-------------");
console.log(args[1].readUtf8String());
},
onLeave: function(retValue){
console.log("-------------返回-------------");
console.log(retValue.readUtf8String());
}

})

});

image-20230520193033949

最后剩下一个X-Auth-Token,在我们抓包的时候,可以去看一个包,这个包的响应头就是我们要的X-Auth-Token,而newSign我们已经获取到了,所以我们可以直接获取到newSign后,拼接请求,获取X-Auth-Token,在拼接请求,获取我们想要的评论

image-20230520193417490

直接上代码

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
import time
import requests
import hashlib
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from urllib.parse import quote_plus
import base64
import json
import random


def create_android_id():
data_list = []
for i in range(1, 9):
part = "".join(random.sample("0123456789ABCDEF", 2))
data_list.append(part)
return "".join(data_list).lower()


def md5(data_bytes):
hash_object = hashlib.md5()
hash_object.update(data_bytes)
return hash_object.hexdigest()


def aes_encrypt(data_string):
key = "d245a0ba8d678a61"
aes = AES.new(
key=key.encode('utf-8'),
mode=AES.MODE_ECB,
)
raw = pad(data_string.encode('utf-8'), 16)
return aes.encrypt(raw)


uid = create_android_id()
ctime = str(int(time.time() * 1000))

# param_dict = {"loginToken": "", "platform": "android", "timestamp": ctime, "uuid": uid, "v": "4.84.0"}
param_dict = {"loginToken": "", "platform": "android", "timestamp": ctime, "uuid": uid, "v": "5.0.6"}

ordered_string = "".join(["{}{}".format(key, param_dict[key]) for key in sorted(param_dict.keys())])
aes_string = aes_encrypt(ordered_string)
aes_string = base64.encodebytes(aes_string)
aes_string = aes_string.replace(b"\n", b"")
sign = md5(aes_string)
param_dict['newSign'] = sign

res = requests.post(
url="https://app.dewu.com/api/v1/app/user_core/users/getVisitorUserId",
headers={
"duuuid": uid,
"duimei": "",
"duplatform": "android",
"appId": "duapp",
"duchannel": "pp",
"humeChannel": "",
# "duv": "4.84.0",
"duv": "5.0.6",
"duloginToken": "",
"dudeviceTrait": "MI+6+Plus",
"dudeviceBrand": "Xiaomi",
"timestamp": ctime,
"shumeiid": "20210807163758afabf6d66401eb2e4f234dbe68af5705014c79067d50b114",
"oaid": "",
"User-Agent": "duapp/5.0.6(android;6.0.1)",
"X-Auth-Token": "",
"isRoot": "0",
"emu": "1",
"isProxy": "0",
"Content-Type": "application/json; charset=utf-8"
},
json=param_dict,
# verify=False
)
x_auth_token = res.headers['X-Auth-Token']

reply_param_dict = {
# "contentId": "83662484",
"contentId": "91820099",
"contentType": "0",
"anchorReplyId": "0",
"lastId": "",
"source": "",
"limit": "20",
# "newSign": "80c378ba571fd250385a4a9d5cb120b6"
}
import copy

new_dict = copy.deepcopy(reply_param_dict)
new_dict.update(
# {"loginToken": "", "platform": "android", "timestamp": str(int(time.time() * 1000)), "uuid": uid, "v": "4.84.0"})
{"loginToken": "", "platform": "android", "timestamp": str(int(time.time() * 1000)), "uuid": uid, "v": "5.0.6"})
ordered_string = "".join(["{}{}".format(key, new_dict[key]) for key in sorted(new_dict.keys())])

aes_string = aes_encrypt(ordered_string)
aes_string = base64.encodebytes(aes_string)
aes_string = aes_string.replace(b"\n", b"")
sign_string = md5(aes_string)
reply_param_dict['newSign'] = sign_string

res = requests.get(
url="https://app.dewu.com/sns-itr/v1/reply/content-reply-list",
params=reply_param_dict,
headers={
"X-Auth-Token": x_auth_token,
"User-Agent": "duapp/5.0.6(android;10)"
},
# verify=False
)
print(res.text)

返回结果:成功拿到数据

image-20230520193611365