Unidbg入门介绍

一、unidbg介绍

1.1 unidbg

unidbg 是一个基于 unicorn 的逆向工具,可以黑盒调用安卓和 iOS 中的 so 文件。这使得逆向人员可以在无需了解 so 内部算法原理的情况下,主动调用 so 中的函数,让其中的算法“为我所用”,只需要传入所需的参数、补全运行所需的环境,即可运行出所需要的结果。及由此衍生的辅助分析、算法还原、SO 调试与逆向等等功能。

对于Android逆向来说,unidbg的特点有以下几种:

  1. 模拟 JNI 调用的 API,因此可以调用JNI_OnLoad函数。
  2. 支持JavaVMJNIEnv
  3. 支持模拟系统调用指令。
  4. 支持ARM32ARM64
  5. 支持基于Dobby的inline hook。
  6. 支持基于xHook的GOT hook。
  7. unicorn后端支持简单的控制台调试器,gdb stub,指令追踪和内存读写追踪。
  8. 支持dynarmic快速的后端。

1.2 unicorn

Unicorn 是一个轻量级, 多平台, 多架构的 CPU 模拟器框架,可支持的架构包括 x86、ARM、MIPS 和 PowerPC 等,是一个强大的逆向工程和动态分析工具。

二、环境搭建

unidbg 项目地址:https://github.com/zhkl0228/unidbg

三、unidbg入门用例

3.1 基本框架

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
public class TTEncrypt{

private final AndroidEmulator emulator;

private final VM vm;

private final Module module;

private final DvmClass TTEncryptUtils;

TTEncrypt(){
// 创建模拟器
emulator = AndroidEmulatorBuilder.for32Bit()//32bit
.addBackendFactory(new DynarmicFactory(true))//添加后端工厂,见项目目录中的backend目录下
.setProcessName("com.qidian.dldl.official")
.build();//创建实例

Memory memory = emulator.getMemory();// 模拟器的内存操作接口

memory.setLibraryResolver(new AndroidResolver(23)); // 设置系统类库解析,即SDK版本

// 创建Android虚拟机
vm = emulator.createDalvikVM(new File("sssss.apk"));
vm.setVerbose(true);//设置是否打印Jni调用细节

// 加载so库到unicorn虚拟内存,加载成功以后会默认调用init_array等函数(第二个参数决定)
DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/resources/example_binaries/libttEncrypt.so"), false);
dm.callJNI_OnLoad(emulator); // 手动执行JNI_OnLoad函数(如果不存在动态加载函数可不使用)
module = dm.getModule(); // 加载好的so库对应为一个模块

// 加载目标类,相当于findClass
TTEncryptUtils = vm.resolveClass("com/bytedance/frameworks/core/encrypt/TTEncryptUtils");
}
}

3.2 unidbg中的函数

3.2.1 AndroidEmulator相关函数

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
// AndroidEmulator实例创建
AndroidEmulator emulator = AndroidEmulatorBuilder
//指定32位CPU
.for32Bit()
//添加后端,推荐使用Dynarmic,运行速度快,但并不支持某些新特性
.addBackendFactory(new DynarmicFactory(true))
//指定进程名,推荐以安卓包名做进程名
.setProcessName("com.github.unidbg")
//设置根路径
.setRootDir(new File("target/rootfs/default"))
//生成AndroidEmulator实例
.build();

// 获取内存操作接口
Memory memory = emulator.getMemory();

// 获取进程id
int pid = emulator.getPid();

//创建虚拟机
VM dalvikVM = emulator.createDalvikVM();

//创建虚拟机并指定APK文件
VM dalvikVM1 = emulator.createDalvikVM(new File("path of apkfile"));

//获取已经创建的虚拟机
VM dalvikVM2 = emulator.getDalvikVM();

//显示当前所有寄存器的状态 带参可指定寄存器
emulator.showRegs();

// 获取后端CPU
Backend backend = emulator.getBackend();

//获取进程名
String processName = emulator.getProcessName();

// 获取寄存器信息
RegisterContext context = emulator.getContext();

//Trace 读取内存(从 begin 到 end)
emulator.traceRead(long begin, long end [, TraceReadListener listener]);

// trace 写内存
emulator.traceWrite(long begin, long end [, TraceWriteListener listener]);

//trace 汇编
emulator.traceCode(long begin, long end [, TraceCodeListener listener]);

// 是否在运行
boolean running = emulator.isRunning();

3.2.2 Memory相关函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 模拟器的内存操作接口
Memory memory = emulator.getMemory();

// 指定安卓sd版本 只支持sdk19和sdk23
memory.setLibraryResolver(new AndroidResolver(23));

// 拿到一个指针 指向内存地址 通过该指针可操作内存
UnidbgPointer pointer = memory.pointer(0x11111111);

//获取当前内存映射的情况
Collection<MemoryMap> memoryMap = memory.getMemoryMap();

//根据模块名获取某个模块
Module sss = memory.findModule("module name");

// 根据地址获取某个模块
Module moduleByAddress = memory.findModuleByAddress(0x111111);

3.2.3 VM相关函数

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
//推荐指定apk文件,unidbg会自动做许多固定的操作
VM vm = emulator.createDalvikVM(new File("path of apk"));

// 是否输出jni运行日志
vm.setVerbose(true);

//加载so模块 参数二设置是否自动调用init函数
DalvikModule dalvikModule = vm.loadLibrary(new File("path of so file"), true);

// 设置jni交互接口,参数需要实现jni接口,推荐使用this(this所在当前类需要继承AbstractJni)
vm.setJni(this);

//获取JNIEnv指针,可以作为参数传递
Pointer jniEnv = vm.getJNIEnv();

//获取JavaVM 指针,可作为参数传递
Pointer javaVM = vm.getJavaVM();

//调用jni_onload函数
dalvikModule.callJNI_OnLoad(emulator);
vm.callJNI_OnLoad(emulator,dalvikModule.getModule());

//向VM添加全局对象,返回该对象的hash值
int dvmObjHash = vm.addGlobalObject(dvmObj);

//获取虚拟机中的对象,参数为该对象的hash值
DvmObject<?> object = vm.getObject(dvmObjHash);

3.2.4 符号调用

1
2
3
4
5
6
DvmObject<?> obj = vm.resolveClass("com/example/demo01/MainActivity").newObject(null);
String signSting = "123456";
//符号调用
DvmObject dvmObject = obj.callJniMethodObject(emulator, "jniMd52([B)Ljava/lang/String;", signSting.getBytes(StandardCharsets.UTF_8));
String result = (String) dvmObject.getValue();
System.out.println("[symble] Call the so md5 function result is ==> " + result);

3.2.5 地址调用

1
2
3
4
5
6
7
8
9
10
11
ArrayList<Object> args = new ArrayList<>();
Pointer jniEnv = vm.getJNIEnv();
DvmObject object1 = ProxyDvmObject.createObject(vm, this);
args.add(jniEnv);
args.add(null);
args.add(vm.addLocalObject(new StringObject(vm, "123456")));
//地址调用
Number number = module.callFunction(emulator, 0x11AE8 + 1, args.toArray());
System.out.println("[addr] number is ==> " + number.intValue());
DvmObject<?> object = vm.getObject(number.intValue());
System.out.println("[addr] Call the so md5 function result is ==> " + object.getValue());

四、unidbg中的Hook

Unidbg 支持的Hook可以分为两大类:

  • Unidbg 内置的第三方Hook框架,包括xHook/Whale/HookZz/Dobby
  • Unicorn Hook 以及 Unidbg基于它封装的Console Debugger

第一类是Unidbg支持并内置的第三方Hook框架,有Dobby(前身HookZz)/Whale这样的Inline Hook框架,也有xHook这样的PLT Hook 框架。

第二类是当Unidbg的底层引擎选择为Unicorn时(默认引擎),Unicorn自带的Hook功能。Unicorn提供了各种级别和粒度的Hook,内存Hook/指令/基本块 Hook/异常Hook 等等,十分强大和好用,而且Unidbg基于它封装了更便于使用的Console Debugger。

4.1 HookZz

HookZz现在叫Dobby。Unidbg中是HookZz和Dobby是两个独立的Hook库,因为作者认为HookZz在arm32上支持较好,Dobby在arm64上支持较好。HookZz是inline hook方案,因此可以Hook Sub_xxx,缺点是短函数可能出bug,受限于inline Hook 原理。

method hook

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
IHookZz hookZz = HookZz.getInstance(emulator); // 加载HookZz
// inline wrap导出函数
hookZz.wrap(module.findSymbolByName("ss_encrypt"), new WrapCallback<RegisterContext>() {
//目标函数执行之前
@Override
public void preCall(Emulator<?> emulator, RegisterContext ctx, HookEntryInfo info) {
Pointer pointer = ctx.getPointerArg(2);
int length = ctx.getIntArg(3);
byte[] key = pointer.getByteArray(0, length);
Inspector.inspect(key, "ss_encrypt key");
}
//目标函数执行之后
@Override
public void postCall(Emulator<?> emulator, RegisterContext ctx, HookEntryInfo info) {
System.out.println("ss_encrypt.postCall R0=" + ctx.getLongArg(0));
}
},true);
// replace 函数
hookZz.replace(module.findSymbolByName("Java_com_example_demo_MainActivity_stringFromJNI"), new ReplaceCallback() {
@Override
public HookStatus onCall(Emulator<?> emulator, long originFunction) {
System.out.println("stringFromJNI call pre");
return super.onCall(emulator, originFunction);
}
@Override
public void postCall(Emulator<?> emulator, HookContext context) {
System.out.println("stringFromJNI call after");
super.postCall(emulator, context);
}
}, true);

其中replace函数的第三个参数enablePostCall是一个Boolean类型,其值表示是否使ReplaceCallbackpostCall函数生效。

inline hook

1
2
3
4
5
6
7
8
9
//Arm32RegisterContext 对应 32 位
hookZz.instrument(module.base + 0x00000F5C + 1, new InstrumentCallback<Arm32RegisterContext>() {
// 通过base+offset inline wrap内部函数,在IDA看到为sub_xxx那些
@Override
public void dbiCall(Emulator<?> emulator, Arm32RegisterContext ctx, HookEntryInfo info) {
//打印寄存器内容
System.out.println("R3=" + ctx.getLongArg(3) + ", R10=0x" + Long.toHexString(ctx.getR10Long()));
}
});

如果需要修改入参或者返回结果,需要将 context 转成 EditableArm64RegisterContext 或 EditableArm32RegisterContext。例如:

1
2
3
4
5
6
7
8
9
Symbol lrand48 = module.findSymbolByName("lrand48");
hookZz.replace(lrand48, new ReplaceCallback() {
@Override
public void postCall(Emulator<?> emulator, HookContext context) {
EditableArm32RegisterContext ctx = emulator.getContext();
log.info("lrand48 return:" + ctx.getIntArg(0));
ctx.setR0(1);
}
}, true);

4.2 Dobby

Dobby的前身是HookZz,具备HookZz的功能。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Dobby dobby = Dobby.getInstance(emulator);
Symbol stringFromJNI = module.findSymbolByName("Java_com_example_demo_MainActivity_stringFromJNI");
/*method hook*/
dobby.replace(stringFromJNI, new ReplaceCallback() {
@Override
public HookStatus onCall(Emulator<?> emulator, long originFunction) {
System.out.println("Doddy replace hooked");
return super.onCall(emulator, originFunction);
}

@Override
public void postCall(Emulator<?> emulator, HookContext context) {
System.out.println("stringFromJNI finished");
super.postCall(emulator, context);
}
},true);
/*inline hook*/
dobby.instrument(...)

4.3 XHook

XHook 是一个针对 Android 平台 ELF 的 PLT (Procedure Linkage Table) hook 库,即它只能用于 hook 导入函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
IxHook xHook = XHookImpl.getInstance(emulator); // 加载xHook,支持Import hook
// hook libttEncrypt.so的导入函数strlen
xHook.register("libttEncrypt.so", "strlen", new ReplaceCallback() {
@Override
public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) {
Pointer pointer = context.getPointerArg(0);
String str = pointer.getString(0);
System.out.println("strlen=" + str);
context.push(str);
return HookStatus.RET(emulator, originFunction);
}
@Override
public void postCall(Emulator<?> emulator, HookContext context) {
System.out.println("strlen=" + context.pop() + ", ret=" + context.getIntArg(0));
}
}, true);
xHook.refresh(); // 使Import hook生效

4.4 Whale

Whale 支持 inline hooking 和 method hooking。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
IWhale whale = Whale.getInstance(emulator);
Symbol free = memory.findModule("libc.so").findSymbolByName("free");
/*inline hook*/
whale.inlineHookFunction(free, new ReplaceCallback() {
@Override
public HookStatus onCall(Emulator<?> emulator, long originFunction) {
System.out.println("WInlineHookFunction free = " + emulator.getContext().getPointerArg(0));
long addr = emulator.getContext().getPointerArg(0).peer;
if (addr == 0x12175000 || addr == 0x12176000){
return HookStatus.LR(emulator, 0);
}
else {
return HookStatus.RET(emulator,originFunction);
}
}
});
/*导入函数hook*/
whale.importHookFunction(...);
/*method hook*/
whale.replace(...);

4.5 UnicornHook

Unidbg 是基于 Unicorn 的项目,因此也具备 UnicornHook,Unicorn原生的Hook功能强大,而且不容易被检测。使用UnicornHook进行inline hook时也不需要考虑是否 + 1,会自动转换。

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
emulator.getBackend().hook_add_new(new CodeHook() {
@Override
public void hook(Backend backend, long address, int size, Object user) {
RegisterContext context = emulator.getContext();
if (address == module.base + 0x1FF4){// hook的目标地址
Pointer md5Ctx = context.getPointerArg(0);
Inspector.inspect(md5Ctx.getByteArray(0, 32), "md5Ctx");
Pointer plainText = context.getPointerArg(1);
int length = context.getIntArg(2);
Inspector.inspect(plainText.getByteArray(0, length), "plainText");
}else if (address == module.base + 0x2004){
Pointer cipherText = context.getPointerArg(1);
Inspector.inspect(cipherText.getByteArray(0, 16), "cipherText");
}
}
@Override
public void onAttach(UnHook unHook) {
// 这里可以进行一些初始化工作
System.out.println("attach");
}
@Override
public void detach() {
// 这里可以进行一些收尾工作
System.out.println("detach");
}
}, module.base + 0x1FE8/*start*/, module.base + 0x2004/*end*/, emulator)

4.6 SystemPropertyHook

为了方便处理应用访问设备属性(例如ro.build.id),Unidbg实现了SystemPropertyHook,使用方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
SystemPropertyHook systemPropertyHook = new SystemPropertyHook(emulator);
systemPropertyHook.setPropertyProvider(new SystemPropertyProvider() {
@Override
public String getProperty(String key) {
switch (key){
case "ro.build.id":{
return "get id";
}
case "ro.build.version.sdk":{
return "get sdk";
}
}
return null;
}
});
//使hook生效
memory.addHookListener(systemPropertyHook);

五、打印函数调用栈

hook目标函数,添加如下代码:

1
emulator.getUnwinder().unwind();

输出示例:

1
2
3
4
5
[0x040000000][0x040001ff4][libxiaojianbang.so][0x01ff4]Java_com_xiaojianbang_ndk_NativeHelper_md5 + 0xc8
[0x040000000][0x040003b24][Libxiaojianbang.so][0x03b24]MD5Final(MD5_CTX*,unsigned char*) + 0xac
[0x040000000][0x040002000][Libxiaojianbang.so][0x02000]Java_com_xiaojianbang_ndk_NativeHelper_md5 + 0xd4
[0x040000000][0x040003b34][Libxiaojianbang.so][0x03b34]MD5Final(MD5_CTX*,unsigned char*) + 0xbc
[0x040000000][0x040002000][Libxiaojianbang.so][0x02000]Java_com_xiaojianbang_ndk_NativeHelper_md5 + 0xd4

六、Console Debugger

1
2
Debugger attach = emulator.attach();
attach.addBreakPoint(module.base + 0xC365); //断点地址

同样也是基于unicorn的console debugger,因此断点位置不需要考虑是否 + 1,会自动转换。

console模式下支持如下指令:

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
c: 继续
n: 跨过
s|si:步入
bt:回溯

st hex:搜索堆栈
shw hex:搜索可写堆
shr hex:搜索可读堆
shx-hex:搜索可执行堆

m(op)[size]:显示内存,默认大小为0x70,大小可以是十六进制或十进制
mr0-mr7,mfp,mip,msp[size]:显示指定寄存器的内存
m(address)[size]:显示指定地址的内存,地址必须以0x开头

wr0-wr7,wfp,wip,wsp<value>:写入指定寄存器
wb(address),ws(address),wi(address)<value>:写入指定地址的(字节、短、整数)内存,地址必须以0x开头
wx(address)<hex>:将字节写入指定地址的内存,地址必须以0x开头

b(address):添加临时断点,地址必须以0x开头,可以是模块偏移量
b: 添加寄存器PC的断点
r: 删除寄存器PC的断点
blr:添加寄存器LR的临时断点

p (assembly):位于PC地址的修补程序集
where: 显示java堆栈跟踪
trace[begin-end]:设置跟踪指令
traceRead[begin-end]:设置跟踪内存读取
traceWrite〔begin-end〕:设置跟踪内存写入
vm:查看加载的模块
vbs:查看断点
d|dis:显示反汇编
d(0x):在指定地址显示反汇编
stop: 停止模拟
run[arg]:运行测试
gc:运行System.gc()
threads: 显示线程列表
cc size:将asm从PC-PC+size字节转换为c函数

七、监控内存读写

1
2
3
4
5
6
7
8
9
10
String traceFile = "myMonitorFile";
PrintStream traceStream = null;
try {
traceStream = new PrintStream(new FileOutputStream(traceFile), true);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
//监控目标内存读写,并将信息输出定向到指定文件中
emulator.traceRead(module.base, module.base + module.size).setRedirect(traceStream);//读
emulator.traceWrite(module.base, module.base + module.size).setRedirect(traceStream);//写

输出的信息格式:

1
[16:39:35 404] Memory READ at 0x12033d94, data size = 4, data value = 0x12151ec0, PC=RX@0x1203116c[libszstone.so]0x3116c, LR=RX@0x1212f617[libc.so]0x58617

主要是读的位置、读了几个字节、读了什么值、发生读操作的地址、读操作完成后的返回地址。

八、trace

8.1 traceCode

汇编代码追踪。

1
2
3
4
5
6
7
8
9
String traceFile = "myTraceCodeFile";
PrintStream traceStream = null;
try {
traceStream = new PrintStream(new FileOutputStream(traceFile), true);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
//追踪目标库的汇编代码,并将信息输出定向到指定文件中
emulator.traceCode(module.base, module.base + module.size).setRedirect(traceStream);

要注意 trace 的时机,如果我们想 trace 从某个函数开始的执行流,那就让 traceCode 早于它执行即可。比如想 trace 从 JNI_OnLoad 开始的目标 SO 执行流,在如下的代码位置添加 trace 即可。

1
2
3
4
5
6
DalvikModule dm = vm.loadLibrary("mx", true);
module = dm.getModule();
security = vm.resolveClass("com/mengxiang/arch/security/MXSecurity");
// trace
emulator.traceCode(module.base, module.base + module.size);
dm.callJNI_OnLoad(emulator);

如果想到更早的时机开启追踪,即追踪 init_proc、init_array 这些初始化函数的执行情况,那就需要将 traceCode 放到 loadLibrary 之前调用,但此时还没有获取到module对象。因此最正确的处理办法是使用模块监听器,在模块加载的第一时间开始 trace 。

1
2
3
4
5
6
7
8
9
10
11
12
memory.addModuleListener(new ModuleListener() {
@Override
public void onLoaded(Emulator<?> emulator, Module module) {
if(module.name.equals("libmx.so")){
emulator.traceCode(module.base, module.base+module.size);
}
}
});
DalvikModule dm = vm.loadLibrary("mx", true);
module = dm.getModule();
security = vm.resolveClass("com/mengxiang/arch/security/MXSecurity");
dm.callJNI_OnLoad(emulator);

如果想只 trace 某个函数内的汇编,这里分为两种情况:

  1. 只关注某地址处的某一函数调用,而不关注于该函数在别处的调用。

    例如

    1
    2
    3
    4
    5
    6
    7
    8
    .text:000000000000E530                 MOV             X0, X19
    .text:000000000000E534 MOV X2, X21
    .text:000000000000E538 LDR X1, [X8] ; "SHA1"
    .text:000000000000E53C BL ._Z6digestP7_JNIEnvPKcP11_jbyteArray ; digest(_JNIEnv *,char const*,_jbyteArray *)
    .text:000000000000E540 LDR X8, [X19]
    .text:000000000000E544 MOV X1, X0
    .text:000000000000E548 LDR X2, [X8,#0x538]
    .text:000000000000E54C B loc_E55C

    只关注 0xE53C 处的 digest 函数调用,那么需要在 0xE53C 开始 traceCode,0xE540 处停止 trace。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public void traceDigest(){
    long callAddr = module.base + 0xE53C;

    emulator.attach().addBreakPoint(callAddr, new BreakPointCallback() {
    @Override
    public boolean onHit(Emulator<?> emulator, long address) {
    traceHook = emulator.traceCode(module.base, module.base+module.size);
    return true;
    }
    });

    emulator.attach().addBreakPoint(callAddr + 4, new BreakPointCallback() {
    @Override
    public boolean onHit(Emulator<?> emulator, long address) {
    traceHook.stopTrace();
    return true;
    }
    });
    }
  2. 关注某一函数的所有调用,那么在进入该函数前开始 traceCode,离开该函数停止 trace。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    public void traceDigest(){
    // digest 函数地址
    long callAddr = module.base + 0xd804;

    emulator.attach().addBreakPoint(callAddr, new BreakPointCallback() {
    @Override
    public boolean onHit(Emulator<?> emulator, long address) {
    RegisterContext registerContext = emulator.getContext();
    // 开启 traceCode
    traceHook = emulator.traceCode(module.base, module.base+module.size);
    // 在 digest 函数执行完后的返回地址处下断点,取消 traceCode
    // 由于返回地址可以是module中任意一点
    // 那为什么不在 digest 函数的最后一条指令处结束 traceCode,虽然会损失最后一条指令的 trace,但最后一条指令一般是 ret
    emulator.attach().addBreakPoint(registerContext.getLR(), new BreakPointCallback() {
    @Override
    public boolean onHit(Emulator<?> emulator, long address) {
    traceHook.stopTrace();
    return true;
    }
    });
    return true;
    }
    });
    }

8.2 traceFunctionCall

顾名思义,跟踪函数的调用。

1
2
3
4
5
6
7
8
9
10
11
// function trace
Debugger debugger = emulator.attach();
debugger.traceFunctionCall(module, new FunctionCallListener() {
@Override
public void onCall(Emulator<?> emulator, long callerAddress, long functionAddress) {
}
@Override
public void postCall(Emulator<?> emulator, long callerAddress, long functionAddress, Number[] args) {
System.out.println("onCallFinish caller=" + UnidbgPointer.pointer(emulator, callerAddress) + ", function=" + UnidbgPointer.pointer(emulator, functionAddress));
}
});

两个时机都可以访问 callerAddress 和 functionAddress,postCall 时机还可以访问 args,即参数。

打印结果如下

1
2
3
onCallFinish caller=RX@0x4000d954[libmx.so]0xd954, function=RX@0x4000dab4[libmx.so]0xdab4
onCallFinish caller=RX@0x4000db30[libmx.so]0xdb30, function=RX@0x4000d620[libmx.so]0xd620
onCallFinish caller=RX@0x4000d954[libmx.so]0xd954, function=RX@0x4000dab4[libmx.so]0xdab4

九、日志系统

9.1 日志系统

Unidbg 中使用 Apache 的开源项目 log4jcommons-logging 处理日志。Unidbg 的绝大多数代码逻辑都提供了信息展示,但只在DEBUG日志等级下才做输出打印。Unidbg 基于模块去管理输出,想了解哪部分日志,就指定具体的类为 DEBUG 等级。

1
2
3
4
5
6
7
8
9
10
import org.apache.log4j.Level;
import org.apache.log4j.Logger;

public static void main(String[] args) {
NetWork nw = new NetWork();
Logger.getLogger(ARM32SyscallHandler.class).setLevel(Level.DEBUG);
Logger.getLogger(AndroidSyscallHandler.class).setLevel(Level.DEBUG);
String result = nw.callSign();
System.out.println("call s result:"+result);
}

比如上面的代码,就是对 ARM32 下的系统调用类、系统调用父类做日志输出。

如果希望输出所有模块的日志,可以修改unidbg-android/src/test/resources/log4j.properties文件,将 unidbg 的日志等级从 INFO 改为 DEBUG。

1
2
3
4
# 修改前
log4j.logger.com.github.unidbg=INFO
# 修改后
log4j.logger.com.github.unidbg=DEBUG

那么不管是系统调用、指针管理、多线程,还是多线程处理器等等,所有组件的日志都会打印出来。

但一般不会这么做,过多过杂的日志输出对使用者并无益处,还会拖累 Unidbg 执行的速度。

9.2 虚拟机日志

除了常规日志,Unidbg 还有另一套日志输出,主要打印 JNI 、Syscall 调用相关的内容。它和常规日志的输出有重叠,但内容更详细一些。我们通过 vm.setVerbose 开启或关闭它。在一般情况下,我们都会开启这个日志。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public WeiBo() {
emulator = AndroidEmulatorBuilder
.for32Bit()
.addBackendFactory(new Unicorn2Factory(true))
.setProcessName("com.weico.international")
.build();
Memory memory = emulator.getMemory();
memory.setLibraryResolver(new AndroidResolver(23));
vm = emulator.createDalvikVM(new File("unidbg-android/src/test/resources/weibo/sinaInternational.apk"));
vm.setJni(this);
// 设置是否打印以 JNI 为主的虚拟机调用细节
vm.setVerbose(false);
DalvikModule dm = vm.loadLibrary("utility", true);
WeiboSecurityUtils = vm.resolveClass("com/sina/weibo/security/WeiboSecurityUtils");
dm.callJNI_OnLoad(emulator);
}

参考:

【转载】Unidbg Hook 大全 - SeeFlowerX

https://whitebird0.github.io/post/unidbg%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0.html

[原创]unidbg入门笔记-Android安全-看雪-安全社区|安全招聘|kanxue.com

[Unidbg 的日志系统](https://www.yuque.com/lilac-2hqvv/xdwlsg/yaxqgh?# 《Unidbg 的日志系统》)


Unidbg入门介绍
http://example.com/2024/12/05/Unidbg模拟执行/Unidbg学习与实践/
作者
gla2xy
发布于
2024年12月5日
许可协议