美文网首页
qimao小说sign字段逆向及unidbg实现

qimao小说sign字段逆向及unidbg实现

作者: ever_hu | 来源:发表于2022-01-25 19:44 被阅读0次

    qimao小说sign字段逆向及unidbg实现

    Java层

    image-20220123212829789

    apk是加固的,这次脱壳使用的是frida_dump

    frida -U --no-pause -f com.kmxs.reader -l dump_dex.js
    

    然后再重新打包

    import pathlib
    import zipfile
    
    
    def get_files(dex_dir):
        fdir = pathlib.Path(dex_dir)
        infos = {}
        for item in fdir.glob('*.dex'):
            size = item.stat().st_size
            infos[size] = item
        
        fdict = {}
        for idx, key in enumerate(sorted(infos, reverse=True)):
            name = 'classes{}.dex'.format(str(idx) if idx else '')
            fdict[name] = infos[key]
    
        return fdict
    
    def pack(apk_path, dex_dir):
        dst = apk_path + '.pack.apk'
        with zipfile.ZipFile(apk_path) as zf, zipfile.ZipFile(dst, 'w') as zout:
            for item in zf.infolist():
                if item.filename.startswith('classes') and item.filename.endswith('.dex'):
                    print('Ignore:', item.filename)
                else:
                    buffer = zf.read(item.filename)
                    zout.writestr(item, buffer)
    
            for filename, fpath in get_files(dex_dir).items():
                print('Add:', fpath)
                zinfo = zipfile.ZipInfo(filename)
                with open(fpath, 'rb') as fin:
                    zout.writestr(zinfo, fin.read())
    
    if __name__ == '__main__':
        pack(r"E:\workspace\qimao\qimao613.apk", r"E:\workspace\qimao\dump_dex_com.kmxs.reader")
    

    然后jadx打开搜索"sign"

    image-20220122235915472

    可以看到url和header里面的sign都是调用同一个加密函数。

    com.km.repository.net.config.interceptor.HeaderInterceptor.b

    image-20220123000109559

    com.qimao.qmsdk.tools.encryption.Encryption.sign

    image-20220123000150079

    com.km.encryption.api.Security.sign

    image-20220123000236135

    先hook看看

    android hooking watch class_method com.km.encryption.api.Security.sign --dump-args --dump-return
    
    image-20220123140659715 image-20220123140752489

    虽然找到了native函数,但是看不出是在哪个so里面注册的。

    a函数查找用例

    image-20220123141149646

    com.qimao.qmsdk.tools.encryption.Encryption.init

    image-20220123141229042

    init函数查找用例

    image-20220123141321652

    defpackage.qf.run

    image-20220123141406108

    看来就是在libcommon-encryption.so注册的。

    so层

    由于手机和app都支持64位指令,所以分析的是64位so

    函数窗口搜索java

    image-20220123002352558

    有点奇怪,其他几个函数都有了,唯独少了sign函数。每个都点进去看看

    image-20220123003710978

    Java_com_km_encryption_api_Security_token这个函数里看到了Java_com_km_encryption_api_Security_sign,难道它们是同一个函数?看看这个函数

    image-20220123003830781

    从它的实现来看,就是在Java层的输入后面加了个keyData,然后做个MD5,这很大概率就是sign函数,因为签名就是32位长度的。

    看看MessageDigestAlgorithm::MessageDigestAlgorithm函数

    image-20220123004312636

    看看MessageDigestAlgorithm::init

    image-20220123004426099

    hook一下MessageDigestAlgorithm::init函数

    function dump(name, addr, length) {
        console.log("======================== " + name + " ============");
        console.log(hexdump(addr, {length:length||32}));
    }
    
    function hook_key(){
        var bptr = Module.findBaseAddress("libcommon-encryption.so");
        Interceptor.attach(bptr.add(0x19394), {
            onEnter: function(args) {
                console.log(args[0], args[1], args[2]);
                dump("input", args[1], parseInt(args[2]));
            },
            onLeave: function(retval) {
            }
        })
    }
    
    image-20220123142052258

    只有第一次的是输入,其他的是算法的填充。cyberchef上验证一下是不是标准MD5

    image-20220123142115586

    没有问题,是对的。

    header的sign调用的也是这个函数,只是输入不一样。

    unidbg实现

    习惯性选择调用32位的so,按照惯例,搭个框架

    public class Qimao extends AbstractJni {
        private final AndroidEmulator emulator;
        private final VM vm;
        private final Module module;
    
        public static String pkgName = "com.kmxs.reader";
        public static String apkPath = "unidbg-android/src/test/java/com/qimao/qimao613.apk";
        public static String soPath = "";
    
        public Qimao() {
            emulator = AndroidEmulatorBuilder.for32Bit().setProcessName(pkgName).build();
            Memory memory = emulator.getMemory();
            memory.setLibraryResolver(new AndroidResolver(23));
            vm = emulator.createDalvikVM(new File(apkPath));
            vm.setJni(this);
            vm.setVerbose(true);
            DalvikModule dm = vm.loadLibrary("common-encryption", true);
            module = dm.getModule();
            dm.callJNI_OnLoad(emulator);
        }
        
        public static void main(String[] args) {
            Qimao test = new Qimao();
        }
    }
    

    然后就是报错和补环境

    image-20220123194410678
    @Override
    public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
        switch (signature) {
            case "java/lang/Class->getClassLoader()Ljava/lang/ClassLoader;": {
                return new ClassLoader(vm, signature);
            }
        }
        return super.callObjectMethodV(vm, dvmObject, signature, vaList);
    }
    
    image-20220123195042845

    开始正式调用

    public void call_sign() {
        DvmClass clz = vm.resolveClass("com/km/encryption/api/Security");
        String methodSign = "sign([B)Ljava/lang/String;";
        StringObject ret = clz.callStaticJniMethodObject(emulator, methodSign, new ByteArray(vm, "book_privacy=1cache_ver=1642759975gender=2read_preference=2tab_type=2".getBytes(StandardCharsets.UTF_8)));
    }
    
    public static void main(String[] args) {
        Qimao test = new Qimao();
        test.call_sign();
    }
    
    image-20220123214354010

    日志里可以看到sign函数的地址,跳转过去也是Java_com_km_encryption_api_Security_token这个函数

    image-20220123214536950

    继续补环境

    @Override
    public DvmObject<?> getStaticObjectField(BaseVM vm, DvmClass dvmClass, String signature) {
        switch (signature) {
            case "com/km/encryption/generator/KeyGenerator->assetManager:Landroid/content/res/AssetManager;": {
                return new AssetManager(vm, signature);
            }
        }
        return super.getStaticObjectField(vm, dvmClass, signature);
    }
    
    image-20220123195727498

    报错了,但看不出什么。不过如果对AssetManager在native层的实现有所了解的话,就知道它是通过libandroid.so实现的。可惜的是unidbg并没有实现这个so,不过它提供了一个Android VirtualModule,实现了libandroid.so中的几个函数。

    image-20220123202647299

    从打印的日志也可以看到,libcommon-encryption.so依赖了libandroid.so

    public Qimao() {
        emulator = AndroidEmulatorBuilder.for32Bit().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);  // Load AndroidModule
        DalvikModule dm = vm.loadLibrary("common-encryption", true);
        module = dm.getModule();
        dm.callJNI_OnLoad(emulator);
    }
    

    再次运行

    image-20220123203430887

    还是报错了,这次跳转到0xfd29看看。

    image-20220123203814508

    可以看到调用了几个AAsset_*函数,可惜的是目前unidbg并没有实现其中的AAsset_seek函数

    image-20220123204048124

    所以需要自己实现一下,在unidbg-android/src/main/java/com/github/unidbg/virtualmodule/android/AndroidModule.java添加代码

    @Override
    protected void onInitialize(Emulator<?> emulator, final VM vm, Map<String, UnidbgPointer> symbols) {
        // ..
        symbols.put("AAsset_seek", svcMemory.registerSvc(is64Bit ? new Arm64Svc() {
            @Override
            public long handle(Emulator<?> emulator) {
                return seek(emulator, vm);
            }
        } : new ArmSvc() {
            @Override
            public long handle(Emulator<?> emulator) {
                return seek(emulator, vm);
            }
        }));
    }
    
    private static int seek(Emulator<?> emulator, VM vm) {
        RegisterContext context = emulator.getContext();
        UnidbgPointer pointer = context.getPointerArg(0);
        int offset = context.getIntArg(1);
        int whence = context.getIntArg(2);
        if (log.isDebugEnabled()) {
            log.debug("AAset_seek pointer=" + pointer + ", offset=" + offset + ", whence=" + whence + ", LR=" + context.getLRPointer());
        }
        final int SEEK_SET = 0;
        final int SEEK_CUR = 1;
        final int SEEK_END = 2;
        if ((whence == SEEK_SET && offset >= 0) || whence == SEEK_CUR || whence == SEEK_END) {
            Asset asset = vm.getObject(pointer.toIntPeer());
            return asset.seek(offset, whence);
        }
        throw new BackendException("offset=" + offset + ", whence=" + whence + ", LR=" + context.getLRPointer());
    }
    

    unidbg-android/src/main/java/com/github/unidbg/linux/android/dvm/api/Asset.java添加代码

    public int seek(int offset, int whence) {
        Pointer pointer = memoryBlock.getPointer();
        int index = pointer.getInt(0);
        int length = pointer.getInt(4);
    
        final int SEEK_SET = 0;
        final int SEEK_CUR = 1;
        final int SEEK_END = 2;
    
        if (whence == SEEK_SET) {
            index = offset;
        }
        else if (whence == SEEK_CUR) {
            index = index + offset;
        }
        else if (whence == SEEK_END) {
            index = length + offset;
        }
        pointer.setInt(0, index);
        return index;
    }
    

    重新运行

    image-20220123205804729

    恢复正常的报错了,getKey()需要返回一个字符串,jadx看看这个类。

    image-20220123205952471

    可以看到是返回成员变量key,可以使用objection + Wallbreaker查看

    plugin wallbreaker classdump com.km.encryption.generator.KeyGenerator
    
    image-20220123210219431

    所以返回"8w1"

    @Override
    public DvmObject<?> callStaticObjectMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
        switch (signature) {
            case "com/km/encryption/generator/KeyGenerator->getKey()Ljava/lang/String;": {
                return new StringObject(vm, "8w1");
            }
        }
        return super.callStaticObjectMethodV(vm, dvmClass,  signature, vaList);
    }
    
    image-20220123210350433

    和抓包结果一样。

    完整代码

    public class Qimao extends AbstractJni {
        private final AndroidEmulator emulator;
        private final VM vm;
        private final Module module;
    
        public static String pkgName = "com.kmxs.reader";
        public static String apkPath = "unidbg-android/src/test/java/com/qimao/qimao613.apk";
        public static String soPath = "";
    
        public Qimao() {
            emulator = AndroidEmulatorBuilder.for32Bit().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("common-encryption", true);
            module = dm.getModule();
            dm.callJNI_OnLoad(emulator);
        }
    
        @Override
        public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
            switch (signature) {
                case "java/lang/Class->getClassLoader()Ljava/lang/ClassLoader;": {
                    return new ClassLoader(vm, signature);
                }
            }
            return super.callObjectMethodV(vm, dvmObject, signature, vaList);
        }
    
        @Override
        public DvmObject<?> getStaticObjectField(BaseVM vm, DvmClass dvmClass, String signature) {
            switch (signature) {
                case "com/km/encryption/generator/KeyGenerator->assetManager:Landroid/content/res/AssetManager;": {
                    return new AssetManager(vm, signature);
                }
            }
            return super.getStaticObjectField(vm, dvmClass, signature);
        }
    
        @Override
        public DvmObject<?> callStaticObjectMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
            switch (signature) {
                case "com/km/encryption/generator/KeyGenerator->getKey()Ljava/lang/String;": {
                    return new StringObject(vm, "8w1");
                }
            }
            return super.callStaticObjectMethodV(vm, dvmClass,  signature, vaList);
        }
    
        public void call_sign() {
            DvmClass clz = vm.resolveClass("com/km/encryption/api/Security");
            String methodSign = "sign([B)Ljava/lang/String;";
            StringObject ret = clz.callStaticJniMethodObject(emulator, methodSign, new ByteArray(vm, "book_privacy=1cache_ver=1642759975gender=2read_preference=2tab_type=2".getBytes(StandardCharsets.UTF_8)));
    //        System.out.println("sign:" + ret.getValue());
        }
    
        public static void main(String[] args) {
            Qimao test = new Qimao();
            test.call_sign();
        }
    }
    

    相关文章

      网友评论

          本文标题:qimao小说sign字段逆向及unidbg实现

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