• 某车联网App 通讯协议加密分析


    一、目标

    李老板:最近刚买了辆新车,他带的App挺有意思,要不要盘一盘?

    奋飞: 我去,加壳了,还挺有意思,搞一搞。

    v6.1.0

    二、步骤

    抓包

    我的抓包环境是 Mac 10.14.6 + httpToolKit, 这一步很顺利的抓到包了。

    main.png

    可以看到,http请求和返回值都是加密的,我们的目标就是这个 request 和 response的来历的。

    脱壳

    脱壳我们首选的就是 BlackDex ,使用方便,效果好。不过针对这个样本却不好使,只脱出来一个dex,大概率是失败了。

    然后就上葫芦娃大佬的 FRIDA-DEXDump, 这个样本很狡猾,frida Spawn模式一跑就崩,看来壳比较硬。

    这下棘手了,回想起之前我们曾经用 Xcube http://91fans.com.cn/post/antifridaoper/ 来对付加壳应用, 这次继续试试。

    Xcube没有让我们失望,注入成功。 但是在Xcube模式下如何跑 FRIDA-DEXDump 呢?

    本来想改改代码合并进去。无意中发现yang神也写了个DumpDex https://github.com/lasting-yang/frida_dump

    轻松合并到js里面,这下成功的Dump出来了。

    字符串搜索

    直接搜索 “request” 结果不多。

    find.png

    很明显,这个 jsonObject.addProperty 嫌疑最大。

    点进去看看,顺利的找到了这个 CheckCodeUtil 类。 他的checkcode和decheckcode大概率就是我们这次的目标。

    utilcls.png

    hook 之

    var CheckCodeUtilCls = Java.use("com.bangcle.comapiprotect.CheckCodeUtil");
    CheckCodeUtilCls.checkcode.implementation = function(a,b,c){
        var rc = this.checkcode(a,b,c);
        console.log(TAG + "checkcode >>> a = " + a);
        console.log(TAG + "checkcode >>> b = " + b);
        console.log(TAG + "checkcode >>> c = " + c);
        console.log(TAG + "checkcode >>> rc = " + rc);
        return rc;
    }
    
    CheckCodeUtilCls.decheckcode.implementation = function(a){
        var rc = this.decheckcode(a);
        console.log(TAG + "decheckcode >>> a = " + a);
        console.log(TAG + "decheckcode >>> rc = " + rc);
        return rc;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    跑一下,奇怪,一点输出都没有,这个没有道理呀?

    仔细看看,犯傻了,这类里面有两个checkcode函数,所以我们需要指定hook哪一个。 由于我们打印输出的时候忽略了错误输出,导致没有看到报错。

    注意: Xcube环境下如果没有任何输出,大概率是脚本报错了,这时候就不要过滤,直接查看全部输出日志,就可以看到报错了。

    改改代码

    CheckCodeUtilCls.checkcode.overload('java.lang.String','int','java.lang.String').implementation = function(a,b,c){
    
    • 1

    这次果然很顺利的打印出来了结果。但是奇怪的事情又发生了,App崩了。

    挽救崩溃的App

    为什么会崩,难道是我们打印数据有bug?

    先把打印入参和结果的代码注释掉。 还是崩。

    把所有hook代码注释掉,不崩了,但是我不hook没法玩呀?

    使出终极大法,换手机。 很多时候换个手机 就好了,也许这个手机水土不服吧。

    结果打脸了,换了手机依然崩溃。

    木有任何侥幸心理了,说明App或者壳,对关键函数的Hook做了检测,发觉被hook就摆烂。

    App或者壳肯定是在Native层做的检测,我们要对付它,就得和它站在同一高度。

    不去hook Jave层的函数了,直接取hook Native层的 checkcode 和 decheckcode。

    Hook Native

    从CheckCodeUtil的java代码中找到了 System.loadLibrary(“encrypt”);, 说明我们要对付的目标是 libencrypt.so。

    IDA打开它,导出表,暴露了两个信息。

    1、checkcode函数的地址在 0x24424 , decheckcode函数的地址在 0x2B1BC 。

    2、这两个函数大概率使用了AES算法。

    继续Hook吧

    var targetSo = Module.findBaseAddress('libencrypt.so');
    console.log(TAG +" ############# libencrypt.so: " +targetSo);
    // 24424    2B1BC
    
    Interceptor.attach(targetSo.add(0x24424 ),{
        onEnter: function(args){
            var strCls = Java.use('java.lang.String');
    
            this.rBuf = ptr(this.context.x0);
            console.log(TAG + " ======================================== ");
            // console.log(TAG + "-------- checkcode x0 = " + ptr(this.context.x0) ) ;
    
            var strA = Java.cast(this.context.x2, strCls);
            console.log(TAG + "-------- checkcode a = " + strA);
    
            console.log(TAG + "-------- checkcode b = " + ptr(this.context.x3));
    
            var strC = Java.cast(this.context.x4, strCls);
            console.log(TAG + "-------- checkcode b = " + strC);
    
    
        },
        onLeave: function(retval){
            var strCls = Java.use('java.lang.String');
            var strRc = Java.cast(retval, strCls);
            console.log(TAG + "-------- checkcode rc = " + strRc);
    
        }
    });
    
    Interceptor.attach(targetSo.add(0x2B1BC ),{
        onEnter: function(args){
            var strCls = Java.use('java.lang.String');
    
            this.rBuf = ptr(this.context.x0);
            console.log(TAG + " ======================================== ");
            // console.log(TAG + "-------- checkcode x0 = " + ptr(this.context.x0) ) ;
    
            var strA = Java.cast(this.context.x2, strCls);
            console.log(TAG + "-------- decheckcode a = " + strA);
    
    
        },
        onLeave: function(retval){
            var strCls = Java.use('java.lang.String');
            var strRc = Java.cast(retval, strCls);
            console.log(TAG + "-------- decheckcode rc = " + strRc);
    
        }
    });
    
    • 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
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    李老板: 奋飞呀,函数的第一个参数不是 X0吗? 你为什么打印第一个参数是 X2 ?

    奋飞: 老板,早就让你多批点经费买书,你不同意,这下露怯了吧。去翻翻 jni编程就知道了,java调用C/C++ 函数的前两个参数是固定的。真实传递进来的参数是从第三个开始。

    这次再跑一下。

    rc.png

    真相大白了。

    帝都已经有秋意了,不好上鲜啤了,差不多可以上二锅头了。

    三、总结

    脱壳方法千千万,重点还是Dump Dex。

    加壳应用不要怕,大概率脱完壳之后就都是线索了。不加壳的App才是真的可怕。

    脚本没有任何输出,不一定是位置找错了,还有可能是脚本的报错你没有看到。

    App崩了,换手机是有效的,虽然这次打脸了。

    针对这个样本,IDA还告诉我们,so里面的函数入口的代码也被抽取了,要分析这个so,估计还得Dump so。

    ffshow.jpg

    只有去穿越和反思痛苦,才能得到更高的思想深度,没有捷径

  • 相关阅读:
    获取vue当前页面url问号后面的参数
    kubernetes进阶 (三) 基础练习
    20240425金融读报:银行绿色数据收集ESG&银行物联网智慧贵金属案例
    SQL语句记了又忘?常用的SQL语句,配语句和图解超详细o
    ALTERA FPGA IPCORE核之单口RAM详细教程
    Kubernetes学习笔记-保障集群内节点和网络安全(3)限制pod使用安全相关的特性20220828
    Linux自用命令
    C++面试八股文:什么是RAII?
    如何做好网页配色,分享一些配色方案和方法
    C++模板与STL(六):内存空间配置器及内存池技术模拟
  • 原文地址:https://blog.csdn.net/fenfei331/article/details/126609168