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<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, "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<HookZzArm32RegisterContext>() {
        @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<HookZzArm32RegisterContext>() {
        @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<HookZzArm32RegisterContext>() {
        @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<HookZzArm32RegisterContext>() {
        @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<HookZzArm32RegisterContext>() {
            @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<HookZzArm32RegisterContext>() {
            @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<HookZzArm32RegisterContext>() {
            @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<HookZzArm32RegisterContext>() {
            @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<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, "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();
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容