5

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

 2 years ago
source link: https://blog.51cto.com/u_15527932/5662283
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

有一段时间没有写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);
    }

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

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

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

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手把手跑通_java

1:ffshow

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK