美文网首页
kuaidui作业unidbg逆向(2)

kuaidui作业unidbg逆向(2)

作者: ever_hu | 来源:发表于2022-02-17 21:37 被阅读0次

    kuaidui作业unidbg逆向

    nativeInitBaseUtil

    在unidbg实现中,调用nativeGetSign之前,需要先调用nativeSetToken,这是因为需要先设置objSpamServer.random_number

    image-20220126151954449 image-20220126152038418

    nativeSetToken的入参又来自于nativeInitBaseUtil

    kuaidui

    先分析代码比较少的nativeInitBaseUtil

    image-20220126152555193

    看看getChallenge函数

    image-20220126153147089

    很简单,生成随机10位字符串

    剩下的函数就比较容易明白它的作用。CRYMd5生成MD5,CRYStringCat字符串拼接,DES_Encryptdes加密,str2hex将输入转为16进制字符串。现在需要分析的其实只有后面两个函数。

    unidbg实现

    先用unidbg调用nativeInitBaseUtil,之后再结合ida来逆向

    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_init() {
            List<Object> list = new ArrayList<>(10);
            list.add(vm.getJNIEnv());
            list.add(0);
            list.add(vm.addLocalObject(vm.resolveClass("android/content/Context").newObject(null)));
            list.add(vm.addLocalObject(new StringObject(vm, "F5D53AD5A66144B57783C7C67611F0F7|0")));
            Number ret = module.callFunction(emulator, 0x1010, list.toArray());
        }
    
        public static void main(String[] args) {
            Kuaidui test = new Kuaidui();
            test.call_init();
        }
    
    image-20220126153904882

    报了个错,跳转过去看看

    image-20220126153935089

    好像不支持,我们简单的把它加上去试试

    image-20220126154052050 image-20220126170756492

    出结果了,不过有一点不好的就是,每次运行生成的结果都不一样。这是因为getChallenge函数里面的rand函数导致的。将它hook住,改为固定值。

    public void hook_libc() {
        IHookZz hookZz = HookZz.getInstance(emulator);
        hookZz.enable_arm_arm64_b_branch();
        hookZz.wrap(module.findSymbolByName("rand"), new WrapCallback<HookZzArm64RegisterContext>() {
            @Override
            public void preCall(Emulator<?> emulator, HookZzArm64RegisterContext ctx, HookEntryInfo info) {
            }
    
            @Override
            public void postCall(Emulator<?> emulator, HookZzArm64RegisterContext ctx, HookEntryInfo info) {
                ctx.setXLong(0, 1L);
            }
        });
    }
    
    public static void main(String[] args) {
        Kuaidui test = new Kuaidui();
        test.hook_libc();
        test.call_init();
    }
    

    由于随机数的结果一直为1,所以getChallenge的结果为BBBBBBBBBB

    逆向

    接下来开始逆向DES_Encryptstr2hex

    image-20220126154835129

    hook看看参数

    emulator.attach().addBreakPoint(module.base+0x61f4);
    
    image-20220126154937169 image-20220126155041419

    很容易构造出。

    blr在函数返回处下断点,c运行到函数返回处,mx0看看返回结果

    image-20220126155539906 image-20220126155605178

    有了输入,key和输出,在cyberchef验证下

    image-20220126155837910

    emm,和标准结果差的不是一点半点,有可能是DES-CBC,或者DES经过了魔改。

    image-20220126160111449

    这里是实现块加密的代码,可以看出就是DES-ECB,没有向量或其他东西。那说明DES很可能经过了魔改。

    image-20220126160805050

    这部分是密钥生成部分,涉及到的常量有PC_1, MOVE_TIMES, PC_2

    image-20220126161310755

    比较容易看到的修改就是PC_2有个值被改了。

    还有就是块加密部分的常量

    image-20220126170237660 image-20220126170203044

    实际操作上,直接把这些常量全部拷贝下来就行了,根本不用对比,免得自己看漏了。

    image-20220126161829920 image-20220126162404458

    另外一个就是byte转bit的时候高位低位是相反的,比如0x8d在标准实现中是10001101,这里则是11010001

    在标准DES实现的基础上做出修改后,再调用试试

    image-20220126162654331

    和unidbg hook到的结果一致。

    接下来就是str2hex函数。

    image-20220126163012195 image-20220126163041739

    str2hex的输入就是DES_Encrypt的结果,输出就是最后的结果。

    image-20220126163255158

    其实对照着代码也挺容易实现的,就是把一个byte转成bit之后再逆序,然后再转成2个byte,最后变成hex形式。

    def byte2hex(data):
        """
            8d
            |
        10001101
            |
        10110001
        /    \
        1011 0001
        |    |
        0b   01
        """
        result = []
        for v9 in data:
            d = f'{v9:08b}'[::-1]
            result.append(int(d[:4], 2))
            result.append(int(d[4:], 2))
        return bytes(result).hex()
    

    把前面的拼接起来验证下

    def calc_request_hex(challenge, devid, pkg_sign='2fb53de6d38eff7109f19d68e047123b'):
        """
        Args:
            challenge: length-of-10 random string, charset: a-zA-Z0-9
            devid: cuid
            pkg_sign: md5 of pkg signature, default: sign of v5.4.0
        """
        data = '##'.join(('8&%d*', challenge, pkg_sign, devid))
        data2 = encrypt_hex(data.encode(), b'@fG2SuLA')
        return data2
    
    def encrypt_hex(data, key):
        cryptor = DES(key, padmode=PAD_PKCS5)
        data2 = cryptor.encrypt(data)
        data3 = byte2hex(data2)
        return data3
    
    def test_request_hex():
        challenge = 'B' * 10
        devid = 'F5D53AD5A66144B57783C7C67611F0F7|0'
        req_str = calc_request_hex(challenge, devid)
        print(req_str)
    
    image-20220126164022514

    和unidbg结果一致。

    nativeSetToken

    nativeSetTokennativeInitBaseUtil相比是负责解密数据。

    image-20220126164334084

    其实主要也就这几个地方。

    image-20220126164414998

    hex2Str就是str2hex的反向操作,重写一下即可。

    def hex2byte(data):
        """
        0b   01
        |    | 
        1011 0001
        \    /
        10110001
            |
        10001101
            |
            8d
        """
        data2 = bytes.fromhex(data)
        result = []
        for idx in range(0, len(data2), 2):
            d = f'{data2[idx]:04b}{data2[idx+1]:04b}'
            result.append(int(d[::-1], 2))
        return bytes(result)
    

    接下来用hook到的真实数据试试。

    先将请求数据解密看看

    def decrypt_hex(data, key):
        data2 = hex2byte(data)
        cryptor = DES(key, padmode=PAD_PKCS5)
        data3 = cryptor.decrypt(data2)
        return data3
    
    def test_decode2():
        key = b'@fG2SuLA'
        data = '03090b0106000807080e00080201090708060f00040700020d0503070c0606000c01000a080e07050808020d00060b0d070f0f080306060b00000f0d04070d0d02070b0f020a01010c0d030a09080e090b040f02080c0b040c0e0e060c0d0201020f0e02030d0107020d000e0e02090401070505030a0c0605080303040e0803020c020e0d020408010b030e0b090f060302000e0f0902010706040c00080d0e060d000f0805040b0e07000d020b0f07'
        req_str = decrypt_hex(data, key)
        print(req_str)
    
    image-20220126165057321

    可以看到和nativeInitBaseUtil中的形式是一样的。

    然后是响应数据解密,它的key是原始请求数据的7-12位加上#G4,也就是wobBX#G4

    def test_decode():
        key = b'wobBX#G4'
        data = '0a040609080d020b0a03090e010306050d0d050c0c060207070105010b0b01090801000a020d0c0b03030209030e0d0a'
        resp_str = decrypt_hex(data, key)
        print(resp_str)
    
    image-20220126165540276

    objSpamServer.random_number映入眼帘。

    相关文章

      网友评论

          本文标题:kuaidui作业unidbg逆向(2)

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