mxtakatak android app 加解密分析

android 逆向 25 / 38

本文只作学习研究,禁止用于非法用途,否则后果自负,如有侵权,请告知删除,谢谢!

前言

帮朋友看的一个 app,印度佬搞的 takatak 版本 1.18.10
链接 https://play.google.com/store/apps/details?id=com.next.innovation.takatak&hl=zh&gl=US

charles 抓包

主要分析 x-guard-key 的加密, response 的解密

java 分析

反编译全局搜索,但是没有结果,怎么办呢

根据请求头的 ua 可以猜测,用的 okhttp 发包的,熟悉 okhttp 的小伙伴可以使用 frida hook okhttp header 相关的函数,但是巧了,俺对 okhttp 不熟悉,无奈之后寻找其他关键词,搜索 url

评论接口,全局搜索有结果,那就好办了,打开 jeb 搜索该类

查看 o 的交叉引用

看不出来啥,点进 y.g 看看

file

这里的逻辑也比较简单,先看 y.m

了解 okhttp 的小伙伴大概都猜到了,这里就是构建 request 对象的逻辑,再来分析 y.i

b.a.a.c.i0.s 这个函数有点可疑,点进去看看

果然有猫腻,这里就是加密的逻辑,jeb 直接显示了字符串名称,不知道为啥 jadx 不行,离谱

  • x-guard-key 的值是 rsa 加密

  • x-guard-value 的值是 hmacsha1 加密

下面在分析分析 response 解密

回到这里,b.a.a.c.i0.s 是加密逻辑,我们直接看最后这个调用的函数

点进 y.d 函数,意外的看到下面函数有些 log 日志,抱着好奇心点进函数看一下

结果看到了 aes 解密,不管是不是这个逻辑,咱们先来 frida hook 一下相关函数看看

frida hook

function getMapData(mapSet) {
    try {
        var result = {};
        var key_set = mapSet.keySet();
        var it = key_set.iterator();
        while (it.hasNext()) {
            var key_str = it.next().toString();
            result[key_str] = mapSet.get(key_str).toString();
        }
        return result
    } catch (error) {
        return mapSet
    }
}

function bytes2Hex(arrBytes) {
    var str = "";
    for (var i = 0; i < arrBytes.length; i++) {
        var tmp;
        var num = arrBytes[i];
        if (num < 0) {
            tmp = (255 + num + 1).toString(16);
        } else {
            tmp = num.toString(16);
        }
        if (tmp.length === 1) {
            tmp = "0" + tmp;
        }
        if (i > 0) {
            str += " " + tmp;
        } else {
            str += tmp;
        }
    }
    return str;
}

function bytes2String(arr) {
    if (typeof arr === 'string') {
        return arr;
    }
    var str = '',
        _arr = arr;
    for (var i = 0; i < _arr.length; i++) {
        var one = _arr[i].toString(2),
            v = one.match(/^1+?(?=0)/);
        if (v && one.length === 8) {
            var bytesLength = v[0].length;
            var store = _arr[i].toString(2).slice(7 - bytesLength);
            for (var st = 1; st < bytesLength; st++) {
                store += _arr[st + i].toString(2).slice(2);
            }
            try {
                str += String.fromCharCode(parseInt(store, 2));
            } catch (error) {
                str += parseInt(store, 2).toString();
                console.log(error);
            }

            i += bytesLength - 1;
        } else {
            try {
                str += String.fromCharCode(_arr[i]);
            } catch (error) {
                str += parseInt(store, 2).toString();
                console.log(error);
            }

        }
    }
    return str;
}

function getProcessId() {
    var androidProcess = Java.use('android.os.Process');
    return 'processId: ' + androidProcess.myTid() + ' - ';
}

function printLog() {
    var result = '';

    for (var i = 0; i < arguments.length; i++) {
        result += arguments[i] + ' ';
    }

    console.log(getProcessId(), result)
}

function hook() {
    var i0Cls = Java.use('b.a.a.c.i0');
    var hCls = Java.use('b.a.a.b.h');
    var mac = Java.use('javax.crypto.Mac');

    // ---------- request ----------

    i0Cls.s.implementation = function (a, b, c, d, e) {
        printLog('i0Cls.s.a: ', a);
        printLog('i0Cls.s.b: ', b);
        printLog('i0Cls.s.c: ', c);
        printLog('i0Cls.s.c: ', JSON.stringify(getMapData(c)));
        printLog('i0Cls.s.d: ', d);
        printLog('i0Cls.s.e: ', e);

        return this.s(a, b, c, d, e);
    }

    hCls.e0.implementation = function (a) {
        printLog('hCls.e0.a: ', bytes2Hex(a));

        var res = this.e0(a);
        printLog('hCls.e0.res: ', res);

        return res;
    }

    mac.doFinal.overload('[B').implementation = function (a) {
        printLog('mac.doFinal.a: ', bytes2String(a));
        printLog('mac.doFinal.a: ', bytes2Hex(a));

        var res = this.doFinal(a);
        printLog('mac.doFinal.res: ', bytes2Hex(res));

        return res;
    }

    // ---------- response ----------

    var MessageDigest = Java.use('java.security.MessageDigest');
    var IvParameterSpec = Java.use("javax.crypto.spec.IvParameterSpec");
    var Cipher = Java.use("javax.crypto.Cipher");

    MessageDigest.digest.overload('[B').implementation = function (a) {
        printLog('MessageDigest.digest.a: ', bytes2Hex(a));

        var res = this.digest(a);
        printLog('MessageDigest.digest.res: ', bytes2Hex(res));

        return res;
    }

    IvParameterSpec.$init.overload('[B').implementation = function (a) {
        printLog('IvParameterSpec.$init.a: ', bytes2Hex(a));

        var res = this.$init(a);
        printLog('IvParameterSpec.$init.res: ', bytes2Hex(res));

        return res;
    }

    Cipher.doFinal.overload('[B', 'int', 'int').implementation = function (a, b, c) {
        printLog('Cipher.doFinal.a: ', bytes2Hex(a));
        printLog('Cipher.doFinal.b: ', b);
        printLog('Cipher.doFinal.c: ', c);

        var res = this.doFinal(a, b, c);
        printLog('Cipher.doFinal.res: ', bytes2Hex(res));

        return res;
    }
}

function main() {
    Java.perform(function () {
        hook();
    })
}

setImmediate(main);

全部代码,跑起来 hook 一下,记得同时抓个包,好验证一些数据

因为 rsa 有随机数,不好验证码,就直接去看 response 的解密逻辑,上图是抓包内容

这里是 frida hook 结果

  • 1、aes key iv

  • 2、response 响应数据

  • 3、response 解密结果,根据特征可以判断出来是 gzip

使用 CyberChef 解密成功,aes 正是解密逻辑,至于 rsa 加密,还有 aes key iv 的生成逻辑,大家可以自行分析一下

最后

这个样本还是比较简单,没有 so 没有混淆,定位到之后可以很方便的还原出来

暂无评论
本文作者:
本文链接: https://www.qinless.com/?p=1138
版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 qinless 的博客!
100

发表评论

返回顶部