soul cs字段unidbg實(shí)現(xiàn)
環(huán)境
app 3.83.0
Java
jadx搜索
查找用例
cn.soulapp.android.net.q.j.b
cn.soulapp.android.net.SoulNetworkSDK.g
cn.soulapp.android.soulpower.SoulPowerful.i
cn.soulapp.android.soulpower.SoulPowerful.h
hook 看看
android hooking watch class_method cn.soulapp .android.soulpower.SoulPowerful.h --dump-args --dump-return
可以看到再愈,第二個(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 |
記錄如上表所示勤揩。
在第一組中,當(dāng)時(shí)間戳為0x11111111
和0x01234567
時(shí)秘蛔,我們可以清晰地分辨出哪些位置是由時(shí)間戳構(gòu)成的陨亡,是由時(shí)間戳的哪一位構(gòu)成的∩钤保可以看出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)境
@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);
}
// wrong case
case "android/os/Build$VERSION->SDK:Ljava/lang/String;": {
return new StringObject(vm, "23");
}
然后出現(xiàn)了讓我無(wú)法理解的情況玷或。
這個(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");
}
這個(gè)需要返回一個(gè)byte,那具體要返回多少呢舞竿?可以使用objection和hluwa/Wallbreaker來(lái)獲取
clone下來(lái)后京景,啟動(dòng)objection,然后加載插件
plugin load E:\Wallbreaker
然后dump整個(gè)類的實(shí)例
plugin wallbreaker classdump cn.soulapp.andro id.soulpower.InfoGather
所以應(yīng)該返回119(實(shí)際上骗奖,隨便一個(gè)值也能返回結(jié)果)确徙。
也可以查看android.os.Build $VERSION
plugin wallbreaker classdump android.os.Build$VERSION
@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);
}
注意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();
}
}