首页
社区
课程
招聘
[原创]一个被众多逆向人员凌辱的app --> 某du牛
发表于: 2023-6-27 10:14 24964

[原创]一个被众多逆向人员凌辱的app --> 某du牛

2023-6-27 10:14
24964

app版本:4.54
抓包工具:Charles
反汇编工具:JEB、JADX
inject:frida
查壳:360加固

POST: /api/user/login HTTP/1.1
Content-Type: application/json; charset=utf-8
User-Agent: Dalvik/2.1.0 (Linux; U; Android 8.1.0; Pixel 2 XL Build/OPM4.171019.021.R1)
Host: api.dodovip.com
Accept-Encoding: gzip
Content-Length: 262
Connection: keep-alive

{"Encrypt":"NIszaqFPos1vd0pFqKlB42Np5itPxaNH//FDsRnlBfgL4lcVxjXii+GOZz1l+A5V9FPOSMf47jbE\n010Kk+PbNyEDRjj1zY76jXa7VyHLkjxpqsrJYht6LX1PcVabK8oBp/fiOE4l2lC5JVjqx/JI7CJm\neUXVXkgJ6rgPne3WCJUYU+ztDNEi+mvECeOktUk0KxqBbPzuJj3LKsW5Ux080rWm4NZWHxPFbZYl\nIs2IRcs=\n"}

2v+DC2gq7RuAC8PE5GZz5wH3/y9ZVcWhFwhDY9L19g9iEd075+Q7xwewvfIN0g0ec/NaaF43/S0=

多次抓包仅 Encrypt 参数变化,需要分析的就是它了。

对脱壳流程有不明白的可参考我之前写的文章:[原创]ART环境下dex加载流程分析及frida dump dex方案
上脚本,手机端启动fs后执行即可,脱壳的dex会在/data/data/com.dodonew.online目录下:

将脱壳后的dex推出:
图片描述
其中第一个为加壳程序;
图片描述
第二个为IjkMediaPlayer和rx库,IjkMediaPlayer是基于FFmpeg的Android多媒体播放器库,大佬们可自行百度了解;
图片描述
第三个为应用程序界面信息dex;
图片描述
第四个为应用程序逻辑代码。
图片描述
既然是分析登陆逻辑,那肯定是在第四个dex中分析啦!

jadx每次生成的参数名称会有所出入,各位在对照这这份教程进行分析的时候只需把握整体步骤即可。

将第四个文件拖入jadx等待加载完成,搜 "Encrypt" 结果还挺多:
图片描述
挺好定位 com.dodonew.online.http.JsonRequest 类中存在
addRequestMap(Map<String, String>, int) void 方法和 paraMap(Map<String, String>) void 方法, 两方法中都有进行参数存放操作。
第一个方法 addRequestMap 翻译以下:添加请求的 Map,可疑,跟进去看看:

看这两句代码:

第一句中生成的encodeDesMap就是Encrypt,入口点定位无误。

继续分析addRequestMap函数代码,看代码:

获取时间戳,然后将时间戳添加进 Map 中,再调用:

跟进RequestUtil.paraMap函数看看:

首先将 Map 中的键提取出来存入 Set 中,再定义一个 List 集合用来存放键值信息,and 进行 sort 排序,
其中有处:sb.append("key=" + str); str是入参参数二,向上跟一下是个固定值:

经过一系列操作完后对值进行 md5,md5 得到的值就是 sign 的值,hook 看看那些值需进行 md5:

hook 结果:

md5 is called, string: equtype=ANDROID&loginImei=Androidc0b30f35fc9535b5&timeStamp=1687772161410&userPwd=12334&username=123456789&k
ey=sdlkjsdljf0j2fsjk
md5 ret value is e888bef28d91b42fc10cf91540ec057b

试着 python 还原下看看是不是标准 md5 算法:

结果:e888bef28d91b42fc10cf91540ec057b,对照一致,标准md5算法。

5.3 des 加密算法分析
继续分析addRequestMap函数代码,看代码:

其中this.desKey, this.desIV,猜测为des算法,先hook看看数据,hook代码:

hook 结果:

encodeDesMap is called, data: {"equtype":"ANDROID","loginImei":"Androidc0b30f35fc9535b5","sign":"0FAFB81829C15EF86EBD30E214675BBC",
"timeStamp":"1687772424834","userPwd":"12334","username":"123456789"}, desKey: 65102933, desIV: 32028092
encodeDesMap ret value is NIszaqFPos1vd0pFqKlB42Np5itPxaNH//FDsRnlBfgL4lcVxjXii+GOZz1l+A5V9FPOSMf47jbE
010Kk+PbN/jjSVvUEnMkBeVQY2tdy+to9cUXg0XyzdSi3Wehubi6R5t5NLiRanFipatR61mx4ISH
B/wjHUkmAFDl2b3zZIYs2UMZhz4YfC4HgFeRqA/9X1+m1LNZQYUkOLl/HqD5GFDgdRel9stq/g+8
ZB8fY84=

在此吃了个亏,直接用 hook 出来的 desKey、desIV 进行加密,怎么搞都不对,后面发现它还进行了操作,还是太年轻了。跟进 encodeDesMap 方法查看:

先调用 DesSecurity(desKey, desIV); 对 desKey、desIV 进行操作,跟进看看:

查看其构造方法,调用 InitCipher 方法对 desKey、desIV 进行操作:

对 desKey 进行了 MD5 加密,然后才传进去进行 DES 加密,加密模式 CBC 填充方式 PKCS5Padding。再看:

对加密后的数据又进行了一次 Base64 编码,这回清楚了,再进行还原:

执行结果:

NIszaqFPos1vd0pFqKlB42Np5itPxaNH//FDsRnlBfgL4lcVxjXii+GOZz1l+A5V9FPOSMf47jbE010Kk+PbN/jjSVvUEnMkBeVQY2tdy+to9cUXg0XyzdSi3Wehubi6R5t5NLiRanFipatR61mx4ISHB/wjHUkmAFDl2b3zZIYs2UMZhz4YfC4HgFeRqA/9X1+m1LNZQYUkOLl/HqD5GFDgdRel9stq/g+8ZB8fY84=

对照其hook结果一直,还原成功,至此整个协议就分析完成了,Encrypt数据也成功拿到,接下来就是模拟请求了。

前面该分析的也都分析好了,写代码这种事情相信各位佬随手拈来,我就不在讲解了,直接上代码,是在不明白,代码中的注释也很全:

结果,与抓包结果一致,返回数据还是加密的:

2v+DC2gq7RuAC8PE5GZz5wH3/y9ZVcWhFwhDY9L19g9iEd075+Q7xwewvfIN0g0ec/NaaF43/S0=

对于返回结果是密文也是预料之中的,des 为比较早期的对称加密算法,加密与解密就是一个对称的过程。
请求是 addRequestMap 有 request 那么就会有 response,而且这个方法就在我们找到的 addRequestMap 上方:

留意:

hook 它看看:

结果:

decodeDesJson is called, json: 2v+DC2gq7RuAC8PE5GZz5wH3/y9ZVcWhFwhDY9L19g9iEd075+Q7xwewvfIN0g0ec/NaaF43/S0=, desKey: 65102933, desIV: 32028092
decodeDesJson ret value is {"code":-1,"message":"账号或密码错误","data":{}}

因为我在这给的账号和密码本就是错误的,所以提示账号或密码错误一点问题没有。
至此完结。

function find_hook_fun() {
    var fun_Name = "";
    var libart = Module.findBaseAddress('libart.so');   //查找基地址
    var exports = Module.enumerateExportsSync("libart.so");
    for(var i=0; i<exports.length; i++){
        if(exports[i].name.indexOf("OpenMemory") !== -1){
            fun_Name = exports[i].name;
            console.log("导出模块名: " + exports[i].name + "\t\t偏移地址: "+ (exports[i].address - libart - 1));
            break;
        }else if(exports[i].name.indexOf("OpenCommon") !== -1){
            fun_Name = exports[i].name;
            console.log("导出模块名: " + exports[i].name + "\t\t偏移地址: "+ (exports[i].address - libart - 1));
            break;
        }
    }
    return fun_Name;
}
 
function DexFileVerifier(Verify){
    var magic_03x = true;
    var magic_Hex = [0x64, 0x65, 0x78, 0x0a, 0x30, 0x33, 0x35, 0x00];
    for(var i = 0; i < 8; i++){
        if(Memory.readU8(ptr(Verify).add(i)) !== magic_Hex[i]){
            if(Memory.readU8(ptr(Verify).add(i)) === 0x37 || 0x38){
                console.log('new dex');
            }else{
                magic_03x = false;
                break;
            }
        }
    }
    return magic_03x;
}
 
function dump_Dex(fun_Name, apk_Name){
    if (fun_Name !== ''){
        var hook_fun = Module.findExportByName("libart.so", fun_Name);
        Interceptor.attach(hook_fun, {
            onEnter: function (args) {
                var begin = 0;
                var dex_flag = false;
                dex_flag = DexFileVerifier(args[0]);
                if(dex_flag === true){
                    begin = args[0];
                }
                if(begin === 0){
                    dex_flag = DexFileVerifier(args[1]);
                    if(dex_flag === true){
                        begin = args[1];
                    }
                }
                if(dex_flag === true){
                    console.log("magic : " + Memory.readUtf8String(begin));
                    var address = parseInt(begin,16) + 0x20;
                    var dex_size = Memory.readInt(ptr(address));
                    console.log("dex_size :" + dex_size);
                    var dex_path = "/data/data/" + apk_Name + "/" + dex_size + ".dex";
                    var dex_file = new File(dex_path, "wb");
                    dex_file.write(Memory.readByteArray(begin, dex_size));
                    dex_file.flush();
                    dex_file.close();
                }
            },
            onLeave: function (retval) {
 
            }
        });
    }else{
        console.log("Error: no hook function.");
    }
}
 
var fun_Name = find_hook_fun();
var apk_Name = 'com.dodonew.online'
dump_Dex(fun_Name, apk_Name);
// frida -U -f com.dodonew.online -l dumpdex.js --no-pause
function find_hook_fun() {
    var fun_Name = "";
    var libart = Module.findBaseAddress('libart.so');   //查找基地址
    var exports = Module.enumerateExportsSync("libart.so");
    for(var i=0; i<exports.length; i++){
        if(exports[i].name.indexOf("OpenMemory") !== -1){
            fun_Name = exports[i].name;
            console.log("导出模块名: " + exports[i].name + "\t\t偏移地址: "+ (exports[i].address - libart - 1));
            break;
        }else if(exports[i].name.indexOf("OpenCommon") !== -1){
            fun_Name = exports[i].name;
            console.log("导出模块名: " + exports[i].name + "\t\t偏移地址: "+ (exports[i].address - libart - 1));
            break;
        }
    }
    return fun_Name;
}
 
function DexFileVerifier(Verify){
    var magic_03x = true;
    var magic_Hex = [0x64, 0x65, 0x78, 0x0a, 0x30, 0x33, 0x35, 0x00];
    for(var i = 0; i < 8; i++){
        if(Memory.readU8(ptr(Verify).add(i)) !== magic_Hex[i]){
            if(Memory.readU8(ptr(Verify).add(i)) === 0x37 || 0x38){
                console.log('new dex');
            }else{
                magic_03x = false;
                break;
            }
        }
    }
    return magic_03x;
}
 
function dump_Dex(fun_Name, apk_Name){
    if (fun_Name !== ''){
        var hook_fun = Module.findExportByName("libart.so", fun_Name);
        Interceptor.attach(hook_fun, {
            onEnter: function (args) {
                var begin = 0;
                var dex_flag = false;
                dex_flag = DexFileVerifier(args[0]);
                if(dex_flag === true){
                    begin = args[0];
                }
                if(begin === 0){
                    dex_flag = DexFileVerifier(args[1]);
                    if(dex_flag === true){
                        begin = args[1];
                    }
                }
                if(dex_flag === true){
                    console.log("magic : " + Memory.readUtf8String(begin));
                    var address = parseInt(begin,16) + 0x20;
                    var dex_size = Memory.readInt(ptr(address));
                    console.log("dex_size :" + dex_size);
                    var dex_path = "/data/data/" + apk_Name + "/" + dex_size + ".dex";
                    var dex_file = new File(dex_path, "wb");
                    dex_file.write(Memory.readByteArray(begin, dex_size));
                    dex_file.flush();
                    dex_file.close();
                }
            },
            onLeave: function (retval) {
 
            }
        });
    }else{
        console.log("Error: no hook function.");
    }
}
 
var fun_Name = find_hook_fun();
var apk_Name = 'com.dodonew.online'
dump_Dex(fun_Name, apk_Name);
// frida -U -f com.dodonew.online -l dumpdex.js --no-pause
public void addRequestMap(Map<String, String> map, int i) {
    String str = System.currentTimeMillis() + "";
    if (map == null) {
        map = new HashMap<>();
    }
    map.put("timeStamp", str);
    String encodeDesMap = RequestUtil.encodeDesMap(RequestUtil.paraMap(map, Config.BASE_APPEND, "sign"), this.desKey, this.desIV);
    JSONObject jSONObject = new JSONObject();
    try {
        jSONObject.put("Encrypt", encodeDesMap);
        this.mRequestBody = jSONObject + "";
    } catch (JSONException e) {
        e.printStackTrace();
    }
}
public void addRequestMap(Map<String, String> map, int i) {
    String str = System.currentTimeMillis() + "";
    if (map == null) {
        map = new HashMap<>();
    }
    map.put("timeStamp", str);
    String encodeDesMap = RequestUtil.encodeDesMap(RequestUtil.paraMap(map, Config.BASE_APPEND, "sign"), this.desKey, this.desIV);
    JSONObject jSONObject = new JSONObject();
    try {
        jSONObject.put("Encrypt", encodeDesMap);
        this.mRequestBody = jSONObject + "";
    } catch (JSONException e) {
        e.printStackTrace();
    }
}
String encodeDesMap = RequestUtil.encodeDesMap(RequestUtil.paraMap(map, Config.BASE_APPEND, "sign"), this.desKey, this.desIV);
 
jSONObject.put("Encrypt", encodeDesMap);
String encodeDesMap = RequestUtil.encodeDesMap(RequestUtil.paraMap(map, Config.BASE_APPEND, "sign"), this.desKey, this.desIV);
 
jSONObject.put("Encrypt", encodeDesMap);
String str = System.currentTimeMillis() + "";
map.put("timeStamp", str);
String str = System.currentTimeMillis() + "";
map.put("timeStamp", str);
RequestUtil.paraMap(map, Config.BASE_APPEND, "sign");
RequestUtil.paraMap(map, Config.BASE_APPEND, "sign");
public static String paraMap(Map<String, String> map, String str, String str2) {
    try {
        Set<String> keySet = map.keySet();
        StringBuilder sb = new StringBuilder();
        ArrayList arrayList = new ArrayList();
        for (String str3 : keySet) {
            arrayList.add(str3 + "=" + map.get(str3));
        }
        Collections.sort(arrayList);
        for (int i = 0; i < arrayList.size(); i++) {
            sb.append((String) arrayList.get(i));
            sb.append("&");
        }
        sb.append("key=" + str);
        map.put(str2, Utils.md5(sb.toString()).toUpperCase());
        String json = new GsonBuilder().serializeNulls().create().toJson(sortMapByKey(map));
        Log.w(AppConfig.DEBUG_TAG, json + "   result");
        return json;
    } catch (Exception e) {
        e.printStackTrace();
        return "";
    }
}
public static String paraMap(Map<String, String> map, String str, String str2) {
    try {
        Set<String> keySet = map.keySet();
        StringBuilder sb = new StringBuilder();
        ArrayList arrayList = new ArrayList();
        for (String str3 : keySet) {
            arrayList.add(str3 + "=" + map.get(str3));
        }
        Collections.sort(arrayList);
        for (int i = 0; i < arrayList.size(); i++) {
            sb.append((String) arrayList.get(i));
            sb.append("&");
        }
        sb.append("key=" + str);
        map.put(str2, Utils.md5(sb.toString()).toUpperCase());
        String json = new GsonBuilder().serializeNulls().create().toJson(sortMapByKey(map));
        Log.w(AppConfig.DEBUG_TAG, json + "   result");
        return json;
    } catch (Exception e) {
        e.printStackTrace();
        return "";
    }
}
public static final String BASE_APPEND = "sdlkjsdljf0j2fsjk";
public static final String BASE_APPEND = "sdlkjsdljf0j2fsjk";
function main() {
    Java.perform(function () {
        var Utils = Java.use("com.dodonew.online.util.Utils");
        Utils["md5"].implementation = function (string) {
            console.log('md5 is called' + ', ' + 'string: ' + string);
            var ret = this.md5(string);
            console.log('md5 ret value is ' + ret);
            return ret;
        };
    });
}
setImmediate(main)
function main() {
    Java.perform(function () {
        var Utils = Java.use("com.dodonew.online.util.Utils");
        Utils["md5"].implementation = function (string) {
            console.log('md5 is called' + ', ' + 'string: ' + string);
            var ret = this.md5(string);
            console.log('md5 ret value is ' + ret);
            return ret;
        };
    });
}
setImmediate(main)
from hashlib import md5
 
def get_encode_mes(mes):
    new_md5 = md5()
    new_md5.update(mes.encode(encoding='utf-8'))
    return new_md5.hexdigest()
 
if __name__ == '__main__':
    print(get_encode_mes('equtype=ANDROID&loginImei=Androidc0b30f35fc9535b5&timeStamp=1687772161410&userPwd=12334&username=123456789&k
ey=sdlkjsdljf0j2fsjk'))
from hashlib import md5
 
def get_encode_mes(mes):
    new_md5 = md5()
    new_md5.update(mes.encode(encoding='utf-8'))
    return new_md5.hexdigest()
 
if __name__ == '__main__':
    print(get_encode_mes('equtype=ANDROID&loginImei=Androidc0b30f35fc9535b5&timeStamp=1687772161410&userPwd=12334&username=123456789&k
ey=sdlkjsdljf0j2fsjk'))
String encodeDesMap = RequestUtil.encodeDesMap(RequestUtil.paraMap(map, Config.BASE_APPEND, "sign"), this.desKey, this.desIV);
String encodeDesMap = RequestUtil.encodeDesMap(RequestUtil.paraMap(map, Config.BASE_APPEND, "sign"), this.desKey, this.desIV);
function main() {
    Java.perform(function () {
        var RequestUtil = Java.use("com.dodonew.online.http.RequestUtil");
        RequestUtil["encodeDesMap"].overload('java.lang.String', 'java.lang.String', 'java.lang.String').implementation = function (data, desKey, desIV) {
            console.log('encodeDesMap is called' + ', ' + 'data: ' + data + ', ' + 'desKey: ' + desKey + ', ' + 'desIV: ' + desIV);
            var ret = this.encodeDesMap(data, desKey, desIV);
            console.log('encodeDesMap ret value is ' + ret);
            return ret;
        };
    });
}
setImmediate(main)
function main() {
    Java.perform(function () {
        var RequestUtil = Java.use("com.dodonew.online.http.RequestUtil");
        RequestUtil["encodeDesMap"].overload('java.lang.String', 'java.lang.String', 'java.lang.String').implementation = function (data, desKey, desIV) {
            console.log('encodeDesMap is called' + ', ' + 'data: ' + data + ', ' + 'desKey: ' + desKey + ', ' + 'desIV: ' + desIV);
            var ret = this.encodeDesMap(data, desKey, desIV);

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

最后于 2023-6-27 10:19 被行简编辑 ,原因:
收藏
免费 12
支持
分享
最新回复 (10)
雪    币: 2340
活跃值: (10417)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
大佬有没有样本呢,或者说那个平台的那个版本呢,想跟着学习一下
2023-6-28 09:22
0
雪    币: 3113
活跃值: (4279)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
3
你瞒我瞒 大佬有没有样本呢,或者说那个平台的那个版本呢,想跟着学习一下
Android 平台,app版本:4.54,网上搜索下载即可。
2023-6-28 09:47
1
雪    币: 2340
活跃值: (10417)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
行简 Android 平台,app版本:4.54,网上搜索下载即可。
在一些野网找到了
2023-6-28 11:12
0
雪    币: 3059
活跃值: (30876)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
感谢分享
2023-6-28 11:15
1
雪    币: 3565
活跃值: (3950)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
膜拜大佬。
2023-6-28 12:48
0
雪    币: 343
活跃值: (856)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
7
看来360免费版要计划上frida防御了
2023-7-7 11:02
1
雪    币: 1451
活跃值: (3886)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
8
4个dex如何简化合并呢?
2023-7-7 20:50
0
雪    币: 3113
活跃值: (4279)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
9
方向感 4个dex如何简化合并呢?
网上有现成的工具,或者写脚本合并都可
2023-7-9 20:43
0
雪    币: 402
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
10
感谢楼主分享
2023-7-10 08:40
0
雪    币: 5
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
11
大佬为啥你脱壳出来的代码看着没有混淆的样子
2023-7-12 18:00
0
游客
登录 | 注册 方可回帖
返回
//