unidbg 模拟执行案例(某汽车类app)

unidbg 模拟执行案例(某汽车类app)_第1张图片

jadx 配合frida 定位到关键函数处

unidbg 模拟执行案例(某汽车类app)_第2张图片

通过frida 主动调用

入参 = > 

Me3v+nhLnwjf6KapD5+O6m47bPjxMaHJYqujau/xOjHa0nOUeMz33EJlI9Ivs3BvfRzoGIYNBKeFVQwk06iUm2n9hzyCf1gF9UGpZetm2GfIhnTrA8vTycY7Qelb033om3oWx9gjaUs9YwsDkMzEAiJPnDrnf98AAAxhqU1DsbOC/97C1K/oG81Z3D1OTyW+9iNWrk9a+g68H0+EHBl17tUxnVXvMNxv+Udh53pLQejOQUG69FSbOqIiCaky/Qf+tbRkD13kYP1tnZCxjSw99bw==

native层解密结果 => 

H4sIAAAAAAAAADXLsQqDMBCA4VcpNyvcRROTbBItdOjUvkDQKwjRQnIOpfTd6+L8/f8XMpc9CXjJO1cgOU58m8FDb5RGpAE7i20wY3/FxtqAdlRdOAQqmKPEcyyfIrwOUfh4FSqqSdVkL0QeW08OzuS5rHxfUloKeDKN65wx2mlNFWxveezTxOWgV0yFf3++e75CnwAAAA==

所以我们就用unidbg 来模拟执行一下这个so, 看看当传入入参,能否得到与frida主动调用相同的结果

unidbg 模拟执行案例(某汽车类app)_第3张图片

ida 打开这个so,看看jni_onload, 静态注册, jni_onload初始化一些变量。

unidbg 模拟执行案例(某汽车类app)_第4张图片

ok 上unidbg, 跑一下jni_onload函数

    linglingbang() {
        // 创建模拟器实例,进程名建议依照实际进程名填写,可以规避针对进程名的校验
        emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.llb").build();
        // 获取模拟器的内存操作接口
        final Memory memory = emulator.getMemory();
        // 设置系统类库解析
        memory.setLibraryResolver(new AndroidResolver(23));
        // 创建Android虚拟机,传入APK,Unidbg可以替我们做部分签名校验的工作
        vm = emulator.createDalvikVM(new File("unidbg-android\\src\\test\\java\\com\\llb\\t.apk"));
        // 加载目标SO
        DalvikModule dm = vm.loadLibrary(new File("unidbg-android\\src\\test\\java\\com\\llb\\libencrypt.so"), true); // 加载so到虚拟内存
        //获取本SO模块的句柄,后续需要用它
        module = dm.getModule();
        vm.setJni(this); // 设置JNI
        vm.setVerbose(true); // 打印日志

        dm.callJNI_OnLoad(emulator); // 调用JNI OnLoad
    };

跑完后 报错如下:

一个字,

@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;":
//                return new StringObject(vm, "FA6AT0306923");
            case "android/app/ActivityThread->currentPackageName()Ljava/lang/String;":
                return new StringObject(vm, "com.cloudy.llb");
//            case "android/app/ActivityThread->currentActivityThread()Landroid/app/ActivityThread;":
//                return vm.resolveClass("android/app/ActivityThread").newObject(null);

        }
        return super.callStaticObjectMethod(vm, dvmClass, signature, varArg);
    }

补完后,运行结果如下:

unidbg 模拟执行案例(某汽车类app)_第5张图片

接着补

@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;":
//                return new StringObject(vm, "DD6BA0306923");
            case "android/app/ActivityThread->currentPackageName()Ljava/lang/String;":
                return new StringObject(vm, "com.cloudy.llb");
            case "android/app/ActivityThread->currentActivityThread()Landroid/app/ActivityThread;":
                return vm.resolveClass("android/app/ActivityThread").newObject(null);

        }
        return super.callStaticObjectMethod(vm, dvmClass, signature, varArg);
    }

补完后,报错如下

unidbg 模拟执行案例(某汽车类app)_第6张图片

继续补:

@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;":
                return new StringObject(vm, "FA6AT0306923");
            case "android/app/ActivityThread->currentPackageName()Ljava/lang/String;":
                return new StringObject(vm, "com.cloudy.linglingbang");
            case "android/app/ActivityThread->currentActivityThread()Landroid/app/ActivityThread;":
                return vm.resolveClass("android/app/ActivityThread").newObject(null);

        }
        return super.callStaticObjectMethod(vm, dvmClass, signature, varArg);
    }

补完后,运行结果如下

unidbg 模拟执行案例(某汽车类app)_第7张图片

继续补

@Override
    public DvmObject callObjectMethod(BaseVM vm, DvmObject dvmObject, String signature, VarArg varArg) {
        switch (signature) {
            case "android/net/wifi/WifiInfo->getMacAddress()Ljava/lang/String;":
                return new StringObject(vm, "02:00:00:00:00:00");
            case "java/lang/Object->getConnectionInfo()Landroid/net/wifi/WifiInfo;":
                return vm.resolveClass("android/net/wifi/WifiInfo").newObject(null);
            case "android/app/ContextImpl->getSystemService(Ljava/lang/String;)Ljava/lang/Object;":
                return vm.resolveClass("java/lang/Object").newObject(null);
            case "android/app/ContextImpl->getPackageManager()Landroid/content/pm/PackageManager;":
                return vm.resolveClass("android/content/pm/PackageManager").newObject(null);
            case "android/app/ActivityThread->getSystemContext()Landroid/app/ContextImpl;":
                return vm.resolveClass("android/app/ContextImpl").newObject(null);

        }

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

补完后, 运行结果如下

unidbg 模拟执行案例(某汽车类app)_第8张图片

jni 终于补完了,接下来正式进行调用了,  Java代码如下 ,native函数只有一个String参数 

 unidbg 模拟执行案例(某汽车类app)_第9张图片

打开ida, 查看注册函数对应的地址

unidbg 模拟执行案例(某汽车类app)_第10张图片

unidbg代码如下

public String xPreAuthencode(){
        // args list
        List list = new ArrayList<>(10);
        // arg1 env
        list.add(vm.getJNIEnv());
        // arg2 jobject/jclazz 一般用不到,直接填0
        list.add(0);

        String p1 = "Me3v+nhLnwjf6KapD5+O6m47bPjxMaHJYqujau/xOjHa0nOUeMz33EJlI9Ivs3BvfRzoGIYNBKeFVQwk06iUm2n9hzyCf1gF9UGpZetm2GfIhnTrA8vTycY7Qelb033om3oWx9gjaUs9YwsDkMzEAiJPnDrnf98AAAxhqU1DsbOC/97C1K/oG81Z3D1OTyW+9iNWrk9a+g68H0+EHBl17tUxnVXvMNxv+Udh53pLQejOQUG69FSbOqIiCaky/Qf+tbRkD13kYP1tnZCxjSw99bw==";
        list.add(vm.addLocalObject(new StringObject(vm, p1)));
        // 参数准备完成
        // call function
        Number number = module.callFunction(emulator, 0x165E0 + 1, list.toArray())[0];
        String result = vm.getObject(number.intValue()).getValue().toString();
        return result;
    }

 运行结果如下, 与frida 主动调用的结果不一致

unidbg 模拟执行案例(某汽车类app)_第11张图片

这是为何? 我们不妨看看最后一处调用,也就是 

JNIEnv->NewStringUTF("92ec2acd6a6a610561e17e5ea5732d39")

这里的地址是 166cd

我们打开ida, 查看下代码。

unidbg 模拟执行案例(某汽车类app)_第12张图片

 166cd 处实在执行 v9(v10, v11) 的逻辑,我们有理由怀疑, 当 !V7 条件满足时, 就直接返回一串数据了, 而不是解密的结果, 所以  sub_138AC 也大概率是签名校验函数

我们进入到sub_138AC 一瞧究竟.

unidbg 模拟执行案例(某汽车类app)_第13张图片

果不其然, 是在进行签名校验.

我们得找个方法过它得签名校验, 也就是修改v7得结果。


if ( !v7 )
    {
      v9 = v8->NewStringUTF;
      v10 = v3;
      v11 = v6;
      return v9(v10, v11);
    }

我们来看看上面这部分代码对应得汇编代码

unidbg 模拟执行案例(某汽车类app)_第14张图片

可以看到,当签名校验通过后, 会跳转到 loc_16610 进行aes计算, 这个正是我们希望得。

可以看到有个指令是 CBNZ  (compare branch never zero )  也就是 R0 不等于0时候,

就跳转到 loc_16610, 这个指令有个好兄弟,是兄弟也是对头, 那就是 CBZ(compare branch zero)

意为 当R0 等于0 时 就跳转到 loc_16610 。 所以如果我们用 CBZ 替换掉 CBNZ 指令,那么就意味着,当签名校验不通过得时候, 就执行aes 解密, 但是当签名校验正确得时候,反而不走加密得流程了。

理清了这层逻辑后,用unidbg进行patch

public void patchVerify(){
        int patchCode = 0x000020B1; //
        emulator.getMemory().pointer(module.base + 0x16604 + 1).setInt(0, patchCode);
    }

ptach完后,再次运行,结果如下

unidbg 模拟执行案例(某汽车类app)_第15张图片

终于成功了!!! 与frida 主动调用结果一致。

你可能感兴趣的:(安卓篇,android逆向分析案例,安卓逆向案例,一次安卓逆向分析,安卓分析案例,unidbg,安卓案例分析)