某车联网App 通讯协议加密分析(二) Unidbg手把手跑通

公众号: 奋飞安全
• 阅读 412

一、目标

有一段时间没有写unidbg相关的文章了,这个样本挺合适,难度适中,还适当给你挖个小坑。所以后面是一个系列文章,包含 unidbg补环境,Trace Block 对比流程,Trace Code定位差异。掌握好这一系列套路,Native分析可以算入门了。

这次先来把so用unidbg跑通

v6.1.0

二、步骤

Dump so

IDA打开 libencrypt.so 去到我们要分析的两个函数 checkcode 和 decheckcode 对应的偏移地址 0x24424 , 0x2B1BC 。会发现一个奇怪的问题,这两个地址上没有汇编代码,都是 0x00。

估计是壳给我们加戏了,他把这两个关键函数的部分代码给抽取了,等到运行的时候才会补回去,这样就阻碍你去静态分析这个so。

不过壳怎么加戏,运行的时候是一定会在内存中存在完整的代码的,否则App是跑不起来的。

所以我们也加戏,Dump 之

function dumpSo(){
    var libxx = Process.getModuleByName("libencrypt.so");
    console.log("*****************************************************");
    console.log(TAG + "name: " +libxx.name);
    console.log(TAG + "base: " +libxx.base);
    console.log(TAG + "size: " +ptr(libxx.size));

    var file_path = "/data/data/com.xxx.aeri.caranywhere/" + libxx.name + "_" + libxx.base + "_" + ptr(libxx.size) + ".so";
    console.log(TAG + file_path);

    var file_handle = new File(file_path, "wb");
    if (file_handle && file_handle != null) {
        Memory.protect(ptr(libxx.base), libxx.size, 'rwx');
        var libso_buffer = ptr(libxx.base).readByteArray(libxx.size);
        file_handle.write(libso_buffer);
        file_handle.flush();
        file_handle.close();
        console.log(TAG + "[dump]:", file_path);
    }
}

unidbg run so 基本框架

dump出so的完整代码了,我们开始撘unidbg run so的基本框架。unidbg的库代码可以从原作者的github上下载最新的。

public class CaranywhereDemo  extends AbstractJni {
    public AndroidEmulator emulator;
    public VM vm;
    public Module module;
    public DvmClass dvmClass;

    public static void main(String[] args) throws DecoderException, IOException {
        String apkPath = "/Users/fenfei/Desktop/xxx/6.1.0.apk";
        CaranywhereDemo carObj = new CaranywhereDemo(apkPath);
        carObj.destroy();
    }

    public CaranywhereDemo(String apkFilePath) throws DecoderException, IOException {
        //*
        emulator = AndroidEmulatorBuilder.for64Bit()
                .setProcessName("com.xxx.aeri.caranywhere")
                .addBackendFactory(new Unicorn2Factory(true))
                .build(); // 创建模拟器实例,要模拟32位或者64位,在这里区分
        //*/

        // 多线程处理  true的情况下 在 faccessat  的时候就卡死, 所以关掉, 这个样本暂时也不需要多线程
        emulator.getSyscallHandler().setEnableThreadDispatcher(false);

        final Memory memory = emulator.getMemory();                // 模拟器的内存操作接口
        memory.setLibraryResolver(new AndroidResolver(23)); // 设置系统类库解析

        vm = emulator.createDalvikVM(new File(apkFilePath)); // 创建Android虚拟机
        vm.setJni(this);
        vm.setVerbose(true); // 设置是否打印Jni调用细节

        new JniGraphics(emulator, vm).register(memory);
        new AndroidModule(emulator, vm).register(memory);

                dvmClass = vm.resolveClass("com/bangcle/comapiprotect/CheckCodeUtil");

                DalvikModule dm = vm.loadLibrary("encrypt", false);

        module = dm.getModule();
        dm.callJNI_OnLoad(emulator);
    }
    private void destroy() {
        try {
            emulator.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

一个unidbg的Hello World就是这样,先别着急跑它,之前我们说过,so的关键代码被抽取了,所以不能直接跑so。得换成我们dump出来的结果。

// DalvikModule dm = vm.loadLibrary("encrypt", false);
DalvikModule dm = vm.loadLibrary(new File("/Users/fenfei/Desktop/work/blogCode/xxx/libencrypt.so_0x7634ee7000_0x1d6000.so"), false);

一步一步补unidbg run so环境

先看第一个错误

[07:17:09 068]  WARN [com.github.unidbg.linux.ARM64SyscallHandler] (ARM64SyscallHandler:384) - handleInterrupt intno=2, NR=30, svcNumber=0x16e, PC=unidbg@0xfffe0774, LR=RX@0x40018c9c[libencrypt.so]0x18c9c, syscall=null
java.lang.UnsupportedOperationException: android/app/ActivityThread->currentActivityThread()Landroid/app/ActivityThread;
        at com.github.unidbg.linux.android.dvm.AbstractJni.callStaticObjectMethod(AbstractJni.java:432)
        at com.github.unidbg.linux.android.dvm.AbstractJni.callStaticObjectMethod(AbstractJni.java:421)
        at com.github.unidbg.linux.android.dvm.DvmMethod.callStaticObjectMethod(DvmMethod.java:59)
        at com.github.unidbg.linux.android.dvm.DalvikVM64$111.handle(DalvikVM64.java:1723)
        at com.github.unidbg.linux.ARM64SyscallHandler.hook(ARM64SyscallHandler.java:130)

unidbg的报错提示非常清晰,说明在 callStaticObjectMethod 中调用 ActivityThread 类的静态方法 currentActivityThread,并且返回值是 ActivityThread 类型。

我们在CaranywhereDemo.java中重载 callStaticObjectMethod 函数来解决这个问题:

@Override
    public DvmObject<?> callStaticObjectMethod(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
        switch (signature) {
            case "android/app/ActivityThread->currentActivityThread()Landroid/app/ActivityThread;":
                return vm.resolveClass("android/app/ActivityThread").newObject(null);
        }
        return super.callStaticObjectMethod(vm, dvmClass, signature, varArg);
    }

我们先不关心这个 currentActivityThread 要被做什么用,直接返回一个空的类就行。

继续跑,下一个错误还是在 callStaticObjectMethod 里面,看上去像是获取 一些系统信息

java.lang.UnsupportedOperationException: android/os/SystemProperties->get(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
        at com.github.unidbg.linux.android.dvm.AbstractJni.callStaticObjectMethod(AbstractJni.java:432)
        at com.fenfei.test.CaranywhereDemo.callStaticObjectMethod(CaranywhereDemo.java:78)
        at com.github.unidbg.linux.android.dvm.AbstractJni.callStaticObjectMethod(AbstractJni.java:421)
        at com.github.unidbg.linux.android.dvm.DvmMethod.callStaticObjectMethod(DvmMethod.java:59)
        at com.github.unidbg.linux.android.dvm.DalvikVM64$111.handle(DalvikVM64.java:1723)
        at com.github.unidbg.linux.ARM64SyscallHandler.hook(ARM64SyscallHandler.java:130)

补这中有入参的函数可以简单粗暴的给他返回一个空字符串,但是讲究人先要把他的入参打印出来。

@Override
    public DvmObject<?> callStaticObjectMethod(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
        switch (signature) {
            case "android/os/SystemProperties->get(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;":
                System.out.println("android/os/SystemProperties->get 入参:" + varArg.formatArgs());
                return new StringObject(vm, "705KPGS001091");

            case "android/app/ActivityThread->currentActivityThread()Landroid/app/ActivityThread;":
                return vm.resolveClass("android/app/ActivityThread").newObject(null);
        }
        return super.callStaticObjectMethod(vm, dvmClass, signature, varArg);
    }
Tip:

unidbg库函数formatArgs提示不是公有函数,在VarArg类里面改下 public final String formatArgs()

输出

android/os/SystemProperties->get 入参:"ro.serialno", "unknown"

原来是为了获取Android序列号,随便给他编一个就好, 由于本样本只调用一次,所以就懒得判断入参了。

Tip:

adb shell getprop ro.serialno 可以获取到Android序列号

这个报错和第一个报错类似,不过它是在 callObjectMethod 函数里面

java.lang.UnsupportedOperationException: android/app/ActivityThread->getSystemContext()Landroid/app/ContextImpl;
        at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethod(AbstractJni.java:921)
        at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethod(AbstractJni.java:855)
        at com.github.unidbg.linux.android.dvm.DvmMethod.callObjectMethod(DvmMethod.java:74)
        at com.github.unidbg.linux.android.dvm.DalvikVM64$31.handle(DalvikVM64.java:504)
        at com.github.unidbg.linux.ARM64SyscallHandler.hook(ARM64SyscallHandler.java:130)

重载 callObjectMethod 函数,然后 直接返回 ContextImpl 类型就行

public DvmObject<?> callObjectMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {
        switch (signature) {
            case "android/app/ActivityThread->getSystemContext()Landroid/app/ContextImpl;":
                return vm.resolveClass("android/app/ContextImpl").newObject(null);
        }

        return super.callObjectMethod(vm, dvmObject, signature, varArg);
    }

继续报错,这个是获取包管理类

java.lang.UnsupportedOperationException: android/app/ContextImpl->getPackageManager()Landroid/content/pm/PackageManager;
        at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethod(AbstractJni.java:921)
        at com.fenfei.test.CaranywhereDemo.callObjectMethod(CaranywhereDemo.java:91)
        at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethod(AbstractJni.java:855)
        at com.github.unidbg.linux.android.dvm.DvmMethod.callObjectMethod(DvmMethod.java:74)
        at com.github.unidbg.linux.android.dvm.DalvikVM64$31.handle(DalvikVM64.java:504)
        at com.github.unidbg.linux.ARM64SyscallHandler.hook(ARM64SyscallHandler.java:130)

callObjectMethod 构造 PackageManager类返回

case "android/app/ContextImpl->getPackageManager()Landroid/content/pm/PackageManager;":
                return vm.resolveClass("android/content/pm/PackageManager").newObject(null);

下一个报错。

java.lang.UnsupportedOperationException: android/app/ContextImpl->getSystemService(Ljava/lang/String;)Ljava/lang/Object;
        at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethod(AbstractJni.java:921)
        at com.fenfei.test.CaranywhereDemo.callObjectMethod(CaranywhereDemo.java:93)
        at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethod(AbstractJni.java:855)
        at com.github.unidbg.linux.android.dvm.DvmMethod.callObjectMethod(DvmMethod.java:74)

这个函数有参数,老规矩,打印下参数看看。

case "android/app/ContextImpl->getSystemService(Ljava/lang/String;)Ljava/lang/Object;":
                System.out.println("android/app/ContextImpl->getSystemService 入参:" + varArg.formatArgs());
                return vm.resolveClass("java/lang/Object").newObject(null);

参数打印出来是

android/app/ContextImpl->getSystemService 入参:"wifi"

估摸是wifi相关,先给他返回一个空的 Object再说

后面两个报错就是获取wifi Mac地址相关的,直接补上

case "android/net/wifi/WifiInfo->getMacAddress()Ljava/lang/String;":
                return new StringObject(vm, "00:00:00:00:00:00");
            case "java/lang/Object->getConnectionInfo()Landroid/net/wifi/WifiInfo;":
                return vm.resolveClass("android/net/wifi/WifiInfo").newObject(null);

到这里终于把 JNI_OnLoad 给跑通了, 可以干一杯了。

call checkcode

搞了半天才开始进入正题,我们来调用 checkcode

public void callA() {

    String strA = "F{"appInnerVersion":"125","appOutVersion":"6.1.0","deviceType":0,"imeiMD5":"EE6431DEBB1E02FE469FA5E8467CD693","mobileModel":"GOOGLE PIXEL 2 XL","softType":"0"}";
    String strC = "1662109202156";

    String methodName = "checkcode(Ljava/lang/String;ILjava/lang/String;)Ljava/lang/String;";
    DvmObject ret = dvmClass.callStaticJniMethodObject(emulator, methodName,strA,1,strC);
    String strOut = (String)ret.getValue();
    System.out.println("call checkcode: " + strOut);
}

又有新的报错了

java.lang.UnsupportedOperationException: android/os/Build->MODEL:Ljava/lang/String;
        at com.github.unidbg.linux.android.dvm.AbstractJni.getStaticObjectField(AbstractJni.java:103)
        at com.github.unidbg.linux.android.dvm.AbstractJni.getStaticObjectField(AbstractJni.java:53)
        at com.github.unidbg.linux.android.dvm.DvmField.getStaticObjectField(DvmField.java:106)
        at com.github.unidbg.linux.android.dvm.DalvikVM64$142.handle(DalvikVM64.java:2228)

这次要重载 getStaticObjectField 类了

public DvmObject<?> getStaticObjectField(BaseVM vm, DvmClass dvmClass, String signature) {
        switch (signature){
            case "android/os/Build$VERSION->SDK:Ljava/lang/String;":
                return new StringObject(vm, "23");
            case "android/os/Build->MANUFACTURER:Ljava/lang/String;":
                return new StringObject(vm, "Google");
            case "android/os/Build->MODEL:Ljava/lang/String;":
                return new StringObject(vm, "pixel");
        }
        return super.getStaticObjectField(vm,dvmClass,signature);
    }

这几个值比较简单,都是字符串型,我们给他赋值一把。

耶,大功告成了

call checkcode: FDAEKCAcOAQcNBgkEAwoCDQgEDQ4JBAgAAA4ODgcJBgkKBwYPBwwEBw4LBwsODQcFDQMMCAUJDAcEDQsADwEDDAIGDgQJAQYNDggNDQsCAQcNAwwIBQkMBwQNCwAPAQMMDQYCDAgBBQwGBAUIAwULBAoHBg8HDAQHDgsHCw4NBwUPAwEADAkPBQcODAcDDgYCDwMJCQUEAAgHDAUIBwEDBwMKDgcGBg4NDAgLBAAEAw8PAwEADAkPBQcODAcDDgYCCAQNDgkECAAADg4OBwkGCQIIAwkLBgACCgoGAgcCAwEMAQoIBw4BBw0GCQQDCgINDwMBAAwJDwUHDgwHAw4GAggHBwEMBAAAAwMJDQUDDQECBg4ECQEGDQ4IDQ0LAgEHDw0ADwMJDgQICAsJAgILBw8NAA8DCQ4ECAgLCQICCwcNBgIMCAEFDAYEBQgDBQsEAgYOBAkBBg0OCA0NCwIBBw==

先别高兴的太早了,这个结果怎么看都有点不对劲,和我们hook的结果相差有点大。

怎么判断结果是对是错?怎么和app对比来拿到正确的结果? 等待下次的 Trace Block 和 Trace Code 教程吧。

三、总结

unidbg补环境实际是考验你的Android编程能力。

谷歌一下关键字 unidbg + 报错信息,一般都有同道趟过坑。

什么?你打不开谷歌?我现在劝你改行还来得及吗?

某车联网App 通讯协议加密分析(二) Unidbg手把手跑通

1:ffshow

好味止园葵,大欢止稚子。平生不止酒,止酒情无喜。

Tip:

: 本文的目的只有一个就是学习更多的逆向技巧和思路,如果有人利用本文技术去进行非法商业获取利益带来的法律责任都是操作者自己承担,和本文以及作者没关系,本文涉及到的代码项目可以去 奋飞的朋友们 知识星球自取,欢迎加入知识星球一起学习探讨技术。有问题可以加我wx: fenfei331 讨论下。

关注微信公众号:奋飞安全,最新技术干货实时推送

点赞
收藏
评论区
推荐文章
https://cloud.tencent.com/developer/article/write/1830331
一、目标今天的目标是这个sign和appcode二、步骤Jadx没法上了app加了某梆的企业版,Jadx表示无能为力了。FRIDADEXDumpDexDump出来,木有找到有效的信息。Wallbreaker葫芦娃的Wallbreaker可以做些带壳分析,不过这个样本,用Frida的Spawn模式可以载入,Attach模式会失败。而直接用Objecti
某A系电商App x-sign签名分析
一、目标前不久(我去,都大半年了)分析过我们找到了几个伪装成so的jar包。虽然rpc调用ok了,但是它的实际运算过程还是在so里面的。今天我们从它们同族的App来入手,利用Native层字符串定位的方式来定位下在哪个so中去做的运算。App版本:v4.15.1二、步骤特征字符串定位一开始选择的特征字符串是x,后来发现没有xsign好使In
Unidbg + Web = Unidbg-server 手把手教你搭个签名服务器
一、目标爆肝熬夜,终于把so用unidbg跑起来了,总不能放在硬盘里发霉吧。我们得在生成环境下用起来。最方便的方式就是租台云服务器,然后把Unidbg部署上去,就可以给我们的工作者程序提供云签名服务了。unidbgUnidbgserver二、步骤Unidbgserver玩Unidbg还得是java老艺术家才行,cxapython大佬提供了一个spr
某汽车社区App 签名和加解密分析 (二) : Frida Dump so
一、目标App安全的主战场在Native层,分析Native层的so,最趁手的兵器就是Frida和Unidbg了。今天我们的目标是某汽车社区Appv8.0.1so的分析。二、步骤特征字符串定位我们在上一篇教程已经定位了,数据加密和解密函数再java层的位置。按照常理来说,这个java类文件中,应该有个System.loadLibrary("
某A系电商App doCommandNative浅析
一、目标李老板:奋飞呀,xsign你都水了好几篇了,一直在Apk里面打转,咱们啥时候分析分析它的so?奋飞:循序渐进嘛,我们上次刚定位了它的so,今天我们来分析分析。App版本:v4.15.1二、步骤Native层的入口先回忆下这个堆栈这个jni函数有两个参数,第一个参数是int型,第二个参数是Object数组我们先上frida看看它是不是我们的目
某车联网App 通讯协议加密分析
一、目标李老板:最近刚买了辆新车,他带的App挺有意思,要不要盘一盘?奋飞:我去,加壳了,还挺有意思,搞一搞。v6.1.0二、步骤抓包我的抓包环境是Mac10.14.6httpToolKit,这一步很顺利的抓到包了。1:main可以看到,http请求和返回值都是加密的,我们的目标就是这个 request 和 response的来历的。脱壳
Chase620 Chase620
3年前
Dubbo 源码分析 - 集群容错之 Router
注:本系列文章已捐赠给Dubbo社区,你也可以在Dubbo中阅读本系列文章。1\.简介上一篇文章分析了集群容错的第一部分–服务目录Directory。服务目录在刷新Invoker列表的过程中,会通过Router进行服务路由。上一篇文章关于服务路由相关逻辑没有细致分析,一笔带过了,本篇文章将对此进行详细的分析。首先,先来介绍一下服务目
代码还原的技术: Unidbg hook_add_new实现条件断点(二)
一、目标在做代码还原的时候,有时候会分析一组结果,希望在中途下个条件断点,比如在代码行0x1234,R00x5678的时候触发断点。今天我们就来试着搞一下。TIP:Unidbg代码同步到官方最新版,最新版已经支持浮点寄存器的显示了。二、步骤先写个floatdemotwo把祖传算法升个级extern"C"JNIEXPORTjstringJNIC
某车联网App 通讯协议加密分析(三) Trace Block
一、目标之前我们已经用unidbg跑通了libencrypt.so,那么如何判断跑出来的结果是对是错?再如何纠正unidbg跑错误的流程,是我们今天的目标。v6.1.0二、步骤找到明显的接口来判断checkcode是加密,加密的结果确实不好判断是否正确。不过我们可以试试解密,能解密就是对的,简单粗暴。这里解密函数是decheckcode。public
某车联网App 通讯协议加密分析(四) Trace Code
一、目标之前我们已经通过TraceBlock来比对了Unidbg和App跑的结果。现在他们运行的流程都差不多了,但是结果还是不对,今天我们就要通过TraceCode进行更细致的对比。v6.1.0二、步骤缩小Trace的范围TraceCode那么好使,我们为什么不一上来就Trace一遍?因为TraceCode的粒度太细了,一上来就搞,跑出几百万行
公众号:  奋飞安全
公众号: 奋飞安全
Lv1
奋飞,国家高级信息系统项目管理师,独立安全研究员。 http://91fans.com.cn/
文章
59
粉丝
4
获赞
44