XX客户端APP签名分析之算法分析篇

VSole2021-08-02 19:06:58

前言

将脱壳后的dex文件在jadx打开,由于脱壳后会产生多个dex,因此先用脚本合并一下比较方便。当然最新版jadx支持增加文件,也可以直接添加。

import os, sys
path = r'xxxx'# 文件夹目录files = os.listdir(path)  out_path =r'xxxxx'  #路径s = []for file in files:  # 遍历文件夹    if file.find("dex") > 0:  ## 查找dex 文件        sh = f'jadx -j 1 -r -d {out_path} {path}\\{file}'        print(sh)        os.system(sh)`

同时使用抓包软件开始对app进行抓包,对于移动端的app抓包,证书的安装一直是一个很繁琐的问题,在这里推荐一款Magisk的模块Move Certificates,可以很方便的将用户证书移动到系统层,实现https的抓包。

这里主要的功能侧重点在bbsapi.domo.cn和class.domo.cn上,因为该app主要的功能点就是在这两个地址下。


查看一下数据包的内容,可以看到path中是包含sign值的

/newapi/live/square/live-count?noncestr=84140992&sign=47e714f977952ad75eb6eeb3165083dae93502b3×tamp=1627008983764<

目标就是要找到这个sign值的生成算法。首先到jad中进行代码定位,为了降低逆向难度,可以选择从noncestr入手,进行搜索可能会简单一点,当然这只能是经验之谈,一切还是要以实际分析情况为准。

可以看一下如果搜索sign,会有多少结果。

继续看noncestr,看到搜索到了14个相关代码,根据类名来看,选择一个最最有可能的先看一下。



可以看到是有getSign函数在这个类下面的,采用直接hook这个函数也不是很现实,由于jadx的问题,是无法显示该函数下的代码,这个时候,可以采用直接hook该函数的系统加密函数的方法,来查看是否是直接采用了比如MD5,SHA1,Base64等等常规加密的方法,对于此,直接hook后查看输入和输出。

编写frida脚本进行测试一下。


update:appSignKey=ac6190d7dfaa77df726f0a82244d3eda68675ccd4e95de802f5042e91d15edc7bae3026d8f0fb2a8287446bb289563970264&noncestr=12520893×tamp=1627023153533digest:e2931f94fdf5cbad61d95bf23b192841a12f1a8c

可以看到上传数据的构成是appSignKey+noncestr和timestamp构成。多次抓包发现appSignKey是一个固定值(只是class.domo.cn下的固定值,该app不同的网址采用不同的Key值)并且结果跟抓包数据完全一致。这时候主要的问题就是找到使用的默认算法。可以看到第一个调用的函数就是java.security.MessageDigest.digest(Native Method),该类提供了消息摘要算法,如 MD5 或 SHA,的功能。因此可以模拟测试一下,看一下采用的是MD5还是SHA算法。


测试发现采用的是SHA1算法。noncestr就是随机8位数字组成,每次可以采用上一次所生成的,或者是自己生成即可。noncestr算法还原。

Random random=new Random();
for(int i=0;i<8;i++){    str.append(random.nextInt(10));}
int num=Integer.parseInt(str.toString());System.out.println(num);

此外,对于bbsapi.domo.cn,它的update是

appSignKey=Wj8BI3VUZ6BuojAkqzBM3HWHNHv08xdZEtaksbRg6snnuLsvivwa8IvR6PvQ76H0IQQsqkIsa5OKJtg6QcBMfCblMMywgZaA8co&noncestr=63606047×tamp=1627023164959采用了不同的key值来进行拼接。

总结

该app的签名算法还原后即为,key+noncestr+timestamp。之后进行SHA1加密,生成sign。最后附上frida hook代码

    base64DecodeChars = new Array((-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), 62, (-1), (-1), (-1), 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, (-1), (-1), (-1), (-1), (-1), (-1), (-1), 0, 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, (-1), (-1), (-1), (-1), (-1), (-1), 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, (-1), (-1), (-1), (-1), (-1));var stringToBase64 = function (e) {    var r, a, c, h, o, t;    for (c = e.length, a = 0, r = ''; a < c;) {        if (h = 255 & e.charCodeAt(a++), a == c) {            r += base64EncodeChars.charAt(h >> 2),                r += base64EncodeChars.charAt((3 & h) << 4),                r += '==';            break        }        if (o = e.charCodeAt(a++), a == c) {            r += base64EncodeChars.charAt(h >> 2),                r += base64EncodeChars.charAt((3 & h) << 4 | (240 & o) >> 4),                r += base64EncodeChars.charAt((15 & o) << 2),                r += '=';            break        }        t = e.charCodeAt(a++),            r += base64EncodeChars.charAt(h >> 2),            r += base64EncodeChars.charAt((3 & h) << 4 | (240 & o) >> 4),            r += base64EncodeChars.charAt((15 & o) << 2 | (192 & t) >> 6),            r += base64EncodeChars.charAt(63 & t)    }    return r}var base64ToString = function (e) {    var r, a, c, h, o, t, d;    for (t = e.length, o = 0, d = ''; o < t;) {        do            r = base64DecodeChars[255 & e.charCodeAt(o++)];        while (o < t && r == -1);        if (r == -1)            break;        do            a = base64DecodeChars[255 & e.charCodeAt(o++)];        while (o < t && a == -1);        if (a == -1)            break;        d += String.fromCharCode(r << 2 | (48 & a) >> 4);        do {            if (c = 255 & e.charCodeAt(o++), 61 == c)                return d;            c = base64DecodeChars[c]        } while (o < t && c == -1);        if (c == -1)            break;        d += String.fromCharCode((15 & a) << 4 | (60 & c) >> 2);        do {            if (h = 255 & e.charCodeAt(o++), 61 == h)                return d;            h = base64DecodeChars[h]        } while (o < t && h == -1);        if (h == -1)            break;        d += String.fromCharCode((3 & c) << 6 | h)    }    return d}

var hexToBytes = function (str) {    var pos = 0;    var len = str.length;    if (len % 2 != 0) {        return null;    }    len /= 2;    var hexA = new Array();    for (var i = 0; i < len; i++) {        var s = str.substr(pos, 2);        var v = parseInt(s, 16);        hexA.push(v);        pos += 2;    }    return hexA;}var bytesToHex = function (arr) {    var str = '';    var k, j;    for (var i = 0; i < arr.length; i++) {        k = arr[i];        j = k;        if (k < 0) {            j = k + 256;        }        if (j < 16) {            str += "0";        }        str += j.toString(16);    }    return str;}var stringToHex = function (str) {    var val = "";    for (var i = 0; i < str.length; i++) {        if (val == "")            val = str.charCodeAt(i).toString(16);        else            val += str.charCodeAt(i).toString(16);    }    return val}var stringToBytes = function (str) {    var ch, st, re = [];    for (var i = 0; i < str.length; i++) {        ch = str.charCodeAt(i);        st = [];
        do {            st.push(ch & 0xFF);            ch = ch >> 8;        }        while (ch);        re = re.concat(st.reverse());    }    return re;}
var bytesToString = function (arr) {    var str = '';    arr = new Uint8Array(arr);    for (var i in arr) {        str += String.fromCharCode(arr[i]);    }    return str;}var bytesToBase64 = function (e) {    var r, a, c, h, o, t;    for (c = e.length, a = 0, r = ''; a < c;) {        if (h = 255 & e[a++], a == c) {            r += base64EncodeChars.charAt(h >> 2),                r += base64EncodeChars.charAt((3 & h) << 4),                r += '==';            break        }        if (o = e[a++], a == c) {            r += base64EncodeChars.charAt(h >> 2),                r += base64EncodeChars.charAt((3 & h) << 4 | (240 & o) >> 4),                r += base64EncodeChars.charAt((15 & o) << 2),                r += '=';            break        }        t = e[a++],            r += base64EncodeChars.charAt(h >> 2),            r += base64EncodeChars.charAt((3 & h) << 4 | (240 & o) >> 4),            r += base64EncodeChars.charAt((15 & o) << 2 | (192 & t) >> 6),            r += base64EncodeChars.charAt(63 & t)    }    return r}var base64ToBytes = function (e) {    var r, a, c, h, o, t, d;    for (t = e.length, o = 0, d = []; o < t;) {        do            r = base64DecodeChars[255 & e.charCodeAt(o++)];        while (o < t && r == -1);        if (r == -1)            break;        do            a = base64DecodeChars[255 & e.charCodeAt(o++)];        while (o < t && a == -1);        if (a == -1)            break;        d.push(r << 2 | (48 & a) >> 4);        do {            if (c = 255 & e.charCodeAt(o++), 61 == c)                return d;            c = base64DecodeChars[c]        } while (o < t && c == -1);        if (c == -1)            break;        d.push((15 & a) << 4 | (60 & c) >> 2);        do {            if (h = 255 & e.charCodeAt(o++), 61 == h)                return d;            h = base64DecodeChars[h]        } while (o < t && h == -1);        if (h == -1)            break;        d.push((3 & c) << 6 | h)    }    return d}

Java.perform(function () {
    function showStacks() {        console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new()));    }

    var secretKeySpec = Java.use('javax.crypto.spec.SecretKeySpec');    secretKeySpec.$init.overload('[B', 'java.lang.String').implementation = function (a, b) {        showStacks();        var result = this.$init(a, b);        console.log("======================================");        console.log("算法名:" + b + "|Dec密钥:" + bytesToString(a));        console.log("算法名:" + b + "|Hex密钥:" + bytesToHex(a));        return result;    }

    var mac = Java.use('javax.crypto.Mac');    mac.getInstance.overload('java.lang.String').implementation = function (a) {        showStacks();        var result = this.getInstance(a);        console.log("======================================");        console.log("算法名:" + a);        return result;    }    mac.update.overload('[B').implementation = function (a) {        showStacks();        this.update(a);        console.log("======================================");        console.log("update:" + bytesToString(a))    }    mac.update.overload('[B', 'int', 'int').implementation = function (a, b, c) {        showStacks();        this.update(a, b, c)        console.log("======================================");        console.log("update:" + bytesToString(a) + "|" + b + "|" + c);    }    mac.doFinal.overload().implementation = function () {        showStacks();        var result = this.doFinal();        console.log("======================================");        console.log("doFinal结果:" + bytesToHex(result));        console.log("doFinal结果:" + bytesToBase64(result));        return result;    }    mac.doFinal.overload('[B').implementation = function (a) {        showStacks();        var result = this.doFinal(a);        console.log("======================================");        console.log("doFinal参数:" + bytesToString(a));        console.log("doFinal结果:" + bytesToHex(result));        console.log("doFinal结果:" + bytesToBase64(result));        return result;    }

    var md = Java.use('java.security.MessageDigest');    md.getInstance.overload('java.lang.String', 'java.lang.String').implementation = function (a, b) {        showStacks();        console.log("======================================");        console.log("算法名:" + a);        return this.getInstance(a, b);    }    md.getInstance.overload('java.lang.String').implementation = function (a) {        showStacks();        console.log("======================================");        console.log("算法名:" + a);        return this.getInstance(a);    }
    md.update.overload('[B').implementation = function (a) {        showStacks();        console.log("======================================");        console.log("update:" + bytesToString(a))        return this.update(a);    }    md.update.overload('[B', 'int', 'int').implementation = function (a, b, c) {        showStacks();        console.log("======================================");        console.log("update:" + bytesToString(a) + "|" + b + "|" + c);        return this.update(a, b, c);    }
    md.digest.overload().implementation = function () {        showStacks();        console.log("======================================");        var result = this.digest();        console.log("digest结果:" + bytesToHex(result));        console.log("digest结果:" + bytesToBase64(result));        return result;    }    md.digest.overload('[B').implementation = function (a) {        showStacks();        console.log("======================================");        console.log("digest参数:" + bytesToString(a));        var result = this.digest(a);        console.log("digest结果:" + bytesToHex(result));        console.log("digest结果:" + bytesToBase64(result));        return result;    }

    var ivParameterSpec = Java.use('javax.crypto.spec.IvParameterSpec');    ivParameterSpec.$init.overload('[B').implementation = function (a) {        showStacks();        var result = this.$init(a);        console.log("======================================");        console.log("iv向量:" + bytesToString(a));        console.log("iv向量:" + bytesToHex(a));        return result;    }

    var cipher = Java.use('javax.crypto.Cipher');    cipher.getInstance.overload('java.lang.String').implementation = function (a) {        showStacks();        var result = this.getInstance(a);        console.log("======================================");        console.log("模式填充:" + a);        return result;    }    cipher.update.overload('[B').implementation = function (a) {        showStacks();        var result = this.update(a);        console.log("======================================");        console.log("update:" + bytesToString(a));        return result;    }    cipher.update.overload('[B', 'int', 'int').implementation = function (a, b, c) {        showStacks();        var result = this.update(a, b, c);        console.log("======================================");        console.log("update:" + bytesToString(a) + "|" + b + "|" + c);        return result;    }    cipher.doFinal.overload().implementation = function () {        showStacks();        var result = this.doFinal();        console.log("======================================");        console.log("doFinal结果:" + bytesToHex(result));        console.log("doFinal结果:" + bytesToBase64(result));        return result;    }    cipher.doFinal.overload('[B').implementation = function (a) {        showStacks();        var result = this.doFinal(a);        console.log("======================================");        console.log("doFinal参数:" + bytesToString(a));        console.log("doFinal结果:" + bytesToHex(result));        console.log("doFinal结果:" + bytesToBase64(result));        return result;    }

    var x509EncodedKeySpec = Java.use('java.security.spec.X509EncodedKeySpec');    x509EncodedKeySpec.$init.overload('[B').implementation = function (a) {        showStacks();        var result = this.$init(a);        console.log("======================================");        console.log("RSA密钥:" + bytesToBase64(a));        return result;    }

    var rSAPublicKeySpec = Java.use('java.security.spec.RSAPublicKeySpec');    rSAPublicKeySpec.$init.overload('java.math.BigInteger', 'java.math.BigInteger').implementation = function (a, b) {        showStacks();        var result = this.$init(a, b);        console.log("======================================");        //console.log("RSA密钥:" + bytesToBase64(a));        console.log("RSA密钥N:" + a.toString(16));        console.log("RSA密钥E:" + b.toString(16));        return result;    }
});

算法还原之后,下一篇将进行完结,打造bp插件,实现无缝衔接测试。

var
本作品采用《CC 协议》,转载必须注明作者和本文链接
据外媒报道,在检测到其系统受到网络攻击近两周后,德国电池制造商Varta AG仍未恢复其工厂的生产。该公司生产各种用于家用和工业用途的电池和存储产品,包括锂离子小型化电池和移动电源。Varta AG首次检测到对其系统的网络攻击2月12日,其本周发表声明称,目前没有可靠的信息表明处理和解决攻击需要多长时间,或者所有五个全球生产基地的何时将全面投入运营。然而,工厂的第一批预计将在下周再次启动。
第十一届信息安全漏洞分析与风险评估大会9月16日,2018世界物联网博览会信息安全高峰论坛暨第十一届信息安全漏洞分析与风险评估大会在无锡市滨湖区成功举办。来自政府相关主管部门、重要行业、高等院校、研究机构、物联网产业界、信息安全产业界等近千名中外嘉宾参加了大会。来自政府部门、高等院校、研究机构、信息安全产业界及应用单位的800余名嘉宾参加了大会。
/usr/目录usr是user的缩写,是曾经的HOME目录,然而现在已经被/home取代了,现在usr被称为是Unix System Resource,即Unix系统资源的缩写。默认软件都会存于该目录下。用于存储只读用户数据的第二层次;包含绝大多数的用户工具和应用程序。
作为世界物联网博览会高峰论坛之一的“2021世界物联网博览会信息安全高峰论坛暨第十三届信息安全漏洞分析与风险评估大会”,将于10月22日召开。
“2021世界物联网博览会信息安全高峰论坛暨第十三届信息安全漏洞分析与风险评估大会”(VARA大会)将于10月22日在江苏省无锡市召开。
Python人工智能第11篇文章介绍如何保存神经网络参数
动态函数PHP中支持一个功能叫 variable function ,变量函数的意思。//最终是system;当一个变量后边带括号,那他就被视作一个函数。编译器会解析出变量的值,然后会去找当前是否存在名为“system()”的函数并执行它。这里就不给实例了,很多免杀案例中都用到了这个特性。也是被疯狂查杀的特征。回调函数回调函数,简单来说就是一个函数不是由我直接调用,而是通过另一个函数去调用它。
1Docker 迁移存储目录默认情况系统会将 Docker 容器存放在 /var/lib/docker 目录下[问题起因]?今天通过监控系统,发现公司其中一台服务器的磁盘快慢,随即上去看了下,发现?由上述原因,我们都知道,在?中存储的都是相关于容器的存储,所以也不能随便的将其删除掉。设备进行扩容来达到相同的目的。的详细参数,请点击查看?但是需要注意的一点就是,尽量不要用软链, 因为一些?容器编排系统不支持这样做,比如我们所熟知的?发现容器启动不了了
在本次2022 RSAC会议中,来自Varonis公司的Matt Radolec分享了议题《Pain in the Apps — Three Attack Scenarios Attackers Are Using to PWN SaaS》,主要介绍了三种针对SaaS平台的攻击场景,详细地说明了每个阶段攻击者的攻击手法以及对应的检测思路
Meta Platforms 近日采取了一系列措施制裁来自意大利、西班牙和阿拉伯等多国的八家间谍软件公司,分别是 Cy4Gate/ELT Group、RCS Labs、IPS Intelligence、Variston IT、TrueL IT、Protect Electronic Systems、Negg Group 和 Mollitiam Industries。
VSole
网络安全专家