meili说mw-sign字段unidbg逆向

meili说mw-sign字段unidbg逆向

Java层

image-20220303144238858

com.mogujie.mwpsdk.valve.RequestSignValve.a

image-20220303144349180

com.mogujie.mwpsdk.valve.RequestSignValve.a

image-20220303144436214

com.mogujie.mwpsdk.security.Signer.a

image-20220303144516123

这是个interface,找它的实现,搜索implements Signer

image-20220303144713892

com.mogujie.mwpsdk.security.SignV1_2.a

image-20220303144735962

com.mogujie.token.UrlTokenMaker.generateNewUrlTokenNative

image-20220303144813795

最后是在libtoken.so里面

hook看看输入输出

android hooking watch class_method com.mogujie.token.UrlTokenMaker.generateNewUrlTokenNative --dump-args --dump-return
image-20220303191842591

输入的第二个字符串最后拼接了个32位字符串,分析了下是post的data参数的md5。

unidbg实现

public class Meilishuo extends AbstractJni {
    private final AndroidEmulator emulator;
    private final VM vm;
    private final Module module;

    public static String pkgName = "com.meilishuo";
    public static String apkPath = "unidbg-android/src/test/java/com/meilishuo/meilishuo1071.apk";
    public static String soName = "token";

    public Meilishuo() {
        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(soName, 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);
    }

    @Override
    public DvmObject callObjectMethod(BaseVM vm, DvmObject dvmObject, String signature, VarArg varArg) {
        switch (signature) {
            case "android/content/pm/Signature->getPublicKey()Ljava/security/PublicKey;": {
                return vm.resolveClass("java/security/interfaces/RSAKey").newObject(null);
            }
            case "java/security/interfaces/RSAKey->getModulus()Ljava/math/BigInteger;": {
                return vm.resolveClass("java/math/BigInteger").newObject(null);
            }
            case "java/math/BigInteger->toString(I)Ljava/lang/String;": {
                String str1 = "bf62993ee6167065c274c37ed192f94bcb8ed0ead720246d8c17a1aa3218882aa2cc8abf5f5bb2e2fc0a590ee6cfe9a3c57c2c87f00c4070a8927ce1ce8b9089158d5ca8c2fae2eaf2c2bd1cf7bdf7b4835b01758c9fea39c54345de02a49c7ef94f02fdead461491212520c2bdc6f876c5341d8e24845bcb5808c0573de5e4b";
                return new StringObject(vm, str1);
            }
        }
        return super.callObjectMethod(vm, dvmObject, signature, varArg);
    }

    public void calc_sign() {
        List 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, "d569bb5990c698efda6b5d8b653fb696")));
        list.add(vm.addLocalObject(new StringObject(vm, "2022-03-03&everhu")));
        module.callFunction(emulator, 0x2145, list.toArray());
    }

    public static void main(String[] args) {
        Meilishuo test = new Meilishuo();
        test.calc_sign();
    }
}
 
 

在补环境的时候,需要返回一个字符串,可以ida先看看代码。

image-20220303145617937
image-20220303145656993

可以看到sub_1838的返回值就是需要的字符串。可以frida hook一下看看返回值。

image-20220303145839347

不过有个问题,只有当byte_5050[0]为0时,才会进入调用sub_1838

一种方式是在byte_5050还没被赋值时进行hook,也就是加密函数第一次被调用时。尝试frida hook一下。

function dump(name, addr, length) {
    console.log("======================== " + name + " ============");
    console.log(hexdump(addr, {length:length||32}));
}

function hook() {
    var bptr = Module.findBaseAddress("libtoken.so");
    Interceptor.attach(bptr.add(0x2145), {
        onEnter: function(args) {
            console.log("call sub_2144");
        },
        onLeave: function(retval){}
    })
    Interceptor.attach(bptr.add(0x1839), {
        onEnter: function(args) {
            console.log("call sub_1838");
        },
        onLeave: function(retval){
            console.log(retval);
            var env = Java.vm.tryGetEnv();
            var ret = env.getStringUtfChars(retval);
            console.log("ret-1838", ret.readCString());
        }
    })
}

Java.perform(function(){
    hook();
})

但是这个代码有个问题,在attach模式下,byte_5050早已被赋值,sub_1838不会被调用。在spawn模式下,libtoken.so还没被加载到内存中,导致找不到so。单纯的加个timeout的话不好控制,所以需要在libtoken.so刚加载的时候进行hook。

Java.perform(function() {
    var android_dlopen_ext = Module.findExportByName(null, "android_dlopen_ext");
    if (android_dlopen_ext != null) {
        Interceptor.attach(android_dlopen_ext, {
            onEnter: function (args) {
                this.ok = false;
                var soName = args[0].readCString();
                if (soName.indexOf("libtoken.so") !== -1) {
                    this.ok = true;
                }
            },
            onLeave: function (retval) {
                if (this.ok) {
                    hook();  // hook after load
                }
            }
        });
    }
})
image-20220303170909506

另一种方式就是每次调用加密函数时,都先把byte_5050的值清空,这样一来,即使是attach模式下,也会调用sub_1838

function hook() {
    var bptr = Module.findBaseAddress("libtoken.so");
    Interceptor.attach(bptr.add(0x2145), {
        onEnter: function(args) {
            console.log("call sub_2144");
            Memory.writeByteArray(bptr.add(0x5050), [0,0,0,0]);  // clear byte_5050
        },
        onLeave: function(retval){}
    })
    Interceptor.attach(bptr.add(0x1839), {
        onEnter: function(args) {
            console.log("call sub_1838");
        },
        onLeave: function(retval){
            console.log(retval);
            var env = Java.vm.tryGetEnv();
            var ret = env.getStringUtfChars(retval);
            console.log("ret-1838", ret.readCString());
        }
    })
}

当然,还有另外一种方式,那就是老老实实的补环境,让unidbg把结果算出来。

@Override
public DvmObject callObjectMethod(BaseVM vm, DvmObject dvmObject, String signature, VarArg varArg) {
    switch (signature) {
        case "android/content/pm/Signature->getPublicKey()Ljava/security/PublicKey;": {
            Signature sig = (Signature) dvmObject;
            System.out.println("sig: " + sig.toCharsString());
            byte[] bytes = sig.toByteArray();
            try {
                CertificateFactory cf = CertificateFactory.getInstance("X.509");
                InputStream in = new ByteArrayInputStream(bytes);
                X509Certificate cert = (X509Certificate) cf.generateCertificate(in);
                PublicKey publicKey = cert.getPublicKey();
                return vm.resolveClass("java/security/interfaces/RSAKey").newObject(publicKey);
            } catch (CertificateException e) {
                e.printStackTrace();
            }
        }
        case "java/security/interfaces/RSAKey->getModulus()Ljava/math/BigInteger;": {
            RSAKey key = (RSAKey) dvmObject.getValue();
            BigInteger integer = key.getModulus();
            return vm.resolveClass("java/math/BigInteger").newObject(integer);
        }
        case "java/math/BigInteger->toString(I)Ljava/lang/String;": {
            BigInteger integer = (BigInteger) dvmObject.getValue();
            int rdx = varArg.getIntArg(0);
            String str2 = integer.toString(rdx);
            System.out.println(str2);
            // String str1 = "bf62993ee6167065c274c37ed192f94bcb8ed0ead720246d8c17a1aa3218882aa2cc8abf5f5bb2e2fc0a590ee6cfe9a3c57c2c87f00c4070a8927ce1ce8b9089158d5ca8c2fae2eaf2c2bd1cf7bdf7b4835b01758c9fea39c54345de02a49c7ef94f02fdead461491212520c2bdc6f876c5341d8e24845bcb5808c0573de5e4b";
            return new StringObject(vm, str2);
        }
    }
    return super.callObjectMethod(vm, dvmObject, signature, varArg);
}

还有一种方式,就是自己把公钥算出来再返回。

image-20220307153212435
image-20220307153635518
image-20220303191456012

结果出来了,不过unidbg每次运行结果都不一样,说明可能有随机数的参与。

固定随机数

public void hook_libc() {
    IHookZz hookZz = HookZz.getInstance(emulator);
    hookZz.wrap(module.findSymbolByName("lrand48"), new WrapCallback() {
        @Override
        public void preCall(Emulator emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
        }

        @Override
        public void postCall(Emulator emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
            ctx.setR0(1);
        }
    });

    hookZz.wrap(module.findSymbolByName("srand48"), new WrapCallback() {
        @Override
        public void preCall(Emulator emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
        }

        @Override
        public void postCall(Emulator emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
            ctx.setR0(1);
        }
    });
}
public static void main(String[] args) {
    Meilishuo test = new Meilishuo();
    test.hook_libc();
    test.calc_sign();
}
image-20220303191428939

多次调用,结果已经不再变化。

unidbg逆向

image-20220303171617203

跳转到0x2145

image-20220303171528507

sub_1F24

image-20220303171753762

sub_1D28

image-20220303171844007

主要代码都在sub_1D28里面了。

看看sub_1B3C

image-20220303172128088

MD5的init函数,sub_1B64对应update函数,sub_1BE8对应final函数。

hook看看输入。

public void hook_md5() {
    IHookZz hookZz = HookZz.getInstance(emulator);
    hookZz.wrap(module.base + 0x1B65, new WrapCallback() {
        @Override
        public void preCall(Emulator emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
            Pointer input = ctx.getPointerArg(1);
            byte[] inputHex = input.getByteArray(0, ctx.getR2Int());
            Inspector.inspect(inputHex, "Input");
        }

        @Override
        public void postCall(Emulator emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
        }
    });

    hookZz.wrap(module.base + 0x1BE9, new WrapCallback() {
        @Override
        public void preCall(Emulator emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
            Pointer output = ctx.getR1Pointer();
            ctx.push(output);
        }

        @Override
        public void postCall(Emulator emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
            Pointer output = ctx.pop();
            byte[] outputHex = output.getByteArray(0, 16);
            Inspector.inspect(outputHex, "Output");
        }
    });
}
public static void main(String[] args) {
    Meilishuo test = new Meilishuo();
    test.hook_libc();
    test.hook_md5();
    test.calc_sign();
}

打印了挺多次。不过有的是input了3次,然后打印1次output;有的却是input了6次,才打印1次output。所以需要再分析下代码。

image-20220303173337588
image-20220303173607410

sub_1BEBa3为0时,直接出结果。当为1时,会把MD5的结果倒序再做一次MD5。

image-20220303180414540

输入是unidbg补函数时的字符串,应该是apk签名。

image-20220303190155677

第一次输入是随机数(0001)+输入参数的第一个字符串+第一次MD5的结果。然后MD5的结果倒序再做一次MD5。

image-20220303190745395

输入是之前MD5的结果+输入参数的第二个字符串。

image-20220303190919249

现在分析签名的构造。

f9ae09999f83d5ba
f 9 ae0 9 99 9 f8 3 d5ba
f   ae0   99   f8   d5ba
  9     9    9    3

f ae0 99 f8 d5ba可以看出是最后一次MD5的前面几位。

9993则是对随机数0001做了个映射变换。

image-20220303191213040

根据代码实现一下即可。

unidbg代码

public class Meilishuo extends AbstractJni {
    private final AndroidEmulator emulator;
    private final VM vm;
    private final Module module;

    public static String pkgName = "com.meilishuo";
    public static String apkPath = "unidbg-android/src/test/java/com/meilishuo/meilishuo1071.apk";
    public static String soName = "token";

    public Meilishuo() {
        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(soName, 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);
    }

    @Override
    public DvmObject callObjectMethod(BaseVM vm, DvmObject dvmObject, String signature, VarArg varArg) {
        switch (signature) {
            case "android/content/pm/Signature->getPublicKey()Ljava/security/PublicKey;": {
                return vm.resolveClass("java/security/interfaces/RSAKey").newObject(null);
            }
            case "java/security/interfaces/RSAKey->getModulus()Ljava/math/BigInteger;": {
                return vm.resolveClass("java/math/BigInteger").newObject(null);
            }
            case "java/math/BigInteger->toString(I)Ljava/lang/String;": {
                String str1 = "bf62993ee6167065c274c37ed192f94bcb8ed0ead720246d8c17a1aa3218882aa2cc8abf5f5bb2e2fc0a590ee6cfe9a3c57c2c87f00c4070a8927ce1ce8b9089158d5ca8c2fae2eaf2c2bd1cf7bdf7b4835b01758c9fea39c54345de02a49c7ef94f02fdead461491212520c2bdc6f876c5341d8e24845bcb5808c0573de5e4b";
                return new StringObject(vm, str1);
            }
        }
        return super.callObjectMethod(vm, dvmObject, signature, varArg);
    }

    public void hook_libc() {
        IHookZz hookZz = HookZz.getInstance(emulator);
        hookZz.wrap(module.findSymbolByName("lrand48"), new WrapCallback() {
            @Override
            public void preCall(Emulator emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
            }

            @Override
            public void postCall(Emulator emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
                ctx.setR0(1);
            }
        });

        hookZz.wrap(module.findSymbolByName("srand48"), new WrapCallback() {
            @Override
            public void preCall(Emulator emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
            }

            @Override
            public void postCall(Emulator emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
                ctx.setR0(1);
            }
        });
    }

    public void hook_md5() {
        IHookZz hookZz = HookZz.getInstance(emulator);
        hookZz.wrap(module.base + 0x1B65, new WrapCallback() {
            @Override
            public void preCall(Emulator emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
                Pointer input = ctx.getPointerArg(1);
                byte[] inputHex = input.getByteArray(0, ctx.getR2Int());
                Inspector.inspect(inputHex, "Input");
            }

            @Override
            public void postCall(Emulator emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
            }
        });

        hookZz.wrap(module.base + 0x1BE9, new WrapCallback() {
            @Override
            public void preCall(Emulator emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
                Pointer output = ctx.getR1Pointer();
                ctx.push(output);
            }

            @Override
            public void postCall(Emulator emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
                Pointer output = ctx.pop();
                byte[] outputHex = output.getByteArray(0, 16);
                Inspector.inspect(outputHex, "Output");
            }
        });
    }

    public void calc_sign() {
        List 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, "d569bb5990c698efda6b5d8b653fb696")));
        list.add(vm.addLocalObject(new StringObject(vm, "2022-03-03&everhu")));
        module.callFunction(emulator, 0x2145, list.toArray());
    }

    public static void main(String[] args) {
        Meilishuo test = new Meilishuo();
        test.hook_libc();
        test.hook_md5();
        test.calc_sign();
    }
}
 

                            
                        
                    
                    
                    

你可能感兴趣的:(meili说mw-sign字段unidbg逆向)