白乐天

道阻且长,行则将至。

瑞幸白盒AES

App信息

包名:com.lucky.luckyclient

过检测

root检测

使用狐妖面具隐藏

frida检测

使用小工具绕过

脱壳

使用脱壳网站进行脱壳

56.al

抓包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
url:
https://capi.lkcoffee.com/resource/m/sys/base/validcode

headers:
x-lk-akv 5260
x-lk-mid
x-lk-sid
event_id 1741678678079
x-lk-csid 08065795-7d98-4363-9c02-30d73c9f252e
sentry-trace 2ea1d59760394ecd9ea18fab48820ffb-a39c080332b849f9-1
baggage sentry-environment=release,sentry-public_key=d4365939f7c346a4b0c6ee50a412c050,sentry-release=com.lucky.luckyclient%405.2.60%2B200,sentry-sample_rate=1,sentry-trace_id=2ea1d59760394ecd9ea18fab48820ffb,sentry-transaction=LoginActivity.tvReqAuthCode
content-type application/x-www-form-urlencoded
content-length 228
accept-encoding gzip
cookie uid=
user-agent okhttp/4.9.3

form:
sign 7979560225808340751212263377520956346
q DURM6YZm8EJjngxNSvqLuaCpyTbBaQ1RNCfPIJl6_JATi85kT_JO5bo-nYuFGfCYknf1KQDtj0IXDItKNgs2ehphXU_Y0FeSA9JBPnZAggLlJK1fwwruC4J8vDz17ovkHjqEZp7bh3iAQYZY22P_NQ==
t 1741680800108
cid 210101

又抓了个登录包

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
url:
https://capi.lkcoffee.com/resource/m/user/login

headers:
x-lk-akv 5260
x-lk-mid
x-lk-sid
event_id 1741678678079
x-lk-csid 08065795-7d98-4363-9c02-30d73c9f252e
sentry-trace 7a99181659144d6fb9c42a30c0da15af-c53cfa10e78b4b98-1
baggage sentry-environment=release,sentry-public_key=d4365939f7c346a4b0c6ee50a412c050,sentry-release=com.lucky.luckyclient%405.2.60%2B200,sentry-sample_rate=1,sentry-trace_id=7a99181659144d6fb9c42a30c0da15af,sentry-transaction=LoginActivity.btnConfirm
content-type application/x-www-form-urlencoded
content-length 685
accept-encoding gzip
cookie uid=e208b39d-8f40-4e93-ab22-430698abc6821741681516422
user-agent okhttp/4.9.3

form:
sign 5893325431374183456150891639716307865
q mOD2mwoFGDWinNSdsn9lI0tvoAQhA0e3_boidwfb61WKbU4LjgnSja-3Ynxdslq_rNFXWN49AcmYjkCEqBG7TCjDzT556tyHhu6wVtv1Kr-QAaK-RRvpSO0nqled_vXMwxgpjUWE2FvF-FfUZNfY8dVfEt7SpOlNsfiKHCWxwPOII5xpWmt4WZawpkq3dPnS5AP3ncMj8MctnB6Z2o2Wu9_s2BEfhweLyBoV8eYgVet8hvp2m_UfiZifKR3_FwD3q6x-iVVECbSjGYXyk-1JaYpcQUQZv8PMLtHfQ7CsfKCtkMlFETWuGNTNDy4kmiAjrFFDGen8D8yJ4-2rJAzsTzRsQI2hI1nvLcPKdoeObzl-C9-kLugMoKrecLKjIKtM0cwbqeGaSuo6F87GTbhmgh2lY85Jbc521efoup-KhMeHzApRp0TaofSDkO_ykmYiN_GsIr77RpT7tTu3a84FV-GfkjX_LgWbZhnPwFyx5boJ41xq7gZLF92ejvlFou4I6vKIfxxYn4-4e96gbbgW9eUepOub_ThVPyIGgrx_KVc=
uid e208b39d-8f40-4e93-ab22-430698abc6821741681516422
t 1741681608374
cid 210101

response:
vaTaFF5L8jW_LFnfCuwtZGulP6wG3s-O__HUjYaf8swfjrR3WG3qoYY5X8Gk_nN52wNx5lYRD4GsXuZWopXQ-SxeK58cf_zVpelAhEpcD_zuavE12lvsriUjHspxgNszHjcK9XTN3jT3S6xNKM8nqbadCDGMS1RQJAc5GTiNyBAqgx6y-P0CNYjDfc5iGXVuwIUBR3WoxJM1Yyfdvk8pvALlMMkF-ja2j63TioNTyKaBrCFJtDbYhQROjKSF7tjk6MQVbFyhHocTm7wBl54h4JzbhdatBO2wRaaym9fYUygUqaZTvhMxF11CWw1sPk5a

请求参数signq,响应体都是加密的

又抓了个包

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
utl:
https://capi.lkcoffee.com/resource/m/user/login

headers={
":authority": "capi.lkcoffee.com",
":method": "POST",
":path": "/resource/m/user/login",
":scheme": "https",
"x-lk-akv": "5265",
"x-lk-mid": "",
"x-lk-sid": "",
"event_id": "1741773373890",
"x-lk-csid": "b23ee6cb-c0f2-4e43-9580-727ce47f7b73",
"sentry-trace": "5d4028a1977c401da7dc5299c9fe942f-11ad2f67d12f4343-0",
"baggage": "sentry-environment=release,sentry-public_key=d4365939f7c346a4b0c6ee50a412c050,sentry-release=com.lucky.luckyclient%405.2.65%2B201,sentry-sample_rate=0,sentry-trace_id=5d4028a1977c401da7dc5299c9fe942f,sentry-transaction=LoginActivity.btnConfirm",
"content-type": "application/x-www-form-urlencoded",
"content-length": "684",
"accept-encoding": "gzip",
"cookie": "uid=ede6c26f-85eb-4f41-b103-051605ae1c821741773364713",
"user-agent": "okhttp/4.9.3"
}

body={
"sign": "7964953862091852252935269275418960748",
"q": "-YqmJeyS17YrIsczlOeETZzPH0DbB-Hyhw09ZJUl9kZ4SZ_KkVh07uBqj8ejykTw1bl02vu1dt67BbIKA-1FqlTuFGh8VoJemyEkmhqnf6w9u98dKvCdpFPDiRktU-PGDtnD9Y8rBYgBHuPyfnMR15jTMaF0HV4SMw6qU_S_3p771jHSE9Kl8Yx6zKgkBv71qLhQP68VNsifnmQFbtEBdQt5hK1-9WLGzrhB1jLiL1EJTtmqGymT21ymquQHXcDudPQ7clgzz0Xzjt_NO-L5mbbklzohRwsDO0eQtHxD58zL6QlQoWsRTqvlVMkUz2wSNPsApVb2hq30D4x9RSvDE3sKJBGAzFqDG_XwE6yoojDNPn2wH0H0o3-wtCso0hRJXlvPTHz_zMYdTcMNKTwccWgu0mmbH5NKcVCxrZ6R2MeCfUb8ACp7PKjZeAecaHq_REu3Kkoi3xXPPK_MU_eLl5A1MFw_d7azTSHHUFmL3RdiC15vvA1bajH1NDbLEAxMV8nUOJVfuWxXDGS0Diknyj0V95At_KBG4y7Ylo-4fv4=",
"uid": "ede6c26f-85eb-4f41-b103-051605ae1c821741773364713",
"t": "1741773407429",
"cid": "210101"
}

定位

jadx打开脱壳后的dex

Java通过System.load()和System.loadLibrary()来加载动态库,直接搜索System.loadLibrary

发现一个可疑类

双击进去看到so的名字是加密的,还有四个native方法和三个用于加解密的方法

主动调用getString2()方法来解密so名字

1
2
3
4
5
6
7
function call_getString2(){
Java.perform(function(){
let StubApp = Java.use("com.stub.StubApp");
let soName = StubApp.getString2("32264");
console.log("so:",soName);
})
}

调用了,但是输出结果是乱码

用hook_RegisterNatives打印动态注册函数及其所在的so

然后找到了libcryptoDD.so

分析

看了源码这几个native方法中只有localAESWorkmd5_crypt被调用了

猜测sign是由md5_crypt生成的

q和响应体是由localAESWork生成的

接下来对localAESWorkmd5_cryptabc进行hook

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
function bArrToString(bArr) {
return Java.use("java.lang.String").$new(bArr);
}

function hook_localAESWork(){
Java.perform(function(){
let CryptoHelper = Java.use("com.luckincoffee.safeboxlib.CryptoHelper");
CryptoHelper["localAESWork"].implementation = function (bArr, i10, bArr2) {
console.log(`CryptoHelper.localAESWork is called!`);
console.log(`bArr=${bArr}`);
console.log(`bArr=${bArrToString(bArr)}`);
console.log(`i10=${i10}`);
console.log(`bArr2=${bArr2}`);
console.log(`bArr2=${bArrToString(bArr2)}`);
let result = this["localAESWork"](bArr, i10, bArr2);
console.log(`CryptoHelper.localAESWork result=${result}`);
console.log(`CryptoHelper.localAESWork result=${bArrToString(result)}`);
return result;
};
})
}
hook_localAESWork()


function hook_md5_crypt(){
Java.perform(function(){
let CryptoHelper = Java.use("com.luckincoffee.safeboxlib.CryptoHelper");
CryptoHelper["md5_crypt"].implementation = function (bArr, i10, bArr2) {
console.log(`CryptoHelper.md5_crypt is called!`);
console.log(`bArr=${bArr}`);
console.log(`bArr=${bArrToString(bArr)}`);
console.log(`i10=${i10}`);
console.log(`bArr2=${bArr2}`);
console.log(`bArr2=${bArrToString(bArr2)}`);
let result = this["md5_crypt"](bArr, i10, bArr2);
console.log(`CryptoHelper.md5_crypt result=${result}`);
console.log(`CryptoHelper.md5_crypt result=${bArrToString(result)}`);
return result;
};
})
}
hook_md5_crypt()

结果

1
2
3
4
5
6
7
8
9
CryptoHelper.md5_crypt is called!
bArr=99,105,100,61,50,49,48,49,48,49,59,113,61,88,122,48,82,56,50,102,83,52,56,53,98,50,51,98,97,56,49,55,101,52,74,122,80,72,48,68,98,66,45,72,121,104,119,48,57,90,74,85,108,57,107,90,52,83,90,95,75,107,86,104,48,55,117,66,113,106,56,101,106,121,107,84,119,49,98,108,48,50,118,117,49,100,116,54,55,66,98,73,75,65,45,49,70,113,108,84,117,70,71,104,56,86,111,74,101,109,121,69,107,109,104,113,110,102,54,119,57,117,57,56,100,75,118,67,100,112,70,80,68,105,82,107,116,85,45,80,71,68,116,110,68,57,89,56,114,66,89,103,66,72,117,80,121,102,110,77,82,49,53,106,84,77,97,70,48,72,86,52,83,77,119,54,113,85,95,83,95,51,112,55,55,49,106,72,83,69,57,75,108,56,89,120,54,122,75,103,107,66,118,55,49,113,76,104,81,80,54,56,86,78,115,105,102,110,109,81,70,98,116,69,66,100,81,116,53,104,75,49,45,57,87,76,71,122,114,104,66,49,106,76,105,76,49,69,74,84,116,109,113,71,121,109,84,50,49,121,109,113,117,81,72,88,99,68,117,107,45,121,81,81,79,69,86,81,104,77,118,78,56,112,95,100,69,107,68,50,51,88,54,110,68,86,110,116,84,51,70,84,67,57,48,100,109,77,73,116,80,68,76,54,81,108,81,111,87,115,82,84,113,118,108,86,77,107,85,122,50,119,83,78,80,115,65,112,86,98,50,104,113,51,48,68,52,120,57,82,83,118,68,69,51,115,75,74,66,71,65,122,70,113,68,71,95,88,119,69,54,121,111,111,106,68,78,80,110,50,119,72,48,72,48,111,51,45,119,116,67,115,111,48,104,82,74,88,108,118,80,84,72,122,95,122,77,89,100,84,99,77,78,75,84,119,99,99,87,103,117,48,109,109,98,72,53,78,75,99,86,67,120,114,90,54,82,50,77,101,67,102,85,98,56,65,67,112,55,80,75,106,90,101,65,101,99,97,72,113,95,82,69,117,51,75,107,111,105,51,120,88,80,80,75,95,77,85,95,101,76,108,53,65,49,77,70,119,95,100,55,97,122,84,83,72,72,85,70,109,76,51,82,100,105,67,49,53,118,118,65,49,98,97,106,72,49,78,68,98,76,69,65,120,77,86,56,110,85,79,74,86,102,117,87,120,88,68,71,83,48,68,105,107,110,121,106,48,86,57,53,65,116,95,75,66,71,52,121,55,89,108,111,45,52,102,118,52,61,59,116,61,49,55,52,49,54,57,57,53,50,52,55,56,57,59,117,105,100,61,49,102,48,48,56,48,102,56,45,50,51,56,102,45,52,56,51,97,45,98,101,48,52,45,57,97,55,101,50,99,97,52,99,48,101,54,49,55,52,49,54,56,56,52,49,51,55,57,49
bArr=cid=210101;q=Xz0R82fS485b23ba817e4JzPH0DbB-Hyhw09ZJUl9kZ4SZ_KkVh07uBqj8ejykTw1bl02vu1dt67BbIKA-1FqlTuFGh8VoJemyEkmhqnf6w9u98dKvCdpFPDiRktU-PGDtnD9Y8rBYgBHuPyfnMR15jTMaF0HV4SMw6qU_S_3p771jHSE9Kl8Yx6zKgkBv71qLhQP68VNsifnmQFbtEBdQt5hK1-9WLGzrhB1jLiL1EJTtmqGymT21ymquQHXcDuk-yQQOEVQhMvN8p_dEkD23X6nDVntT3FTC90dmMItPDL6QlQoWsRTqvlVMkUz2wSNPsApVb2hq30D4x9RSvDE3sKJBGAzFqDG_XwE6yoojDNPn2wH0H0o3-wtCso0hRJXlvPTHz_zMYdTcMNKTwccWgu0mmbH5NKcVCxrZ6R2MeCfUb8ACp7PKjZeAecaHq_REu3Kkoi3xXPPK_MU_eLl5A1MFw_d7azTSHHUFmL3RdiC15vvA1bajH1NDbLEAxMV8nUOJVfuWxXDGS0Diknyj0V95At_KBG4y7Ylo-4fv4=;t=1741699524789;uid=1f0080f8-238f-483a-be04-9a7e2ca4c0e61741688413791
i10=1
bArr2=-87,-114,-67,121,108,-104,-58,66,-48,15,40,31,112,-58,-81,116,73,9,-6,80,-38,87,98,16,114,-89,108,124,97,-82,52,59
bArr2=���yl��B�(pƯtI �P�Wbr�l|a�4;
CryptoHelper.md5_crypt
result=49,53,55,50,54,49,52,51,49,49,53,50,55,54,56,53,54,49,50,53,53,52,48,48,53,56,51,49,49,52,49,55,49,51,48,53,53,50
CryptoHelper.md5_crypt result=15726143115276856125540058311417130552
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
function bArrToString(bArr) {
return Java.use("java.lang.String").$new(bArr);
}

function hook_localAESWork(){
Java.perform(function(){
let CryptoHelper = Java.use("com.luckincoffee.safeboxlib.CryptoHelper");
CryptoHelper["localAESWork"].implementation = function (bArr, i10, bArr2) {
console.log(`CryptoHelper.localAESWork is called!`);
console.log(`bArr=${bArr}`);
console.log(`bArr=${bArrToString(bArr)}`);
console.log(`i10=${i10}`);
console.log(`bArr2=${bArr2}`);
console.log(`bArr2=${bArrToString(bArr2)}`);
let result = this["localAESWork"](bArr, i10, bArr2);
console.log(`CryptoHelper.localAESWork result=${result}`);
console.log(`CryptoHelper.localAESWork result=${bArrToString(result)}`);
return result;
};
})
}
hook_localAESWork()


function hook_md5_crypt(){
Java.perform(function(){
let CryptoHelper = Java.use("com.luckincoffee.safeboxlib.CryptoHelper");
CryptoHelper["md5_crypt"].implementation = function (bArr, i10, bArr2) {
console.log(`CryptoHelper.md5_crypt is called!`);
console.log(`bArr=${bArr}`);
console.log(`bArr=${bArrToString(bArr)}`);
console.log(`i10=${i10}`);
console.log(`bArr2=${bArr2}`);
console.log(`bArr2=${bArrToString(bArr2)}`);
let result = this["md5_crypt"](bArr, i10, bArr2);
console.log(`CryptoHelper.md5_crypt result=${result}`);
console.log(`CryptoHelper.md5_crypt result=${bArrToString(result)}`);
return result;
};
})
}
hook_md5_crypt()

function hook_a(){
Java.perform(function(){
let CryptoHelper = Java.use("com.luckincoffee.safeboxlib.CryptoHelper");
CryptoHelper["a"].implementation = function (str, i10) {
console.log(`CryptoHelper.a is called: str=${str}, i10=${i10}`);
let result = this["a"](str, i10);
console.log(`CryptoHelper.a result=${result}`);
return result;
};
})
}

hook_a()

function hook_b(){
Java.perform(function(){
let CryptoHelper = Java.use("com.luckincoffee.safeboxlib.CryptoHelper");
CryptoHelper["b"].implementation = function (str) {
console.log(`CryptoHelper.b is called: str=${str}`);
let result = this["b"](str);
console.log(`CryptoHelper.b result=${result}`);
return result;
};
})
}

hook_b()

function hook_c(){
Java.perform(function(){
let CryptoHelper = Java.use("com.luckincoffee.safeboxlib.CryptoHelper");
CryptoHelper["c"].implementation = function (str) {
console.log(`CryptoHelper.c is called: str=${str}`);
let result = this["c"](str);
console.log(`CryptoHelper.c result=${result}`);
return result;
};
})
}

hook_c()

结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
CryptoHelper.c is called: str={"mobile":"17888888888","countryNo":"86","validateCode":"123456","appversion":"5265","type":1,"deviceId":"android_lucky_ef5722c5-96ff-4521-8096-27530a0365b2","systemVersion":"29","blackBox":"oGPHE1741688414xYaSQkj72m7","uniqueCode":"DUQKlOhFPQSnh57_KB6erjHdTADHhPk26j60RFVRS2xPaEZQUVNuaDU3X0tCNmVyakhkVEFESGhQazI2ajYwc2h1","regionId":"CO0001","regId":"","deviceBrand":"google","isSecurityVerify":false}
CryptoHelper.c result=Xz0R82fS485b23ba817e4JzPH0DbB-Hyhw09ZJUl9kZ4SZ_KkVh07uBqj8ejykTw1bl02vu1dt67BbIKA-1FqlTuFGh8VoJemyEkmhqnf6w9u98dKvCdpFPDiRktU-PGDtnD9Y8rBYgBHuPyfnMR15jTMaF0HV4SMw6qU_S_3p771jHSE9Kl8Yx6zKgkBv71qLhQP68VNsifnmQFbtEBdQt5hK1-9WLGzrhB1jLiL1EJTtmqGymT21ymquQHXcDuk-yQQOEVQhMvN8p_dEkD23X6nDVntT3FTC90dmMItPDL6QlQoWsRTqvlVMkUz2wSNPsApVb2hq30D4x9RSvDE3sKJBGAzFqDG_XwE6yoojDNPn2wH0H0o3-wtCso0hRJXlvPTHz_zMYdTcMNKTwccWgu0mmbH5NKcVCxrZ6R2MeCfUb8ACp7PKjZeAecaHq_REu3Kkoi3xXPPK_MU_eLl5A1MFw_d7azTSHHUFmL3RdiC15vvA1bajH1NDbLEAxMV8nUOJVfuWxXDGS0Diknyj0V95At_KBG4y7Ylo-4fv4=
CryptoHelper.md5_crypt is called!
bArr=99,105,100,61,50,49,48,49,48,49,59,113,61,88,122,48,82,56,50,102,83,52,56,53,98,50,51,98,97,56,49,55,101,52,74,122,80,72,48,68,98,66,45,72,121,104,119,48,57,90,74,85,108,57,107,90,52,83,90,95,75,107,86,104,48,55,117,66,113,106,56,101,106,121,107,84,119,49,98,108,48,50,118,117,49,100,116,54,55,66,98,73,75,65,45,49,70,113,108,84,117,70,71,104,56,86,111,74,101,109,121,69,107,109,104,113,110,102,54,119,57,117,57,56,100,75,118,67,100,112,70,80,68,105,82,107,116,85,45,80,71,68,116,110,68,57,89,56,114,66,89,103,66,72,117,80,121,102,110,77,82,49,53,106,84,77,97,70,48,72,86,52,83,77,119,54,113,85,95,83,95,51,112,55,55,49,106,72,83,69,57,75,108,56,89,120,54,122,75,103,107,66,118,55,49,113,76,104,81,80,54,56,86,78,115,105,102,110,109,81,70,98,116,69,66,100,81,116,53,104,75,49,45,57,87,76,71,122,114,104,66,49,106,76,105,76,49,69,74,84,116,109,113,71,121,109,84,50,49,121,109,113,117,81,72,88,99,68,117,107,45,121,81,81,79,69,86,81,104,77,118,78,56,112,95,100,69,107,68,50,51,88,54,110,68,86,110,116,84,51,70,84,67,57,48,100,109,77,73,116,80,68,76,54,81,108,81,111,87,115,82,84,113,118,108,86,77,107,85,122,50,119,83,78,80,115,65,112,86,98,50,104,113,51,48,68,52,120,57,82,83,118,68,69,51,115,75,74,66,71,65,122,70,113,68,71,95,88,119,69,54,121,111,111,106,68,78,80,110,50,119,72,48,72,48,111,51,45,119,116,67,115,111,48,104,82,74,88,108,118,80,84,72,122,95,122,77,89,100,84,99,77,78,75,84,119,99,99,87,103,117,48,109,109,98,72,53,78,75,99,86,67,120,114,90,54,82,50,77,101,67,102,85,98,56,65,67,112,55,80,75,106,90,101,65,101,99,97,72,113,95,82,69,117,51,75,107,111,105,51,120,88,80,80,75,95,77,85,95,101,76,108,53,65,49,77,70,119,95,100,55,97,122,84,83,72,72,85,70,109,76,51,82,100,105,67,49,53,118,118,65,49,98,97,106,72,49,78,68,98,76,69,65,120,77,86,56,110,85,79,74,86,102,117,87,120,88,68,71,83,48,68,105,107,110,121,106,48,86,57,53,65,116,95,75,66,71,52,121,55,89,108,111,45,52,102,118,52,61,59,116,61,49,55,52,49,55,48,48,51,49,50,53,53,50,59,117,105,100,61,49,102,48,48,56,48,102,56,45,50,51,56,102,45,52,56,51,97,45,98,101,48,52,45,57,97,55,101,50,99,97,52,99,48,101,54,49,55,52,49,54,56,56,52,49,51,55,57,49
bArr=cid=210101;q=Xz0R82fS485b23ba817e4JzPH0DbB-Hyhw09ZJUl9kZ4SZ_KkVh07uBqj8ejykTw1bl02vu1dt67BbIKA-1FqlTuFGh8VoJemyEkmhqnf6w9u98dKvCdpFPDiRktU-PGDtnD9Y8rBYgBHuPyfnMR15jTMaF0HV4SMw6qU_S_3p771jHSE9Kl8Yx6zKgkBv71qLhQP68VNsifnmQFbtEBdQt5hK1-9WLGzrhB1jLiL1EJTtmqGymT21ymquQHXcDuk-yQQOEVQhMvN8p_dEkD23X6nDVntT3FTC90dmMItPDL6QlQoWsRTqvlVMkUz2wSNPsApVb2hq30D4x9RSvDE3sKJBGAzFqDG_XwE6yoojDNPn2wH0H0o3-wtCso0hRJXlvPTHz_zMYdTcMNKTwccWgu0mmbH5NKcVCxrZ6R2MeCfUb8ACp7PKjZeAecaHq_REu3Kkoi3xXPPK_MU_eLl5A1MFw_d7azTSHHUFmL3RdiC15vvA1bajH1NDbLEAxMV8nUOJVfuWxXDGS0Diknyj0V95At_KBG4y7Ylo-4fv4=;t=1741700312552;uid=1f0080f8-238f-483a-be04-9a7e2ca4c0e61741688413791
i10=1
bArr2=-87,-114,-67,121,108,-104,-58,66,-48,15,40,31,112,-58,-81,116,73,9,-6,80,-38,87,98,16,114,-89,108,124,97,-82,52,59
bArr2=���yl��B�(pƯtI �P�Wbr�l|a�4;
CryptoHelper.md5_crypt result=56,53,56,57,53,48,53,49,49,55,50,50,48,53,48,50,52,49,48,57,49,48,51,48,53,48,56,49,54,52,55,52,50,53,53,53,52
CryptoHelper.md5_crypt result=8589505117220502410910305081647425554
CryptoHelper.b is called: str=Xz0R82fS485b23ba817e4JzPH0DbB-Hyhw09ZJUl9kZ4SZ_KkVh07uBqj8ejykTw1bl02vu1dt67BbIKA-1FqlTuFGh8VoJemyEkmhqnf6w9u98dKvCdpFPDiRktU-PGDtnD9Y8rBYgBHuPyfnMR15jTMaF0HV4SMw6qU_S_3p771jHSE9Kl8Yx6zKgkBv71qLhQP68VNsifnmQFbtEBdQt5hK1-9WLGzrhB1jLiL1EJTtmqGymT21ymquQHXcDuk-yQQOEVQhMvN8p_dEkD23X6nDVntT3FTC90dmMItPDL6QlQoWsRTqvlVMkUz2wSNPsApVb2hq30D4x9RSvDE3sKJBGAzFqDG_XwE6yoojDNPn2wH0H0o3-wtCso0hRJXlvPTHz_zMYdTcMNKTwccWgu0mmbH5NKcVCxrZ6R2MeCfUb8ACp7PKjZeAecaHq_REu3Kkoi3xXPPK_MU_eLl5A1MFw_d7azTSHHUFmL3RdiC15vvA1bajH1NDbLEAxMV8nUOJVfuWxXDGS0Diknyj0V95At_KBG4y7Ylo-4fv4=
CryptoHelper.b result={"mobile":"17888888888","countryNo":"86","validateCode":"123456","appversion":"5265","type":1,"deviceId":"android_lucky_ef5722c5-96ff-4521-8096-27530a0365b2","systemVersion":"29","blackBox":"oGPHE1741688414xYaSQkj72m7","uniqueCode":"DUQKlOhFPQSnh57_KB6erjHdTADHhPk26j60RFVRS2xPaEZQUVNuaDU3X0tCNmVyakhkVEFESGhQazI2ajYwc2h1","regionId":"CO0001","regId":"","deviceBrand":"google","isSecurityVerify":false}
CryptoHelper.b is called: str=nMymqvoWI5UzuGSsfOyxWS9E0JQYtcev4A_O1LXSr_HosyYfsqRmenMCcYf7-mdZjZ8LUfNc6HbRD89pGuGxJhYP475VhWKSXmkD7eAkTSmxA1MpUjQQHOjueGL1F_YWGExcwjd3N-sOHxEfeB59eTF1vGH9C_S6Q5TrXqrOg1R0yv5lrZ0CUyVHr38KtyytHyv4ip5jmin3q-HoLrMNZgkywRFi2RKOdxa8F-I1PLBhIOxZmtNm7IBiSVPvc8q2fnKJLbxMfM680Zr9YwRrxe_XcuzE1YLdSXTc2cmy5cq8M0xQ12eMbJo1sZhGPGs7
CryptoHelper.b result={"busiCode":"BASE001","code":7,"content":null,"handler":"USER","msg":"验证码无效","status":"BASE_ERROR","uid":"1f0080f8-238f-483a-be04-9a7e2ca4c0e61741688413791","version":"101","zeusId":"luckycapiproxy-0ade0957-483805-214303"}
CryptoHelper.b is called: str=nMymqvoWI5UzuGSsfOyxWS9E0JQYtcev4A_O1LXSr_HosyYfsqRmenMCcYf7-mdZjZ8LUfNc6HbRD89pGuGxJhYP475VhWKSXmkD7eAkTSmxA1MpUjQQHOjueGL1F_YWGExcwjd3N-sOHxEfeB59eTF1vGH9C_S6Q5TrXqrOg1R0yv5lrZ0CUyVHr38KtyytHyv4ip5jmin3q-HoLrMNZgkywRFi2RKOdxa8F-I1PLBhIOxZmtNm7IBiSVPvc8q2fnKJLbxMfM680Zr9YwRrxe_XcuzE1YLdSXTc2cmy5cq8M0xQ12eMbJo1sZhGPGs7
CryptoHelper.b result={"busiCode":"BASE001","code":7,"content":null,"handler":"USER","msg":"验证码无效","status":"BASE_ERROR","uid":"1f0080f8-238f-483a-be04-9a7e2ca4c0e61741688413791","version":"101","zeusId":"luckycapiproxy-0ade0957-483805-214303"}

c方法对一堆参数加密,加密结果为q值,md5_crypt对q及其他内容进行签名,b方法对响应体和q解密

c方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public synchronized String c(String str) {
c cVar = this.f93097a;
if (cVar == null) {
throw new RuntimeException(StubApp.getString2("32267"));
}
String str2 = "";
String a10 = cVar.a();
if (TextUtils.isEmpty(a10)) {
return "";
}
try {
str2 = new String(Base64.encode(localAESWork(str.getBytes(), 2, Base64.decode(a10.replace(org.objectweb.asm.signature.b.f127225c, org.objectweb.asm.signature.b.f127224b).replace('_', '/').getBytes(), 2)), 2));
} catch (Exception e10) {
e10.printStackTrace();
}
return str2.replace(org.objectweb.asm.signature.b.f127224b, org.objectweb.asm.signature.b.f127225c).replace('/', '_');
}

public CryptoHelper(c cVar) {
this.f93097a = cVar;
}

这里面有一个字段a10需要找它的来源,它来源于a方法,而它是个接口方法

找它的实现类com.lucky.lib.http2.b

这里面实现了a方法

接下来通过主动调用获取它的值

1
2
3
4
5
6
7
8
function get_a10(){
Java.perform(function(){
let a = Java.use("com.lucky.lib.http2.b$a").$new();
let result = a.a();
console.log("a10:",result);
return result;
})
}

输出

1
a10: qY69eWyYxkLQDygfcMavdEkJ+lDaV2IQcqdsfGGuNDs=

接下来看这部分代码,弄清楚这里面的值和逻辑

1
str2 = new String(Base64.encode(localAESWork(str.getBytes(), 2, Base64.decode(a10.replace('-', '+').replace('_', '/').getBytes(), 2)), 2));

看我们hook到的结果协助分析

1
2
CryptoHelper.c is called: str={"mobile":"17888888888","countryNo":"86","validateCode":"123456","appversion":"5265","type":1,"deviceId":"android_lucky_ef5722c5-96ff-4521-8096-27530a0365b2","systemVersion":"29","blackBox":"oGPHE1741688414xYaSQkj72m7","uniqueCode":"DUQKlOhFPQSnh57_KB6erjHdTADHhPk26j60RFVRS2xPaEZQUVNuaDU3X0tCNmVyakhkVEFESGhQazI2ajYwc2h1","regionId":"CO0001","regId":"","deviceBrand":"google","isSecurityVerify":false}
CryptoHelper.c result=Xz0R82fS485b23ba817e4JzPH0DbB-Hyhw09ZJUl9kZ4SZ_KkVh07uBqj8ejykTw1bl02vu1dt67BbIKA-1FqlTuFGh8VoJemyEkmhqnf6w9u98dKvCdpFPDiRktU-PGDtnD9Y8rBYgBHuPyfnMR15jTMaF0HV4SMw6qU_S_3p771jHSE9Kl8Yx6zKgkBv71qLhQP68VNsifnmQFbtEBdQt5hK1-9WLGzrhB1jLiL1EJTtmqGymT21ymquQHXcDuk-yQQOEVQhMvN8p_dEkD23X6nDVntT3FTC90dmMItPDL6QlQoWsRTqvlVMkUz2wSNPsApVb2hq30D4x9RSvDE3sKJBGAzFqDG_XwE6yoojDNPn2wH0H0o3-wtCso0hRJXlvPTHz_zMYdTcMNKTwccWgu0mmbH5NKcVCxrZ6R2MeCfUb8ACp7PKjZeAecaHq_REu3Kkoi3xXPPK_MU_eLl5A1MFw_d7azTSHHUFmL3RdiC15vvA1bajH1NDbLEAxMV8nUOJVfuWxXDGS0Diknyj0V95At_KBG4y7Ylo-4fv4=

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package com.luckincoffee.safeboxlib;


import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Module;
import com.github.unidbg.arm.backend.Unicorn2Factory;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.virtualmodule.android.AndroidModule;

import java.io.File;
import java.io.IOException;
import java.util.Base64;

public class CryptoHelper extends AbstractJni {
private final AndroidEmulator emulator;
private String process = "";
private final Memory memory;
private final VM vm;
private DalvikModule dm;
private Module module;


public CryptoHelper() {
emulator = AndroidEmulatorBuilder
.for64Bit() // for32Bit()
.setProcessName(process)
.setRootDir(new File("target/rootfs"))
.addBackendFactory(new Unicorn2Factory(true))
.build();
// 设置执行多少条指令切换一次线程
// emulator.getBackend().registerEmuCountHook(10000);
// 开启线程调度器
// emulator.getSyscallHandler().setEnableThreadDispatcher(true);
memory = emulator.getMemory();
memory.setLibraryResolver(new AndroidResolver(23));
vm = emulator.createDalvikVM(new File("apktest/ruixingcoffee_5.2.65.apk"));
vm.setJni(this);
vm.setVerbose(true);
new AndroidModule(emulator,vm).register(memory);
dm = vm.loadLibrary("cryptoDD",true);
dm.callJNI_OnLoad(emulator);
module = dm.getModule();
}

public void localAESWork() {
DvmClass CryptoHelperClass = vm.resolveClass("com.luckincoffee.safeboxlib.CryptoHelper");
ByteArray byteValues = CryptoHelperClass.callStaticJniMethodObject(emulator, "localAESWork([BI[B)[B",
"{\"mobile\":\"17888888888\",\"countryNo\":\"86\",\"validateCode\":\"123456\",\"appversion\":\"5265\",\"type\":1,\"deviceId\":\"android_lucky_ef5722c5-96ff-4521-8096-27530a0365b2\",\"systemVersion\":\"29\",\"blackBox\":\"oGPHE1741688414xYaSQkj72m7\",\"uniqueCode\":\"DUQKlOhFPQSnh57_KB6erjHdTADHhPk26j60RFVRS2xPaEZQUVNuaDU3X0tCNmVyakhkVEFESGhQazI2ajYwc2h1\",\"regionId\":\"CO0001\",\"regId\":\"\",\"deviceBrand\":\"google\",\"isSecurityVerify\":false}".getBytes(),
2,
Base64.getDecoder().decode("qY69eWyYxkLQDygfcMavdEkJ+lDaV2IQcqdsfGGuNDs=".replace('-', '+').replace('_', '/').getBytes()));
String ret = bytesTohexString(byteValues.getValue());
System.out.println("ret="+ret);
String b64encoded = Base64.getEncoder().encodeToString(byteValues.getValue());
System.out.println(b64encoded);
}

public static void main(String[] args) {
CryptoHelper cryptoHelper = new CryptoHelper();
cryptoHelper.localAESWork();
}
}

运行一下,报错了,看打印的信息

1
2
3
JNIEnv->NewByteArray(416) was called from RX@0x12043ae4[libcryptoDD.so]0x43ae4
JNIEnv->SetByteArrayRegion([B@72a7c7e0, 0, 416, RW@0x123991c0) was called from RX@0x12043b08[libcryptoDD.so]0x43b08
[10:38:44 101] WARN [com.github.unidbg.arm.AbstractARM64Emulator] (AbstractARM64Emulator$1:66) - Write memory failed: address=0x0, size=8, value=0x12057000

0x43b08这个地方报错了,IDA分析一下

在下面这个地方,内存无法写入

用unidbg处理一下

1
2
3
4
5
6
7
8
9
10
11
public void patchFreeByConsoleDebugger(){
Symbol free = emulator.getMemory().findModule("libc.so").findSymbolByName("free");

emulator.attach().addBreakPoint(free.getAddress(), new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
emulator.getBackend().reg_write(Arm64Const.UC_ARM64_REG_W0,0);
return true;
}
});
}

然后可以输出正确结果了

1
2
ret=5f3d11f367d2e3ce5bdb76daf35edee03b1b6ce1b3c1846a175a1ec87bc6c9ddc66d65a239b38f7b7727befc6a17c44a3fa7921c901c66596ca7cedacc48a08db0cb0931efcbb9a7afbf9ab379d992ba66f84c3e529d4e2f96072db2212bc9ab4225a162ee55d4ea0e9648d49d532735fe628c175e6b81163b05bf97bd030634c09dc45f0e536b75dd1f29f8ea328eb60907c08ff1c309d7ea64257aad490fe7a50f06c86d33a6cfcd1494ec5ae0b07fc4d1dc5094a6161985ebee8bac53057e07603836b0bcc15b34dcaa4f8ff361b5df5855e5f5f9d4c8b8c5c0f9c936f697c37795ac6ef7fb66513216f2019d08fd477ef712b4cf4036441a0a56e429f3fc440bbfce851936683e74024fc1abcfeb50480f747fcaabf346eebca3d5881ecbbaf639c96a6db5f22d362223bc766e39fc0a4472b197a5be78f2bf947afe15660b97c9b496d0e39103ed77672327ab36c7920e6d763f1271bb017615c49afcb50429218b6f3d76156129095eb1082f33bbc2f732f7ba76584b04cd388aadc16e0faaf872eecb1810a6bb25ae9532a600bf2b597e2e50bb97d0df5fa4a7457f53
Xz0R82fS485b23ba817e4DsbbOGzwYRqF1oeyHvGyd3GbWWiObOPe3cnvvxqF8RKP6eSHJAcZllsp87azEigjbDLCTHvy7mnr7+as3nZkrpm+Ew+Up1OL5YHLbIhK8mrQiWhYu5V1OoOlkjUnVMnNf5ijBdea4EWOwW/l70DBjTAncRfDlNrdd0fKfjqMo62CQfAj/HDCdfqZCV6rUkP56UPBshtM6bPzRSU7FrgsH/E0dxQlKYWGYXr7ousUwV+B2A4NrC8wVs03KpPj/Nhtd9YVeX1+dTIuMXA+ck29pfDd5Wsbvf7ZlEyFvIBnQj9R373ErTPQDZEGgpW5Cnz/EQLv86FGTZoPnQCT8Grz+tQSA90f8qr80buvKPViB7LuvY5yWpttfItNiIjvHZuOfwKRHKxl6W+ePK/lHr+FWYLl8m0ltDjkQPtd2cjJ6s2x5IObXY/EnG7AXYVxJr8tQQpIYtvPXYVYSkJXrEILzO7wvcy97p2WEsEzTiKrcFuD6r4cu7LGBCmuyWulTKmAL8rWX4uULuX0N9fpKdFf1M=

打印详细日志信息

1
2
3
4
5
6
7
8
import org.apache.log4j.Level;
import org.apache.log4j.Logger;

// ARM64 JNI 详细日志
Logger.getLogger(DalvikVM64.class).setLevel(Level.DEBUG);

// ARM32 JNI 详细日志
Logger.getLogger(DalvikVM.class).setLevel(Level.DEBUG);

Logger.getLogger(DalvikVM64.class).setLevel(Level.DEBUG);添加到localAESWork()方法里

使用简单的明文测试

1
2
3
明文用:bileton
hex:fd2bd308e45a3d24b2c715b54b92713f
base64:/SvTCORaPSSyxxW1S5JxPw==

输出

1
2
3
4
5
6
7
8
9
10
11
12

JNIEnv->NewByteArray(16) was called from RX@0x12043ae4[libcryptoDD.so]0x43ae4
JNIEnv->SetByteArrayRegion([B@0x00000000000000000000000000000000, 0, 16, RW@0x12399010) was called from RX@0x12043b08[libcryptoDD.so]0x43b08

>-----------------------------------------------------------------------------<
[11:59:10 243]SetByteArrayRegion array=[B@0x00000000000000000000000000000000, start=0, length=16, buf=RW@0x12399010, md5=f640f07e317bb73715399251206b7cdd, hex=fd2bd308e45a3d24b2c715b54b92713f
size: 16
0000: FD 2B D3 08 E4 5A 3D 24 B2 C7 15 B5 4B 92 71 3F .+...Z=$....K.q?
^-----------------------------------------------------------------------------^
[11:59:10 243] DEBUG [com.github.unidbg.linux.android.dvm.DalvikVM64] (DalvikVM64$189:3075) - ReleaseByteArrayElements arrayPointer=unidbg@0x3e92efc3, pointer=RW@0x12056000, mode=0
ret=fd2bd308e45a3d24b2c715b54b92713f
/SvTCORaPSSyxxW1S5JxPw==

根据日志看到结果是在0x12399010这个地方来的

trace 0x12399010

1
emulator.traceWrite(0x12399010,0x12399010+0x10);

这是trace下俩的数据

1
2
3
4
5
6
[12:07:18 602] Memory WRITE at 0x12399010, data size = 8, data value = 0x0000000000000000, PC=RX@0x121bc688[libc.so]0x1c688, LR=RX@0x12043970[libcryptoDD.so]0x43970
[12:07:18 603] Memory WRITE at 0x12399018, data size = 8, data value = 0x0000000000000000, PC=RX@0x121bc688[libc.so]0x1c688, LR=RX@0x12043970[libcryptoDD.so]0x43970
[12:07:18 603] Memory WRITE at 0x12399010, data size = 8, data value = 0x0000000000000000, PC=RX@0x121bc694[libc.so]0x1c694, LR=RX@0x12043970[libcryptoDD.so]0x43970
[12:07:18 603] Memory WRITE at 0x12399018, data size = 8, data value = 0x0000000000000000, PC=RX@0x121bc694[libc.so]0x1c694, LR=RX@0x12043970[libcryptoDD.so]0x43970
[12:07:18 608] Memory WRITE at 0x12399010, data size = 8, data value = 0x243d5ae408d32bfd, PC=RX@0x12018940[libcryptoDD.so]0x18940, LR=RX@0x1201892c[libcryptoDD.so]0x1892c
[12:07:18 608] Memory WRITE at 0x12399018, data size = 8, data value = 0x3f71924bb515c7b2, PC=RX@0x12018940[libcryptoDD.so]0x18940, LR=RX@0x1201892c[libcryptoDD.so]0x1892c

这里看到数据写入的位置是在0x18940,数据是以小端存储的LR寄存器的值是0x1892c

1
2
0x243d5ae408d32bfd
0x3f71924bb515c7b2

使用IDA分析0x1892c

反汇编

分析sub_189C4()

下断点打印参数

1
emulator.attach().addBreakPoint(module.base+0x189C4);

打印X0寄存器的值

如下是输入的明文

打印X1寄存器的值

如下是一段空值

根据入参的值,看它被传进了哪个函数里找到AES加密的位置

找到一个函数sub_21BEC()

它的内容比较可疑

对这个函数进行hook

1
2
3
4
5
6
7
8
9
emulator.attach().addBreakPoint(module.base + 0x21BEC, new BreakPointCallback() {
int count = 0;
@Override
public boolean onHit(Emulator<?> emulator, long address) {
count++;
System.out.println(count);
return true;
}
});

如下打印了10次

先保留一份正确的密文

1
fd2bd308e45a3d24b2c715b54b92713f

然后在第九轮的时候进行故障注入

故障注入差分

故障注入1

对这几个寄存器进行故障注入

1
2
3
4
5
6
7
8
9
10
11
12
emulator.attach().addBreakPoint(module.base + 0x21E8C, new BreakPointCallback() {
int count = 0;
@Override
public boolean onHit(Emulator<?> emulator, long address) {
count++;
if(count==9){
Random random = new Random();
emulator.getBackend().reg_write(Arm64Const.UC_ARM64_REG_W9,random.nextInt(255));
}
return true;
}
});

循环10次

1
2
3
4
5
6
7
public static void main(String[] args) {
CryptoHelper cryptoHelper = new CryptoHelper();
cryptoHelper.patchFreeByConsoleDebugger();
for (int i = 0; i < 10; i++) {
cryptoHelper.localAESWork();
}
}

循环10次,输出密文

1
2
3
4
5
6
7
8
9
10
fd2bbc08e43b3d24eec715b54b927120
fd2b2c08e48c3d246dc715b54b9271e1
fd2bb208e49f3d2496c715b54b927197
fd2b7f08e4873d24b8c715b54b9271fa
fd2b4e08e4573d24efc715b54b927118
fd2b2608e4eb3d247ec715b54b9271d2
fd2b6708e4963d2442c715b54b927180
fd2b4508e4ea3d2443c715b54b927153
fd2b1908e4c93d24e4c715b54b927166
fd2b5408e4943d246fc715b54b92717c

差分1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import phoenixAES

with open("tracefile","wb") as t:
t.write("""fd2bd308e45a3d24b2c715b54b92713f
fd2bbc08e43b3d24eec715b54b927120
fd2b2c08e48c3d246dc715b54b9271e1
fd2bb208e49f3d2496c715b54b927197
fd2b7f08e4873d24b8c715b54b9271fa
fd2b4e08e4573d24efc715b54b927118
fd2b2608e4eb3d247ec715b54b9271d2
fd2b6708e4963d2442c715b54b927180
fd2b4508e4ea3d2443c715b54b927153
fd2b1908e4c93d24e4c715b54b927166
fd2b5408e4943d246fc715b54b92717c
""".encode('utf8'))

phoenixAES.crack_file('tracefile', [], True, False, 3)

输出

1
2
Round key bytes recovered:
....ED....D2....C0............C0

故障注入2

换一个寄存器

1
2
3
4
5
6
7
8
9
10
11
12
emulator.attach().addBreakPoint(module.base + 0x21E90, new BreakPointCallback() {
int count = 0;
@Override
public boolean onHit(Emulator<?> emulator, long address) {
count++;
if(count==9){
Random random = new Random();
emulator.getBackend().reg_write(Arm64Const.UC_ARM64_REG_W10,random.nextInt(255));
}
return true;
}
});

输出

1
2
3
4
5
6
7
8
9
10
fd2bd34ae45afb24b22315b5a192713f
fd2bd375e45aae24b2b515b5b892713f
fd2bd3aae45abe24b2af15b55a92713f
fd2bd3bbe45ad424b24515b57b92713f
fd2bd38be45a6e24b2a615b56192713f
fd2bd344e45aba24b29515b5f692713f
fd2bd306e45ac324b23a15b5dd92713f
fd2bd3d5e45a4424b24a15b53792713f
fd2bd3bae45a9a24b2e615b51592713f
fd2bd33de45a4724b2a515b51692713f

差分2

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
import phoenixAES

with open("tracefile","wb") as t:
t.write("""fd2bd308e45a3d24b2c715b54b92713f
fd2bbc08e43b3d24eec715b54b927120
fd2b2c08e48c3d246dc715b54b9271e1
fd2bb208e49f3d2496c715b54b927197
fd2b7f08e4873d24b8c715b54b9271fa
fd2b4e08e4573d24efc715b54b927118
fd2b2608e4eb3d247ec715b54b9271d2
fd2b6708e4963d2442c715b54b927180
fd2b4508e4ea3d2443c715b54b927153
fd2b1908e4c93d24e4c715b54b927166
fd2b5408e4943d246fc715b54b92717c
fd2bd34ae45afb24b22315b5a192713f
fd2bd375e45aae24b2b515b5b892713f
fd2bd3aae45abe24b2af15b55a92713f
fd2bd3bbe45ad424b24515b57b92713f
fd2bd38be45a6e24b2a615b56192713f
fd2bd344e45aba24b29515b5f692713f
fd2bd306e45ac324b23a15b5dd92713f
fd2bd3d5e45a4424b24a15b53792713f
fd2bd3bae45a9a24b2e615b51592713f
fd2bd33de45a4724b2a515b51692713f
""".encode('utf8'))

phoenixAES.crack_file('tracefile', [], True, False, 3)

输出

1
2
Round key bytes recovered:
....EDFD..D27D..C0D0....4E....C0

故障注入3

修改hook地址和寄存器

输出

1
2
3
4
5
6
7
8
9
10
fa2bd308e45a3dc7b2c737b54b48713f
132bd308e45a3d96b2c7afb54b72713f
aa2bd308e45a3d7db2c798b54bee713f
7d2bd308e45a3de9b2c708b54ba6713f
252bd308e45a3dd4b2c79db54bb8713f
f92bd308e45a3d40b2c704b54b3f713f
c62bd308e45a3d60b2c782b54b0b713f
022bd308e45a3d7eb2c738b54b5d713f
5a2bd308e45a3d3db2c7b6b54bd0713f
152bd308e45a3d0db2c7c9b54bbc713f

差分3

输出

1
2
Round key bytes recovered:
A3..EDFD..D27DD1C0D010..4EFC..C0

故障注入4

1
2
3
4
5
6
7
8
9
10
fdd7d308275a3d24b2c715bf4b929d3f
fd41d308e75a3d24b2c715b14b92d73f
fdb8d308f15a3d24b2c715954b92fe3f
fd69d3089a5a3d24b2c715094b92823f
fdd4d308b85a3d24b2c7156b4b92cd3f
fd2dd308b45a3d24b2c715254b921c3f
fdd8d308035a3d24b2c715934b927d3f
fd03d308a65a3d24b2c715274b92303f
fdc0d308d35a3d24b2c715674b927b3f
fd98d3087a5a3d24b2c715eb4b92783f

差分4

1
2
Round key bytes recovered:
A3AEEDFD2DD27DD1C0D010724EFCCBC0

好了,这就是第10轮的轮密钥

由第10轮的轮密钥推算初始密钥

推出初始密钥为:32395842753445546A487830596E6643

验证下,如下加密结果正确

CyberChef中处理不了字符串的replace,如下

Python生成q

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import base64

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

data = b'{"mobile":"17888888888","countryNo":"86","validateCode":"123456","appversion":"5265","type":1,"deviceId":"android_lucky_ef5722c5-96ff-4521-8096-27530a0365b2","systemVersion":"29","blackBox":"oGPHE1741688414xYaSQkj72m7","uniqueCode":"DUQKlOhFPQSnh57_KB6erjHdTADHhPk26j60RFVRS2xPaEZQUVNuaDU3X0tCNmVyakhkVEFESGhQazI2ajYwc2h1","regionId":"CO0001","regId":"","deviceBrand":"google","isSecurityVerify":false}'
key = b"29XBu4ETjHx0YnfC"
aes_encrypted_data = aes_encrypt_ecb(data,key=key)
print(base64.b64encode(aes_encrypted_data).decode("utf-8").replace("+","-").replace("/","_"))


unidbg调用md5_crypt

1
2
3
4
5
6
7
8
9
10
11
public void md5_crypt(){
DvmClass CryptoHelperClass = vm.resolveClass("com.luckincoffee.safeboxlib.CryptoHelper");
String bArr = "cid=210101;q=Xz0R82fS485b23ba817e4JzPH0DbB-Hyhw09ZJUl9kZ4SZ_KkVh07uBqj8ejykTw1bl02vu1dt67BbIKA-1FqlTuFGh8VoJemyEkmhqnf6w9u98dKvCdpFPDiRktU-PGDtnD9Y8rBYgBHuPyfnMR15jTMaF0HV4SMw6qU_S_3p771jHSE9Kl8Yx6zKgkBv71qLhQP68VNsifnmQFbtEBdQt5hK1-9WLGzrhB1jLiL1EJTtmqGymT21ymquQHXcDuk-yQQOEVQhMvN8p_dEkD23X6nDVntT3FTC90dmMItPDL6QlQoWsRTqvlVMkUz2wSNPsApVb2hq30D4x9RSvDE3sKJBGAzFqDG_XwE6yoojDNPn2wH0H0o3-wtCso0hRJXlvPTHz_zMYdTcMNKTwccWgu0mmbH5NKcVCxrZ6R2MeCfUb8ACp7PKjZeAecaHq_REu3Kkoi3xXPPK_MU_eLl5A1MFw_d7azTSHHUFmL3RdiC15vvA1bajH1NDbLEAxMV8nUOJVfuWxXDGS0Diknyj0V95At_KBG4y7Ylo-4fv4=;t=1741699524789;uid=1f0080f8-238f-483a-be04-9a7e2ca4c0e61741688413791";
byte[] bArr2 = {-87,-114,-67,121,108,-104,-58,66,-48,15,40,31,112,-58,-81,116,73,9,-6,80,-38,87,98,16,114,-89,108,124,97,-82,52,59};
ByteArray byteValues = CryptoHelperClass.callStaticJniMethodObject(emulator, "md5_crypt([BI[B)[B",
bArr.getBytes(),
1,
bArr2);
String ret = new String(byteValues.getValue());
System.out.println(ret);
}

输出并打印详细日志

1
2
3
4
5
6
7
8
9
10
JNIEnv->SetByteArrayRegion([B@0x0000000000000000000000000000000000000000000000000000000000000000000000000000, 0, 38, RW@0x12393030) was called from RX@0x120441a8[libcryptoDD.so]0x441a8
>-----------------------------------------------------------------------------<
[21:08:40 131]SetByteArrayRegion array=[B@0x0000000000000000000000000000000000000000000000000000000000000000000000000000, start=0, length=38, buf=RW@0x12393030, md5=05dca414c8058fff5156a7ec4d10303f, hex=3135373236313433313135323736383536313235353430303538333131343137313330353532
size: 38
0000: 31 35 37 32 36 31 34 33 31 31 35 32 37 36 38 35 1572614311527685
0010: 36 31 32 35 35 34 30 30 35 38 33 31 31 34 31 37 6125540058311417
0020: 31 33 30 35 35 32 130552
^-----------------------------------------------------------------------------^
[21:08:40 132] DEBUG [com.github.unidbg.linux.android.dvm.DalvikVM64] (DalvikVM64$189:3075) - ReleaseByteArrayElements arrayPointer=unidbg@0x659a969b, pointer=RW@0x12056000, mode=0
15726143115276856125540058311417130552

使用简单明文参数bileton

1
76453193612323879985881527471282403340

根据可以看出数据来源于0x441a8这个地址处,到IDA里找到这个地址

到IDA里看没发现什么

反汇编后,看传入的明文参数的传递过程,到了sub_4095C()

通过断点调试查看参数,发现数据后面加了盐29XBu4ETjHx0YnfC

md5

这里后面分析不下来了,看了别人的文章了解到对md5的结果又进行了处理,这里直接把算法搬过来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def getsign():
res = ''
for i in range(4):
encode_str = 'd26e2b204974bf9edcf18055b39013f4'
i*=4
result = (int(encode_str[(i+1)*2:(i+1)*2+2],16)<<16) | (int(encode_str[i*2:i*2+2],16)<<24) | (int(encode_str[(i+2)*2:(i+2)*2+2],16)<<8) | int(encode_str[(i+3)*2:(i+3)*2+2],16)
result &= 0xffffffff
if result & 0x80000000:
result = -((result ^ 0xffffffff) + 1)
if result<0:
result = -result
res+=str(result)
print(res)
getsign()

# 76453193612323879985881527471282403340

Python实现md5_crypt

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

def md5_call(encoded_data):
md5 = hashlib.md5()
md5.update(encoded_data)
return md5.digest()

def getsign(encode_str):
res = ''
for i in range(4):
i*=4
result = (int(encode_str[(i+1)*2:(i+1)*2+2],16)<<16) | (int(encode_str[i*2:i*2+2],16)<<24) | (int(encode_str[(i+2)*2:(i+2)*2+2],16)<<8) | int(encode_str[(i+3)*2:(i+3)*2+2],16)
result &= 0xffffffff
if result & 0x80000000:
result = -((result ^ 0xffffffff) + 1)
if result<0:
result = -result
res+=str(result)
return res

data = "bileton"
salt = "29XBu4ETjHx0YnfC"
md5 = bytes.hex(md5_call((data+salt).encode("utf-8")))
print(md5)
print(getsign(md5))

# 76453193612323879985881527471282403340

Python实现响应体解密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import base64

def aes_decrypted_ecb(encrypted_data,key):
cipher = AES.new(key,AES.MODE_ECB)
decrypted_data = cipher.decrypt(encrypted_data)
unpadded_data = unpad(decrypted_data,AES.block_size)
return unpadded_data

def toStandardB64(data):
return data.replace("-","+").replace("_","/")


ciphertext = "nMymqvoWI5UzuGSsfOyxWS9E0JQYtcev4A_O1LXSr_HosyYfsqRmenMCcYf7-mdZjZ8LUfNc6HbRD89pGuGxJhYP475VhWKSXmkD7eAkTSmxA1MpUjQQHOjueGL1F_YWGExcwjd3N-sOHxEfeB59eTF1vGH9C_S6Q5TrXqrOg1R0yv5lrZ0CUyVHr38KtyytHyv4ip5jmin3q-HoLrMNZgkywRFi2RKOdxa8F-I1PLBhIOxZmtNm7IBiSVPvc8q2fnKJLbxMfM680Zr9YwRrxe_XcuzE1YLdSXTc2cmy5cq8M0xQ12eMbJo1sZhGPGs7"

standardb64 = toStandardB64(ciphertext).encode("utf-8")
plaintext = aes_decrypted_ecb(base64.b64decode(standardb64),b"29XBu4ETjHx0YnfC")
print(plaintext)

输出

1
{"busiCode":"BASE001","code":7,"content":null,"handler":"USER","msg":"验证码无效","status":"BASE_ERROR","uid":"1f0080f8-238f-483a-be04-9a7e2ca4c0e61741688413791","version":"101","zeusId":"luckycapiproxy-0ade0957-483805-214303"}

整合代码,实现完整流程

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
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import base64
import hashlib
import requests

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 aes_decrypted_ecb(encrypted_data,key):
cipher = AES.new(key,AES.MODE_ECB)
decrypted_data = cipher.decrypt(encrypted_data)
unpadded_data = unpad(decrypted_data,AES.block_size)
return unpadded_data

def toStandardB64(data):
return data.replace("-","+").replace("_","/")

def md5_call(encoded_data):
md5 = hashlib.md5()
md5.update(encoded_data)
return md5.digest()

def getsign(encode_str):
res = ''
for i in range(4):
i*=4
result = (int(encode_str[(i+1)*2:(i+1)*2+2],16)<<16) | (int(encode_str[i*2:i*2+2],16)<<24) | (int(encode_str[(i+2)*2:(i+2)*2+2],16)<<8) | int(encode_str[(i+3)*2:(i+3)*2+2],16)
result &= 0xffffffff
if result & 0x80000000:
result = -((result ^ 0xffffffff) + 1)
if result<0:
result = -result
res+=str(result)
return res

def g_q():
data = b'{"mobile":"17888888888","countryNo":"86","validateCode":"123456","appversion":"5265","type":1,"deviceId":"android_lucky_ef5722c5-96ff-4521-8096-27530a0365b2","systemVersion":"29","blackBox":"oGPHE1741688414xYaSQkj72m7","uniqueCode":"DUQKlOhFPQSnh57_KB6erjHdTADHhPk26j60RFVRS2xPaEZQUVNuaDU3X0tCNmVyakhkVEFESGhQazI2ajYwc2h1","regionId":"CO0001","regId":"","deviceBrand":"google","isSecurityVerify":false}'
key = b"29XBu4ETjHx0YnfC"
aes_encrypted_data = aes_encrypt_ecb(data,key=key)
return base64.b64encode(aes_encrypted_data).decode("utf-8").replace("+","-").replace("/","_")

def decrypt_response(ciphertext):
standardb64 = toStandardB64(ciphertext).encode("utf-8")
plaintext = aes_decrypted_ecb(base64.b64decode(standardb64),b"29XBu4ETjHx0YnfC")
return plaintext.decode("utf-8")




url = "https://capi.lkcoffee.com/resource/m/user/login"

headers = {
"x-lk-akv": "5265",
"x-lk-mid": "",
"x-lk-sid": "",
"event_id": "1741773373890",
"x-lk-csid": "b23ee6cb-c0f2-4e43-9580-727ce47f7b73",
"sentry-trace": "5d4028a1977c401da7dc5299c9fe942f-11ad2f67d12f4343-0",
"baggage": "sentry-environment=release,sentry-public_key=d4365939f7c346a4b0c6ee50a412c050,sentry-release=com.lucky.luckyclient%405.2.65%2B201,sentry-sample_rate=0,sentry-trace_id=5d4028a1977c401da7dc5299c9fe942f,sentry-transaction=LoginActivity.btnConfirm",
"content-type": "application/x-www-form-urlencoded",
"content-length": "684",
"accept-encoding": "gzip",
"cookie": "uid=ede6c26f-85eb-4f41-b103-051605ae1c821741773364713",
"user-agent": "okhttp/4.9.3"
}

body = {
# "sign": "7964953862091852252935269275418960748",
# "q": "-YqmJeyS17YrIsczlOeETZzPH0DbB-Hyhw09ZJUl9kZ4SZ_KkVh07uBqj8ejykTw1bl02vu1dt67BbIKA-1FqlTuFGh8VoJemyEkmhqnf6w9u98dKvCdpFPDiRktU-PGDtnD9Y8rBYgBHuPyfnMR15jTMaF0HV4SMw6qU_S_3p771jHSE9Kl8Yx6zKgkBv71qLhQP68VNsifnmQFbtEBdQt5hK1-9WLGzrhB1jLiL1EJTtmqGymT21ymquQHXcDudPQ7clgzz0Xzjt_NO-L5mbbklzohRwsDO0eQtHxD58zL6QlQoWsRTqvlVMkUz2wSNPsApVb2hq30D4x9RSvDE3sKJBGAzFqDG_XwE6yoojDNPn2wH0H0o3-wtCso0hRJXlvPTHz_zMYdTcMNKTwccWgu0mmbH5NKcVCxrZ6R2MeCfUb8ACp7PKjZeAecaHq_REu3Kkoi3xXPPK_MU_eLl5A1MFw_d7azTSHHUFmL3RdiC15vvA1bajH1NDbLEAxMV8nUOJVfuWxXDGS0Diknyj0V95At_KBG4y7Ylo-4fv4=",
"q" : g_q(),
"uid": "ede6c26f-85eb-4f41-b103-051605ae1c821741773364713",
"t": "1741773407429",
"cid": "210101"
}

body_sorted = dict(sorted(body.items()))

sign_data = ";".join([key+"="+value for key,value in body_sorted.items()])
salt = "29XBu4ETjHx0YnfC"
sign = getsign(bytes.hex(md5_call((sign_data+salt).encode("utf-8"))))

body["sign"] = sign
print(sign)

response = requests.post(url=url,headers=headers,data=body)
print(response.status_code)
print(response.text)

ciphertext = response.text
# ciphertext = "YpEwA8iL58QXeR-ZSZ3A7UAYW87GDqYNVVxccRJRXdrchLKJaUKgoMkCqriRjHJ4SHuZfPrwvOsq-38HXEl4EAM3FqOfGIeNuJ5q6Fu9OmWzWwpD4jYobI1KM2-6q8tRPbAQDw1nckcu6dY1NyzcLI_W1CV81Vyr7bEk33zBS_Qe5ayui3QUeI1r0cKnuzY2X7er31heAHd1jrRAX_rcylBkDe_EvXgcKWMJ8jw_KvXopUOKdAlZAOXaPmRvqP-XXFnmJ1md6AKmhZ4-64eo_iwXkOtjLv3PQHpr3CJr93C3kQIzAKmAR79hII_4WPcITE-Pb3x6ip6QuAEywIYn1_EZrhdvspv6kTdwrzn5kkA="
print(decrypt_response(ciphertext))

输出

1
{"bizId":"","busiCode":"BASE001","code":7,"content":null,"handler":"USER","msg":"验证码无效","shardingId":"","status":"BASE_ERROR","uid":"6e1c943b-cc1f-41ea-8ebd-b5d06ddae5c11741928800889","version":"101","zeusId":"luckycapiproxy-0ade7194-483869-687159"}