soul cs字段unidbg实现

soul cs字段unidbg实现

环境

app 3.83.0

Java

image-20220120150049788

jadx搜索

image-20220117164321104

查找用例

image-20220117164420865

cn.soulapp.android.net.q.j.b

image-20220117164450987

cn.soulapp.android.net.SoulNetworkSDK.g

image-20220117164538640

cn.soulapp.android.soulpower.SoulPowerful.i

image-20220117164704641

cn.soulapp.android.soulpower.SoulPowerful.h

image-20220117165143223

hook 看看

android hooking watch class_method cn.soulapp .android.soulpower.SoulPowerful.h --dump-args --dump-return
image-20220120141036784

可以看到,第二个参数是时间戳,第三个参数url,第四个参数是headers里面的参数拼接起来的。这个的签名长度是36,是一个比较不常见的数字,对比了几个签名,发现都是028f**77ac的形式(实际上,在另一台手机中,cs是02af**008c的形式),接下来研究一下cs的具体构成。

通过frida,我们自己控制传入的参数,看看输出是怎样的。

// hook_soul.js
function callh(ts, url, header) {
    var soul = Java.use("cn.soulapp.android.soulpower.SoulPowerful");
    var currentApplication = Java.use("android.app.ActivityThread").currentApplication();
    var context = currentApplication.getApplicationContext();
    var ret = soul.h(context, ts, url, header);
    // console.log("h-ret:", ret);
    return ret;
}

rpc.exports = {
    callh: callh
}
# hook_soul.py
import frida

def read_js():
    with open(r"soul\hook_soul.js", 'r') as fp:
        return fp.read()

def on_message(message, data):
    pass

if __name__ == '__main__':
    device = frida.get_usb_device()
    pid = device.get_frontmost_application().pid
    session = device.attach(pid)
    js = read_js()
    script = session.create_script(js)
    script.on('message', on_message)
    script.load()

    url = 'hello'
    header = 'everhu'
    for ts in [0x000000ff, 0x0000ff00, 0x00ff0000, 0x01234567, 0x10325476, 0x11111111]:
        cs = script.exports.callh(int(ts), url, header)
        print(f'0x{ts:08x}', url, header, cs)
    
    ts = 0x11111111
    header = 'everhu'
    for c in 'abcdef':
        url = c * 5
        cs = script.exports.callh(ts, url, header)
        print(f'0x{ts:08x}', url, header, cs)

    ts = 0x11111111
    url = 'hello'
    for c in 'abcdef':
        header = c * 6
        cs = script.exports.callh(ts, url, header)
        print(f'0x{ts:08x}', url, header, cs)
ts url header cs
0x000000ff hello everhu 028f 0b 00 5a f0 33 00 27 f0 77 6U 601d8960 77ac
0x0000ff00 hello everhu 028f 0b 00 5a 0f 33 f0 27 00 77 6U dee8bd70 77ac
0x00ff0000 hello everhu 028f 0b f0 5a 00 33 00 27 0f 77 6U 7cbbaebc 77ac
0x01234567 hello everhu 028f 0b 31 5a 65 33 40 27 72 77 6U 12a566c1 77ac
0x10325476 hello everhu 028f 0b 20 5a 74 33 51 27 63 77 6U 8446767b 77ac
0x11111111 hello everhu 028f 0b 11 5a 11 33 11 27 11 77 6U 0b64c40a 77ac
0x11111111 aaaaa everhu 028f 0b 11 5a 11 33 11 27 11 77 6U a2a9352f 77ac
0x11111111 bbbbb everhu 028f 0b 11 5a 11 33 11 27 11 77 6U 776491c4 77ac
0x11111111 ccccc everhu 028f 0b 11 5a 11 33 11 27 11 77 6U 817b3dbe 77ac
0x11111111 ddddd everhu 028f 0b 11 5a 11 33 11 27 11 77 6U 247ea924 77ac
0x11111111 eeeee everhu 028f 0b 11 5a 11 33 11 27 11 77 6U d4f29b3c 77ac
0x11111111 fffff everhu 028f 0b 11 5a 11 33 11 27 11 77 6U cc70a6c4 77ac
0x11111111 hello aaaaaa 028f 3f 11 be 11 86 11 60 11 77 6U 0b64c40a 77ac
0x11111111 hello bbbbbb 028f d8 11 d7 11 80 11 24 11 77 6U 0b64c40a 77ac
0x11111111 hello cccccc 028f 1c 11 a9 11 6b 11 b3 11 77 6U 0b64c40a 77ac
0x11111111 hello dddddd 028f 6a 11 f5 11 99 11 73 11 77 6U 0b64c40a 77ac
0x11111111 hello eeeeee 028f 0b 11 71 11 f2 11 cd 11 77 6U 0b64c40a 77ac
0x11111111 hello ffffff 028f ef 11 fc 11 58 11 63 11 77 6U 0b64c40a 77ac

记录如上表所示。

image-20220120145028155

在第一组中,当时间戳为0x111111110x01234567时,我们可以清晰地分辨出哪些位置是由时间戳构成的,是由时间戳的哪一位构成的。可以看出6-7, 10-11, 14-15, 18-19位都是由时间戳成的。此外,当时间戳变化时,第24-31位也在变化,说明此处有时间戳参与计算。

在第二组中,时间戳和header都不变,当url变化时,第24-31位也在变化,说明此处有url参与计算,结合第一组得出,第24-31位有时间戳和url一起参与计算。

在第三组中,时间戳和url都不变,当header变化时,cs的4-5, 8-9, 12-13, 16-17都在变化,说明这些位置是由header计算得出。

其余位置无变化,可能与app版本或者手机环境本身相关,也有可能直接就是固定的。

unidbg实现

接下来是用unidbg来调用生成cs,先搭个框架。

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

    public static String pkgName = "cn.soulapp.android";
    public static String apkPath = "unidbg-android/src/test/java/com/soul/soul3830.apk";
    public static String soPath = "";

    public Soul() {
        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("soulpower", true);
        module = dm.getModule();
        dm.callJNI_OnLoad(emulator);
    }

    public void call_h() {
        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(0x01234567);
        String url = "hello";
        list.add(vm.addLocalObject(new StringObject(vm, url)));
        String header = "everhu";
        list.add(vm.addLocalObject(new StringObject(vm, header)));

        Number ret = module.callFunction(emulator, 0xaa73c, list.toArray());
        System.out.println("ret h: " + vm.getObject(ret.intValue()).getValue().toString());
    }

    public static void main(String[] args) {
        Soul test = new Soul();
        System.out.println("=== Start call h");
        test.call_h();
    }
}

然后就是报错+补环境

image-20220120150519066
@Override
public DvmObject<?> getStaticObjectField(BaseVM vm, DvmClass dvmClass, String signature) {
    switch (signature) {
        case "android/os/Build$VERSION->RELEASE:Ljava/lang/String;": {
            return new StringObject(vm, "7.1.2");
        }
    }
    return super.getStaticObjectField(vm, dvmClass, signature);
}
image-20220120150651113
// wrong case
case "android/os/Build$VERSION->SDK:Ljava/lang/String;": {
    return new StringObject(vm, "23");
}

然后出现了让我无法理解的情况。

image-20220120150848225

这个报错让我不知道怎么补环境了。。开启全部日志之后,也不知道怎么找到问题。最终是一通乱拳,把"23"改成不存在的SDK版本(如"ff")才得以继续,这让我很不理解,有谁知道什么原因可以分享一下。

case "android/os/Build$VERSION->SDK:Ljava/lang/String;": {
    return new StringObject(vm, "ff");
}
image-20220120151517512

这个需要返回一个byte,那具体要返回多少呢?可以使用objection和hluwa/Wallbreaker来获取

clone下来后,启动objection,然后加载插件

plugin load E:\Wallbreaker

然后dump整个类的实例

plugin wallbreaker classdump cn.soulapp.andro id.soulpower.InfoGather
image-20220120161513916

所以应该返回119(实际上,随便一个值也能返回结果)。

也可以查看android.os.Build $VERSION

plugin wallbreaker classdump android.os.Build$VERSION
image-20220120161717699
@Override
public byte getStaticByteField(BaseVM vm, DvmClass dvmClass, String signature) {
    switch (signature) {
        case "cn/soulapp/android/soulpower/InfoGather->aa:B": {
            return (byte) 119;
        }
    }
    return super.getStaticByteField(vm, dvmClass, signature);
}
image-20220120155318862

注意0x77 = 119

结果出来了,但是和hook到的结果不完全一样。幸运的是,不一样的部分是之前观察到的不变的部分,可能由于补的环境和正常环境不一样,所以这些部分和正常的不一样。不过和签名相关的部分是一样的,这样的话,我们只需要把不一样的部分改一下,应该就能用了?

完整实现

package com.soul;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Module;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.memory.Memory;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

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

    public static String pkgName = "cn.soulapp.android";
    public static String apkPath = "unidbg-android/src/test/java/com/soul/soul3830.apk";
    public static String soPath = "";

    public Soul() {
        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("soulpower", true);
        module = dm.getModule();
        dm.callJNI_OnLoad(emulator);
    }

    @Override
    public DvmObject<?> getStaticObjectField(BaseVM vm, DvmClass dvmClass, String signature) {
        switch (signature) {
            case "android/os/Build$VERSION->RELEASE:Ljava/lang/String;": {
                return new StringObject(vm, "7.1.2");
            }
            case "android/os/Build$VERSION->SDK:Ljava/lang/String;": {
                return new StringObject(vm, "ff");
            }
        }
        return super.getStaticObjectField(vm, dvmClass, signature);
    }

    @Override
    public byte getStaticByteField(BaseVM vm, DvmClass dvmClass, String signature) {
        switch (signature) {
            case "cn/soulapp/android/soulpower/InfoGather->aa:B": {
                return (byte) 119;
            }
        }
        return super.getStaticByteField(vm, dvmClass, signature);
    }

    public void call_h() {
        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(0x01234567);
        String url = "hello";
        list.add(vm.addLocalObject(new StringObject(vm, url)));
        String header = "everhu";
        list.add(vm.addLocalObject(new StringObject(vm, header)));

        Number ret = module.callFunction(emulator, 0xaa73c, list.toArray());
        System.out.println("ret h: " + vm.getObject(ret.intValue()).getValue().toString());
    }

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

推荐阅读更多精彩内容