算法复现:
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
|
var CryptoJS =module.exports;
function getsign(user,pwd,time){ var data = "equtype=ANDROID&loginImei=Androidnull&timeStamp=" + time + "&userPwd="+ pwd + "&username=" + user + "&key=sdlkjsdljf0j2fsjk"; return CryptoJS.MD5(data).toString(); } var md5_data = getsign("11112222333","woshimima","1699870290703"); console.log(md5_data);
|
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(); sign = getsign(user,pwd,time).toUpperCase(); var data = '{"equtype":"ANDROID","loginImei":"Androidnull","sign":"' + sign + ',"timeStamp":"' + time + '","userPwd":"' + pwd + '","username":"' +user + '"}'; var key_md5 = CryptoJS.MD5("65102933").toString(); var deskey = CryptoJS.enc.Hex.parse(key_md5); var desiv = CryptoJS.enc.Utf8.parse("32028092"); return CryptoJS.DES.encrypt ( data , deskey , { iv: desiv, mode: CryptoJS.mode.CBC , padding:CryptoJS.pad.Pkcs7}).toString(); }
|
然后果然还是出事了
什么??!两次的加密结果竟然不一样,离谱,真的不知道哪里错了….
哎,原来是时间戳的问题,我写的代码是根据现实代码生成的,但是那个数据不是…..
略微给time赋值成固定的数值后,尝试一下
寄,时间戳也改了,也还是不一样
算了,还是重新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可以看出来,他俩是不变的
很好,寄,我猜测就可能是我的data数据的问题,还是一样,测试一下:
经过输出调试,发现了,额果然是data定义的时候,引号太乱了导致我加错地方了,稍微改改就行了(困扰了我好久,原来是马虎的问题):
完美!终于出来了呜呜呜呜呜呜~~~~~~~~~~~~~~~~~~~~~~~这问题太致命了
一般复现出问题很大的可能就是这个的问题,所以啊,仔细点吧!
整体的代码:
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
|
var CryptoJS =module.exports;
function getsign(user,pwd,time) { var data = "equtype=ANDROID&loginImei=Androidnull&timeStamp=" + time + "&userPwd="+ pwd + "&username=" + user + "&key=sdlkjsdljf0j2fsjk"; return CryptoJS.MD5(data).toString(); }
function getdata(user,pwd) { var time = new 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); var key_md5 = CryptoJS.MD5("65102933").toString(); var deskey = CryptoJS.enc.Hex.parse(key_md5); var desiv = CryptoJS.enc.Utf8.parse("32028092"); return CryptoJS.DES.encrypt ( data , deskey , { iv: desiv, mode: CryptoJS.mode.CBC , padding:CryptoJS.pad.Pkcs7}).toString(); }
var md5_data = getsign("11112222333","woshimima","1700039457097"); console.log("md5的加密结果:",md5_data); var des_data = getdata("11112222333","woshimima"); console.log(des_data);
|
协议复现:
我选择的是使用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
_locale._getdefaultlocale = (lambda *args: ['zh_CN', 'utf8'])
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]
js = execjs.compile(jscode)
cipherText = js.call('encrypt', '111122223333', 'woshimima')
print("请求密文:" + cipherText)
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)
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); }
|
看一下结果:
浅玩一下
抓包和frida同时运行,判断加密的和解密的结果:
这是用frida_hook获得到的账号Miami,以及一些其他信息,在这里是可以看到各种的加密信息的,包括时间戳
小黄鸟抓到的数据包,果然和我猜的一样,跟hook到的包是一样的
之后,由于时间戳会变,所以我们用hook到的time在伪造协议代码的js中事先定义(就不用获取了)来保证结果的可行性
虽然返回的结果没有账号密码错误(因为时间戳对不上但是可以肯定的是,上面的数据发送的密文是一样的,至此,加密和协议的复现就算是完成了!