从案例中学习unidbg补系统调用(一)

一、引言

目标 APK:dewu.apk

目标方法:com.shizhuang.stone.main.SzSdk.lf

目标方法实现:libszstone.so

二、任务描述

unidbg 模拟执行如下函数。

1
public static native byte[] lf(String str, int i2, int i3);

三、初始化

基本框架以及补 JNI 环境代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
package com.dewu;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.arm.backend.Unicorn2Factory;
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;

public class dewuNew extends AbstractJni {

private final AndroidEmulator emulator;
private final VM vm;
private final Memory memory;
private final DvmClass cSzSdk;

public dewuNew(){

emulator = AndroidEmulatorBuilder
.for32Bit()
.addBackendFactory(new Unicorn2Factory(true))
.setProcessName("com.shizhuang.duapp")
.build();

memory = emulator.getMemory();
memory.setLibraryResolver(new AndroidResolver(23));

vm = emulator.createDalvikVM(new File("unidbg-android/src/test/resources/dewu/4.94.apk"));
vm.setJni(this);
vm.setVerbose(true);

DalvikModule dm = vm.loadLibrary("szstone", true);
cSzSdk = vm.resolveClass("com.shizhuang.stone.main.SzSdk");
dm.callJNI_OnLoad(emulator);

}

public byte[] call_fun() {
String a1 = "awt0bapt/data/user/0/com.shizhuang.duapp/filesbav4.94.0bavn486bcndewubc";
int a2 = 1;
int a3 = 0;
return (byte[]) cSzSdk.callStaticJniMethodObject(emulator, "lf(Ljava/lang/String;II)[B", a1, a2, a3).getValue();
}

public DvmObject<?> callStaticObjectMethod(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
switch (signature) {
case "android/app/ActivityThread->currentActivityThread()Landroid/app/ActivityThread;": {
return vm.resolveClass("android/app/ActivityThread").newObject(null);
}
case "android/provider/Settings$Secure->getString(Landroid/content/ContentResolver;Ljava/lang/String;)Ljava/lang/String;": {
String type = varArg.getObjectArg(1).getValue().toString();
System.out.println("Settings$Secure->getString: type is => " + type);
// adb 获取 android_id:
// settings get secure android_id
if ( type.equals("android_id")) {
return new StringObject(vm, "6fccbcdc8e425372");
}
else {
throw new UnsupportedOperationException();
}
}
}
return super.callStaticObjectMethod(vm, dvmClass, signature, varArg);
}

@Override
public DvmObject<?> callObjectMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {
switch (signature) {
case "android/app/ActivityThread->getApplication()Landroid/app/Application;": {
return vm.resolveClass("android/app/Application").newObject(null);
}
case "android/app/Application->getPackageManager()Landroid/content/pm/PackageManager;": {
return vm.resolveClass("android/content/pm/PackageManager").newObject(null);
}
case "android/app/Application->getContentResolver()Landroid/content/ContentResolver;": {
return vm.resolveClass("android/content/ContentResolver").newObject(null);
}
}
return super.callObjectMethod(vm, dvmObject, signature, varArg);
}


public static void main(String[] args) {
dewuNew dewuNew = new dewuNew();
System.out.println("call fl result: " + Arrays.toString(dewuNew.call_fun()));
}

}

对于补 JNI 环境,这里不细讲,主要讲补系统调用。

四、补系统调用

4.1 报错分析

运行报错

1
2
3
4
5
6
7
 WARN [com.github.unidbg.linux.ARM32SyscallHandler] (ARM32SyscallHandler:538) - handleInterrupt intno=2, NR=263, svcNumber=0x0, PC=RX@0x12117b88[libc.so]0x40b88, LR=RX@0x120f0ef3[libc.so]0x19ef3, syscall=null
java.lang.UnsupportedOperationException: clk_id=2
at com.github.unidbg.linux.ARM32SyscallHandler.clock_gettime(ARM32SyscallHandler.java:1740)
at com.github.unidbg.linux.ARM32SyscallHandler.hook(ARM32SyscallHandler.java:412)
at com.github.unidbg.arm.backend.Unicorn2Backend$11.hook(Unicorn2Backend.java:352)
at com.github.unidbg.arm.backend.unicorn.Unicorn$NewHook.onInterrupt(Unicorn.java:109)
at com.github.unidbg.arm.backend.unicorn.Unicorn.emu_start(Native Method)

这个报错主要信息在首行以及函数调用堆栈的第一行。

报错首行主要关注以下属性

  • intno

    into是异常类型,异常有很多种,比如未定义的指令,软中断、软断点等等,Unidbg 里对它们的定义如下。

    1
    2
    3
    int EXCP_UDEF = 1; /* undefined instruction */
    int EXCP_SWI = 2; /* software interrupt */
    int EXCP_BKPT = 7; /* software breakpoint */

    SVC指令就是软中断,对应于定义里的EXCP_SWI,我们熟悉的系统调用就是通过它发起。因此如果into的值是 2,就说明这是一个软中断,也就是系统调用。如果是其他中断类型,即未定义的指令或者软件中断,在 Unidbg 里会直接断下,交由用户处置,但很少碰到这两种情况。

  • NR

    NR 就是所谓的调用号,32 位存在 R7 寄存器,64 位存在 X8 寄存器。以 64 位为例,Unidbg 代码如下,从 X8 寄存器取出值。

    1
    int NR = backend.reg_read(Arm64Const.UC_ARM64_REG_X8).intValue();

    利用调用号通过查询系统调用表可以获取对应的系统调用的函数名,可依靠Linux System Call Table网站查询。

  • svcNumber

    svcNumber值为 0 表示系统调用,否则为 JNI 调用。

  • PC

    SO 中报错所在地址。

  • LR

    PC指向的地址所处函数的返回地址。

  • syscall

    syscall字段提供系统调用的函数名,但只有很少的几个系统调用这么做了。

    因此在更多时候,我们都需要查看系统调用表,以便确定到底是哪个系统调用出了问题。

分析刚才所发生的报错,NR=263, svcNumber=0x0,所以这个报错是系统调用引起的,而且具体的系统调用名我们在Linux System Call Table网站查询,这里是 32 位下的 263 调用号,查询可知它是 clock_gettime 这个系统调用。

接下来到 PC 和 LR 分别看看具体什么情况。

1
PC=RX@0x12117b88[libc.so]0x40b88, LR=RX@0x120f0ef3[libc.so]0x19ef3

PC 是 libc.so 的 0x40b88 地址处,将src/main/resources/android/sdk23/lib/libc.so从 Unidbg 里拷贝出来,放到 IDA 里解析(千万不要从手机里 pull 出 libc 然后分析,这是明朝的剑斩清朝的官)。

1
2
3
4
5
6
7
8
9
10
11
12
.text:00040B7C                             EXPORT clock_gettime
.text:00040B7C clock_gettime ; CODE XREF: j_clock_gettime+8↑j
.text:00040B7C ; DATA XREF: LOAD:00000DA0↑o ...
.text:00040B7C 07 C0 A0 E1 MOV R12, R7
.text:00040B80 14 70 9F E5 LDR R7, =0x107
.text:00040B84 00 00 00 EF SVC 0
.text:00040B88 0C 70 A0 E1 MOV R7, R12
.text:00040B8C 01 0A 70 E3 CMN R0, #0x1000
.text:00040B90 1E FF 2F 91 BXLS LR
.text:00040B94 00 00 60 E2 RSB R0, R0, #0
.text:00040B98 79 86 00 EA B j___set_errno_internal
.text:00040B98 ; End of function clock_gettime

LR 是执行完函数后的返回地址。

1
2
.text:00019EEE FA F7 B8 E8                 BLX             j_clock_gettime
.text:00019EF2 43 1C ADDS R3, R0, #1

值为0x19ef3是因为arm32的情况下地址会多+1。

4.2 clock_gettime 系统调用处理

那遇到系统调用相关的报错怎么处理呢?

第一步肯定是要了解这个系统调用的基本语义。

clock_gettime的语义和功能相当丰富,可以获取真实时间,进程时间等多种类型的时间,其函数原型如下。

1
int clock_gettime(clockid_t __clock, struct timespec* __ts);

timespec 结构体

1
2
3
4
struct timespec {
long tv_sec; // 秒时间戳
long tv_nsec; // 余下的纳秒时间戳
};

参数1 是时钟类型,时钟类型列举如下。

1
2
3
4
5
6
7
8
9
10
11
12
#define CLOCK_REALTIME 0 //当前的实际时间,是自 1970 年 1 月 1 日(UNIX 时间纪元)以来经过的秒数,受到系统时钟调整的影响
#define CLOCK_MONOTONIC 1 //自系统启动以来经过的时间
#define CLOCK_PROCESS_CPUTIME_ID 2//当前进程消耗的 CPU 时间
#define CLOCK_THREAD_CPUTIME_ID 3//当前线程消耗的 CPU 时间
#define CLOCK_MONOTONIC_RAW 4//类似于CLOCK_MONOTONIC,但是它不会受到任何系统时间调整或时钟源变化的影响
#define CLOCK_REALTIME_COARSE 5//类似于 CLOCK_REALTIME,但是提供较低精度的时间
#define CLOCK_MONOTONIC_COARSE 6//类似于 CLOCK_MONOTONIC,但提供较低精度的时间
#define CLOCK_BOOTTIME 7//系统启动以来经过的时间,包含了系统休眠时间
#define CLOCK_REALTIME_ALARM 8//当前时间,但以时间报警机制的精度为准
#define CLOCK_BOOTTIME_ALARM 9//系统启动以来的时间,类似于 CLOCK_BOOTTIME,但它同样考虑了定时器或闹钟机制的精度。
#define CLOCK_SGI_CYCLE 10//SGI(Silicon Graphics)的专有时钟
#define CLOCK_TAI 11//国际原子时

简而言之,时钟可以分为真实时间、CPU 时间、开机时间、从不确定的某个时间点开始计时这四大类。

我们来看一下unidbg是怎么处理这些时间类型的。根据报错所打印的函数调用堆栈,来到ARM32SyscallHandler.java:1746位置看一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
protected int clock_gettime(Backend backend, Emulator<?> emulator) {
// 获取clk_id(即时钟类型) 和 timespec 结构体
int clk_id = backend.reg_read(ArmConst.UC_ARM_REG_R0).intValue();
Pointer tp = UnidbgPointer.register(emulator, ArmConst.UC_ARM_REG_R1);
// 真实时间类型的处理逻辑
long offset = clk_id == CLOCK_REALTIME ? System.currentTimeMillis() * 1000000L : System.nanoTime() - nanoTime;
long tv_sec = offset / 1000000000L;//秒
long tv_nsec = offset % 1000000000L;//纳秒
if (log.isDebugEnabled()) {
log.debug("clock_gettime clk_id={}, tp={}, offset={}, tv_sec={}, tv_nsec={}", clk_id, tp, offset, tv_sec, tv_nsec);
}
switch (clk_id) {
// 真实时间
case CLOCK_REALTIME://0
// 从不确定的某个时间点开始计时
case CLOCK_MONOTONIC://1
case CLOCK_MONOTONIC_RAW://4
case CLOCK_MONOTONIC_COARSE://6
// 开机时间
case CLOCK_BOOTTIME://7
tp.setInt(0, (int) tv_sec);
tp.setInt(4, (int) tv_nsec);
return 0;
//CPU执行时间
case CLOCK_THREAD_CPUTIME_ID://3
tp.setInt(0, 0);
tp.setInt(4, 1);
return 0;
}
throw new UnsupportedOperationException("clk_id=" + clk_id);
}

再来看我们遇到的时间类型,结合报错信息java.lang.UnsupportedOperationException: clk_id=2可知,clk_id=2 的情况没有处理。而clk_id=2属于CPU时间类型,对于CPU执行时间,Unidbg 则直接设置为 1 纳秒,也属于摆烂。这里我们也破罐子破摔,处理同 CLOCK_THREAD_CPUTIME_ID 一样。

由于我们模拟的样例是 32 位的,所以这里自定义deWuSyscallHandler类继承自ARM32SyscallHandler,重写clock_gettime方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.dewu;

import com.github.unidbg.Emulator;
import com.github.unidbg.arm.backend.Backend;
import com.github.unidbg.linux.ARM32SyscallHandler;
import com.github.unidbg.memory.SvcMemory;
import com.github.unidbg.pointer.UnidbgPointer;
import com.sun.jna.Pointer;
import unicorn.ArmConst;

public class deWuSyscallHandler extends ARM32SyscallHandler {

public deWuSyscallHandler(SvcMemory svcMemory) {
super(svcMemory);
}

@Override
protected int clock_gettime(Backend backend, Emulator<?> emulator) {
int clk_id = backend.reg_read(ArmConst.UC_ARM_REG_R0).intValue();
Pointer tp = UnidbgPointer.register(emulator, ArmConst.UC_ARM_REG_R1);
if (clk_id == 2) {
tp.setInt(0, 0);
tp.setInt(4,0);
return 0;
}
return super.clock_gettime(backend, emulator);
}
}

然后在创建模拟器的时候使用它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package com.dewu;
...
public class dewuNew extends AbstractJni {

private final AndroidEmulator emulator;
private final VM vm;
private final Memory memory;
private final DvmClass cSzSdk;

public dewuNew(){
// AndroidEmulatorBuilder 的参数表示是否是 64 位的
AndroidEmulatorBuilder builder = new AndroidEmulatorBuilder(false){
@Override
public AndroidEmulator build() {
return new AndroidARMEmulator(processName,rootDir,backendFactories) {
@Override
protected UnixSyscallHandler<AndroidFileIO> createSyscallHandler(SvcMemory svcMemory) {
// 使用自定义的SyscallHandler
return new deWuSyscallHandler(svcMemory);
}
};
}
};

emulator = builder
.addBackendFactory(new Unicorn2Factory(true))
.setProcessName("com.shizhuang.duapp")
.build();

memory = emulator.getMemory();
memory.setLibraryResolver(new AndroidResolver(23));

vm = emulator.createDalvikVM(new File("unidbg-android/src/test/resources/dewu/4.94.apk"));
vm.setJni(this);
vm.setVerbose(true);

DalvikModule dm = vm.loadLibrary("szstone", true);
cSzSdk = vm.resolveClass("com.shizhuang.stone.main.SzSdk");
dm.callJNI_OnLoad(emulator);

}
...
}

4.3 popen 系统调用处理

运行后报错

1
2
3
4
5
6
7
[com.github.unidbg.linux.ARM32SyscallHandler] (ARM32SyscallHandler:1891) - openat dirfd=-100, pathname=/proc/sys/kernel/random/boot_id, oflags=0x20000, mode=0
[17:24:11 483] INFO [com.github.unidbg.linux.AndroidSyscallHandler] (AndroidSyscallHandler:499) - Return default pipe pair.
[17:24:11 484] WARN [com.github.unidbg.linux.ARM32SyscallHandler] (ARM32SyscallHandler:538) - handleInterrupt intno=2, NR=190, svcNumber=0x0, PC=RX@0x12118b5c[libc.so]0x41b5c, LR=RX@0x121065cb[libc.so]0x2f5cb, syscall=null
[17:24:11 485] WARN [com.github.unidbg.linux.ARM32SyscallHandler] (ARM32SyscallHandler:538) - handleInterrupt intno=2, NR=358, svcNumber=0x0, PC=RX@0x12118db0[libc.so]0x41db0, LR=RX@0x12106635[libc.so]0x2f635, syscall=null
java.lang.AbstractMethodError: com.github.unidbg.linux.file.PipedWriteFileIO
at com.github.unidbg.file.AbstractFileIO.dup2(AbstractFileIO.java:167)
at com.github.unidbg.linux.ARM32SyscallHandler.dup3(ARM32SyscallHandler.java:2104)

首先是NR=190,对应于vfork,其次是 NR = 358 ,对应于dup3

为什么这里有多个系统调用会WARN?事实上这是库函数popen惹的祸,只要看到com.github.unidbg.linux.file.PipedWriteFileIO报错,那么就应该直接转入处理popen的逻辑。

同样,我们首先需要了解这个系统调用的基本语义。popen 在 Android Native 上是一个高频函数,设备信息收集、环境检测都可以用到它。函数原型如下

1
FILE *popen(const char *command, const char *type);

popen 以创建管道的方式启动一个进程, 并调用 shell,像下面这样使用,比如这里在作用上等价于 adb shell 里输入getprop ro.build.id

1
2
3
4
5
std::string cmd = "getprop ro.build.id";
char value[PROP_VALUE_MAX] = {0};
FILE* file = popen(cmd.c_str(), "r");
fread(value, PROP_VALUE_MAX, 1, file);
pclose(file);

popen 在底层原理上是创建了一个新进程,然后通过管道去做通信,返回数据。但 Unidbg 在多进程相关的系统调用处理上十分不完善,无法支撑popen正常的执行它的底层逻辑。

那如何处理它呢?一种思路就是越过创建多线程的相关逻辑,只处理好管道的返回值,来让它的功能基本满足,这可以当成固定处理模式,遇到了就这么做就行。

第一步是用 Dobby 或任意 Hook 工具,在库函数的层面 Hook 获取到 popen 的 command,放到 emulator 的全局变量里,方便后续处理。我这里使用的是 xhook,代码位于 hookPopen 函数内。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package com.dewu;
...
public class dewuNew extends AbstractJni {
private final AndroidEmulator emulator;
private final VM vm;
private final Memory memory;
private final DvmClass cSzSdk;

public dewuNew(){
// AndroidEmulatorBuilder 的参数表示是否是 64 位的
AndroidEmulatorBuilder builder = new AndroidEmulatorBuilder(false){
@Override
public AndroidEmulator build() {
return new AndroidARMEmulator(processName,rootDir,backendFactories) {
@Override
protected UnixSyscallHandler<AndroidFileIO> createSyscallHandler(SvcMemory svcMemory) {
return new deWuSyscallHandler(svcMemory);
}
};
}
};

emulator = builder
.addBackendFactory(new Unicorn2Factory(true))
.setProcessName("com.shizhuang.duapp")
.build();

memory = emulator.getMemory();
memory.setLibraryResolver(new AndroidResolver(23));

vm = emulator.createDalvikVM(new File("unidbg-android/src/test/resources/dewu/4.94.apk"));
vm.setJni(this);
vm.setVerbose(true);

DalvikModule dm = vm.loadLibrary("szstone", true);
cSzSdk = vm.resolveClass("com.shizhuang.stone.main.SzSdk");
dm.callJNI_OnLoad(emulator);
//hookPopen();//这里调用hookPopen也行
}
public void hookPopen(){
IxHook ixHook = XHookImpl.getInstance(emulator);
ixHook.register("libszstone.so", "popen", new ReplaceCallback() {
@Override
public HookStatus onCall(Emulator<?> emulator, long originFunction) {
RegisterContext registerContext = emulator.getContext();
String command = registerContext.getPointerArg(0).getString(0);
// 设置全局变量command
emulator.set("command", command);
System.out.println("command: " + command);
return HookStatus.RET(emulator, originFunction);
}
}, true);
//使 Hook 生效
ixHook.refresh();
}
...
public static void main(String[] args) {
dewuNew dewuNew = new dewuNew();
// 由于hook的是libszstone.so里的popen函数,所以等该库加载之后再调用hook函数
dewuNew.hookPopen();
System.out.println("call fl result: " + Arrays.toString(dewuNew.call_fun()));
}
}

运行可以发现 Hook 生效,打印出了具体的命令。

1
2
[17:44:40 317]  INFO [com.github.unidbg.linux.ARM32SyscallHandler] (ARM32SyscallHandler:1891) - openat dirfd=-100, pathname=/proc/sys/kernel/random/boot_id, oflags=0x20000, mode=0
command: stat /data

通过adb shell 执行该指令,得到信息如下

1
2
3
4
5
6
7
8
marlin:/ # stat /data
File: /data
Size: 4096 Blocks: 16 IO Blocks: 512 directory
Device: 10313h/66323d Inode: 2 Links: 52
Access: (0771/drwxrwx--x) Uid: ( 1000/ system) Gid: ( 1000/ system)
Access: 1970-01-03 22:03:32.000000000 -0500
Modify: 2019-10-28 19:34:44.446668305 -0400
Change: 1970-01-21 18:09:19.113333494 -0500

获取的是文件的详细信息。接下来在系统调用端做处理,重写 pipe2(部分代码逻辑可参考AndroidSyscallHandler中的代码),根据当前的 command 返回从 adb shell 获取到的值,以及出现 popen 内部会使用的 vfork 和 wait4 系统调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
package com.dewu;

import com.github.unidbg.Emulator;
import com.github.unidbg.arm.backend.Backend;
import com.github.unidbg.arm.context.EditableArm32RegisterContext;
import com.github.unidbg.arm.context.RegisterContext;
import com.github.unidbg.linux.ARM32SyscallHandler;
import com.github.unidbg.linux.file.ByteArrayFileIO;
import com.github.unidbg.linux.file.DumpFileIO;
import com.github.unidbg.memory.SvcMemory;
import com.github.unidbg.pointer.UnidbgPointer;
import com.sun.jna.Pointer;
import unicorn.ArmConst;

import java.util.concurrent.ThreadLocalRandom;

public class deWuSyscallHandler extends ARM32SyscallHandler {

public deWuSyscallHandler(SvcMemory svcMemory) {
super(svcMemory);
}

@Override
protected boolean handleUnknownSyscall(Emulator<?> emulator, int NR) {
switch (NR) {
case 190: {
vfork(emulator);
return true;
}
case 114: {//???
wait4(emulator);
return true;
}
}
return super.handleUnknownSyscall(emulator, NR);
}

private void wait4(Emulator<?> emulator) {
return;
}
//线程创建
private void vfork(Emulator<?> emulator) {
EditableArm32RegisterContext context = emulator.getContext();
int childPid = emulator.getPid() + ThreadLocalRandom.current().nextInt(256);
System.out.println("vfork pid: " + childPid);
context.setR0(childPid);
}

@Override
protected int clock_gettime(Backend backend, Emulator<?> emulator) {
int clk_id = backend.reg_read(ArmConst.UC_ARM_REG_R0).intValue();
Pointer tp = UnidbgPointer.register(emulator, ArmConst.UC_ARM_REG_R1);
if (clk_id == 2) {
tp.setInt(0, 0);
tp.setInt(4,0);
return 0;
}
return super.clock_gettime(backend, emulator);
}

@Override
protected int pipe2(Emulator<?> emulator) {
EditableArm32RegisterContext context = emulator.getContext();
Pointer pipefd = context.getPointerArg(0);
int write = getMinFd();
this.fdMap.put(write, new DumpFileIO(write));
int read = getMinFd();
// 获取全局变量 command
String command = emulator.get("command");
System.out.println("deal command: " + command);
//stdout中写入popen command 应该返回的结果
String stdout = "\n";
switch (command) {
case "stat /data": {
stdout = " File: /data\n" +
" Size: 4096 Blocks: 16 IO Blocks: 512 directory\n" +
"Device: 10313h/66323d Inode: 2 Links: 52\n" +
"Access: (0771/drwxrwx--x) Uid: ( 1000/ system) Gid: ( 1000/ system)\n" +
"Access: 1970-01-03 22:03:32.000000000 -0500\n" +
"Modify: 2019-10-28 19:34:44.446668305 -0400\n" +
"Change: 1970-01-21 18:09:19.113333494 -0500";
break;
}
}
this.fdMap.put(read, new ByteArrayFileIO(0, "pipe2_read_side", stdout.getBytes()));
pipefd.setInt(0, read);
pipefd.setInt(4, write);
context.setR0(0);
return 0;
}

}

运行发现一大堆 popen 调用,根据 command 逐一返回合适的值。

4.4 loctl 系统调用处理

继续运行,报错

1
2
3
4
WARN [com.github.unidbg.linux.ARM32SyscallHandler] (ARM32SyscallHandler:538) - handleInterrupt intno=2, NR=54, svcNumber=0x0, PC=RX@0x12118aa8[libc.so]0x41aa8, LR=RX@0x1211e7f1[libc.so]0x477f1, syscall=null
java.lang.AbstractMethodError: com.github.unidbg.linux.file.TcpSocket: request=0x8927, argp=0xe4fff470
at com.github.unidbg.file.AbstractFileIO.ioctl(AbstractFileIO.java:64)
at com.github.unidbg.linux.ARM32SyscallHandler.ioctl(ARM32SyscallHandler.java:2025)

NR=54 对应 ioctl ,在 Android Native 里,它常用来获取 MAC 地址。它的参数 request用于指令操作类型,对于各种各样的文件和需求,有不同的request,Unidbg 首先预处理了其中的一部分,但这里报错的 0x8927 尚未处理。

一个最简单的处理办法就是来到报错点com.github.unidbg.file.AbstractFileIO.ioctl(AbstractFileIO.java:64),添加判断request=0x8927 的情况,让其返回0。

1
2
3
4
5
6
7
8
9
10
@Override
public int ioctl(Emulator<?> emulator, long request, long argp) {
if (log.isTraceEnabled()) {
emulator.attach().debug();
}
if (request == 0x8927){
return 0;
}
throw new AbstractMethodError(getClass().getName() + ": request=0x" + Long.toHexString(request) + ", argp=0x" + Long.toHexString(argp));
}

最后运行成功,结果如下

1
call fl result: [50, 50, 55, 69, 69, 57, 50, 66, 57, 52, 55, 56, 52, 66, 69, 67, 55, 57, 65, 70, 55, 54, 52, 69, 69, 68, 65, 53, 49, 70, 49, 54, 46, 99, 71, 70, 121, 89, 87, 48, 102, 89, 50, 77, 49, 89, 106, 90, 107, 79, 84, 107, 116, 77, 50, 77, 52, 89, 105, 48, 48, 89, 50, 73, 50, 76, 84, 107, 52, 89, 106, 89, 116, 78, 68, 69, 51, 79, 87, 86, 107, 90, 106, 103, 119, 78, 106, 81, 49, 72, 110, 90, 108, 99, 110, 78, 112, 98, 50, 52, 102, 77, 82, 53, 119, 98, 71, 70, 48, 90, 109, 57, 121, 98, 82, 57, 104, 98, 109, 82, 121, 98, 50, 108, 107, 72, 109, 86, 106, 72, 122, 69, 61, 46, 7, -97, 20, 68, 100, -43, -14, -89, -97, 71, -7, 61, 61, 94, -73, 32, -86, -22, 41, 107, 127, -64, 123, 57, 60, -38, 20, -15, -91, -71, 24, 38, -126, -28, 4, -44, 51, -86, -101, -27, -94, -82, 73, 87, -91, 5, 3, 87, -24, -88, 6, -65, 105, -25, -83, 60, 98, 20, -10, 86, 75, -27, -55, 58, 73, -100, -59, -7, 118, 30, -99, 13, -9, 53, -5, -44, 15, 69, 72, 51, -68, 92, -22, 88, 28, -110, -6, -92, -89, 105, 39, -25, -103, 95, -71, 21, -43, 81, 7, -75, 71, -102, -60, 36, -48, 15, -79, -96, 19, 114, 78, -107, -60, -33, 67, -76, 121, -77, -9, -27, -24, 23, -15, 34, 109, -91, -108, -54, 17, -10, -92, -120, 1, -55, 28, 33, -94, -78, 88, -5, 1, -49, 52, -29, -16, -68, -73, 12, 55, 78, 84, -118, 44, 11, -96, 113, -33, -98, -64, 122, -66, 120, -5, -14, -106, -40, 22, -65, 103, 116, -61, -32, -44, -83, -115, -98, 44, -22, -20, 45, 64, -24, 61, 22, 8, -8, 110, -52, -108, -75, -23, -29, 74, -54, -41, 118, -13, 5, 59, 28, -45, 80, 121, -66, 27, -3, -53, -67, -32, 95, -30, 108, 96, -58, -120, 98, 84, 34, 49, 50, -47, 85, 127, 17, 108, -86, -83, -78, 68, -118, 119, -70, -110, -16, -103, 72, 5, 37, -67, 8, 27, 23, 104, -45, 25, -105, -116, 94, -10, -123, -101, 51, 114, -35, 125, 125, -49, -109, 20, -77, 39, -65, -7, -96, 71, -58, -41, -27, 85, -58, 95, -33, -85, 43, -61, 119, 116, -68, 126, -79, -115, 114, -53, -101, 44, -118, -111, 12, 27, -37, 5, -8, 113, 102, 22, 36, 94, -68, 14, -81, -40, 5, -6, 88, 102, -86, 21, 87, 13, -88, -87, 107, -108, 113, -83, -22, -55, -32, 70, -29, -104, 30, 47, 99, -52, 2, -3, -40, 8, -31, 120, -87, 122, -90, 90, -86, 93, -12, 0, 65, 42, -25, 41, 18, 50, -112, -77, -58, -75, -118, -58, 79, -16, 4, -35, 60, -106, -123, -46, -7, 96, 112, 36, 0, -123, 61, 75, -28, 24, 126, -98, -27, -88, -3, 85, -23, 108, -100, -41, -22, 39, 50, -84, -102, -42, 25, 102, 21, 119, -41, 0, 19, -64, -78, 118, 62, -128, 62, 45, 122, -79, 13, 114, -66, -45, -41, -90, -41, 85, 9, 32, -102, 64, 30, -74, -19, 76, -102, 91, 106, -92, -85, -58, 75, -123, -87, 98, -125, -18, 24, 57, 6, -25, -3, 95, -33, -116, 102, 39, -43, 47, 36, 118, 51, 126, 24, 26, -19, -90, 124, -31, 38, -29, -110, 50, 89, 103, 94, -62, 87, -40, -45, 25, 48, -43, -68, 11, -65, 112, -33, -113, -14, 96, -29, 106, 70, -28, -74, -112, -22, -26, 51, -50, -111, -125, -95, 61, 36, -58, -1, -78, 44, 35, 11, -58, 63, 49, 65, 109, 117, -16, -127, -1, 125, 59, -30, 7, 3, 19, 87, -25, -106, -51, -81, 88, 12, -101, -26, 31, 72, -98, 94, 53, 74, -88, -60, -40, -57, 119, -31, -126, -8, 5, -98, -30, 55, 17, -119, 37, -82, 31, 95, -82, 35, 82, 81, -43]

从案例中学习unidbg补系统调用(一)
http://example.com/2024/12/05/Unidbg模拟执行/从案例中学习unidbg补系统调用(一)/
作者
gla2xy
发布于
2024年12月5日
许可协议