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(); final Memory memory = emulator.getMemory(); memory.setLibraryResolver(new AndroidResolver(23));
vm = emulator.createDalvikVM(); vm.setVerbose(logging); DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/java/com/xiaojianbang/ndk/libxiaojianbang.so"), false); module = dm.getModule(); 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); 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(); final Memory memory = emulator.getMemory(); memory.setLibraryResolver(new AndroidResolver(23));
vm = emulator.createDalvikVM(); vm.setJni(new AbstractJni() { }); vm.setVerbose(logging); DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/java/com/xiaojianbang/ndk/libxiaojianbang.so"), false);
module = dm.getModule();
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")); 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(); final Memory memory = emulator.getMemory(); memory.setLibraryResolver(new AndroidResolver(23));
vm = emulator.createDalvikVM(); 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); DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/java/com/xiaojianbang/ndk/libxiaojianbang.so"), false);
module = dm.getModule();
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")); 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) 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() { UnidbgPointer MD5Ctx = emulator.getMemory().malloc(200, false).getPointer(); module.callFunction(emulator, 0x2230, MD5Ctx); UnidbgPointer plainText = emulator.getMemory().malloc(200, false).getPointer(); byte[] buffer = "xiaojianbang_unidbg".getBytes(); plainText.write(buffer); module.callFunction(emulator, 0x22A0, MD5Ctx, plainText, buffer.length); 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.wrap(module.findSymbolByName("_Z9MD5UpdateP7MD5_CTXPhj"), new WrapCallback<RegisterContext>() { @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")); 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) { 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>() { @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) {
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")));
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 HookStatus.LR(emulator, 100); }
});
int md5Result = NativeHelper.callStaticJniMethodInt(emulator, "md5(Ljava/lang/String;)Ljava/lang/String;", new StringObject(vm, "xiaojianbang")); 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(); 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")); System.out.println("md5Result: " + md5Result.getValue()); }
|
打印调用栈
emulator.getUnwinder().unwind();
|
unidgb中的动态调试
基于unicorn的console debugger同样不用管地址是否 + 1,会自己转换
- 附加下断点
Debugger debugger = emulator.attach();
debugger.addBreakPoint(module.base + 0x1AF4);
debugger.addBreakPoint(module.base + 0x1AEC);
|
- emulator.attach支持几种调试方式,默认是console debugger
- 类似IDA动态调试,断点触发后,会显示寄存器信息,汇编指令
- 可以通过输入命令进行打印内存、写寄存器、跳到下一个断点、打印函数栈等操作
- 回车两下或者随便输错一个指令,就会打印出命令的帮助信息
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")); 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