dodonew登录算法逆向
Created At :
Views 👀 :
打开dodonew,输入账号密码点击登录抓包,看到一串加密的数据
1 2 3 4 5 6 7 8 9 10
| POST /api/user/login HTTP/1.1 If-Modified-Since: Mon, 11 Mar 2024 12:09:07 GMT Content-Type: application/json; charset=utf-8 User-Agent: Dalvik/2.1.0 (Linux; U; Android 13; Pixel 4a Build/TP1A.221105.002) Host: api.dodovip.com Connection: Keep-Alive Accept-Encoding: gzip Content-Length: 250
{"Encrypt":"NIszaqFPos1vd0pFqKlB42Np5itPxaNH\/\/FDsRnlBfgL4lcVxjXii\/UNcdXYMk0E528uVVD\/0ojS\n\/CfQpVGA88EtRCaHnc+pKNmbScm9kxa6O3hJ4KxkBB+1vCpWa4jfbRYHYbLzPYXmyUksg4bXHumw\nVdH\/JVoS3kS2D6BzukuijAEj3WBYez3FKIDycA0W4550eyiUIxOCifvYHDDdBcXjeasSKzkJ\n"}
|
把apk拖进jadx中反编译,搜索一下关键字 “Encrypt”,
看到com.dodonew.online这个关键类,直接跟进
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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(); } }
|
或者通过objection注入找到关键类DesSecurity,查找用例,同样能定位到Encrypt的关键加密代码
可以看到Encrypt是由RequestUtil.encodeDesMap这个方法生成的,所以选择去hook一下这个方法先
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| function hook_addRequest(){ console.log("[*] script onloaded"); Java.perform(function(){ console.log("[*] function onloaded"); var Util = Java.use("com.dodonew.online.http.RequestUtil"); Util.encodeDesMap.overload('java.lang.String','java.lang.String','java.lang.String').implementation = function(code,desKey,desIV){ console.log("[*] Method onloaded"); console.log("code: ",code); console.log("Key: ",desKey); console.log("IV: ",desIV); var retval = this.encodeDesMap(code,desKey,desIV); console.log("retval: ",retval); return retval; } }); }
|
可以看到传入的code,为了方便,可以写一个主动调用函数,直接将这个code的信息传进去
1 2 3 4 5 6 7 8 9 10 11
| function call_java(){ console.log("[*] call_java onloaded"); Java.perform(function(){ console.log("[*]function call_java onloaded"); var Util = Java.use("com.dodonew.online.http.RequestUtil"); var retval = Util.encodeDesMap('{"equtype":"ANDROID","loginImei":"Android010067025328409","sign":"810B5A337BF16B53855B3FB68F6B6AD3","timeStamp":"1710243370998","userPwd":"a123456","username":"123456789"}','65102933','32028092'); console.log("retval :",retval);
});
}
|
可以看到返回值和之前hook的结果一致
继续跟进encodeDesMap
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
| public DesSecurity(String key, String iv) throws Exception { if (key == null) { throw new NullPointerException("Parameter is null!"); } InitCipher(key.getBytes(), iv.getBytes()); }
private void InitCipher(byte[] secKey, byte[] secIv) throws Exception { MessageDigest md = MessageDigest.getInstance("MD5"); md.update(secKey); DESKeySpec dsk = new DESKeySpec(md.digest()); SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES"); SecretKey key = keyFactory.generateSecret(dsk); IvParameterSpec iv = new IvParameterSpec(secIv); this.enCipher = Cipher.getInstance("DES/CBC/PKCS5Padding"); this.deCipher = Cipher.getInstance("DES/CBC/PKCS5Padding"); this.enCipher.init(1, key, iv); this.deCipher.init(2, key, iv); }
public String encrypt64(byte[] data) throws Exception { return Base64.encodeToString(this.enCipher.doFinal(data), 0); }
public byte[] decrypt64(String data) throws Exception { return this.deCipher.doFinal(Base64.decode(data, 0)); }
|
可以看出来就是Des的加密,但是先对key做了一个md5的加密,需要注意的是md5加密的结果有16个字节,但是des的密钥只需要8个字节所以只需要取前八个字节即可
分析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 25
| 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 ""; } }
|
定位到这个关键类中发现sign是经过md5加密后的结果,先对md5这个函数进行hook
得到了sign加密前的值
1
| 'equtype=ANDROID&loginImei=Android010067025328409&timeStamp=1710245935391&userPwd=a123456&username=123456789&key=sdlkjsdljf0j2fsjk'
|
就是由账号密码等信息拼接而成的字符串,然后在结尾加了一个固定的字符串
所以总的来说登录的算法就是先将账号密码等信息进行拼接得到signStr,将signStr进行md5加密得到sign,然后将sign和账号密码等信息以及一个固定的字符串进行拼接,然后进行Des加密
现在进行算法还原,因为这里的算法比较简单,所以直接用js写个加密脚本或者在线工具就能还原出来
整理一下之前hook得到的信息
1 2 3 4 5 6 7 8 9
| MD5 string: equtype=ANDROID&loginImei=Android010067025328409&timeStamp=1710319473611&userPwd=a123456&username=123456789&key=sdlkjsdljf0j2fsjk retval: a488c4fc490922773b01b4ab80c00891 data: {"equtype":"ANDROID","loginImei":"Android010067025328409","sign":"A488C4FC490922773B01B4AB80C00891","timeStamp":"1710319473611","userPwd":"a123456","username":"123456789"} desKey: 65102933 desIV: 32028092 retval: NIszaqFPos1vd0pFqKlB42Np5itPxaNH//FDsRnlBfgL4lcVxjXii4KhogG/C4u8PgeD9Zh0gWcA 680+dOMEUnyV5wEgEthduq/stMtUrc2Rg19aHc4dErjIPuqOAlRqI8Mh0+PVmndQXJvU+IDkE+Fo DZ1jtmOAAVVW2PsceforwI17yAKhZNRCd5uWrKp2jwhNinmihuhmdyDcsPpax+warkoSWbrR0IG1 tnHN1c0=
|
写一个js脚本
和hook到的信息一致
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 3049155267@qq.com