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

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

作者: fenfei331 | 来源:发表于2022-09-08 14:53 被阅读0次

    一、目标

    有一段时间没有写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 + 报错信息,一般都有同道趟过坑。

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

    ffshow.jpeg

    1:ffshow

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

    相关文章

      网友评论

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

          本文链接:https://www.haomeiwen.com/subject/aoronrtx.html