kuaidui作业sign逆向及unidbg实现

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 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 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 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 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实现)