抓包 打开手机端小黄鸟 / 进入嘟嘟牛登录页面 / 输入账号和密码 /点击登录
抓取到一个数据包,分析得到一个 “Encrypt”
查壳
pkid.jar
脱壳 拖入Jadx 分析代码 搜索 “Encrypt” 出现 两条 可疑结果
使用 Frida 定位 确定点击登录时会走哪条方法
Hook第一个可疑函数 paraMap
1 2 3 4 5 6 7 8 9 10 11 Java.perform(function(){ // 使用app的类 var jsonRequest = Java.use("com.dodonew.online.http.JsonRequest"); // 确认以下看是否找到 console.log("jsonRequest:",jsonRequest); jsonRequest.paraMap.implementation = function(a){ console.log("param1:",a); this.paraMap(); } });
成功找到该类 / 进入嘟嘟牛 ,点击登录 / 没有反应
App 可能没有走这个函数,也可能是代码写错了或者Frida版本问题
Hook第二个可疑函数 addRequestMap
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Java.perform(function(){ // 使用app的类 var jsonRequest = Java.use("com.dodonew.online.http.JsonRequest"); // 确认看是否找到 console.log("jsonRequest:",jsonRequest); jsonRequest.paraMap.implementation = function(a){ console.log("param1:",a); this.paraMap(); } jsonRequest.addRequestMap.overload('java.util.Map', 'int').implementation = function(a,b){ console.log("addRequestMap param:",a,b); this.addRequestMap(a,b); } });
进入App 点击登录,会出现参数
确定该App在登陆时走的是第二个可疑函数
打印可疑函数的参数 addRequestMap
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Java.perform(function(){ // 使用app的类 var jsonRequest = Java.use("com.dodonew.online.http.JsonRequest"); // 确认看是否找到 console.log("jsonRequest:",jsonRequest); jsonRequest.paraMap.implementation = function(a){ console.log("param1:",a); this.paraMap(); } jsonRequest.addRequestMap.overload('java.util.Map', 'int').implementation = function(a,b){ console.log("addRequestMap param:",a,b); var v = Java.cast(a,Java.use("java.util.HashMap")); console.log("addRequestMap param:",v.toString()); this.addRequestMap(a,b); } });
app协议分析 找到第一个加密点 分析确定的可疑方法 addRequestMap
找到第一个加密点
“sign” 值加密
分析RequestUtil.encodeDesMap 方法
Hook Utils.md5 方法查看传入的参数 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 Java.perform(function(){ // 使用app的类 var jsonRequest = Java.use("com.dodonew.online.http.JsonRequest"); // 确认看是否找到 console.log("jsonRequest:",jsonRequest); jsonRequest.paraMap.implementation = function(a){ console.log("param1:",a); this.paraMap(); } jsonRequest.addRequestMap.overload('java.util.Map', 'int').implementation = function(a,b){ console.log("addRequestMap param:",a,b); var v = Java.cast(a,Java.use("java.util.HashMap")); console.log("addRequestMap param:",v.toString()); this.addRequestMap(a,b); } var utils = Java.use("com.dodonew.online.util.Utils"); utils.md5.implementation = function(a){ console.log("md5 param:",a); var retval = this.md5(a); console.log("md5 retval:",retval); return retval; } });
测试下看是不是标准的md5
是标准的md5
找第二个加密点 返回 可疑方法 addRequestMap
Hook RequestUtil.encodeDesMap函数查看传入的参数 1 2 3 4 5 6 7 8 var requestUtil = Java.use("com.dodonew.online.http.RequestUtil"); requestUtil.encodeDesMap.overload('java.lang.String', 'java.lang.String', 'java.lang.String').implementation = function(a,b,c){ console.log("encodeDesMap params:",a); console.log("encodeDesMap key:",b); console.log("encodeDesMap iv:",c); var retval = this.encodeDesMap(a,b,c); console.log("encodeDesMap retval:",retval); return retval;
这里将第一个加密点的 sign 值换成大写后放入 data 参数中
进入 encodeDesMap 方法分析 分析该方法对参数的操作
该方法操作了 data, key, iv 参数
涉及 DesSecurity 方法 和 encrypt64 方法
进入 DesSecurity 方法分析
对密钥 key 进行了 md5 加密
利用所得密钥 对data 进行了 DES 加密
Hook DESKeySpec 方式 取 md5 加密后的前八位
1 2 3 4 5 var base64 = Java.use("android.util.Base64"); var dESkeySpec = Java.use("javax.crypto.spec.DESKeySpec"); dESkeySpec.$init.overload('[B').implementation = function(a){ console.log("DESKeySpec param:",base64.encodeToString(a,0)); this.$init(a);
算法复现 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 // md5 param: equtype=ANDROID&loginImei=Androidd1c9f85c8bbeb3f4&timeStamp=1742895425805&userPwd=12345678&username=15639245016&key=sdlkjsdljf0j2fsjk // md5 retval: 174d17095a6c843775597826b40dacc2 import CryptoJS from "crypto-js"; function getSign(user,pwd,time){ var data = "equtype=ANDROID&loginImei=Androidd1c9f85c8bbeb3f4&timeStamp="+time +"&userPwd="+pwd +"&username=" + user+"&key=sdlkjsdljf0j2fsjk"; return CryptoJS.MD5(data).toString(); } // encodeDesMap params: {"equtype":"ANDROID","loginImei":"Androidd1c9f85c8bbeb3f4","sign":"174D17095A6C843775597826B40DACC2","timeStamp":"1742895425805","userPwd":"12345678","username":"15639245016"} // encodeDesMap key: 65102933 // encodeDesMap iv: 32028092 function getData(user,pwd){ var time = "1742904583925"; var sign = getSign(user,pwd,time).toUpperCase(); console.log(sign); var _plainText = '{"equtype":"ANDROID","loginImei":"Androidd1c9f85c8bbeb3f4","sign":"'+ sign +'","timeStamp":"'+time +'","userPwd":"'+pwd +'","username":"'+user +'"}'; var keyMD5 = CryptoJS.MD5("65102933").toString(); var _key= CryptoJS.enc.Hex.parse(keyMD5); var _iv = CryptoJS.enc.Utf8.parse("32028092"); return CryptoJS.DES.encrypt(_plainText,_key,{ iv:_iv, mode:CryptoJS.mode.CBC, padding:CryptoJS.pad.Pkcs7 }).toString(); } console.log(getData("15639245016","12345678"));
协议复现 app 与服务器的交互 是依靠数据(抓包抓取到的)
js
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 // md5 param: equtype=ANDROID&loginImei=Androidd1c9f85c8bbeb3f4&timeStamp=1742895425805&userPwd=12345678&username=15639245016&key=sdlkjsdljf0j2fsjk // md5 retval: 174d17095a6c843775597826b40dacc2 var CryptoJS = require('crypto-js'); function getSign(user,pwd,time){ var data = "equtype=ANDROID&loginImei=Androidd1c9f85c8bbeb3f4&timeStamp="+time +"&userPwd="+pwd +"&username=" + user+"&key=sdlkjsdljf0j2fsjk"; return CryptoJS.MD5(data).toString(); } // encodeDesMap params: {"equtype":"ANDROID","loginImei":"Androidd1c9f85c8bbeb3f4","sign":"174D17095A6C843775597826B40DACC2","timeStamp":"1742895425805","userPwd":"12345678","username":"15639245016"} // encodeDesMap key: 65102933 // encodeDesMap iv: 32028092 function encrypt(user,pwd){ var time = new Date().getTime(); var sign = getSign(user,pwd,time).toUpperCase(); console.log(sign); var _plainText = '{"equtype":"ANDROID","loginImei":"Androidd1c9f85c8bbeb3f4","sign":"'+ sign +'","timeStamp":"'+time +'","userPwd":"'+pwd +'","username":"'+user +'"}'; var keyMD5 = CryptoJS.MD5("65102933").toString(); var _key= CryptoJS.enc.Hex.parse(keyMD5); 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); } // var a = decrypt("2v+DC2gq7RuAC8PE5GZz5wH3/y9ZVcWhFwhDY9L19g9iEd075+Q7xwewvfIN0g0ec/NaaF43/S0=") // console.log(a)
使用 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 import base64 import requests import json import execjs import _locale _locale._getdefaultlocale = (lambda *args: ['zh_CN', 'utf8']) f = open("demo.js", "r") demo_list = f.readlines() print(list(range(0, len(demo_list)))) jscode = "" for i in range(0,len(demo_list)): jscode += demo_list[i] f.close() js = execjs.compile(jscode) plainText = js.call('encrypt', '15639245016', '12345678') print(plainText) url = 'http://api.dodovip.com/api/user/login' data = json.dumps({"Encrypt": plainText}) headers = { "Content-Type": "application/json;charset=utf-8", "User-Agent": "Dalvik/2.1.0 (Linux; U; Android 9; Pixel 3 Build/PD1A.180720.030)" } r = requests.post(url=url, data=data, headers=headers) print(r) print(r.text) print(type(r.text)) print(r.content) cipherText = js.call('decrypt', 'r.text') print(cipherText)
伪造协议成功