soul cs字段unidbg實(shí)現(xiàn)

soul cs字段unidbg實(shí)現(xiàn)

環(huán)境

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

可以看到,第二個(gè)參數(shù)是時(shí)間戳,第三個(gè)參數(shù)url,第四個(gè)參數(shù)是headers里面的參數(shù)拼接起來(lái)的。這個(gè)的簽名長(zhǎng)度是36,是一個(gè)比較不常見(jiàn)的數(shù)字,對(duì)比了幾個(gè)簽名,發(fā)現(xiàn)都是028f**77ac的形式(實(shí)際上,在另一臺(tái)手機(jī)中,cs是02af**008c的形式),接下來(lái)研究一下cs的具體構(gòu)成。

通過(guò)frida,我們自己控制傳入的參數(shù),看看輸出是怎樣的。

// 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

在第一組中,當(dāng)時(shí)間戳為0x111111110x01234567時(shí),我們可以清晰地分辨出哪些位置是由時(shí)間戳構(gòu)成的,是由時(shí)間戳的哪一位構(gòu)成的??梢钥闯?code>6-7, 10-11, 14-15, 18-19位都是由時(shí)間戳成的。此外,當(dāng)時(shí)間戳變化時(shí),第24-31位也在變化,說(shuō)明此處有時(shí)間戳參與計(jì)算。

在第二組中,時(shí)間戳和header都不變,當(dāng)url變化時(shí),第24-31位也在變化,說(shuō)明此處有url參與計(jì)算,結(jié)合第一組得出,第24-31位有時(shí)間戳和url一起參與計(jì)算。

在第三組中,時(shí)間戳和url都不變,當(dāng)header變化時(shí),cs的4-5, 8-9, 12-13, 16-17都在變化,說(shuō)明這些位置是由header計(jì)算得出。

其余位置無(wú)變化,可能與app版本或者手機(jī)環(huán)境本身相關(guān),也有可能直接就是固定的。

unidbg實(shí)現(xiàn)

接下來(lái)是用unidbg來(lái)調(diào)用生成cs,先搭個(gè)框架。

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();
    }
}

然后就是報(bào)錯(cuò)+補(bǔ)環(huán)境

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");
}

然后出現(xiàn)了讓我無(wú)法理解的情況。

image-20220120150848225

這個(gè)報(bào)錯(cuò)讓我不知道怎么補(bǔ)環(huán)境了。。開(kāi)啟全部日志之后,也不知道怎么找到問(wèn)題。最終是一通亂拳,把"23"改成不存在的SDK版本(如"ff")才得以繼續(xù),這讓我很不理解,有誰(shuí)知道什么原因可以分享一下。

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

這個(gè)需要返回一個(gè)byte,那具體要返回多少呢?可以使用objection和hluwa/Wallbreaker來(lái)獲取

clone下來(lái)后,啟動(dòng)objection,然后加載插件

plugin load E:\Wallbreaker

然后dump整個(gè)類(lèi)的實(shí)例

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

所以應(yīng)該返回119(實(shí)際上,隨便一個(gè)值也能返回結(jié)果)。

也可以查看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

結(jié)果出來(lái)了,但是和hook到的結(jié)果不完全一樣。幸運(yùn)的是,不一樣的部分是之前觀察到的不變的部分,可能由于補(bǔ)的環(huán)境和正常環(huán)境不一樣,所以這些部分和正常的不一樣。不過(guò)和簽名相關(guān)的部分是一樣的,這樣的話,我們只需要把不一樣的部分改一下,應(yīng)該就能用了?

完整實(shí)現(xiàn)

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();
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容