unidbg介绍

unicorn介绍

都需要自己来实现

好比是一个CPU,可以模拟执行各种指令

提供了很多编程语言接口,可以操作内存、寄存器等

但它不是一个系统,内存管理、文件管理、系统调用等

基于unicorn开发的框架

cemu 用来学习汇编的好工具

AndroidNativeEmu Python开发

unidbg Java开发

unidbg

支持模拟JNI调用

支持模拟系统调用指令

支持ARM32和ARM64

支持Hookzz、Dobby、xHook、原生unicorn Hook等Hook方式

支持Android、iOS

好比是在CPU上搭建了一个系统,因此可以很方便地在PC端模拟运行so

学习成本较低,不需要复现so算法,补环境后直接运行即可

unidbg项目地址

https://github.com/zhkl0228/unidbg

导入工程

从GitHub下载项目,解压后,使用IDEA打开

工程结构

unidbg-master -> unidbg-android -> src

main 工程源码

test 测试案例

java 测试案例的源码

resources 测试案例的资源

调用add

package com.xiaojianbang.ndk;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.Module;
import com.github.unidbg.Symbol;
import com.github.unidbg.arm.HookStatus;
import com.github.unidbg.arm.context.Arm32RegisterContext;
import com.github.unidbg.arm.context.RegisterContext;
import com.github.unidbg.debugger.DebuggerType;
import com.github.unidbg.hook.HookContext;
import com.github.unidbg.hook.ReplaceCallback;
import com.github.unidbg.hook.hookzz.Dobby;
import com.github.unidbg.hook.hookzz.HookEntryInfo;
import com.github.unidbg.hook.hookzz.HookZz;
import com.github.unidbg.hook.hookzz.IHookZz;
import com.github.unidbg.hook.hookzz.InstrumentCallback;
import com.github.unidbg.hook.hookzz.WrapCallback;
import com.github.unidbg.hook.xhook.IxHook;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.XHookImpl;
import com.github.unidbg.linux.android.dvm.DalvikModule;
import com.github.unidbg.linux.android.dvm.DvmClass;
import com.github.unidbg.linux.android.dvm.VM;
import com.github.unidbg.linux.android.dvm.array.ByteArray;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.utils.Inspector;
import com.sun.jna.Pointer;

import java.io.File;
import java.io.IOException;

public class NativeHelper {

private final AndroidEmulator emulator;
private final VM vm;
private final Module module;

private final DvmClass NativeHelper;

private final boolean logging;

NativeHelper(boolean logging) {
this.logging = logging;

emulator = AndroidEmulatorBuilder.for64Bit().setProcessName("com.xiaojianbang.app").build(); // 创建模拟器实例,要模拟32位或者64位,在这里区分
final Memory memory = emulator.getMemory(); // 模拟器的内存操作接口
memory.setLibraryResolver(new AndroidResolver(23)); // 设置系统类库解析

vm = emulator.createDalvikVM(); // 创建Android虚拟机
vm.setVerbose(logging); // 设置是否打印Jni调用细节
DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/java/com/xiaojianbang/ndk/libxiaojianbang.so"), false); // 加载libttEncrypt.so到unicorn虚拟内存,加载成功以后会默认调用init_array等函数
//dm.callJNI_OnLoad(emulator); // 手动执行JNI_OnLoad函数
module = dm.getModule(); // 加载好的 libxiaojianbang.so 对应为一个模块
NativeHelper = vm.resolveClass("com/xiaojianbang/ndk/NativeHelper");
}

void destroy() throws IOException {
emulator.close();
if (logging) {
System.out.println("destroy");
}
}

public static void main(String[] args) throws Exception {
NativeHelper test = new NativeHelper(true);
int retval = test.callFunc();
System.out.println("retval: 0x" + Integer.toHexString(retval));
test.destroy();
}

int callFunc() {
int retval = NativeHelper.callStaticJniMethodInt(emulator, "add(III)I", 0x100, 0x200, 0x300); // 执行Jni方法
return retval;
}
}

调用MD5方法

这个方法使用了callObjectMethod,unidbg无法直接帮我们调用这个,需要我们自己实现

使用AbstractJni()

package com.xiaojianbang.ndk;

import com.bytedance.frameworks.core.encrypt.TTEncrypt;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.Module;
import com.github.unidbg.Symbol;
import com.github.unidbg.arm.HookStatus;
import com.github.unidbg.arm.context.Arm32RegisterContext;
import com.github.unidbg.arm.context.RegisterContext;
import com.github.unidbg.debugger.DebuggerType;
import com.github.unidbg.hook.HookContext;
import com.github.unidbg.hook.ReplaceCallback;
import com.github.unidbg.hook.hookzz.Dobby;
import com.github.unidbg.hook.hookzz.HookEntryInfo;
import com.github.unidbg.hook.hookzz.HookZz;
import com.github.unidbg.hook.hookzz.IHookZz;
import com.github.unidbg.hook.hookzz.InstrumentCallback;
import com.github.unidbg.hook.hookzz.WrapCallback;
import com.github.unidbg.hook.xhook.IxHook;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.XHookImpl;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.linux.android.dvm.array.ByteArray;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.utils.Inspector;
import com.sun.jna.Pointer;

import java.io.File;
import java.io.IOException;

import static java.lang.Long.toHexString;

public class NativeHelper {
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;

private final DvmClass NativeHelper;

private final boolean logging;

NativeHelper(boolean logging) {
this.logging = logging;

emulator = AndroidEmulatorBuilder.for64Bit().setProcessName("com.xiaojianbang.app").build(); // 创建模拟器实例,要模拟32位或者64位,在这里区分
final Memory memory = emulator.getMemory(); // 模拟器的内存操作接口
memory.setLibraryResolver(new AndroidResolver(23)); // 设置系统类库解析

vm = emulator.createDalvikVM(); // 创建Android虚拟机
vm.setJni(new AbstractJni() {
});
vm.setVerbose(logging); // 设置是否打印Jni调用细节
DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/java/com/xiaojianbang/ndk/libxiaojianbang.so"), false); // 加载libttEncrypt.so到unicorn虚拟内存,加载成功以后会默认调用init_array等函数
// dm.callJNI_OnLoad(emulator); // 手动执行JNI_OnLoad函数
module = dm.getModule(); // 加载好的libttEncrypt.so对应为一个模块

NativeHelper = vm.resolveClass("com/xiaojianbang/ndk/NativeHelper");
}

void destroy() throws IOException {
emulator.close();
if (logging) {
System.out.println("destroy");
}
}

public static void main(String[] args) throws Exception {
NativeHelper test = new NativeHelper(true);
test.callFunc();
test.destroy();
}

void callFunc() {
StringObject md5Result = NativeHelper.callStaticJniMethodObject(emulator, "md5(Ljava/lang/String;)Ljava/lang/String",new StringObject(vm,"xiaojianbang")); // 执行Jni方法
System.out.println(md5Result.getValue());
}
}

处理so调用系统java类

unidbg实现了大部分的JNI函数,对于没有实现的,需要自己来实现

已经实现的,类似callObjectMethodV,需要自己分析so,做有针对性的覆写

通过vm.setjni(this)覆写父类AbstractJni方法

如果so通过JNI访问较多Java类,或IDA反编译的so逻辑不清楚,存在混淆,可借助jnitrace来补

package com.xiaojianbang.ndk;

import com.bytedance.frameworks.core.encrypt.TTEncrypt;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.Module;
import com.github.unidbg.Symbol;
import com.github.unidbg.arm.HookStatus;
import com.github.unidbg.arm.context.Arm32RegisterContext;
import com.github.unidbg.arm.context.RegisterContext;
import com.github.unidbg.debugger.DebuggerType;
import com.github.unidbg.hook.HookContext;
import com.github.unidbg.hook.ReplaceCallback;
import com.github.unidbg.hook.hookzz.Dobby;
import com.github.unidbg.hook.hookzz.HookEntryInfo;
import com.github.unidbg.hook.hookzz.HookZz;
import com.github.unidbg.hook.hookzz.IHookZz;
import com.github.unidbg.hook.hookzz.InstrumentCallback;
import com.github.unidbg.hook.hookzz.WrapCallback;
import com.github.unidbg.hook.xhook.IxHook;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.XHookImpl;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.linux.android.dvm.array.ByteArray;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.utils.Inspector;
import com.sun.jna.Pointer;

import java.io.File;
import java.io.IOException;

import static java.lang.Long.toHexString;

public class NativeHelper {
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;

private final DvmClass NativeHelper;

private final boolean logging;

NativeHelper(boolean logging) {
this.logging = logging;

emulator = AndroidEmulatorBuilder.for64Bit().setProcessName("com.xiaojianbang.app").build(); // 创建模拟器实例,要模拟32位或者64位,在这里区分
final Memory memory = emulator.getMemory(); // 模拟器的内存操作接口
memory.setLibraryResolver(new AndroidResolver(23)); // 设置系统类库解析

vm = emulator.createDalvikVM(); // 创建Android虚拟机
vm.setJni(new AbstractJni() {
@Override
public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
System.out.println("signatures: " + signature);
if(signature.equals("java/lang/String->getBytes(Ljava/lang/String;)[B")){
String args = (String) dvmObject.getValue();
byte[] strBytes = "unidbg".getBytes();
return new ByteArray(vm, strBytes);
}
return super.callObjectMethodV(vm, dvmObject, signature, vaList);
}

});
vm.setVerbose(logging); // 设置是否打印Jni调用细节
DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/java/com/xiaojianbang/ndk/libxiaojianbang.so"), false); // 加载libttEncrypt.so到unicorn虚拟内存,加载成功以后会默认调用init_array等函数
// dm.callJNI_OnLoad(emulator); // 手动执行JNI_OnLoad函数
module = dm.getModule(); // 加载好的libttEncrypt.so对应为一个模块

NativeHelper = vm.resolveClass("com/xiaojianbang/ndk/NativeHelper");
}

void destroy() throws IOException {
emulator.close();
if (logging) {
System.out.println("destroy");
}
}

public static void main(String[] args) throws Exception {
NativeHelper test = new NativeHelper(true);
test.callFunc();
test.destroy();
}

void callFunc() {
StringObject md5Result = NativeHelper.callStaticJniMethodObject(emulator, "md5(Ljava/lang/String;)Ljava/lang/String",new StringObject(vm,"xiaojianbang")); // 执行Jni方法
System.out.println(md5Result.getValue());
}
}

处理so调用其它so

. 处理so调用其他so

如果被调用的函数,需要先执行JNI_OnLoad或者其他函数,那就按顺序调用

如果so中调用了其他的so,只需按顺序加载所需的so即可

dlopen会自己处理,因为unidbg加载了libdl.so

C/C++标准库也会自己处理,因为unidbg加载了libc.so、libc++.so

通过符号调用函数

module.findSymbolByName
Symbol symbol = module.findSymbolByName(...);
Number numbers = symbol.call(...);
int retval = numbers.intValue();

获取到Symbol以后,最好先判断下是否为null

得到Java的int数据后,根据这个数据去内存中捞对象或者数据

比如返回的是Java对象

vm.getObject(retval)	//相当于vm.addLocalObject的反过程
void callFunc() {
Symbol symbol = module.findSymbolByName("_Z7_strcatP7_JNIEnvP7_jclass");
Number[] numbers = symbol.call(emulator, vm.getJNIEnv(), vm.addLocalObject(NativeHelper));
int result = numbers[0].intValue();
System.out.println(vm.getObject(result).getValue());

Number[] numbers = module.callFunction(emulator, "_Z7_strcatP7_JNIEnvP7_jclass", vm.getJNIEnv(), vm.addLocalObject(NativeHelper));
int result = numbers[0].intValue();
System.out.println(vm.getObject(result).getValue());

DvmObject obj = NativeHelper.newObject(null);
System.out.println(vm.addLocalObject(obj));
System.out.println(obj.hashCode());
}

比如返回的是地址

emulator.getMemory().pointer(retval).getByteArray(..., ...);

比如返回的是长度

emulator.getMemory().getByteArray(..., retval);

unidbg操作内存

void callFunc() {
//MD5Init 2230
UnidbgPointer MD5Ctx = emulator.getMemory().malloc(200, false).getPointer();
module.callFunction(emulator, 0x2230, MD5Ctx);
//MD5Update 22A0
UnidbgPointer plainText = emulator.getMemory().malloc(200, false).getPointer();
byte[] buffer = "xiaojianbang_unidbg".getBytes();
plainText.write(buffer);
module.callFunction(emulator, 0x22A0, MD5Ctx, plainText, buffer.length);
//MD5Final 3A78
UnidbgPointer cipherText = emulator.getMemory().malloc(200, false).getPointer();
module.callFunction(emulator, 0x3A78, MD5Ctx, cipherText);
byte[] byteArray = cipherText.getByteArray(0, 16);
Inspector.inspect(byteArray, "MD5Result");
}

unidbg的hook

hookzz_wrap

void callFunc() {
IHookZz hookZz = HookZz.getInstance(emulator); // 加载HookZz,支持inline hook,文档看https://github.com/jmpews/HookZz

hookZz.wrap(module.findSymbolByName("_Z9MD5UpdateP7MD5_CTXPhj"), new WrapCallback<RegisterContext>() { // inline wrap导出函数
@Override
public void preCall(Emulator<?> emulator, RegisterContext ctx, HookEntryInfo info) {
md5_ctx = ctx.getPointerArg(0);
Pointer plainText = ctx.getPointerArg(1);
int length = ctx.getIntArg(2);
Inspector.inspect(md5_ctx.getByteArray(0, 64), "preCall md5_ctx");
Inspector.inspect(plainText.getByteArray(0, length), "plainText");
}
@Override
public void postCall(Emulator<?> emulator, RegisterContext ctx, HookEntryInfo info) {
Inspector.inspect(md5_ctx.getByteArray(0, 64), "postCall md5_ctx");
}
});
StringObject md5Result = NativeHelper.callStaticJniMethodObject(emulator, "md5(Ljava/lang/String;)Ljava/lang/String;", new StringObject(vm, "xiaojianbang")); // 执行Jni方法
System.out.println("md5Result: " + md5Result.getValue());
}

hookzz_instrument

hookZz.instrument(module.base + 0x1AEC, new InstrumentCallback<Arm64RegisterContext>() {
@Override
public void dbiCall(Emulator<?> emulator, Arm64RegisterContext ctx, HookEntryInfo info) { // 通过base+offset inline wrap内部函数,在IDA看到为sub_xxx那些
System.out.println("W8=0x" + Integer.toHexString(ctx.getXInt(8)) + ", W9=0x" + Long.toHexString(ctx.getXInt(9)));
}
});

参数的获取

    void callFunc() {
IHookZz hookZz = HookZz.getInstance(emulator);
hookZz.wrap(module.findSymbolByName("_Z12jstring2cstrP7_JNIEnvP8_jstring"), new WrapCallback<HookZzArm64RegisterContext>() { // inline wrap导出函数
@Override
public void preCall(Emulator<?> emulator, HookZzArm64RegisterContext ctx, HookEntryInfo info) {
int intArg = ctx.getIntArg(1);
StringObject str = vm.getObject(intArg);
System.out.println("preCall str = " + str.getValue());
}

@Override
public void postCall(Emulator<?> emulator, HookZzArm64RegisterContext ctx, HookEntryInfo info) {
// byte[] bytes = ctx.getXPointer(0).getByteArray(0, 16);
// Inspector.inspect(bytes, "postCall cstrAddr");

String str = ctx.getXPointer(0).getString(0);
System.out.println("postCall str: " + str);

int hashcode = vm.addLocalObject(new StringObject(vm, "xiaojianbangResult"));
ctx.setXLong(0, hashcode);
}
});

Number[] numbers = module.callFunction(
emulator,
"_Z12jstring2cstrP7_JNIEnvP8_jstring",
vm.getJNIEnv(),
vm.addLocalObject(new StringObject(vm, "xiaojianbang")));
// long cstrAddr = numbers[0].longValue();
// byte[] bytes = emulator.getMemory().pointer(cstrAddr).getByteArray(0, 16);
// Inspector.inspect(bytes, "cstrAddr");

int hashcode = numbers[0].intValue();
StringObject strResult = vm.getObject(hashcode);
System.out.println("final result: " + strResult.getValue());
}

hookzz_replace

void callFunc() {
IHookZz hookZz = HookZz.getInstance(emulator);
hookZz.replace(module.findSymbolByName("Java_com_xiaojianbang_ndk_NativeHelper_md5"), new ReplaceCallback() {
@Override
public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) {
System.out.println("onCall:123456");
//return super.onCall(emulator, context, originFunction);
//return HookStatus.RET(emulator, originFunction);
return HookStatus.LR(emulator, 100);
}

});

int md5Result = NativeHelper.callStaticJniMethodInt(emulator, "md5(Ljava/lang/String;)Ljava/lang/String;", new StringObject(vm, "xiaojianbang")); // 执行Jni方法
System.out.println("md5Result: " + md5Result);
}

原生UnicornHook

emulator.getBackend().hook_add_new(new CodeHook() {
@Override
public void hook(Backend backend, long address, int size, Object user) {
RegisterContext context = emulator.getContext();
//System.out.println("xiaojianbang");
//System.out.println(user);
//System.out.println(size);
if (address == module.base + 0x1FF4){
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) {
}
@Override
public void detach() {
}
}, module.base + 0x1FE8, module.base + 0x2004, "xiaojianbang");
StringObject md5Result = NativeHelper.callStaticJniMethodObject(emulator, "md5(Ljava/lang/String;)Ljava/lang/String;", new StringObject(vm, "xiaojianbang")); // 执行Jni方法
System.out.println("md5Result: " + md5Result.getValue());
}

打印调用栈

emulator.getUnwinder().unwind();

unidgb中的动态调试

基于unicorn的console debugger同样不用管地址是否 + 1,会自己转换

  1. 附加下断点
Debugger debugger = emulator.attach();

debugger.addBreakPoint(module.base + 0x1AF4);

debugger.addBreakPoint(module.base + 0x1AEC);
  1. emulator.attach支持几种调试方式,默认是console debugger
  2. 类似IDA动态调试,断点触发后,会显示寄存器信息,汇编指令
  3. 可以通过输入命令进行打印内存、写寄存器、跳到下一个断点、打印函数栈等操作
  4. 回车两下或者随便输错一个指令,就会打印出命令的帮助信息

b 用于下断点

blr、b0x40228000

m 用于读内存

mr0、mx0、m0x40228000、msp

bt用于查看函数栈

c 跳到下一个断点

s 单步调试

void callFunc() {
Debugger debugger = emulator.attach();
debugger.addBreakPoint(module.base + 0x1AF4);
debugger.addBreakPoint(module.base + 0x1AFC);
int retval = NativeHelper.callStaticJniMethodInt(emulator, "add(III)", 0x100, 0x200, 0x300);
System.out.println("retval: 0x" + Integer.toHexString(retval));
}

监控内存读写

void callFunc() {
PrintStream traceStream = null;
String traceFile = "yourpath";
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);
System.out.println(Long.toHexString(module.base));

int md5Result = NativeHelper.callStaticJniMethodInt(emulator, "md5(Ljava/lang/String;)Ljava/lang/String;", new StringObject(vm, "xiaojianbang")); // 执行Jni方法
System.out.println("md5Result: " + md5Result);
}

unidgb trace

代码写法

String traceFile = "yourpath";
PrintStream traceStream = new PrintStream(new FileOutputStream(traceFile), true);
emulator.traceCode(module, base, moduel.size).setRedirect(traceStream);

源码修改

unidbg默认的traceCode,只打印汇编指令,不打印寄存器的值

通过修改源码,可以让其答应变动的寄存器的值

首先修改AbstractARM644Emulator.java类的printAssemble函数

private void printAssemble(PrintStream out, Capstone.CsInsn[] insns, long address) {
StringBuilder sb = new StringBuilder();
for (Capstone.CsInsn ins : insns) {
sb.append("### Trace Instruction ");
sb.append(ARM.assembleDetail(this, ins, address, false));
sb.append('\t');
sb.append('\t');
sb.append(ARM.showRegsARM64(this));
sb.append('\n');
address += ins.size;
}
out.print(sb);
}

然后再在ARM.java类里面封装showRegsARM64方法

public static String showRegsARM64(Emulator<?> emulator){
return showRegs64(emulator, ARM64_REGS);
}

最后ARM.java类中的showRegs64方法的返回值也要修改成String