dodonew登录算法逆向

打开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”,dodonew1

看到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;
}
});
}

dodonew2

可以看到传入的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);

});

}

dodonew3

可以看到返回值和之前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

dodonew4

得到了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脚本

dodonew5

和hook到的信息一致


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 3049155267@qq.com

💰

×

Help us with donation