美文网首页
kuaidui作业sign逆向及unidbg实现

kuaidui作业sign逆向及unidbg实现

作者: ever_hu | 来源:发表于2022-02-16 15:00 被阅读0次

    kuaidui作业sign逆向及unidbg实现

    Java层

    apk用frida_dump脱壳后,重新打包,jadx搜索sign=

    image-20220125144710183

    nativeGetSign这个名字就值得点进去看看

    com.zuoyebang.baseutil.a.b

    image-20220125144844816

    com.zuoyebang.baseutil.NativeHelper.nativeGetSign

    image-20220125144925303

    找到函数了,hook验证一下

    android hooking watch class_method com.zuoyebang.baseutil.NativeHelper.nativeGetSign --dump-args --dum-return
    
    image-20220125151803309

    输入是一个base64字符串,解码后是请求参数做个拼接。

    接下来找找函数在哪个so里面,nativeInitBaseUtil看这函数名,应该是初始化的,查看用例

    image-20220125150251992

    com.zuoyebang.baseutil.a.a

    image-20220125150416858

    看来应该是在libbaseutil.so

    so层

    ida查看JNI_OnLoad

    image-20220125150735443 image-20220125150811631

    几个函数都找到了

    image-20220125150909577

    看看CRYMd5

    image-20220125151008457

    hook一下CRYMd5

    function hook_md5() {
        var bptr = Module.findBaseAddress("libbaseutil.so");
        Interceptor.attach(bptr.add(0x2ae8), {
            onEnter: function(args) {
                console.log("md5-arg0: ", args[0].readCString());
            },
            onLeave: function(retval) {
                console.log("md5-ret:", retval.readCString(32));
            }
        })
    }
    
    image-20220125151533967

    cyberchef验证是不是标准MD5

    image-20220125165726612

    多请求几次,发现objSpamServer.random_number,也就是cdAgblSOFM不变,说明它是个相对固定的值。

    卸载app重装后,发现还是不变,说明和会话无关。

    更换设备后,发现值改变了,说明该值和设备相关。

    一个比较取巧也比较无奈的办法就是建立设备号和字符串的映射表,计算sign时选用对应的字符串即可。

    unidbg实现

    由于app只提供了64位的so,所以此次运行的是64位so。依旧是先搭个框架

    public class Kuaidui extends AbstractJni {
        private final AndroidEmulator emulator;
        private final VM vm;
        private final Module module;
    
        public static String pkgName = "com.kuaiduizuoye.scan";
        public static String apkPath = "unidbg-android/src/test/java/com/kuaidui/kuaidui540.apk";
        public static String soPath = "";
    
        public Kuaidui() {
            emulator = AndroidEmulatorBuilder.for64Bit().setProcessName(pkgName).build();
            Memory memory = emulator.getMemory();
            memory.setLibraryResolver(new AndroidResolver(23));
            vm = emulator.createDalvikVM(new File(apkPath));
            vm.setJni(this);
            vm.setVerbose(true);
            new AndroidModule(emulator, vm).register(memory);
            DalvikModule dm = vm.loadLibrary("baseutil", true);
            module = dm.getModule();
            dm.callJNI_OnLoad(emulator);
        }
        
        public static void main(String[] args) {
            Kuaidui test = new Kuaidui();
        }
    }
    
    image-20220125163430533

    罕见的没有报错,那就开始调用。

    public void call_sign() {
        List<Object> list = new ArrayList<>(10);
        list.add(vm.getJNIEnv());
        list.add(0);
        list.add(vm.addLocalObject(new StringObject(vm, "everhu")));
        Number ret = module.callFunction(emulator, 0x1500, list.toArray());
        System.out.println(vm.getObject(ret.intValue()).getValue());
    }
    
    public static void main(String[] args) {
        Kuaidui test = new Kuaidui();
        test.call_sign();
    }
    
    image-20220125163950114

    调用出结果了,只是不是正确结果。说明环境不对,可能有些参数没有设置。

    image-20220125164156818

    native函数里面应该有设置环境的函数,hook看看哪个先被调用了。

    objection.exe -g com.kuaiduizuoye.scan explore --startup-command="android hooking watch class com.zuoyebang.baseutil.NativeHelper"
    
    image-20220125170504364

    可以看到nativeSetToken被调用了,再hook看看入参

    objection -g com.kuaiduizuoye.scan explore --startup-command="android hooking watch class_method com.zuoyebang.baseutil.NativeHelper.nativeSetToken --dump-args --dump-return"
    
    image-20220125172845540

    在unidbg补上。

    public void call_token() {
        List<Object> list = new ArrayList<>(10);
        list.add(vm.getJNIEnv());
        list.add(0);
        list.add(vm.addLocalObject(vm.resolveClass("android/content/Context").newObject(null)));
        String devid = "F5D53AD5A66144B57783C7C67611F0F7|0";
        list.add(vm.addLocalObject(new StringObject(vm, devid)));
        String request = "03090b0106000807080e00080201090708060f00040700020d0503070c0606000c01000a080e07050808020d00060b0d070f0f080306060b00000f0d04070d0d02070b0f020a01010c0d030a09080e090b040f02080c0b040c0e0e060c0d0201020f0e02030d0107020d000e0e02090401070505030a0c0605080303040e0803020c020e0d020408010b030e0b090f060302000e0f0902010706040c00080d0e060d000f0805040b0e07000d020b0f07";
        list.add(vm.addLocalObject(new StringObject(vm, request)));
        String response = "0a040609080d020b0a03090e010306050d0d050c0c060207070105010b0b01090801000a020d0c0b03030209030e0d0a";
        list.add(vm.addLocalObject(new StringObject(vm, response)));
        Number ret = module.callFunction(emulator, 0x1264, list.toArray());
    }
    
    public static void main(String[] args) {
        Kuaidui test = new Kuaidui();
        test.call_token();
        test.call_sign();
    }
    
    image-20220125165148129

    需要返回int,用objection + Wallbreaker查看是64

    @Override
    public int getStaticIntField(BaseVM vm, DvmClass dvmClass, String signature) {
        switch (signature) {
            case "android/content/pm/PackageManager->GET_SIGNATURES:I": {
                return 64;
            }
        }
        return super.getStaticIntField(vm, dvmClass, signature);
    }
    
    image-20220125165335872

    然后就出结果了。

    完整实现

    public class Kuaidui extends AbstractJni {
        private final AndroidEmulator emulator;
        private final VM vm;
        private final Module module;
    
        public static String pkgName = "com.kuaiduizuoye.scan";
        public static String apkPath = "unidbg-android/src/test/java/com/kuaidui/kuaidui540.apk";
        public static String soPath = "";
    
        public Kuaidui() {
            emulator = AndroidEmulatorBuilder.for64Bit().setProcessName(pkgName).build();
            Memory memory = emulator.getMemory();
            memory.setLibraryResolver(new AndroidResolver(23));
            vm = emulator.createDalvikVM(new File(apkPath));
            vm.setJni(this);
            vm.setVerbose(true);
            new AndroidModule(emulator, vm).register(memory);
            DalvikModule dm = vm.loadLibrary("baseutil", true);
            module = dm.getModule();
            dm.callJNI_OnLoad(emulator);
        }
    
        @Override
        public int getStaticIntField(BaseVM vm, DvmClass dvmClass, String signature) {
            switch (signature) {
                case "android/content/pm/PackageManager->GET_SIGNATURES:I": {
                    return 64;
                }
            }
            return super.getStaticIntField(vm, dvmClass, signature);
        }
    
        public void call_sign() {
            List<Object> list = new ArrayList<>(10);
            list.add(vm.getJNIEnv());
            list.add(0);
            list.add(vm.addLocalObject(new StringObject(vm, "everhu")));
            Number ret = module.callFunction(emulator, 0x1500, list.toArray());
            System.out.println(vm.getObject(ret.intValue()).getValue());
        }
    
        public void call_token() {
            List<Object> list = new ArrayList<>(10);
            list.add(vm.getJNIEnv());
            list.add(0);
            list.add(vm.addLocalObject(vm.resolveClass("android/content/Context").newObject(null)));
            String devid = "F5D53AD5A66144B57783C7C67611F0F7|0";
            list.add(vm.addLocalObject(new StringObject(vm, devid)));
            String request = "03090b0106000807080e00080201090708060f00040700020d0503070c0606000c01000a080e07050808020d00060b0d070f0f080306060b00000f0d04070d0d02070b0f020a01010c0d030a09080e090b040f02080c0b040c0e0e060c0d0201020f0e02030d0107020d000e0e02090401070505030a0c0605080303040e0803020c020e0d020408010b030e0b090f060302000e0f0902010706040c00080d0e060d000f0805040b0e07000d020b0f07";
            list.add(vm.addLocalObject(new StringObject(vm, request)));
            String response = "0a040609080d020b0a03090e010306050d0d050c0c060207070105010b0b01090801000a020d0c0b03030209030e0d0a";
            list.add(vm.addLocalObject(new StringObject(vm, response)));
            Number ret = module.callFunction(emulator, 0x1264, list.toArray());
        }
    
        public static void main(String[] args) {
            Kuaidui test = new Kuaidui();
            test.call_token();
            test.call_sign();
        }
    }
    

    nativeSetToken

    nativeSetToken入参的3个字符串也是会随着设备而改变,分析一下它的来源。

    image-20220125171720998

    可以看出是从Preference里取值。

    image-20220125172235883 image-20220125173029331

    不过,当apk刚安装的时候,shared_prefs里面是没有这两个值的。

    image-20220125173219732

    此时是从上图处取值的,可以看出调用了native的nativeInitBaseUtil函数,之后发了个请求,抓包可以看到

    image-20220125173538268

    可以看出,post的请求数据就是第二个字符串,post的响应数据就是第三个字符串。

    其他

    通过com.zuoyebang.baseutil.NativeHelper这个类看到了另一个app的名字zuoyebang,很自然认为二者用的是同一个签名方案。jadx打开zuoyebang的apk查看下,发现了同样的接口。

    再查看下so文件

    image-20220126103238435

    稍微不同的是zuoyebang只提供了32位so,而kuaidui作业只提供64位so。

    ida查看

    image-20220126103919355 image-20220126104112002

    可以看到kuaidui作业的函数名全是显式的,能够通过函数名知道函数的作用;而zuoyebang则全都是sub_*形式,很难直观的了解函数的作用。二者都是用相同的方案,逆向的难度却相差好几倍,看来逆向的时候除了看看旧版本的apk,还可以看看其他使用相同方案的产品。

    相关文章

      网友评论

          本文标题:kuaidui作业sign逆向及unidbg实现

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