app版本:4.54 抓包工具:Charles 反汇编工具:JEB、JADX inject:frida 查壳:360加固
POST: /api/user/login HTTP/1.1 Content-Type: application/json; charset=utf-8 User-Agent: Dalvik/2.1.0 (Linux; U; Android 8.1.0; Pixel 2 XL Build/OPM4.171019.021.R1) Host: api.dodovip.com Accept-Encoding: gzip Content-Length: 262 Connection: keep-alive
{"Encrypt":"NIszaqFPos1vd0pFqKlB42Np5itPxaNH//FDsRnlBfgL4lcVxjXii+GOZz1l+A5V9FPOSMf47jbE\n010Kk+PbNyEDRjj1zY76jXa7VyHLkjxpqsrJYht6LX1PcVabK8oBp/fiOE4l2lC5JVjqx/JI7CJm\neUXVXkgJ6rgPne3WCJUYU+ztDNEi+mvECeOktUk0KxqBbPzuJj3LKsW5Ux080rWm4NZWHxPFbZYl\nIs2IRcs=\n"}
2v+DC2gq7RuAC8PE5GZz5wH3/y9ZVcWhFwhDY9L19g9iEd075+Q7xwewvfIN0g0ec/NaaF43/S0=
多次抓包仅 Encrypt 参数变化,需要分析的就是它了。
对脱壳流程有不明白的可参考我之前写的文章:[原创]ART环境下dex加载流程分析及frida dump dex方案 。 上脚本,手机端启动fs后执行即可,脱壳的dex会在/data/data/com.dodonew.online目录下:
将脱壳后的dex推出: 其中第一个为加壳程序; 第二个为IjkMediaPlayer和rx库,IjkMediaPlayer是基于FFmpeg的Android多媒体播放器库,大佬们可自行百度了解; 第三个为应用程序界面信息dex; 第四个为应用程序逻辑代码。 既然是分析登陆逻辑,那肯定是在第四个dex中分析啦!
jadx每次生成的参数名称会有所出入,各位在对照这这份教程进行分析的时候只需把握整体步骤即可。
将第四个文件拖入jadx等待加载完成,搜 "Encrypt" 结果还挺多: 挺好定位 com.dodonew.online.http.JsonRequest 类中存在 addRequestMap(Map<String, String>, int) void 方法和 paraMap(Map<String, String>) void 方法, 两方法中都有进行参数存放操作。 第一个方法 addRequestMap 翻译以下:添加请求的 Map,可疑,跟进去看看:
看这两句代码:
第一句中生成的encodeDesMap就是Encrypt,入口点定位无误。
继续分析addRequestMap函数代码,看代码:
获取时间戳,然后将时间戳添加进 Map 中,再调用:
跟进RequestUtil.paraMap函数看看:
首先将 Map 中的键提取出来存入 Set 中,再定义一个 List 集合用来存放键值信息,and 进行 sort 排序, 其中有处:sb.append("key=" + str); str是入参参数二,向上跟一下是个固定值:
经过一系列操作完后对值进行 md5,md5 得到的值就是 sign 的值,hook 看看那些值需进行 md5:
hook 结果:
md5 is called, string: equtype=ANDROID&loginImei=Androidc0b30f35fc9535b5&timeStamp=1687772161410&userPwd=12334&username=123456789&k ey=sdlkjsdljf0j2fsjk md5 ret value is e888bef28d91b42fc10cf91540ec057b
试着 python 还原下看看是不是标准 md5 算法:
结果:e888bef28d91b42fc10cf91540ec057b,对照一致,标准md5算法。
5.3 des 加密算法分析 继续分析addRequestMap函数代码,看代码:
其中this.desKey, this.desIV,猜测为des算法,先hook看看数据,hook代码:
hook 结果:
encodeDesMap is called, data: {"equtype":"ANDROID","loginImei":"Androidc0b30f35fc9535b5","sign":"0FAFB81829C15EF86EBD30E214675BBC", "timeStamp":"1687772424834","userPwd":"12334","username":"123456789"}, desKey: 65102933, desIV: 32028092 encodeDesMap ret value is NIszaqFPos1vd0pFqKlB42Np5itPxaNH//FDsRnlBfgL4lcVxjXii+GOZz1l+A5V9FPOSMf47jbE 010Kk+PbN/jjSVvUEnMkBeVQY2tdy+to9cUXg0XyzdSi3Wehubi6R5t5NLiRanFipatR61mx4ISH B/wjHUkmAFDl2b3zZIYs2UMZhz4YfC4HgFeRqA/9X1+m1LNZQYUkOLl/HqD5GFDgdRel9stq/g+8 ZB8fY84=
在此吃了个亏,直接用 hook 出来的 desKey、desIV 进行加密,怎么搞都不对,后面发现它还进行了操作,还是太年轻了。跟进 encodeDesMap 方法查看:
先调用 DesSecurity(desKey, desIV); 对 desKey、desIV 进行操作,跟进看看:
查看其构造方法,调用 InitCipher 方法对 desKey、desIV 进行操作:
对 desKey 进行了 MD5 加密,然后才传进去进行 DES 加密,加密模式 CBC 填充方式 PKCS5Padding。再看:
对加密后的数据又进行了一次 Base64 编码,这回清楚了,再进行还原:
执行结果:
NIszaqFPos1vd0pFqKlB42Np5itPxaNH//FDsRnlBfgL4lcVxjXii+GOZz1l+A5V9FPOSMf47jbE010Kk+PbN/jjSVvUEnMkBeVQY2tdy+to9cUXg0XyzdSi3Wehubi6R5t5NLiRanFipatR61mx4ISHB/wjHUkmAFDl2b3zZIYs2UMZhz4YfC4HgFeRqA/9X1+m1LNZQYUkOLl/HqD5GFDgdRel9stq/g+8ZB8fY84=
对照其hook结果一直,还原成功,至此整个协议就分析完成了,Encrypt数据也成功拿到,接下来就是模拟请求了。
前面该分析的也都分析好了,写代码这种事情相信各位佬随手拈来,我就不在讲解了,直接上代码,是在不明白,代码中的注释也很全:
结果,与抓包结果一致,返回数据还是加密的:
2v+DC2gq7RuAC8PE5GZz5wH3/y9ZVcWhFwhDY9L19g9iEd075+Q7xwewvfIN0g0ec/NaaF43/S0=
对于返回结果是密文也是预料之中的,des 为比较早期的对称加密算法,加密与解密就是一个对称的过程。 请求是 addRequestMap 有 request 那么就会有 response,而且这个方法就在我们找到的 addRequestMap 上方:
留意:
hook 它看看:
结果:
decodeDesJson is called, json: 2v+DC2gq7RuAC8PE5GZz5wH3/y9ZVcWhFwhDY9L19g9iEd075+Q7xwewvfIN0g0ec/NaaF43/S0=, desKey: 65102933, desIV: 32028092 decodeDesJson ret value is {"code":-1,"message":"账号或密码错误","data":{}}
因为我在这给的账号和密码本就是错误的,所以提示账号或密码错误一点问题没有。 至此完结。
function find_hook_fun() {
var fun_Name
=
"";
var libart
=
Module.findBaseAddress(
'libart.so'
);
/
/
查找基地址
var exports
=
Module.enumerateExportsSync(
"libart.so"
);
for
(var i
=
0
; i<exports.length; i
+
+
){
if
(exports[i].name.indexOf(
"OpenMemory"
) !
=
=
-
1
){
fun_Name
=
exports[i].name;
console.log(
"导出模块名: "
+
exports[i].name
+
"\t\t偏移地址: "
+
(exports[i].address
-
libart
-
1
));
break
;
}
else
if
(exports[i].name.indexOf(
"OpenCommon"
) !
=
=
-
1
){
fun_Name
=
exports[i].name;
console.log(
"导出模块名: "
+
exports[i].name
+
"\t\t偏移地址: "
+
(exports[i].address
-
libart
-
1
));
break
;
}
}
return
fun_Name;
}
function DexFileVerifier(Verify){
var magic_03x
=
true;
var magic_Hex
=
[
0x64
,
0x65
,
0x78
,
0x0a
,
0x30
,
0x33
,
0x35
,
0x00
];
for
(var i
=
0
; i <
8
; i
+
+
){
if
(Memory.readU8(ptr(Verify).add(i)) !
=
=
magic_Hex[i]){
if
(Memory.readU8(ptr(Verify).add(i))
=
=
=
0x37
||
0x38
){
console.log(
'new dex'
);
}
else
{
magic_03x
=
false;
break
;
}
}
}
return
magic_03x;
}
function dump_Dex(fun_Name, apk_Name){
if
(fun_Name !
=
=
''){
var hook_fun
=
Module.findExportByName(
"libart.so"
, fun_Name);
Interceptor.attach(hook_fun, {
onEnter: function (args) {
var begin
=
0
;
var dex_flag
=
false;
dex_flag
=
DexFileVerifier(args[
0
]);
if
(dex_flag
=
=
=
true){
begin
=
args[
0
];
}
if
(begin
=
=
=
0
){
dex_flag
=
DexFileVerifier(args[
1
]);
if
(dex_flag
=
=
=
true){
begin
=
args[
1
];
}
}
if
(dex_flag
=
=
=
true){
console.log(
"magic : "
+
Memory.readUtf8String(begin));
var address
=
parseInt(begin,
16
)
+
0x20
;
var dex_size
=
Memory.readInt(ptr(address));
console.log(
"dex_size :"
+
dex_size);
var dex_path
=
"/data/data/"
+
apk_Name
+
"/"
+
dex_size
+
".dex"
;
var dex_file
=
new
File
(dex_path,
"wb"
);
dex_file.write(Memory.readByteArray(begin, dex_size));
dex_file.flush();
dex_file.close();
}
},
onLeave: function (retval) {
}
});
}
else
{
console.log(
"Error: no hook function."
);
}
}
var fun_Name
=
find_hook_fun();
var apk_Name
=
'com.dodonew.online'
dump_Dex(fun_Name, apk_Name);
/
/
frida
-
U
-
f com.dodonew.online
-
l dumpdex.js
-
-
no
-
pause
function find_hook_fun() {
var fun_Name
=
"";
var libart
=
Module.findBaseAddress(
'libart.so'
);
/
/
查找基地址
var exports
=
Module.enumerateExportsSync(
"libart.so"
);
for
(var i
=
0
; i<exports.length; i
+
+
){
if
(exports[i].name.indexOf(
"OpenMemory"
) !
=
=
-
1
){
fun_Name
=
exports[i].name;
console.log(
"导出模块名: "
+
exports[i].name
+
"\t\t偏移地址: "
+
(exports[i].address
-
libart
-
1
));
break
;
}
else
if
(exports[i].name.indexOf(
"OpenCommon"
) !
=
=
-
1
){
fun_Name
=
exports[i].name;
console.log(
"导出模块名: "
+
exports[i].name
+
"\t\t偏移地址: "
+
(exports[i].address
-
libart
-
1
));
break
;
}
}
return
fun_Name;
}
function DexFileVerifier(Verify){
var magic_03x
=
true;
var magic_Hex
=
[
0x64
,
0x65
,
0x78
,
0x0a
,
0x30
,
0x33
,
0x35
,
0x00
];
for
(var i
=
0
; i <
8
; i
+
+
){
if
(Memory.readU8(ptr(Verify).add(i)) !
=
=
magic_Hex[i]){
if
(Memory.readU8(ptr(Verify).add(i))
=
=
=
0x37
||
0x38
){
console.log(
'new dex'
);
}
else
{
magic_03x
=
false;
break
;
}
}
}
return
magic_03x;
}
function dump_Dex(fun_Name, apk_Name){
if
(fun_Name !
=
=
''){
var hook_fun
=
Module.findExportByName(
"libart.so"
, fun_Name);
Interceptor.attach(hook_fun, {
onEnter: function (args) {
var begin
=
0
;
var dex_flag
=
false;
dex_flag
=
DexFileVerifier(args[
0
]);
if
(dex_flag
=
=
=
true){
begin
=
args[
0
];
}
if
(begin
=
=
=
0
){
dex_flag
=
DexFileVerifier(args[
1
]);
if
(dex_flag
=
=
=
true){
begin
=
args[
1
];
}
}
if
(dex_flag
=
=
=
true){
console.log(
"magic : "
+
Memory.readUtf8String(begin));
var address
=
parseInt(begin,
16
)
+
0x20
;
var dex_size
=
Memory.readInt(ptr(address));
console.log(
"dex_size :"
+
dex_size);
var dex_path
=
"/data/data/"
+
apk_Name
+
"/"
+
dex_size
+
".dex"
;
var dex_file
=
new
File
(dex_path,
"wb"
);
dex_file.write(Memory.readByteArray(begin, dex_size));
dex_file.flush();
dex_file.close();
}
},
onLeave: function (retval) {
}
});
}
else
{
console.log(
"Error: no hook function."
);
}
}
var fun_Name
=
find_hook_fun();
var apk_Name
=
'com.dodonew.online'
dump_Dex(fun_Name, apk_Name);
/
/
frida
-
U
-
f com.dodonew.online
-
l dumpdex.js
-
-
no
-
pause
public void addRequestMap(
Map
<String, String>
map
,
int
i) {
String
str
=
System.currentTimeMillis()
+
"";
if
(
map
=
=
null) {
map
=
new HashMap<>();
}
map
.put(
"timeStamp"
,
str
);
String encodeDesMap
=
RequestUtil.encodeDesMap(RequestUtil.paraMap(
map
, Config.BASE_APPEND,
"sign"
), this.desKey, this.desIV);
JSONObject jSONObject
=
new JSONObject();
try
{
jSONObject.put(
"Encrypt"
, encodeDesMap);
this.mRequestBody
=
jSONObject
+
"";
} catch (JSONException e) {
e.printStackTrace();
}
}
public void addRequestMap(
Map
<String, String>
map
,
int
i) {
String
str
=
System.currentTimeMillis()
+
"";
if
(
map
=
=
null) {
map
=
new HashMap<>();
}
map
.put(
"timeStamp"
,
str
);
String encodeDesMap
=
RequestUtil.encodeDesMap(RequestUtil.paraMap(
map
, Config.BASE_APPEND,
"sign"
), this.desKey, this.desIV);
JSONObject jSONObject
=
new JSONObject();
try
{
jSONObject.put(
"Encrypt"
, encodeDesMap);
this.mRequestBody
=
jSONObject
+
"";
} catch (JSONException e) {
e.printStackTrace();
}
}
String encodeDesMap
=
RequestUtil.encodeDesMap(RequestUtil.paraMap(
map
, Config.BASE_APPEND,
"sign"
), this.desKey, this.desIV);
jSONObject.put(
"Encrypt"
, encodeDesMap);
String encodeDesMap
=
RequestUtil.encodeDesMap(RequestUtil.paraMap(
map
, Config.BASE_APPEND,
"sign"
), this.desKey, this.desIV);
jSONObject.put(
"Encrypt"
, encodeDesMap);
String
str
=
System.currentTimeMillis()
+
"";
map
.put(
"timeStamp"
,
str
);
String
str
=
System.currentTimeMillis()
+
"";
map
.put(
"timeStamp"
,
str
);
RequestUtil.paraMap(
map
, Config.BASE_APPEND,
"sign"
);
RequestUtil.paraMap(
map
, Config.BASE_APPEND,
"sign"
);
public static String paraMap(
Map
<String, String>
map
, String
str
, String str2) {
try
{
Set
<String> keySet
=
map
.keySet();
StringBuilder sb
=
new StringBuilder();
ArrayList arrayList
=
new ArrayList();
for
(String str3 : keySet) {
arrayList.add(str3
+
"="
+
map
.get(str3));
}
Collections.sort(arrayList);
for
(
int
i
=
0
; i < arrayList.size(); i
+
+
) {
sb.append((String) arrayList.get(i));
sb.append(
"&"
);
}
sb.append(
"key="
+
str
);
map
.put(str2, Utils.md5(sb.toString()).toUpperCase());
String json
=
new GsonBuilder().serializeNulls().create().toJson(sortMapByKey(
map
));
Log.w(AppConfig.DEBUG_TAG, json
+
" result"
);
return
json;
} catch (Exception e) {
e.printStackTrace();
return
"";
}
}
public static String paraMap(
Map
<String, String>
map
, String
str
, String str2) {
try
{
Set
<String> keySet
=
map
.keySet();
StringBuilder sb
=
new StringBuilder();
ArrayList arrayList
=
new ArrayList();
for
(String str3 : keySet) {
arrayList.add(str3
+
"="
+
map
.get(str3));
}
Collections.sort(arrayList);
for
(
int
i
=
0
; i < arrayList.size(); i
+
+
) {
sb.append((String) arrayList.get(i));
sb.append(
"&"
);
}
sb.append(
"key="
+
str
);
map
.put(str2, Utils.md5(sb.toString()).toUpperCase());
String json
=
new GsonBuilder().serializeNulls().create().toJson(sortMapByKey(
map
));
Log.w(AppConfig.DEBUG_TAG, json
+
" result"
);
return
json;
} catch (Exception e) {
e.printStackTrace();
return
"";
}
}
public static final String BASE_APPEND
=
"sdlkjsdljf0j2fsjk"
;
public static final String BASE_APPEND
=
"sdlkjsdljf0j2fsjk"
;
function main() {
Java.perform(function () {
var Utils
=
Java.use(
"com.dodonew.online.util.Utils"
);
Utils[
"md5"
].implementation
=
function (string) {
console.log(
'md5 is called'
+
', '
+
'string: '
+
string);
var ret
=
this.md5(string);
console.log(
'md5 ret value is '
+
ret);
return
ret;
};
});
}
setImmediate(main)
function main() {
Java.perform(function () {
var Utils
=
Java.use(
"com.dodonew.online.util.Utils"
);
Utils[
"md5"
].implementation
=
function (string) {
console.log(
'md5 is called'
+
', '
+
'string: '
+
string);
var ret
=
this.md5(string);
console.log(
'md5 ret value is '
+
ret);
return
ret;
};
});
}
setImmediate(main)
from
hashlib
import
md5
def
get_encode_mes(mes):
new_md5
=
md5()
new_md5.update(mes.encode(encoding
=
'utf-8'
))
return
new_md5.hexdigest()
if
__name__
=
=
'__main__'
:
print
(get_encode_mes('equtype
=
ANDROID&loginImei
=
Androidc0b30f35fc9535b5&timeStamp
=
1687772161410
&userPwd
=
12334
&username
=
123456789
&k
ey
=
sdlkjsdljf0j2fsjk'))
from
hashlib
import
md5
def
get_encode_mes(mes):
new_md5
=
md5()
new_md5.update(mes.encode(encoding
=
'utf-8'
))
return
new_md5.hexdigest()
if
__name__
=
=
'__main__'
:
print
(get_encode_mes('equtype
=
ANDROID&loginImei
=
Androidc0b30f35fc9535b5&timeStamp
=
1687772161410
&userPwd
=
12334
&username
=
123456789
&k
ey
=
sdlkjsdljf0j2fsjk'))
String encodeDesMap
=
RequestUtil.encodeDesMap(RequestUtil.paraMap(
map
, Config.BASE_APPEND,
"sign"
), this.desKey, this.desIV);
String encodeDesMap
=
RequestUtil.encodeDesMap(RequestUtil.paraMap(
map
, Config.BASE_APPEND,
"sign"
), this.desKey, this.desIV);
function main() {
Java.perform(function () {
var RequestUtil
=
Java.use(
"com.dodonew.online.http.RequestUtil"
);
RequestUtil[
"encodeDesMap"
].overload(
'java.lang.String'
,
'java.lang.String'
,
'java.lang.String'
).implementation
=
function (data, desKey, desIV) {
console.log(
'encodeDesMap is called'
+
', '
+
'data: '
+
data
+
', '
+
'desKey: '
+
desKey
+
', '
+
'desIV: '
+
desIV);
var ret
=
this.encodeDesMap(data, desKey, desIV);
console.log(
'encodeDesMap ret value is '
+
ret);
return
ret;
};
});
}
setImmediate(main)
function main() {
Java.perform(function () {
var RequestUtil
=
Java.use(
"com.dodonew.online.http.RequestUtil"
);
RequestUtil[
"encodeDesMap"
].overload(
'java.lang.String'
,
'java.lang.String'
,
'java.lang.String'
).implementation
=
function (data, desKey, desIV) {
console.log(
'encodeDesMap is called'
+
', '
+
'data: '
+
data
+
', '
+
'desKey: '
+
desKey
+
', '
+
'desIV: '
+
desIV);
var ret
=
this.encodeDesMap(data, desKey, desIV);
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2023-6-27 10:19
被行简编辑
,原因: