# 下载地址
https://github.com/zhkl0228/unidbg
源码下载完成后使用 IDEA 打开即可使用
# 快速使用
套用以下模板,将包名等参数替换成对应的字符串即可快速使用 unidbg
| package com.oacia; |
| |
| import com.github.unidbg.AndroidEmulator; |
| import com.github.unidbg.Module; |
| 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.DalvikModule; |
| import com.github.unidbg.linux.android.dvm.VM; |
| import com.github.unidbg.memory.Memory; |
| |
| import java.io.File; |
| |
| public class Myunidbg { |
| private final AndroidEmulator emulator; |
| private final VM vm; |
| private DalvikModule dm; |
| private Module module; |
| public Myunidbg() |
| { |
| |
| emulator = AndroidEmulatorBuilder |
| .for64Bit() |
| |
| hypervisor 引擎可以在搭载了 Apple Silicon 芯片的设备上模拟执行 |
| KVM 引擎可以在树莓派上模拟执行 |
| Dynarmic 引擎是为了更快的模拟执行 |
| Unicorn 是最强大最完善的模拟执行引擎,但它的缺点是慢 |
| */ |
| .addBackendFactory(new Unicorn2Factory(true)) |
| .setProcessName("com.oacia.test") |
| .build(); |
| |
| Memory memory = emulator.getMemory(); |
| |
| memory.setLibraryResolver(new AndroidResolver(26)); |
| |
| vm = emulator.createDalvikVM(); |
| |
| vm.setVerbose(true); |
| |
| dm = vm.loadLibrary(new File("E:\\oacia\\libtprt.so"), false); |
| module = dm.getModule(); |
| |
| dm.callJNI_OnLoad(emulator); |
| } |
| |
| |
| public static void main(String[] args) { |
| |
| } |
| } |
如果运行时提示缺少某些库,可以在加载目标库前添加如下代码预加载缺少的系统库,系统库的位置位于 /system/lib64/
, 将这些系统库从手机复制到电脑上即可
| vm.loadLibrary(new File("f:\\androidlib\\libc.so"),false); |
| vm.loadLibrary(new File("f:\\androidlib\\libm.so"),false); |
| vm.loadLibrary(new File("f:\\androidlib\\libstdc++.so"),false); |
| vm.loadLibrary(new File("f:\\androidlib\\ld-android.so"),false); |
| vm.loadLibrary(new File("f:\\androidlib\\libdl.so"),false); |
如果在 JNI 层中调用了 java 层中的函数,同样会引起 unidbg 引擎报错,此时需要自行补充缺失的 java 层的方法,并重写调用 Java 层函数时用到的 JNI 方法,缺少变量的情况同理
举个例子,在这个 JNI 函数中调用了 java 层中 base64
这个方法
所以我们需要在 unidbg 的 Java 文件中补充上对应的方法
| public class MainActivitymethod1 extends AbstractJni { |
| private static DvmClass MainActivityClass; |
| |
| @Override |
| |
| * base64 是 java 层的方法,这里重写 callObjectMethodV 方法:一旦发现调用的是 java 层的 base64 方法,这里就用自己复现的 base64 方法替换 |
| * */ |
| public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) { |
| System.out.println("callObjectMethodV->"+signature); |
| if(signature.equals("com/example/testjni/MainActivity->base64(Ljava/lang/String;)Ljava/lang/String;")){ |
| DvmObject dvmobj=vaList.getObjectArg(0); |
| String arg= (String) dvmobj.getValue(); |
| String result=base64(arg); |
| return new StringObject(vm,result); |
| } |
| return super.callObjectMethodV(vm, dvmObject, signature, vaList); |
| } |
| |
| |
| @Override |
| |
| * staticcontent 是 java 层的静态变量;getStaticObjectField,一旦检测到 so 层引用这个变量,那么自己返回这个变量的值 |
| * */ |
| public DvmObject<?> getStaticObjectField(BaseVM vm, DvmClass dvmClass, String signature) { |
| System.out.println("getStaticObjectField->"+signature); |
| if(signature.equals("com/example/testjni/MainActivity->staticcontent:Ljava/lang/String;")){ |
| return new StringObject(vm,"staticcontent"); |
| } |
| return super.getStaticObjectField(vm, dvmClass, signature); |
| } |
| |
| |
| |
| @Override |
| |
| * objcontent 是 java 层的变量;这里重写 getObjectField 方法,一旦检测到 so 层引用这个变量,那么自己返回这个变量的值 |
| * */ |
| public DvmObject<?> getObjectField(BaseVM vm, DvmObject<?> dvmObject, String signature) { |
| System.out.println("getObjectField->"+signature); |
| if(signature.equals("com/example/testjni/MainActivity->objcontent:Ljava/lang/String;")){ |
| return new StringObject(vm,"objcontent"); |
| } |
| return super.getObjectField(vm, dvmObject, signature); |
| } |
| |
| * java 层的方法,这里需要复现,否则不知道怎么执行 |
| * */ |
| public String base64(String arg3) { |
| String result=Base64.encodeBase64String(arg3.getBytes()); |
| return result; |
| } |
| |
| |
| } |
# AndroidEmulator 操作
| public void AndroidEmulator_use(){ |
| |
| Memory memory = emulator.getMemory(); |
| |
| int pid = emulator.getPid(); |
| |
| VM dalvikVM = emulator.createDalvikVM(); |
| |
| VM dalvikVM = emulator.createDalvikVM(new File("E:/oacia/my.apk")); |
| |
| VM dalvikVM = emulator.getDalvikVM(); |
| |
| emulator.showRegs(); |
| |
| Backend backend = emulator.getBackend(); |
| |
| String processName = emulator.getProcessName(); |
| |
| RegisterContext context = emulator.getContext(); |
| |
| emulator.traceRead(1,0); |
| |
| emulator.traceWrite(1,0); |
| |
| emulator.traceCode(1,0); |
| |
| boolean running = emulator.isRunning(); |
| } |
# Memory 操作
| public void Memory_use(){ |
| Memory memory = emulator.getMemory(); |
| |
| memory.setLibraryResolver(new AndroidResolver(23)); |
| |
| |
| UnidbgPointer pointer = memory.pointer(0x4000000); |
| |
| |
| Collection<MemoryMap> memoryMap = memory.getMemoryMap(); |
| |
| |
| Module module = memory.findModule("module name"); |
| |
| |
| Module module = memory.findModuleByAddress(0x40000000); |
| } |
# VM 操作
| public void VM_use(){ |
| |
| VM vm = emulator.createDalvikVM(new File("E:\\oacia\\my.apk")); |
| |
| |
| vm.setVerbose(true); |
| |
| |
| DalvikModule dalvikModule = vm.loadLibrary(new File("E:\\oacia\\libc.so"), true); |
| |
| |
| vm.setJni(this); |
| |
| |
| Pointer jniEnv = vm.getJNIEnv(); |
| |
| |
| Pointer javaVM = vm.getJavaVM(); |
| |
| |
| vm.callJNI_OnLoad(emulator,dalvikModule.getModule()); |
| |
| |
| int hash = vm.addGlobalObject(dvmObj); |
| |
| |
| DvmObject<?> object = vm.getObject(hash); |
| } |
# 方法调用
# 调用导出函数
| |
| public void call_export_function(){ |
| Module oacia_module = vm.loadLibrary(new File("E:/oacia/liboacia.so"),false).getModule(); |
| Number result = oacia_module.callFunction(emulator,"_Z3addii", 1,2); |
| System.out.println("_Z3addii result:"+result.intValue()); |
| } |
# 调用 JNI 函数
| |
| public void call_JNI_function(){ |
| |
| DvmClass MainActivity_dvmclass = vm.resolveClass("com/example/unicorncourse08/MainActivity"); |
| DvmObject<?> resultobj = MainActivity_dvmclass.callStaticJniMethodObject(emulator,"stringFromJNI1(Ljava/lang/String;)Ljava/lang/String;","helloworld"); |
| System.out.println("resultobj:"+resultobj); |
| } |
# 调用任意函数
| |
| public void call_any_function(){ |
| |
| Pointer jniEnv = vm.getJNIEnv(); |
| |
| |
| DvmObject<?> thiz = vm.resolveClass("com.oacia.test").newObject(null); |
| |
| |
| List<Object> args = new ArrayList<>(); |
| args.add(jniEnv); |
| args.add(vm.addLocalObject(thiz)); |
| args.add(vm.addLocalObject(new StringObject(vm,"XuE"))); |
| |
| |
| Number numbers = module.callFunction(emulator, 0x9180 + 1, args.toArray()); |
| System.out.println(numbers.intValue()); |
| } |
# 调用形参是指针类型的函数
| |
| |
| |
| public void call_argv_point_type_function(){ |
| String initial = "unidbg"; |
| int initial_length = initial.length(); |
| |
| |
| MemoryBlock initial_msg = emulator.getMemory().malloc(initial_length+1, false); |
| UnidbgPointer initial_msg_ptr = initial_msg.getPointer(); |
| |
| |
| initial_msg_ptr.write(initial.getBytes()); |
| |
| |
| MemoryBlock digest = emulator.getMemory().malloc(16, false); |
| UnidbgPointer digest_ptr=digest.getPointer(); |
| |
| |
| List<Object> args = new ArrayList<>(); |
| args.add(initial_msg); |
| args.add(initial_length); |
| args.add(digest_ptr); |
| |
| module.callFunction(emulator, 0x7A8D + 1, args.toArray()); |
| |
| |
| Inspector.inspect(digest_ptr.getByteArray(0, 0x10), "digest"); |
| } |
# unidbg hook
Unidbg 在 Android 上支持的 Hook,可以分为两大类
- Unidbg 内置的第三方 Hook 框架,包括 xHook/Whale/HookZz
- Unicorn Hook 以及 Unidbg 基于它封装的 Console Debugger
选用哪种 hook 框架,可以参考以下两点
- 如果以模拟执行为目的,建议使用第三方 Hook 方案,arm32 下 HookZz 的支持较好,arm64 下 Dobby 的支持较好,HookZz/Dobby Hook 不成功时,如果函数是导出函数,使用 xHook,否则使用 Whale。
- 如果以算法还原为目的,建议使用 Console Debugger 和 Unicorn Hook,并建议不优先使用第三方 Hook 方案。
# 基础知识
# 获取 so 的基址
加载一个 so
| |
| DalvikModule dm = vm.loadLibrary("libnative-lib.so", true); |
| |
| module = dm.getModule(); |
| |
| System.out.println("baseAddr:"+module.base); |
加载多个 so
| |
| Module yourModule = emulator.getMemory().findModule("yourModuleName"); |
| |
| System.out.println("baseAddr:"+yourModule.base); |
如果只主动加载一个 so,其基址恒为 0x40000000 , 这是一个检测 Unidbg 的点,可以在 com/github/unidbg/memory/Memory.java
中做修改
| public interface Memory extends IO, Loader, StackMemory { |
| |
| long STACK_BASE = 0xc0000000L; |
| int STACK_SIZE_OF_PAGE = 256; |
| |
| |
| long MMAP_BASE = 0x40000000L; |
| |
| UnidbgPointer allocateStack(int size); |
| UnidbgPointer pointer(long address); |
| void setStackPoint(long sp); |
| } |
# 获取导出函数地址
| |
| DalvikModule dm = vm.loadLibrary("libnative-lib.so", true); |
| |
| module = dm.getModule(); |
| int address = (int) module.findSymbolByName("funcNmae").getAddress(); |
# 获取非导出函数地址
| |
| DalvikModule dm = vm.loadLibrary("libnative-lib.so", true); |
| |
| module = dm.getModule(); |
| |
| int offset = 0x1768; |
| |
| int address = (int) (module.base + offset); |
# Console Debugger
# 基础 hook
使用 Console Debugger 下断点
| |
| emulator.attach().addBreakPoint(module.findSymbolByName("base64_encode").getAddress()); |
- 运行到对应地址时触发断点,类似于 GDB 调试或者 IDA 调试,时机为目标指令执行前
- Console Debugger 用于辅助算法分析,快速分析、确认某个函数的功能。并且只能在 Unicorn 引擎下才可以使用
交互命令
| c: continue |
| n: step over |
| bt: back trace |
| |
| st hex: search stack |
| shw hex: search writable heap |
| shr hex: search readable heap |
| shx hex: search executable heap |
| |
| nb: break at next block |
| s|si: step into |
| s[decimal]: execute specified amount instruction |
| s(blx): execute util BLX mnemonic, low performance |
| |
| m(op) [size]: show memory, default size is 0x70, size may hex or decimal |
| mr0-mr7, mfp, mip, msp [size]: show memory of specified register |
| m(address) [size]: show memory of specified address, address must start with 0x |
| |
| wr0-wr7, wfp, wip, wsp <value>: write specified register |
| wb(address), ws(address), wi(address) <value>: write (byte, short, integer) memory of specified address, address must start with 0x |
| wx(address) <hex>: write bytes to memory at specified address, address must start with 0x |
| |
| b(address): add temporarily breakpoint, address must start with 0x, can be module offset |
| b: add breakpoint of register PC |
| r: remove breakpoint of register PC |
| blr: add temporarily breakpoint of register LR |
| |
| p (assembly): patch assembly at PC address |
| where: show java stack trace |
| |
| trace [begin end]: Set trace instructions |
| traceRead [begin end]: Set trace memory read |
| traceWrite [begin end]: Set trace memory write |
| vm: view loaded modules |
| vbs: view breakpoints |
| d|dis: show disassemble |
| d(0x): show disassemble at specify address |
| stop: stop emulation |
| run [arg]: run test |
| cc size: convert asm from 0x400008a0 - 0x400008a0 + size bytes to c function |
持久化 hook
onHit 返回 ture 时,断点触发时不会进入交互界面;为 false 时会。当函数被调用了三五百次时,我们不希望它反复停下来,然后不停 “c” 来继续运行。
| public void HookByConsoleDebugger(){ |
| emulator.attach().addBreakPoint(module.findSymbolByName("base64_encode").getAddress(), new BreakPointCallback() { |
| @Override |
| public boolean onHit(Emulator<?> emulator, long address) { |
| RegisterContext context = emulator.getContext(); |
| Pointer input = context.getPointerArg(0); |
| int length = context.getIntArg(1); |
| Pointer buffer = context.getPointerArg(2); |
| |
| Inspector.inspect(input.getByteArray(0, length), "base64 input"); |
| |
| emulator.attach().addBreakPoint(context.getLRPointer().peer, new BreakPointCallback() { |
| @Override |
| public boolean onHit(Emulator<?> emulator, long address) { |
| String result = buffer.getString(0); |
| System.out.println("base64 result:"+result); |
| return true; |
| } |
| }); |
| return true; |
| } |
| }); |
| } |
# 修改参数
- 下断点,运行代码后进入 debugger
| emulator.attach().addBreakPoint(module.findSymbolByName("base64_encode").getAddress()); |
- 通过命令修改参数 1 和 2
| wx0x40002403 68656c6c6f20776f726c64 |
| |
| >-----------------------------------------------------------------------------< |
| [14:06:46 165]RX@0x40002403[libhookinunidbg.so]0x2403, md5=5eb63bbbe01eeed093cb22bb8f5acdc3, hex=68656c6c6f20776f726c64 |
| size: 11 |
| 0000: 68 65 6C 6C 6F 20 77 6F 72 6C 64 hello world |
| ^-----------------------------------------------------------------------------^ |
| wr1 11 |
| >>> r1=0xb |
Console Debugger 支持下列写操作
| wr0-wr7, wfp, wip, wsp <value>: write specified register |
| wb(address), ws(address), wi(address) <value>: write (byte, short, integer) memory of specified address, address must start with 0x |
| wx(address) <hex>: write bytes to memory at specified address, address must start with 0x |
持久化中的修改参数
| public void ReplaceArgByConsoleDebugger(){ |
| emulator.attach().addBreakPoint(module.findSymbolByName("base64_encode").getAddress(), new BreakPointCallback() { |
| @Override |
| public boolean onHit(Emulator<?> emulator, long address) { |
| RegisterContext context = emulator.getContext(); |
| String fakeInput = "hello world"; |
| int length = fakeInput.length(); |
| |
| emulator.getBackend().reg_write(ArmConst.UC_ARM_REG_R1, length); |
| MemoryBlock fakeInputBlock = emulator.getMemory().malloc(length, true); |
| fakeInputBlock.getPointer().write(fakeInput.getBytes(StandardCharsets.UTF_8)); |
| |
| emulator.getBackend().reg_write(ArmConst.UC_ARM_REG_R0, fakeInputBlock.getPointer().peer); |
| |
| Pointer buffer = context.getPointerArg(2); |
| |
| emulator.attach().addBreakPoint(context.getLRPointer().peer, new BreakPointCallback() { |
| @Override |
| public boolean onHit(Emulator<?> emulator, long address) { |
| String result = buffer.getString(0); |
| System.out.println("base64 result:"+result); |
| return true; |
| } |
| }); |
| return true; |
| } |
| }); |
| } |
# 修改返回值
| public void ReplaceRetByConsoleDebugger(){ |
| emulator.attach().addBreakPoint(module.findSymbolByName("verifyApkSign").getAddress(), new BreakPointCallback() { |
| @Override |
| public boolean onHit(Emulator<?> emulator, long address) { |
| RegisterContext context = emulator.getContext(); |
| |
| emulator.attach().addBreakPoint(context.getLRPointer().peer, new BreakPointCallback() { |
| @Override |
| public boolean onHit(Emulator<?> emulator, long address) { |
| emulator.getBackend().reg_write(ArmConst.UC_ARM_REG_R0, 0); |
| return true; |
| } |
| }); |
| return true; |
| } |
| }); |
| } |
# 替换函数
| public void ReplaceFuncByConsoleDebugger(){ |
| emulator.attach().addBreakPoint(module.findSymbolByName("verifyApkSign").getAddress(), new BreakPointCallback() { |
| @Override |
| public boolean onHit(Emulator<?> emulator, long address) { |
| System.out.println("替换函数 verifyApkSign"); |
| RegisterContext registerContext = emulator.getContext(); |
| emulator.getBackend().reg_write(ArmConst.UC_ARM_REG_PC, registerContext.getLRPointer().peer); |
| emulator.getBackend().reg_write(ArmConst.UC_ARM_REG_R0, 0); |
| return true; |
| } |
| }); |
| } |
# Unicorn Hook
| public void HookByUnicorn(){ |
| long start = module.base+0x97C; |
| long end = module.base+0x97C+0x17A; |
| emulator.getBackend().hook_add_new(new CodeHook() { |
| @Override |
| public void hook(Backend backend, long address, int size, Object user) { |
| RegisterContext registerContext = emulator.getContext(); |
| if(address == module.base + 0x97C){ |
| int r0 = registerContext.getIntByReg(ArmConst.UC_ARM_REG_R0); |
| System.out.println("0x97C 处 r0:"+Integer.toHexString(r0)); |
| } |
| |
| |
| Capstone capstone = new Capstone(Capstone.CS_ARCH_ARM64,Capstone.CS_MODE_ARM); |
| byte[] bytes = emulator.getBackend().mem_read(address, 4); |
| Instruction[] disasm = capstone.disasm(bytes, 0); |
| } |
| |
| @Override |
| public void onAttach(Unicorn.UnHook unHook) { |
| |
| } |
| |
| @Override |
| public void detach() { |
| |
| } |
| }, start, end, null); |
| } |
# xHook
xHook 是爱奇艺开源的 Android PLT hook 框架,优点是挺稳定好用,缺点是不能 Hook Sub_xxx 子函数。
# 基础 hook
| public void HookByXhook(){ |
| IxHook xHook = XHookImpl.getInstance(emulator); |
| xHook.register("libhookinunidbg.so", "base64_encode", new ReplaceCallback() { |
| @Override |
| public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) { |
| Pointer input = context.getPointerArg(0); |
| int length = context.getIntArg(1); |
| Pointer buffer = context.getPointerArg(2); |
| Inspector.inspect(input.getByteArray(0, length), "base64 input"); |
| context.push(buffer); |
| return HookStatus.RET(emulator, originFunction); |
| } |
| @Override |
| public void postCall(Emulator<?> emulator, HookContext context) { |
| Pointer buffer = context.pop(); |
| System.out.println("base64 result:"+buffer.getString(0)); |
| } |
| }, true); |
| |
| xHook.refresh(); |
| } |
# 修改参数
| public void ReplaceArgByXhook(){ |
| IxHook xHook = XHookImpl.getInstance(emulator); |
| xHook.register("libhookinunidbg.so", "base64_encode", new ReplaceCallback() { |
| @Override |
| public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) { |
| String fakeInput = "hello world"; |
| int length = fakeInput.length(); |
| |
| emulator.getBackend().reg_write(ArmConst.UC_ARM_REG_R1, length); |
| MemoryBlock fakeInputBlock = emulator.getMemory().malloc(length, true); |
| fakeInputBlock.getPointer().write(fakeInput.getBytes(StandardCharsets.UTF_8)); |
| |
| emulator.getBackend().reg_write(ArmConst.UC_ARM_REG_R0, fakeInputBlock.getPointer().peer); |
| |
| Pointer buffer = context.getPointerArg(2); |
| context.push(buffer); |
| return HookStatus.RET(emulator, originFunction); |
| } |
| @Override |
| public void postCall(Emulator<?> emulator, HookContext context) { |
| Pointer buffer = context.pop(); |
| System.out.println("base64 result:"+buffer.getString(0)); |
| } |
| }, true); |
| |
| xHook.refresh(); |
| } |
# 替换函数
| public void ReplaceFuncByHookZz(){ |
| HookZz hook = HookZz.getInstance(emulator); |
| hook.replace(module.findSymbolByName("verifyApkSign").getAddress(), new ReplaceCallback() { |
| @Override |
| public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) { |
| emulator.getBackend().reg_write(Unicorn.UC_ARM_REG_R0,0); |
| return HookStatus.RET(emulator,context.getLR()); |
| } |
| }); |
| } |
# HookZz
HookZz 现在叫 Dobby,Unidbg 中是 HookZz 和 Dobby 是两个独立的 Hook 库,因为作者认为 HookZz 在 arm32 上支持较好,Dobby 在 arm64 上支持较好。HookZz 是 inline hook 方案,因此可以 Hook Sub_xxx,缺点是短函数可能出 bug,受限于 inline Hook 原理。
# 基础 hook
| public void HookByHookZz(){ |
| IHookZz hookZz = HookZz.getInstance(emulator); |
| hookZz.enable_arm_arm64_b_branch(); |
| hookZz.wrap(module.findSymbolByName("base64_encode"), new WrapCallback<HookZzArm32RegisterContext>() { |
| @Override |
| public void preCall(Emulator<?> emulator, HookZzArm32RegisterContext context, HookEntryInfo info) { |
| Pointer input = context.getPointerArg(0); |
| int length = context.getIntArg(1); |
| Pointer buffer = context.getPointerArg(2); |
| Inspector.inspect(input.getByteArray(0, length), "base64 input"); |
| context.push(buffer); |
| } |
| @Override |
| public void postCall(Emulator<?> emulator, HookZzArm32RegisterContext context, HookEntryInfo info) { |
| Pointer buffer = context.pop(); |
| System.out.println("base64 result:"+buffer.getString(0)); |
| } |
| }); |
| hookZz.disable_arm_arm64_b_branch(); |
| } |
# 修改参数
| public void ReplaceArgByHookZz(){ |
| IHookZz hookZz = HookZz.getInstance(emulator); |
| hookZz.enable_arm_arm64_b_branch(); |
| hookZz.wrap(module.findSymbolByName("base64_encode"), new WrapCallback<HookZzArm32RegisterContext>() { |
| @Override |
| public void preCall(Emulator<?> emulator, HookZzArm32RegisterContext context, HookEntryInfo info) { |
| Pointer input = context.getPointerArg(0); |
| String fakeInput = "hello world"; |
| input.setString(0, fakeInput); |
| context.setR1(fakeInput.length()); |
| |
| Pointer buffer = context.getPointerArg(2); |
| context.push(buffer); |
| } |
| @Override |
| public void postCall(Emulator<?> emulator, HookZzArm32RegisterContext context, HookEntryInfo info) { |
| Pointer buffer = context.pop(); |
| System.out.println("base64 result:"+buffer.getString(0)); |
| } |
| }); |
| hookZz.disable_arm_arm64_b_branch(); |
| } |
# Whale
Whale 是一个跨平台的 Hook 框架,在 Andorid Native Hook 上也是 inline Hook 方案
| public void HookByWhale(){ |
| IWhale whale = Whale.getInstance(emulator); |
| whale.inlineHookFunction(module.findSymbolByName("base64_encode"), new ReplaceCallback() { |
| Pointer buffer; |
| @Override |
| public HookStatus onCall(Emulator<?> emulator, long originFunction) { |
| RegisterContext context = emulator.getContext(); |
| Pointer input = context.getPointerArg(0); |
| int length = context.getIntArg(1); |
| buffer = context.getPointerArg(2); |
| Inspector.inspect(input.getByteArray(0, length), "base64 input"); |
| return HookStatus.RET(emulator, originFunction); |
| } |
| |
| @Override |
| public void postCall(Emulator<?> emulator, HookContext context) { |
| System.out.println("base64 result:"+buffer.getString(0)); |
| } |
| }, true); |
| } |
# unidbg 常用脚本
# Patch
-
方法一
| public void Patch1(){ |
| |
| int patchCode = 0xBF002000; |
| emulator.getMemory().pointer(module.base + 0x8CA).setInt(0,patchCode); |
| } |
-
方法二
| public void Patch2(){ |
| byte[] patchCode = {0x00, 0x20, 0x00, (byte) 0xBF}; |
| emulator.getBackend().mem_write(module.base + 0x8CA, patchCode); |
| } |
-
方法三
| public void Patch3(){ |
| try (Keystone keystone = new Keystone(KeystoneArchitecture.Arm, KeystoneMode.ArmThumb)) { |
| KeystoneEncoded encoded = keystone.assemble("movs r0,0;nop"); |
| byte[] patchCode = encoded.getMachineCode(); |
| emulator.getMemory().pointer(module.base + 0x8CA).write(0, patchCode, 0, patchCode.length); |
| } |
| } |
# 内存检索
| public void SearchAndPatch(){ |
| byte[] patterns = {(byte) 0x80, (byte) 0xb5,0x6f,0x46, (byte) 0x84, (byte) 0xb0,0x03, (byte) 0x90,0x02, (byte) 0x91}; |
| Collection<Pointer> pointers = searchMemory(module.base, module.base+module.size, patterns); |
| if(pointers.size() > 0){ |
| try (Keystone keystone = new Keystone(KeystoneArchitecture.Arm, KeystoneMode.ArmThumb)) { |
| KeystoneEncoded encoded = keystone.assemble("movs r0,0;nop"); |
| byte[] patchCode = encoded.getMachineCode(); |
| ((ArrayList<Pointer>) pointers).get(0).write(10, patchCode, 0, patchCode.length); |
| } |
| } |
| |
| } |
| |
| private Collection<Pointer> searchMemory(long start, long end, byte[] data) { |
| List<Pointer> pointers = new ArrayList<>(); |
| for (long i = start, m = end - data.length; i < m; i++) { |
| byte[] oneByte = emulator.getBackend().mem_read(i, 1); |
| if (data[0] != oneByte[0]) { |
| continue; |
| } |
| |
| if (Arrays.equals(data, emulator.getBackend().mem_read(i, data.length))) { |
| pointers.add(UnidbgPointer.pointer(emulator, i)); |
| i += (data.length - 1); |
| } |
| } |
| return pointers; |
| } |
# 参考资料
- Unidbg 文档更新 (一)
- Unidbg 文档慢更 (二)
- Unidbg Hook 大全