白乐天

道阻且长,行则将至。

得物app过强制更新及获取首页推荐数据

App信息

包名:com.shizhuang.duapp

过强制更新

jadx反编译apk

搜索下载最新版就可以找到相关字符串,然后双击跳转

如下是源码

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
public Dialog a(Activity activity) {
PatchProxyResult proxy = PatchProxy.proxy(new Object[]{activity}, this, changeQuickRedirect, false, 168458, new Class[]{Activity.class}, Dialog.class);
if (proxy.isSupported) {
return (Dialog) proxy.result;
}
MaterialDialog.Builder d = new MaterialDialog.Builder(activity).e("版本更新提示").a((CharSequence) "为保证您的服务和体验,我们对产品进行了更新,优化了应用性能,马上下载最新版得物App,抢先体验最新的产品服务!感谢您对得物App的支持").d("更新").d(new MaterialDialog.SingleButtonCallback() { // from class: com.shizhuang.duapp.modules.userv2.setting.user.ui.MerchantCheckNotifierDialog.1
public static ChangeQuickRedirect changeQuickRedirect;

@Override // com.afollestad.materialdialogs.MaterialDialog.SingleButtonCallback
public void onClick(MaterialDialog materialDialog, DialogAction dialogAction) {
if (PatchProxy.proxy(new Object[]{materialDialog, dialogAction}, this, changeQuickRedirect, false, 168459, new Class[]{MaterialDialog.class, DialogAction.class}, Void.TYPE).isSupported) {
return;
}
MerchantCheckNotifierDialog.this.a();
SafeDialogHandle.b(materialDialog);
}
});
d.b("取消").b(new MaterialDialog.SingleButtonCallback() { // from class: com.shizhuang.duapp.modules.userv2.setting.user.ui.MerchantCheckNotifierDialog.2
public static ChangeQuickRedirect changeQuickRedirect;

@Override // com.afollestad.materialdialogs.MaterialDialog.SingleButtonCallback
public void onClick(@NonNull MaterialDialog materialDialog, @NonNull DialogAction dialogAction) {
if (PatchProxy.proxy(new Object[]{materialDialog, dialogAction}, this, changeQuickRedirect, false, 168460, new Class[]{MaterialDialog.class, DialogAction.class}, Void.TYPE).isSupported) {
return;
}
MerchantCheckNotifierDialog.this.b();
SafeDialogHandle.b(materialDialog);
}
});
d.b(false);
return d.d();
}

这个方法返回一个Dialog,一个更新对话框。

方法里面使用MaterialDialog.Builder构建对话框,设置对话框的标题和内容。

这里面有一行代码d.b(false);,用来设置对话框不可通过点击外部区域或返回键取消。

frida hook

使用frida把参数改成true实现绕过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function hook_update(){
Java.perform(function(){
let Builder = Java.use("com.afollestad.materialdialogs.MaterialDialog$Builder");
Builder["b"].overload('boolean').implementation = function (z) {
console.log(`Builder.b is called: z=${z}`);
z = true; // 改成了true
let result = this["b"](z);
console.log(`Builder.b result=${result}`);
return result;
};
})
}

hook_update();

然后进入app之后,点击空白处,更新窗口就不见了。

抓包首页推荐数据

找到相应的接口

抓包数据

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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
url: https://app.dewu.com/sns-rec/v1/recommend/all/feed

get

params = {
"abType": "social_brand_strategy_v454",
"abValue": "1",
"lastId": "",
"limit": "20",
"deliveryProjectId": "0",
"abVideoCover": "2",
"abRectagFengge": "0",
"abRecReason": "0",
"abLiveEntranceClose": "0",
"newSign": "ea992f16a5e15cbfa611ab96efb1f015"
}

headers = {
"debugable": "0",
"duuuid": "bc11eeafc514fa6b",
"duimei": "",
"duplatform": "android",
"appId": "duapp",
"duchannel": "myapp",
"humeChannel": "",
"duv": "4.75.5",
"duloginToken": "",
"dudeviceTrait": "Pixel+3",
"dudeviceBrand": "google",
"timestamp": "1739958080487",
"shumeiid": "202502191641573cd0dc0c35cca79e0b43264e08ce4f320080707d260494d8",
"oaid": "",
"User-Agent": "duapp/4.75.5(android;10)",
"X-Auth-Token": "Bearer eyJhbGciOiJSUzI1NiJ9.eyJpYXQiOjE3Mzk5NTY3NDgsImV4cCI6MTc3MTQ5Mjc0OCwiaXNzIjoiYmMxMWVlYWZjNTE0ZmE2YiIsInN1YiI6ImJjMTFlZWFmYzUxNGZhNmIiLCJ1dWlkIjoiYmMxMWVlYWZjNTE0ZmE2YiIsInVzZXJJZCI6MjQ5Njk3MjI1NSwidXNlck5hbWUiOiLlvpfniallci05SzFGMEI2SCIsImlzR3Vlc3QiOnRydWV9.s1T7_E62uvd1nCTPVb1dpIo1ggqjXmixXDeLQep5wsO8erOXpKV-6Zsr5pB7e1a6aiHyPbwh0moYHqjS4dW8l4tQ9KJrUrGCTEAYMnIJyUXSnQLXg6VTUPGtTikcMjWKYs1acxUt-ak6mGZt8wDXqBNJmGDIlOkT_H2cMnuB0JkcTHSNDKcgTxETEMnVD17mkK4SOBJRGpTiDsPFlkqAympFLRyVzKum-b6L1fXpDUIkE3sU-0IjLowmkgaheLXEC4JtYz6Jy9l0zJbyIUFFpu5Ey9-xzF_61hgpvHcfaN3oMsJBx3YXdj4R9cZWO8_gh_QdPCEIjQrA4dw695hPAQ",
"isRoot": "0",
"emu": "0",
"isProxy": "0",
"SK": "9RNrI7q2DZBTU9eJoIOOBsyH8lpcfWexhNr0a7Zkm916pFB84VoQCnFbpt1xL24IsQMpxG16EjzAUB7Kw7Ze6OHpK81r",
"Host": "app.dewu.com",
"Connection": "Keep-Alive",
"Accept-Encoding": "gzip",
"Cookie": "HWWAFSESTIME=1739956746046; HWWAFSESID=cdf322fe67256a33a30"
}

url: https://app.dewu.com/sns-rec/v1/recommend/all/feed?

get

params: {
"abType": "social_brand_strategy_v454",
"abValue": "0",
"lastId": "",
"limit": "20",
"deliveryProjectId": "0",
"abVideoCover": "0",
"abRectagFengge": "1",
"abRecReason": "0",
"newSign": "455cbb7f4983593149710f1e6a7d150c"
}

headers:{
"debugable": "0",
"duuuid": "bc11eeafc514fa6b",
"duimei": "",
"duplatform": "android",
"appId": "duapp",
"duchannel": "myapp",
"humeChannel": "",
"duv": "4.74.5",
"duloginToken": "",
"dudeviceTrait": "Pixel+3",
"dudeviceBrand": "google",
"timestamp": "1740103378954",
"shumeiid": "202502211001376b2a86c46bd029859670f6b24db7f5be0030988becd7a9cc",
"oaid": "",
"User-Agent": "duapp/4.74.5(android;10)",
"X-Auth-Token": "Bearer eyJhbGciOiJSUzI1NiJ9.eyJpYXQiOjE3NDAxMDMyOTgsImV4cCI6MTc3MTYzOTI5OCwiaXNzIjoiYmMxMWVlYWZjNTE0ZmE2YiIsInN1YiI6ImJjMTFlZWFmYzUxNGZhNmIiLCJ1dWlkIjoiYmMxMWVlYWZjNTE0ZmE2YiIsInVzZXJJZCI6MjQ5Njk3MjI1NSwidXNlck5hbWUiOiLlvpfniallci05SzFGMEI2SCIsImlzR3Vlc3QiOnRydWV9.u3DBybDzMvee-V9rWdGuDePZU1TvvbOvodPeJ88-lKmxMbJpK8U93jyxIdcQ8BzhdSahkWn29T6sG3S3-bq1dz9p_MWgfxw5yYSIOE4qd2NfAr1OQ4n-BGQXw7EYdC-eg0uDOIctGCJ2OSfyKlqzqeIx9RIQieJf2bkYeYFF1vkF8SKU2rf-1ESjWdpiUkvPuBN5NrafGHvL4VRkdxkCjT_NTezQ8tXm1PgrYy_73jGGma1nX8Vp49wLqoxhrimQwUremdcNUEWWdeTNsy7nSbobiArkONoRIknrO922QU4nOmFWBuPRnRRGmKHZ371Q3MobmAz7s7yCXt1uDsY4QQ",
"isRoot": "0",
"emu": "0",
"isProxy": "0",
"Host": "app.dewu.com",
"Connection": "Keep-Alive",
"Accept-Encoding": "gzip",
"Cookie": "HWWAFSESTIME=1740103292438; HWWAFSESID=775c333dec0422ea694"
}

url: https://app.dewu.com/sns-rec/v1/recommend/all/feed

get

params: {
"abType": "social_brand_strategy_v454",
"abValue": "0",
"lastId": "",
"limit": "20",
"deliveryProjectId": "0",
"abVideoCover": "0",
"abRectagFengge": "1",
"abRecReason": "0",
"newSign": "f78e63f1c3d0e7fad6d8d6688ef7e5a3"
}

loginTokenpagehomeplatformandroidtestatimestamp1740054362656uuidbc11eeafc514fa6bv4.74.5

headers: {
"debugable": "0",
"duuuid": "bc11eeafc514fa6b",
"duimei": "",
"duplatform": "android",
"appId": "duapp",
"duchannel": "myapp",
"humeChannel": "",
"duv": "4.74.5",
"duloginToken": "",
"dudeviceTrait": "Pixel+3",
"dudeviceBrand": "google",
"timestamp": "1740103393181",
"shumeiid": "202502211001376b2a86c46bd029859670f6b24db7f5be0030988becd7a9cc",
"oaid": "",
"User-Agent": "duapp/4.74.5(android;10)",
"X-Auth-Token": "Bearer eyJhbGciOiJSUzI1NiJ9.eyJpYXQiOjE3NDAxMDMyOTgsImV4cCI6MTc3MTYzOTI5OCwiaXNzIjoiYmMxMWVlYWZjNTE0ZmE2YiIsInN1YiI6ImJjMTFlZWFmYzUxNGZhNmIiLCJ1dWlkIjoiYmMxMWVlYWZjNTE0ZmE2YiIsInVzZXJJZCI6MjQ5Njk3MjI1NSwidXNlck5hbWUiOiLlvpfniallci05SzFGMEI2SCIsImlzR3Vlc3QiOnRydWV9.u3DBybDzMvee-V9rWdGuDePZU1TvvbOvodPeJ88-lKmxMbJpK8U93jyxIdcQ8BzhdSahkWn29T6sG3S3-bq1dz9p_MWgfxw5yYSIOE4qd2NfAr1OQ4n-BGQXw7EYdC-eg0uDOIctGCJ2OSfyKlqzqeIx9RIQieJf2bkYeYFF1vkF8SKU2rf-1ESjWdpiUkvPuBN5NrafGHvL4VRkdxkCjT_NTezQ8tXm1PgrYy_73jGGma1nX8Vp49wLqoxhrimQwUremdcNUEWWdeTNsy7nSbobiArkONoRIknrO922QU4nOmFWBuPRnRRGmKHZ371Q3MobmAz7s7yCXt1uDsY4QQ",
"isRoot": "0",
"emu": "0",
"isProxy": "0",
"Host": "app.dewu.com",
"Connection": "Keep-Alive",
"Accept-Encoding": "gzip",
"Cookie": "HWWAFSESTIME=1740103292438; HWWAFSESID=775c333dec0422ea694"
}

参数分析

需要破解的参数newSignX-Auth-Token

参数还原

使用Jadx反编译

newSign参数还原

搜索newSign

c()

它有多种生成方式,这里面RequestUtils.c()出现了多次,分析这个方法

尝试用frida对c()进行hook

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function printHashMap(map){
let hashMap = Java.use("java.util.HashMap");
let cast_map = Java.cast(map,hashMap);
console.log(cast_map.toString());
}

function get_request_utils_c() {
Java.perform(function () {
let RequestUtils = Java.use("com.shizhuang.duapp.common.utils.RequestUtils");
RequestUtils["c"].implementation = function (map, j2) {
printHashMap(map);
console.log("j2:",j2);
let result = this["c"](map, j2);
console.log(`RequestUtils.c result=${result}`);
return result;
};
});
}

get_request_utils_c();

hook 结果

1
2
3
4
5
6
7
8
9
10
11
{abValue=1, deliveryProjectId=0, abRectagFengge=0, abType=social_brand_strategy_v454, limit=20, lastId=, abRecReason=0, abLiveEntranceClose=0, abVideoCover=2}
j2: 1739983064504
RequestUtils.c result=035cccf08759851fa52b47d064a75d59

{test=a, page=home}
j2: 1740101929941
RequestUtils.c result=b1f54f142de32376c4e5bbf9d075f291

{}
j2: 1740101929945
RequestUtils.c result=ed4a6aafe8fbdca3709f33a665af55db

在c()方法里,向map添加了几个内容之后拼接成字符串然后调用AESEncrypt.encode方法,返回值又作为参数传给了a方法

通过hook发现这里面添加的参数只有timestamp不是定值,其他都是定值。

encode()

这里面出现了getByteValues()方法并且返回encodeByte()

这个方法里把byteValues的值进行了取反,就是0改成11改成0

getByteValues()

它在native里

hook getByteValues()

1
2
3
4
5
6
7
8
9
10
11
12
13
function hook_getbytevalue(){
Java.perform(function(){
let AESEncrypt = Java.use("com.duapp.aesjni.AESEncrypt");
AESEncrypt["getByteValues"].implementation = function () {
console.log(`AESEncrypt.getByteValues is called`);
let result = this["getByteValues"]();
console.log(`AESEncrypt.getByteValues result=${result}`);
return result;
};
})
}

hook_getbytevalue();

hook 结果

1
2
3
4
5
6
7
8
AESEncrypt.getByteValues is called
AESEncrypt.getByteValues result=101001011101110101101101111100111000110100010101010111010001000101100101010010010101110111010011101001011101110101100101001100110000110100011101010111011011001101001101011101010100001101000011
AESEncrypt.getByteValues is called
AESEncrypt.getByteValues result=101001011101110101101101111100111000110100010101010111010001000101100101010010010101110111010011101001011101110101100101001100110000110100011101010111011011001101001101011101010100001101000011
AESEncrypt.getByteValues is called
AESEncrypt.getByteValues result=101001011101110101101101111100111000110100010101010111010001000101100101010010010101110111010011101001011101110101100101001100110000110100011101010111011011001101001101011101010100001101000011
AESEncrypt.getByteValues is called
AESEncrypt.getByteValues result=101001011101110101101101111100111000110100010101010111010001000101100101010010010101110111010011101001011101110101100101001100110000110100011101010111011011001101001101011101010100001101000011

这里打印出来的结果都是一样的

hook encodeByte()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function bArrToString(bArr) {
return Java.use("java.lang.String").$new(bArr);
}

function hook_encodeByte(){
Java.perform(function(){
let AESEncrypt = Java.use("com.duapp.aesjni.AESEncrypt");
AESEncrypt["encodeByte"].implementation = function (bArr, str) {
console.log(`AESEncrypt.encodeByte is called: bArr=${bArr}, str=${str}`);
console.log(`bArrToString(bArr)=${bArrToString(bArr)}`);
let result = this["encodeByte"](bArr, str);

console.log(`AESEncrypt.encodeByte result=${result}`);
return result;
};
})
}

hook_encodeByte();

hook 结果

1
2
3
4
5
6
7
8
9
10
11
12
AESEncrypt.encodeByte is called: bArr=108,111,103,105,110,84,111,107,101,110,112,97,103,101,104,111,109,101,112,108,97,116,102,111,114,109,97,110,100,114,111,105,100,116,101,115,116,97,116,105,109,101,115,116,97,109,112,49,55,52,48,48,53,52,51,54,50,54,53,54,117,117,105,100,98,99,49,49,101,101,97,102,99,53,49,52,102,97,54,98,118,52,46,55,52,46,53, str=010110100010001010010010000011000111001011101010101000101110111010011010101101101010001000101100010110100010001010011010110011001111001011100010101000100100110010110010100010101011110010111100
bArrToString(bArr)=loginTokenpagehomeplatformandroidtestatimestamp1740054362656uuidbc11eeafc514fa6bv4.74.5
AESEncrypt.encodeByte result=wMz+jQaNMznb1+IAkqC/I69ZXRCgSLPm9wlJoFRbBzr3CQhDvJTqF+jy8DeE3ISxZKvHxp1mu4wEFJV1nt3Txi9SRLYxX4owLGWeMYA2cIKTAw55onwRa1Ma1U4AGlqt
AESEncrypt.encodeByte is called: bArr=97,98,84,101,115,116,123,34,110,97,109,101,34,58,34,67,111,109,109,111,100,105,116,121,95,115,117,98,106,101,99,116,34,44,34,118,97,108,117,101,34,58,34,48,34,125,44,123,34,110,97,109,101,34,58,34,67,111,109,109,111,100,105,116,121,95,99,104,117,97,110,100,97,34,44,34,118,97,108,117,101,34,58,34,49,34,125,44,123,34,110,97,109,101,34,58,34,114,101,99,111,109,109,101,110,100,95,108,105,110,101,34,44,34,118,97,108,117,101,34,58,34,48,34,125,104,111,109,101,86,101,114,115,105,111,110,49,108,97,115,116,73,100,108,105,109,105,116,50,48,108,111,103,105,110,84,111,107,101,110,112,108,97,116,102,111,114,109,97,110,100,114,111,105,100,116,105,109,101,115,116,97,109,112,49,55,52,48,48,53,52,51,54,50,54,53,55,117,117,105,100,98,99,49,49,101,101,97,102,99,53,49,52,102,97,54,98,118,52,46,55,52,46,53, str=010110100010001010010010000011000111001011101010101000101110111010011010101101101010001000101100010110100010001010011010110011001111001011100010101000100100110010110010100010101011110010111100
bArrToString(bArr)=abTest{"name":"Commodity_subject","value":"0"},{"name":"Commodity_chuanda","value":"1"},{"name":"recommend_line","value":"0"}homeVersion1lastIdlimit20loginTokenplatformandroidtimestamp1740054362657uuidbc11eeafc514fa6bv4.74.5
AESEncrypt.encodeByte result=3VUAf22pQJc12obDQnsqvYlw0+yJxzwTa9UlRxJdVEvNbb4Lm0nSxOIXt7uaQUBJMyPBrC5RsVvrAYDNVuobkC5NwI68VoN4V9+AtU6Ua9abLYBoLXkpb3lLQHC2PdcAu5MmZbMSM6XuYm7gCkfIAnbMm8+dsr3fzbG8BlIpUfh/vrBlCHJNuNhdqsLnjl0s2OzGQQs4AcuAogxYqrZ+KJpADsrG578j4yW18jF8/mhgr/NUim02mxzxpr3HSs8oFSzeCZjBbyzFbUoR1QcgZ+75PCzssOndHlk8JguMh/8yDwto9jibBSjAgfR9FwV3
AESEncrypt.encodeByte is called: bArr=108,111,103,105,110,84,111,107,101,110,112,108,97,116,102,111,114,109,97,110,100,114,111,105,100,116,105,109,101,115,116,97,109,112,49,55,52,48,48,53,52,51,54,50,54,53,55,117,117,105,100,98,99,49,49,101,101,97,102,99,53,49,52,102,97,54,98,118,52,46,55,52,46,53, str=010110100010001010010010000011000111001011101010101000101110111010011010101101101010001000101100010110100010001010011010110011001111001011100010101000100100110010110010100010101011110010111100
bArrToString(bArr)=loginTokenplatformandroidtimestamp1740054362657uuidbc11eeafc514fa6bv4.74.5
AESEncrypt.encodeByte result=knGGXR0bR7LQn4eRCvJsdZ4D96wrRcYi2zPWWxLMOs3J9iG9qM2nOpCfCRHSSOX156P6zz4vEmSVdQ4XAQqeU5MngoDG9Hs7puz32nrsq3k=
AESEncrypt.encodeByte is called: bArr=108,111,103,105,110,84,111,107,101,110,112,108,97,116,102,111,114,109,97,110,100,114,111,105,100,116,105,109,101,115,116,97,109,112,49,55,52,48,48,53,52,51,54,50,54,54,48,117,117,105,100,98,99,49,49,101,101,97,102,99,53,49,52,102,97,54,98,118,52,46,55,52,46,53, str=010110100010001010010010000011000111001011101010101000101110111010011010101101101010001000101100010110100010001010011010110011001111001011100010101000100100110010110010100010101011110010111100
bArrToString(bArr)=loginTokenplatformandroidtimestamp1740054362660uuidbc11eeafc514fa6bv4.74.5
AESEncrypt.encodeByte result=knGGXR0bR7LQn4eRCvJsdZ4D96wrRcYi2zPWWxLMOs02RXd6rjP/aGB/uSRK4ukC56P6zz4vEmSVdQ4XAQqeU5MngoDG9Hs7puz32nrsq3k=

后面在SO中分析它。

hook encode()

1
2
3
4
5
6
7
8
9
10
11
12
13
function hook_encode() {
Java.perform(function () {
let AESEncrypt = Java.use("com.duapp.aesjni.AESEncrypt");
AESEncrypt["encode"].overload('java.lang.Object', 'java.lang.String').implementation = function (obj, str) {
console.log(`AESEncrypt.encode is called: obj=${obj}, str=${str}`);
let result = this["encode"](obj, str);
console.log(`AESEncrypt.encode result=${result}`);
return result;
};
});
}

hook_encode();

hook结果

1
2
3
4
5
AESEncrypt.encode is called: obj=com.shizhuang.duapp.modules.app.DuApplication@ae024e1, str=abTest{"name":"Commodity_subject","value":"2"},{"name":"Commodity_chuanda","value":"1"},{"name":"recommend_line","value":"0"}homeVersion1lastIdlimit20loginTokenplatformandroidtimestamp1740029457724uuidbc11eeafc514fa6bv4.75.5
AESEncrypt.encode result=3VUAf22pQJc12obDQnsqvYlw0+yJxzwTa9UlRxJdVEtJdsrt8Ug8vJMXbRvBmLHUMyPBrC5RsVvrAYDNVuobkC5NwI68VoN4V9+AtU6Ua9abLYBoLXkpb3lLQHC2PdcAu5MmZbMSM6XuYm7gCkfIAnbMm8+dsr3fzbG8BlIpUfh/vrBlCHJNuNhdqsLnjl0s2OzGQQs4AcuAogxYqrZ+KJpADsrG578j4yW18jF8/mghgF2+DXCS5YOACC4b7xYyoPqaHDeYQ8l7G+3WuLEBo4hPLAYREE5dF2Brg4bXY9EyDwto9jibBSjAgfR9FwV3
AESEncrypt.encode is called: obj=com.shizhuang.duapp.modules.app.DuApplication@ae024e1, str=loginTokenplatformandroidtimestamp1740029457725uuidbc11eeafc514fa6bv4.75.5
AESEncrypt.encode result=
knGGXR0bR7LQn4eRCvJsdZ4D96wrRcYi2zPWWxLMOs3uksv9DjGS2nyb46U5bseV56P6zz4vEmSVdQ4XAQqeUwgOb5YdMIVqBmEY2kuLXS4=

它的返回值传递给了a方法

a()

a方法是MD5

hook a

1
2
3
4
5
6
7
8
9
10
11
12
13
function hook_a(){
Java.perform(function(){
let RequestUtils = Java.use("com.shizhuang.duapp.common.utils.RequestUtils");
RequestUtils["a"].overload('java.lang.String').implementation = function (str) {
console.log(`RequestUtils.a is called: str=${str}`);
let result = this["a"](str);
console.log(`RequestUtils.a result=${result}`);
return result;
};
})
}

hook_a();

hook 结果

1
2
RequestUtils.a is called: str=3VUAf22pQJc12obDQnsqvYlw0+yJxzwTa9UlRxJdVEtJdsrt8Ug8vJMXbRvBmLHUMyPBrC5RsVvrAYDNVuobkC5NwI68VoN4V9+AtU6Ua9abLYBoLXkpb3lLQHC2PdcAu5MmZbMSM6XuYm7gCkfIAnbMm8+dsr3fzbG8BlIpUfh/vrBlCHJNuNhdqsLnjl0s2OzGQQs4AcuAogxYqrZ+KJpADsrG578j4yW18jF8/mg8giv5nPp2mKhwHVYxQFU+N9biMQihdFvvr/Aac6kCd4hPLAYREE5dF2Brg4bXY9EyDwto9jibBSjAgfR9FwV3
RequestUtils.a result=d6be95d0204cf4ff8dbe1b5afaf9f9ca

经验证,它是标准的MD5

SO分析

JNIEncrypt

getByteValues()encodeByte()都是通过动态注册的

找到JNI_OnLoad,

找到动态注册函数的偏移地址

get_bytes()

这里的返回值就是hook得到的返回值

encode()

它返回的是AES_128_ECB_PKCS5Padding_Encrypt()的返回值

AES_128_ECB_PKCS5Padding_Encrypt()

这里面调用了AES_128_ECB_PKCS5Padding_Encrypt()

在这里进行加密,加密结果进行base64编码

hook AES_128_ECB_PKCS5Padding_Encrypt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function hook_encrypt(){
let module = Module.findBaseAddress("libJNIEncrypt.so");
Interceptor.attach(module.add(0x2E24),{
onEnter:function(args){
console.log("onEnter");
console.log("args[0]:",args[0].readCString());
console.log("args[1]:",args[1].readCString());
},
onLeave:function(retval){
console.log("onLeave");
console.log("retval:",retval.readCString());
}
})
}

hook_encrypt();

hook 结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
onEnter
args[0]: loginTokenpagehomeplatformandroidtestatimestamp1740070306835uuidbc11eeafc514fa6bv4.74.5
args[1]: d245a0ba8d678a61
onLeave
retval: wMz+jQaNMznb1+IAkqC/I69ZXRCgSLPm9wlJoFRbBzr3CQhDvJTqF+jy8DeE3ISx83Sv0LiV9r0kR3Wr7hekHi9SRLYxX4owLGWeMYA2cIKTAw55onwRa1Ma1U4AGlqt
onEnter
args[0]: abTest{"name":"Commodity_subject","value":"2"},{"name":"Commodity_chuanda","value":"1"},{"name":"recommend_line","value":"0"}homeVersion1lastIdlimit20loginTokenplatformandroidtimestamp1740070306836uuidbc11eeafc514fa6bv4.74.5
args[1]: d245a0ba8d678a61
onLeave
retval: 3VUAf22pQJc12obDQnsqvYlw0+yJxzwTa9UlRxJdVEtJdsrt8Ug8vJMXbRvBmLHUMyPBrC5RsVvrAYDNVuobkC5NwI68VoN4V9+AtU6Ua9abLYBoLXkpb3lLQHC2PdcAu5MmZbMSM6XuYm7gCkfIAnbMm8+dsr3fzbG8BlIpUfh/vrBlCHJNuNhdqsLnjl0s2OzGQQs4AcuAogxYqrZ+KJpADsrG578j4yW18jF8/mg0VqcbuIBNm/WE9Bebirio+oQRj7Nm07copMyx4hIdlO75PCzssOndHlk8JguMh/8yDwto9jibBSjAgfR9FwV3
onEnter
args[0]: loginTokenplatformandroidtimestamp1740070306837uuidbc11eeafc514fa6bv4.74.5
args[1]: d245a0ba8d678a61
onLeave
retval: knGGXR0bR7LQn4eRCvJsdZ4D96wrRcYi2zPWWxLMOs0HawbIpLaYwOy2chTWGFnm56P6zz4vEmSVdQ4XAQqeU5MngoDG9Hs7puz32nrsq3k=
onEnter
args[0]: loginTokenplatformandroidtimestamp1740070306844uuidbc11eeafc514fa6bv4.74.5
args[1]: d245a0ba8d678a61
onLeave
retval: knGGXR0bR7LQn4eRCvJsdZ4D96wrRcYi2zPWWxLMOs2cgKXmJbFf+k/bWQGFU8Vz56P6zz4vEmSVdQ4XAQqeU5MngoDG9Hs7puz32nrsq3k=

这里出现了一个固定值d245a0ba8d678a61,猜测它是密钥

密钥验证

Python生成newSign

hook与newSign有关的完整数据

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
function printHashMap(map){
let hashMap = Java.use("java.util.HashMap");
let cast_map = Java.cast(map,hashMap);
console.log(cast_map.toString());
}

function get_request_utils_c() {
Java.perform(function () {
let RequestUtils = Java.use("com.shizhuang.duapp.common.utils.RequestUtils");
RequestUtils["c"].implementation = function (map, j2) {
printHashMap(map);
console.log("j2:",j2);
let result = this["c"](map, j2);
console.log(`RequestUtils.c result=${result}`);
return result;
};
});
}

get_request_utils_c();

function bArrToString(bArr) {
return Java.use("java.lang.String").$new(bArr);
}

function hook_encodeByte(){
Java.perform(function(){
let AESEncrypt = Java.use("com.duapp.aesjni.AESEncrypt");
AESEncrypt["encodeByte"].implementation = function (bArr, str) {
console.log(`AESEncrypt.encodeByte is called: bArr=${bArr}, str=${str}`);
console.log(`bArrToString(bArr)=${bArrToString(bArr)}`);
let result = this["encodeByte"](bArr, str);

console.log(`AESEncrypt.encodeByte result=${result}`);
return result;
};
})
}

hook_encodeByte();

function hook_encode() {
Java.perform(function () {
let AESEncrypt = Java.use("com.duapp.aesjni.AESEncrypt");
AESEncrypt["encode"].overload('java.lang.Object', 'java.lang.String').implementation = function (obj, str) {
console.log(`AESEncrypt.encode is called: obj=${obj}, str=${str}`);
let result = this["encode"](obj, str);
console.log(`AESEncrypt.encode result=${result}`);
return result;
};
});
}

hook_encode();

结果如下

1
2
3
4
5
6
7
8
{abValue=0, deliveryProjectId=0, abRectagFengge=1, abType=social_brand_strategy_v454, limit=20, lastId=, abRecReason=0, abVideoCover=0}
j2: 1740104568977
AESEncrypt.encode is called: obj=com.shizhuang.duapp.modules.app.DuApplication@edc8ec, str=abRecReason0abRectagFengge1abTypesocial_brand_strategy_v454abValue0abVideoCover0deliveryProjectId0lastIdlimit20loginTokenplatformandroidtimestamp1740104568977uuidbc11eeafc514fa6bv4.74.5
AESEncrypt.encodeByte is called: bArr=97,98,82,101,99,82,101,97,115,111,110,48,97,98,82,101,99,116,97,103,70,101,110,103,103,101,49,97,98,84,121,112,101,115,111,99,105,97,108,95,98,114,97,110,100,95,115,116,114,97,116,101,103,121,95,118,52,53,52,97,98,86,97,108,117,101,48,97,98,86,105,100,101,111,67,111,118,101,114,48,100,101,108,105,118,101,114,121,80,114,111,106,101,99,116,73,100,48,108,97,115,116,73,100,108,105,109,105,116,50,48,108,111,103,105,110,84,111,107,101,110,112,108,97,116,102,111,114,109,97,110,100,114,111,105,100,116,105,109,101,115,116,97,109,112,49,55,52,48,49,48,52,53,54,56,57,55,55,117,117,105,100,98,99,49,49,101,101,97,102,99,53,49,52,102,97,54,98,118,52,46,55,52,46,53, str=010110100010001010010010000011000111001011101010101000101110111010011010101101101010001000101100010110100010001010011010110011001111001011100010101000100100110010110010100010101011110010111100
bArrToString(bArr)=abRecReason0abRectagFengge1abTypesocial_brand_strategy_v454abValue0abVideoCover0deliveryProjectId0lastIdlimit20loginTokenplatformandroidtimestamp1740104568977uuidbc11eeafc514fa6bv4.74.5
AESEncrypt.encodeByte result=VwGEkNCFuNTmwD6w19ogqTZGaLOwO89CuqSmIFmSsXLcvS7y81d64hsABQvdu03vdj76OpM/O2awG8h9UpcJbxqhRPz5vWbp+byrFtiHVNUIdeR4xJw0+TRbBSp2lMcQMGn2O4/9BKR+RkYgwOUZd4MFCaYlia6t5gNc3rbNTJjA6aJyja63+nAcybbrLaS0yu36g6F9mlVgWMlxVSx7/ftyEz0R7CuXrCsgcRf7Q/OPUH42VNLM8McBvKNbkyQ4
AESEncrypt.encode result=VwGEkNCFuNTmwD6w19ogqTZGaLOwO89CuqSmIFmSsXLcvS7y81d64hsABQvdu03vdj76OpM/O2awG8h9UpcJbxqhRPz5vWbp+byrFtiHVNUIdeR4xJw0+TRbBSp2lMcQMGn2O4/9BKR+RkYgwOUZd4MFCaYlia6t5gNc3rbNTJjA6aJyja63+nAcybbrLaS0yu36g6F9mlVgWMlxVSx7/ftyEz0R7CuXrCsgcRf7Q/OPUH42VNLM8McBvKNbkyQ4
RequestUtils.c result=da11eda195bb1e88f4bb67d7f37f4b3e

写Python

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
import time
import base64
import hashlib

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad

def aes_encrypt_ecb(plaintext,key):
cipher = AES.new(key,AES.MODE_ECB)
padded_data = pad(plaintext,AES.block_size)
encrypted_data = cipher.encrypt(padded_data)
return encrypted_data

def md5_call(data):
# 创建md5对象
md5 = hashlib.md5()
# 更新要加密的数据,参数要是字节类型
md5.update(data)
# 获取加密后的数据,以16进制表示
md5_hex = md5.hexdigest()
return md5_hex

timestamp = str(int(time.time() * 1000))

url = "https://app.dewu.com/sns-rec/v1/recommend/all/feed"

params = {
"abType": "social_brand_strategy_v454",
"abValue": "0",
"lastId": "",
"limit": "20",
"deliveryProjectId": "0",
"abVideoCover": "0",
"abRectagFengge": "1",
"abRecReason": "0",
"uuid" : "bc11eeafc514fa6b",
"platform" : "android",
"v": "4.74.5",
"loginToken" : "",
"timestamp" : "1740104568977"# timestamp
}

params_sorted = dict(sorted(params.items()))

params_str = "".join([f"{k}{v}" for k, v in params_sorted.items()])

# print(params_str)

key = b"d245a0ba8d678a61"

encrypted_data = aes_encrypt_ecb(params_str.encode("utf-8"),key)

# print(encrypted_data)

encrypted_data_base64 = base64.b64encode(encrypted_data)

# print(encrypted_data_base64)

md5_data = md5_call(encrypted_data_base64)

print(md5_data)

X-Auth-Token参数还原

搜索X-Auth-Token

这里获取JwtToken,分析抓包抓到的JwtToken

1
"X-Auth-Token": "Bearer eyJhbGciOiJSUzI1NiJ9.eyJpYXQiOjE3NDAxMDMyOTgsImV4cCI6MTc3MTYzOTI5OCwiaXNzIjoiYmMxMWVlYWZjNTE0ZmE2YiIsInN1YiI6ImJjMTFlZWFmYzUxNGZhNmIiLCJ1dWlkIjoiYmMxMWVlYWZjNTE0ZmE2YiIsInVzZXJJZCI6MjQ5Njk3MjI1NSwidXNlck5hbWUiOiLlvpfniallci05SzFGMEI2SCIsImlzR3Vlc3QiOnRydWV9.u3DBybDzMvee-V9rWdGuDePZU1TvvbOvodPeJ88-lKmxMbJpK8U93jyxIdcQ8BzhdSahkWn29T6sG3S3-bq1dz9p_MWgfxw5yYSIOE4qd2NfAr1OQ4n-BGQXw7EYdC-eg0uDOIctGCJ2OSfyKlqzqeIx9RIQieJf2bkYeYFF1vkF8SKU2rf-1ESjWdpiUkvPuBN5NrafGHvL4VRkdxkCjT_NTezQ8tXm1PgrYy_73jGGma1nX8Vp49wLqoxhrimQwUremdcNUEWWdeTNsy7nSbobiArkONoRIknrO922QU4nOmFWBuPRnRRGmKHZ371Q3MobmAz7s7yCXt1uDsY4QQ"

这个token分为三段,通过.分开

1
2
3
4
5
Bearer eyJhbGciOiJSUzI1NiJ9
.
eyJpYXQiOjE3NDAxMDMyOTgsImV4cCI6MTc3MTYzOTI5OCwiaXNzIjoiYmMxMWVlYWZjNTE0ZmE2YiIsInN1YiI6ImJjMTFlZWFmYzUxNGZhNmIiLCJ1dWlkIjoiYmMxMWVlYWZjNTE0ZmE2YiIsInVzZXJJZCI6MjQ5Njk3MjI1NSwidXNlck5hbWUiOiLlvpfniallci05SzFGMEI2SCIsImlzR3Vlc3QiOnRydWV9
.
u3DBybDzMvee-V9rWdGuDePZU1TvvbOvodPeJ88-lKmxMbJpK8U93jyxIdcQ8BzhdSahkWn29T6sG3S3-bq1dz9p_MWgfxw5yYSIOE4qd2NfAr1OQ4n-BGQXw7EYdC-eg0uDOIctGCJ2OSfyKlqzqeIx9RIQieJf2bkYeYFF1vkF8SKU2rf-1ESjWdpiUkvPuBN5NrafGHvL4VRkdxkCjT_NTezQ8tXm1PgrYy_73jGGma1nX8Vp49wLqoxhrimQwUremdcNUEWWdeTNsy7nSbobiArkONoRIknrO922QU4nOmFWBuPRnRRGmKHZ371Q3MobmAz7s7yCXt1uDsY4QQ

这个token串主要用来做前后端登录认证,第一段是头,第二段是荷载,第三段是签名,三段都是base64编码后的内容,其中签名是加密的

1
2
3
4
5
6
7
8
import base64

print(base64.b64decode(b"eyJhbGciOiJSUzI1NiJ9").decode("utf-8"))
# {"alg":"RS256"}

print(base64.b64decode(b"eyJpYXQiOjE3NDAxMDMyOTgsImV4cCI6MTc3MTYzOTI5OCwiaXNzIjoiYmMxMWVlYWZjNTE0ZmE2YiIsInN1YiI6ImJjMTFlZWFmYzUxNGZhNmIiLCJ1dWlkIjoiYmMxMWVlYWZjNTE0ZmE2YiIsInVzZXJJZCI6MjQ5Njk3MjI1NSwidXNlck5hbWUiOiLlvpfniallci05SzFGMEI2SCIsImlzR3Vlc3QiOnRydWV9").decode("utf-8"))
# {"iat":1740103298,"exp":1771639298,"iss":"bc11eeafc514fa6b","sub":"bc11eeafc514fa6b","uuid":"bc11eeafc514fa6b","userId":2496972255,"userName":"得物er-9K1F0B6H","isGuest":true}

token串是由后端生成的,可以通过伪造请求获取

这里我们直接使用抓包获取的token

Python抓取推荐数据

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
import requests
import time
import base64
import hashlib

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad

def aes_encrypt_ecb(plaintext,key):
cipher = AES.new(key,AES.MODE_ECB)
padded_data = pad(plaintext,AES.block_size)
encrypted_data = cipher.encrypt(padded_data)
return encrypted_data




def md5_call(data):
md5 = hashlib.md5()
md5.update(data)
md5_hex = md5.hexdigest()
return md5_hex

timestamp = str(int(time.time() * 1000))

url = "https://app.dewu.com/sns-rec/v1/recommend/all/feed"

params = {
"abType": "social_brand_strategy_v454",
"abValue": "0",
"lastId": "",
"limit": "20",
"deliveryProjectId": "0",
"abVideoCover": "0",
"abRectagFengge": "1",
"abRecReason": "0",
"uuid" : "bc11eeafc514fa6b",
"platform" : "android",
"v": "4.74.5",
"loginToken" : "",
"timestamp" : timestamp # "1740103393181"#
}

params_sorted = dict(sorted(params.items()))

params_str = "".join([f"{k}{v}" for k, v in params_sorted.items()])

# print(params_str)

key = b"d245a0ba8d678a61"

encrypted_data = aes_encrypt_ecb(params_str.encode("utf-8"),key)

# print(encrypted_data)

encrypted_data_base64 = base64.b64encode(encrypted_data)

# print(encrypted_data_base64)

md5_data = md5_call(encrypted_data_base64)

# print(md5_data)

params["newSign"] = md5_data

headers = {
"debugable": "0",
"duuuid": "bc11eeafc514fa6b",
"duimei": "",
"duplatform": "android",
"appId": "duapp",
"duchannel": "myapp",
"humeChannel": "",
"duv": "4.74.5",
"duloginToken": "",
"dudeviceTrait": "Pixel+3",
"dudeviceBrand": "google",
"timestamp": timestamp,
"shumeiid": "202502211001376b2a86c46bd029859670f6b24db7f5be0030988becd7a9cc",
"oaid": "",
"User-Agent": "duapp/4.74.5(android;10)",
"X-Auth-Token": "Bearer eyJhbGciOiJSUzI1NiJ9.eyJpYXQiOjE3NDAxMDMyOTgsImV4cCI6MTc3MTYzOTI5OCwiaXNzIjoiYmMxMWVlYWZjNTE0ZmE2YiIsInN1YiI6ImJjMTFlZWFmYzUxNGZhNmIiLCJ1dWlkIjoiYmMxMWVlYWZjNTE0ZmE2YiIsInVzZXJJZCI6MjQ5Njk3MjI1NSwidXNlck5hbWUiOiLlvpfniallci05SzFGMEI2SCIsImlzR3Vlc3QiOnRydWV9.u3DBybDzMvee-V9rWdGuDePZU1TvvbOvodPeJ88-lKmxMbJpK8U93jyxIdcQ8BzhdSahkWn29T6sG3S3-bq1dz9p_MWgfxw5yYSIOE4qd2NfAr1OQ4n-BGQXw7EYdC-eg0uDOIctGCJ2OSfyKlqzqeIx9RIQieJf2bkYeYFF1vkF8SKU2rf-1ESjWdpiUkvPuBN5NrafGHvL4VRkdxkCjT_NTezQ8tXm1PgrYy_73jGGma1nX8Vp49wLqoxhrimQwUremdcNUEWWdeTNsy7nSbobiArkONoRIknrO922QU4nOmFWBuPRnRRGmKHZ371Q3MobmAz7s7yCXt1uDsY4QQ",
"isRoot": "0",
"emu": "0",
"isProxy": "0",
"Host": "app.dewu.com",
"Connection": "Keep-Alive",
"Accept-Encoding": "gzip",
"Cookie": "HWWAFSESTIME=1740103292438; HWWAFSESID=775c333dec0422ea694"
}


response = requests.get(url,params=params,headers=headers)

print(response.text)

print(response.status_code)

Unidbg模拟执行

模拟执行encode函数,这里面有两个参数,其中的obj并没有使用,所以不用管它,只需要一个str参数,而str参数又被传入了encodeByte方法里,它是一个native方法。这里面还有一个字段byteValues,它经过异或处理之后作为参数传给了encodeByte方法

接下来通过unidbg来实现

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
public class dewu extends AbstractJni {
private final AndroidEmulator emulator;
private final Memory memory;
private final VM vm;
private DalvikModule dm;

public dewu() {
emulator = AndroidEmulatorBuilder
.for64Bit() // for32Bit()
.build();
memory = emulator.getMemory();
memory.setLibraryResolver(new AndroidResolver(23));
vm = emulator.createDalvikVM(new File("unidbg-android/src/test/java/com/shizhuang/duapp/dewu_4.74.5.apk"));
vm.setJni(this);
vm.setVerbose(true);
dm = vm.loadLibrary("JNIEncrypt",true);
dm.callJNI_OnLoad(emulator);
}

public String encode(String str){
DvmClass AESEncryptClass = vm.resolveClass("com.duapp.aesjni.AESEncrypt");
StringObject byteValuesObj = AESEncryptClass.callStaticJniMethodObject(emulator,"getByteValues()Ljava/lang/String;");
String byteValues = byteValuesObj.getValue();
System.out.println("byteValues="+byteValues);
StringBuilder sb = new StringBuilder(byteValues.length());
for (int i2 = 0; i2 < byteValues.length(); i2++) {
if (byteValues.charAt(i2) == '0') {
sb.append('1');
} else {
sb.append('0');
}
}
System.out.println("sb:"+sb);
return (String) AESEncryptClass.callStaticJniMethodObject(emulator,"encodeByte([BLjava/lang/String;)Ljava/lang/String;",new ByteArray(vm,str.getBytes()),sb.toString()).getValue();
}

public static void main(String[] args) {
dewu dewu = new dewu();
System.out.println("encode:"+dewu.encode("loginTokenpagehomeplatformandroidtestatimestamp1740070306835uuidbc11eeafc514fa6bv4.74.5"));
// wMz+jQaNMznb1+IAkqC/I69ZXRCgSLPm9wlJoFRbBzr3CQhDvJTqF+jy8DeE3ISx83Sv0LiV9r0kR3Wr7hekHi9SRLYxX4owLGWeMYA2cIKTAw55onwRa1Ma1U4AGlqt
}
}