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<Object> 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<Object> 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<Object> 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<Object> 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,还可以看看其他使用相同方案的产品。

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

推荐阅读更多精彩内容