soul cs字段unidbg实现
环境
app 3.83.0
Java
image-20220120150049788jadx搜索
image-20220117164321104查找用例
image-20220117164420865cn.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
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在第一组中,当时间戳为0x11111111
和0x01234567
时,我们可以清晰地分辨出哪些位置是由时间戳构成的,是由时间戳的哪一位构成的。可以看出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();
}
}
网友评论