算法复现:

md5部分

首先是调用,就直接是调用大神写的Crypto了(全当python的库文件了)

首先复现一下md5的加密算法

md5的加密之前数值是: equtype=ANDROID&loginImei=Androidnull&timeStamp=1699870290703&userPwd=woshimima&username=11112222333&key=sdlkjsdljf0j2fsjk
md5加密之后的数值是: f47d70a0d19de14449c66eada93469af

先看一下jadx的源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public void addRequestMap(Map<String, String> addMap, int a) {
String time = System.currentTimeMillis() + "";
if (addMap == null) {
addMap = new HashMap<>();
}
addMap.put("timeStamp", time);
String code = RequestUtil.paraMap(addMap, Config.BASE_APPEND, "sign");
String encrypt = RequestUtil.encodeDesMap(code, this.desKey, this.desIV);
JSONObject obj = new JSONObject();
try {
obj.put("Encrypt", encrypt);
this.mRequestBody = obj + "";
} catch (JSONException e) {
e.printStackTrace();
}
}

想要复现 RequestUtil.encodeDesMap(code, this.desKey, this.desIV)这个方法话,要先获取三个传入的参数

code:

1
code = RequestUtil.paraMap(addMap, Config.BASE_APPEND, "sign");

这个是code的值的来源

先获取sign

找到了这个函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static String paraMap(Map<String, String> addMap, String append, String sign) {
try {
Set<String> keyset = addMap.keySet();
StringBuilder builder = new StringBuilder();
List<String> list = new ArrayList<>();
for (String keyName : keyset) {
list.add(keyName + "=" + addMap.get(keyName));
}
Collections.sort(list);
for (int i = 0; i < list.size(); i++) {
builder.append(list.get(i));
builder.append("&");
}
builder.append("key=" + append);
String checkCode = Utils.md5(builder.toString()).toUpperCase();
addMap.put("sign", checkCode);
String result = new Gson().toJson(sortMapByKey(addMap));
Log.w(AppConfig.DEBUG_TAG, result + " result");
return result;
} catch (Exception e) {
e.printStackTrace();
return "";
}
}

可以看到,这个函数大概就是获取username和passwad(猜的,因为之前hook的时候,hook过 函数的两个参数,然后这里的 paraMap应该是一个表,有对应的账号和密码,之前hook过的

以下是复现md5部分的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//md5的加密之前数值是: equtype=ANDROID&loginImei=Androidnull&timeStamp=1699870290703&userPwd=woshimima&username=11112222333&key=sdlkjsdljf0j2fsjk
//md5加密之后的数值是: f47d70a0d19de14449c66eada93469af
var CryptoJS =module.exports;
//将CryptoJS模块导出为CryptoJS对象,以便其他文件可以使用require语句将其引入,并通过CryptoJS对象访问CryptoJS模块中的功能和方法。
function getsign(user,pwd,time){
var data = "equtype=ANDROID&loginImei=Androidnull&timeStamp=" + time + "&userPwd="+ pwd + "&username=" + user + "&key=sdlkjsdljf0j2fsjk";
//加在变量值data,用于进行加密
return CryptoJS.MD5(data).toString();//调用函数里的toString()方法,转化成字符串便于输出
//调用crypto中的md5加密,对data的数据进行加密并且将结果返回
}
var md5_data = getsign("11112222333","woshimima","1699870290703");
console.log(md5_data);
//这两段代码就是根据我之前hook的结果,然后把输入再加到函数里面,然后再进行加密,判断一下和hook的数值是否一样
//很显然,是一样的 输出:f47d70a0d19de14449c66eada93469af

des部分

之前也分析过,就找data、sign、iv、key就行。具体可以看代码的注释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function  getdata(user,pwd)
{
var time = new Date().getTime();
//首先定义getdata的函数,然后时间戳的话可以用Date的内置函数getTime();就行
sign = getsign(user,pwd,time).toUpperCase();
var data = '{"equtype":"ANDROID","loginImei":"Androidnull","sign":"' + sign + ',"timeStamp":"' + time + '","userPwd":"' + pwd + '","username":"' +user + '"}';
//首先是先用刚刚定义的getsign();函数计算出sign的数值,然后再给他加到我们的data中用于des加密
//注意一定要把data的定义写对了,双括号内可以用单括号
var key_md5 = CryptoJS.MD5("65102933").toString();
var deskey = CryptoJS.enc.Hex.parse(key_md5);
//这里的key是需要先经过md5加密,然后再转化成16进制编码的形式
var desiv = CryptoJS.enc.Utf8.parse("32028092");
//iv则是utf-8编码,两种编码格式是不同的,在使用的时候要先解码
return CryptoJS.DES.encrypt ( data , deskey , { iv: desiv, mode: CryptoJS.mode.CBC , padding:CryptoJS.pad.Pkcs7}).toString();
//这个就是调用了des的加密代码,并且要传入要加密的数据和key,然后还有des加密的模式为cdc模式和填充方式为Pkcs7,然后结果变为字符串作为返回值
}

然后果然还是出事了

image-20231115165521214

什么??!两次的加密结果竟然不一样,离谱,真的不知道哪里错了….

哎,原来是时间戳的问题,我写的代码是根据现实代码生成的,但是那个数据不是…..

image-20231115170219917

image-20231115170256819

略微给time赋值成固定的数值后,尝试一下

image-20231115170603369

寄,时间戳也改了,也还是不一样

算了,还是重新hook重新改一下参数吧!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[MI 9 Transparent Edition::嘟嘟牛在线]-> 参数是: [object Object] 0
账号是: 11112222333
密码是: woshimima
md5的加密之前数值是: equtype=ANDROID&loginImei=Androidnull&timeStamp=1700039457097&userPwd=woshimima&username=11112222333&key=sdlkjsdljf0j2fsjk
md5加密之后的数值是: e1fd2763045255154a0e0555c22acd3a
勾到了类: <class: com.dodonew.online.http.RequestUtil>
data: {"equtype":"ANDROID","loginImei":"Androidnull","sign":"E1FD2763045255154A0E0555C22ACD3A","timeStamp":"1700039457097","userPwd":"woshimima","username":"11112222333"}
deskey: 65102933
desiv: 32028092
构造函数的输入是: w9JtyoYll4K9kYZIH4kq5g==

加密结果是: NIszaqFPos1vd0pFqKlB42Np5itPxaNH//FDsRnlBfgL4lcVxjXii/UNcdXYMk0ERT0cRWceVzqg
+cOuftIR4oi1GdHUYCnRvTo/LKiY6Y8ifQLa+MYPxkEzJL6azhgZEwfrwsrqcq3NutNJFSv5+w2h
LztLWB4ZU4sIBkXoLuQgHyzdrQpLHO0KMX2aKZDt/NY1HRn3Ll0CYpNwW3dpu1iQr3vao+Z7

构造函数的输入是: w9JtyoYll4K9kYZIH4kq5g==

重新设置一下时间戳,key和iv可以看出来,他俩是不变的

image-20231115171454949

很好,寄,我猜测就可能是我的data数据的问题,还是一样,测试一下:

image-20231115171733370

经过输出调试,发现了,额果然是data定义的时候,引号太乱了导致我加错地方了,稍微改改就行了(困扰了我好久,原来是马虎的问题):

image-20231115172213814

完美!终于出来了呜呜呜呜呜呜~~~~~~~~~~~~~~~~~~~~~~~这问题太致命了

一般复现出问题很大的可能就是这个的问题,所以啊,仔细点吧!

整体的代码:

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
//md5的加密之前数值是: equtype=ANDROID&loginImei=Androidnull&timeStamp=1699870290703&userPwd=woshimima&username=11112222333&key=sdlkjsdljf0j2fsjk
//md5加密之后的数值是: f47d70a0d19de14449c66eada93469af
var CryptoJS =module.exports;
//将CryptoJS模块导出为CryptoJS对象,以便其他文件可以使用require语句将其引入,并通过CryptoJS对象访问CryptoJS模块中的功能和方法。
function getsign(user,pwd,time)
{
var data = "equtype=ANDROID&loginImei=Androidnull&timeStamp=" + time + "&userPwd="+ pwd + "&username=" + user + "&key=sdlkjsdljf0j2fsjk";
//加在变量值data,用于进行加密
return CryptoJS.MD5(data).toString();//调用函数里的toString()方法,转化成字符串便于输出
//调用crypto中的md5加密,对data的数据进行加密并且将结果返回
}


//这两段代码就是根据我之前hook的结果,然后把输入再加到函数里面,然后再进行加密,判断一下和hook的数值是否一样
//很显然,是一样的 输出:f47d70a0d19de14449c66eada93469af
//data: {"equtype":"ANDROID","loginImei":"Androidnull","sign":"F47D70A0D19DE14449C66EADA93469AF","timeStamp":"1699870290703","userPwd":"woshimima","username":"11112222333"}
//deskey: 65102933
//desiv: 32028092
function getdata(user,pwd)
{
var time = new Date().getTime();
//time = "1700039457097";
//首先定义getdata的函数,然后时间戳的话可以用Date的内置函数getTime();就行
sign = getsign(user,pwd,time).toUpperCase();
console.log("des内的sign是:",sign);
var data = '{"equtype":"ANDROID","loginImei":"Androidnull","sign":"' + sign + '","timeStamp":"' + time + '","userPwd":"' + pwd + '","username":"' +user + '"}';
console.log(data);
//console.log('{"equtype":"ANDROID","loginImei":"Androidnull","sign":"E1FD2763045255154A0E0555C22ACD3A","timeStamp":"1700039457097","userPwd":"woshimima","username":"11112222333"}');
//首先是先用刚刚定义的getsign();函数计算出sign的数值,然后再给他加到我们的data中用于des加密
//注意一定要把data的定义写对了,双括号内可以用单括号
var key_md5 = CryptoJS.MD5("65102933").toString();
var deskey = CryptoJS.enc.Hex.parse(key_md5);
//这里的key是需要先经过md5加密,然后再转化成16进制编码的形式
var desiv = CryptoJS.enc.Utf8.parse("32028092");
//iv则是utf-8编码,两种编码格式是不同的,在使用的时候要先解码
return CryptoJS.DES.encrypt ( data , deskey , { iv: desiv, mode: CryptoJS.mode.CBC , padding:CryptoJS.pad.Pkcs7}).toString();
//这个就是调用了des的加密代码,并且要传入要加密的数据和key,然后还有des加密的模式为cdc模式和填充方式为Pkcs7,然后结果变为字符串作为返回值
}


var md5_data = getsign("11112222333","woshimima","1700039457097");
console.log("md5的加密结果:",md5_data);
var des_data = getdata("11112222333","woshimima");
console.log(des_data);
//console.log("NIszaqFPos1vd0pFqKlB42Np5itPxaNH//FDsRnlBfgL4lcVxjXii/UNcdXYMk0ERT0cRWceVzqg+cOuftIR4oi1GdHUYCnRvTo/LKiY6Y8ifQLa+MYPxkEzJL6azhgZEwfrwsrqcq3NutNJFSv5+w2hLztLWB4ZU4sIBkXoLuQgHyzdrQpLHO0KMX2aKZDt/NY1HRn3Ll0CYpNwW3dpu1iQr3vao+Z7");

协议复现:

我选择的是使用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
import requests
import json
import execjs
import _locale

# 设置默认的本地化为 'zh_CN',编码为 'utf8'
_locale._getdefaultlocale = (lambda *args: ['zh_CN', 'utf8'])

# 打开并读取 'fuxian.js' 文件
f = open('fuxian.js', 'r')
demo_list = f.readlines()
f.close()

# 输出文件行数的索引列表
print(list(range(0, len(demo_list))))

# 将文件内容合并为一个字符串
jscode = ""
for i in range(0, len(demo_list)):
jscode += demo_list[i]

# 编译 JavaScript 代码
js = execjs.compile(jscode)

# 使用 JavaScript 函数加密数据
cipherText = js.call('encrypt', '111122223333', 'woshimima')

# 打印请求密文
print("请求密文:" + cipherText)

# 发送 POST 请求
url = 'http://api.dodovip.com/api/user/login'
data = json.dumps({"Encrypt": cipherText})
r = requests.post(url=url, data=data)

# 打印响应状态码
print("响应状态码:", end="")
print(r)

# 打印响应内容
print("响应内容:" + r.text)

# 打印响应的原始字节数据
print("响应密文:", end="")
print(r.content)

# 使用 JavaScript 函数解密响应内容
cipherText = js.call('decrypt', r.text)

# 打印解密后的响应明文
print("响应明文:" + cipherText)
''' print(r):打印了请求响应对象 r,它包含了关于请求的各种信息。这包括响应状态码、响应头和响应正文等。
print(r.text):打印了响应的文本内容。它是服务器返回的响应正文部分。
print(type(r.text)):打印了 r.text 的数据类型。它会显示 r.text 对象的类型,通常是字符串。
print(r.content):打印了响应的内容。r.content 返回的是响应的原始字节数据,而不是经过解码的字符串。
print(cipherText):打印了使用 JavaScript 中的 decrypt 函数解密后的结果。cipherText 是使用 JavaScript 中的 encrypt 函数加密的数据,经过服务器处理后,你使用 JavaScript 中的 decrypt 函数对其进行解密,此处打印的是解密后的结果。
'''

用的post请求去向服务器发送请求,这里也给出我的js代码,可以去复制粘贴使用:

上面加上CryptoJS的函数部分(很长)

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
var CryptoJS = module.exports;

function getSign(user, pass, time) {
var signStr = "equtype=ANDROID&loginImei=Androidnull&timeStamp=" + time + "&userPwd=" + pass + "&username=" + user + "&key=sdlkjsdljf0j2fsjk";
return CryptoJS.MD5(signStr).toString().toUpperCase();
}

function encrypt(user, pass) {
var time = new Date().getTime();
var sign = getSign(user, pass, time);
var plainText = '{"equtype":"ANDROID","loginImei":"Androidnull","sign":"' + sign
+ '","timeStamp":"' + time + '","userPwd":"' + pass + '","username":"' + user + '"}';
var key = CryptoJS.enc.Hex.parse(CryptoJS.MD5("65102933").toString());
var iv = CryptoJS.enc.Utf8.parse("32028092");
return CryptoJS.DES.encrypt(plainText, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
}).toString();
}

function decrypt(cipherText) {
var key = CryptoJS.enc.Hex.parse(CryptoJS.MD5("65102933").toString());
var iv = CryptoJS.enc.Utf8.parse("32028092");
return CryptoJS.DES.decrypt(cipherText, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
}).toString(CryptoJS.enc.Utf8);
}

看一下结果:

image-20231116010155832

image-20231116005709135

浅玩一下

抓包和frida同时运行,判断加密的和解密的结果:

这是用frida_hook获得到的账号Miami,以及一些其他信息,在这里是可以看到各种的加密信息的,包括时间戳

image-20231116010854576

小黄鸟抓到的数据包,果然和我猜的一样,跟hook到的包是一样的

image-20231116011022265

之后,由于时间戳会变,所以我们用hook到的time在伪造协议代码的js中事先定义(就不用获取了)来保证结果的可行性

image-20231116011328670

虽然返回的结果没有账号密码错误(因为时间戳对不上但是可以肯定的是,上面的数据发送的密文是一样的,至此,加密和协议的复现就算是完成了!