unidbg中的Hook
- unidbg支持dobby、hookzz、whale、xhook
- hookzz是dobby的前身,hookzz对32位支持较好,dobby对64位支持较好
- unidbg支持Unicorn自带的各种Hook(指令级Hook,块级Hook,内存读写Hook,异常Hook)以及unidbg封装后的console debugger
- 掌握原生unicorn hook以及console debugger即可
原生unicorn hook不容易被检测
console debugger可下多个断点,用于快速验证 Hook的作用
利用Hook来做算法分析,类似Frida的操作
预先Hook一些系统函数,可以看出APP是否调用了某些方法来检测
Hook掉一些检测函数或指令,来过反调试、反Hook等
unidbg没法处理子线程中的操作,Hook相应位置,把子线程计算结果赋值给寄存器unidbg中的Hook-HookZz
HookZz在32位更稳定
- 可以通过符号Hook,也可以通过地址Hook
- HookZz支持函数Hook、inline Hook(hookzz.wrap、hookzz.instrument)
new WrapCallback()
new InstrumentCallback()
RegisterContext、Arm32RegisterContext、Arm64RegisterContext(RegisterContext是Arm32RegisterContext,Arm64RegisterContext的父类)
HookZzArm32RegisterContext、HookZzArm64RegisterContextwrap-hook
void callFunc() {IHookZz hookZz = HookZz.getInstance(emulator);hookZz.wrap(module.findSymbolByName("_Z9MD5UpdateP7MD5_CTXPhj"), new WrapCallback<Arm64RegisterContext>() { // inline wrap导出函数@Overridepublic void preCall(Emulator<?> emulator, Arm64RegisterContext ctx, HookEntryInfo info) {md5_ctx = ctx.getPointerArg(0);//(这里的0代表第一个参数)Pointer plainText = ctx.getPointerArg(1);//(这里的1代表第二个参数)int length = ctx.getIntArg(2);//(这里的1代表第三个参数)Inspector.inspect(md5_ctx.getByteArray(0, 64), "preCall md5_ctx");Inspector.inspect(plainText.getByteArray(0, length), "plainText");}@Overridepublic void postCall(Emulator<?> emulator, Arm64RegisterContext 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, "a123456789"));System.out.println("md5Result: " + md5Result.getValue());}

- 我们hook的是md5中的Update函数,根据图片所示,我们传入的第一个参数是一个结构体,第二个是明文,第三个是长度
- 传入的顺序如代码所示
- 传入的类型根据结构体下方法所展示,例如 ctx.getIntArg(2)传入的第三个参数是一个整数型的;
- 我们在此,必须调用了该函数,才能hook。如代码所示我们用callStaticJniMethodObject进行对md5函数的调用
wrap方法下有两个方法,一个是preCall,一个是postCall,对比我们Frida中是onEnter和onLeave
inline Hook

我们针对汇编级别的hook,就需要要用到inline的hook,这里我们对add函数里面的w8,w9进行hook
void callFunc() {IHookZz hookZz = HookZz.getInstance(emulator);hookZz.instrument(module.base + 0x1AEC, new InstrumentCallback<Arm64RegisterContext>() {@Overridepublic void dbiCall(Emulator<?> emulator, Arm64RegisterContext ctx, HookEntryInfo info) {System.out.println("w8=0x" + Integer.toHexString(ctx.getXInt(8)) + ", w9=0x" + Integer.toHexString(ctx.getXInt(9)));}});int retval = NativeHelper.callStaticJniMethodInt(emulator, "add(III)I", 0x100, 0x200, 0x300); // 执行Jni方法System.out.println("retval: 0x" + Integer.toHexString(retval));}
参数的获取
1.1 取出Java类型int number = emulator.getContext().getIntArg(1);vm.getObject(number);
1.2 以内存写入方式传递参数,取参数时就读内存
1.3 以addLocalObject方式传入Java类型参数,取参数时用getObject
1.4 Pointer下有getString方法,可以直接获取内存中的字符串
void callFunc() {IHookZz hookZz = HookZz.getInstance(emulator);hookZz.wrap(module.findSymbolByName("_Z12jstring2cstrP7_JNIEnvP8_jstring"), new WrapCallback<Arm64RegisterContext>() {@Overridepublic void preCall(Emulator<?> emulator, Arm64RegisterContext ctx, HookEntryInfo info) {int intArg = ctx.getIntArg(1);StringObject object = vm.getObject(intArg);}@Overridepublic void postCall(Emulator<?> emulator, Arm64RegisterContext ctx, HookEntryInfo info) {//方法一byte[] byteArray = ctx.getPointerArg(0).getByteArray(0, 16);Inspector.inspect(byteArray,"byteArray");//方法二byte[] byteArray1 = ctx.getXPointer(0).getByteArray(0, 16);Inspector.inspect(byteArray1,"byteArray1");}});Number number = module.callFunction(emulator, "_Z12jstring2cstrP7_JNIEnvP8_jstring",vm.getJNIEnv(), vm.addLocalObject(new StringObject(vm, "123456")));long cstrAddr = number.longValue();byte[] bytes = emulator.getMemory().pointer(cstrAddr).getByteArray(0, 16);Inspector.inspect(bytes, "cstrAddr");}}
- 我们是以jstring转换为cstring的这个函数为例子的,值得注意的是此函数返回的char数组的一个指针;
- emulator.getMemory().pointer(cstrAddr).getByteArray(0, 16); 针对指针的读取,以十六进制的方式读取;
- preCall函数中,我们的ctx 上下文,对应的使我们函数传入的内容,是以数组的形式存放的;我们的jstring2cstr函数第一个传入的是jnienv,第二个传入的是我们的字符串所包装成的对方放在vm虚拟机中; ctx.getIntArg(1) 获取数组中第一个参数,这里对应的是对象的hashcode,vm.getObject(intArg)获取hashcode所对应的值;
获取函数的返回值,有两种方法;方法一:ctx.getPointerArg(0).getByteArray(0, 16); 这里直接获取了ctx的指针,getPointerArg(0)传入的0是代表了ctx的本身指针地址;方法二:Arm64RegisterContext这个类下面有一个获取寄存器的方法ctx.getXPointer(0) 注意,我们这个意思就是获取的第x0寄存器,x0寄存器是存放我们的最终结果的;
修改寄存器-HookZzArm64RegisterContext类下的setXLong
void callFunc() {IHookZz hookZz = HookZz.getInstance(emulator);hookZz.wrap(module.findSymbolByName("_Z12jstring2cstrP7_JNIEnvP8_jstring"), new WrapCallback<HookZzArm64RegisterContext>() {@Overridepublic void preCall(Emulator<?> emulator, HookZzArm64RegisterContext ctx, HookEntryInfo info) {}@Overridepublic void postCall(Emulator<?> emulator, HookZzArm64RegisterContext ctx, HookEntryInfo info) {String str = ctx.getXPointer(0).getString(0);int hashcode = vm.addLocalObject(new StringObject(vm,"hahah123456"));ctx.setXLong(0,hashcode);}});Number number = module.callFunction(emulator, "_Z12jstring2cstrP7_JNIEnvP8_jstring",vm.getJNIEnv(), vm.addLocalObject(new StringObject(vm, "123456")));int i = number.intValue();System.out.println(vm.getObject(i).getValue());}
ctx.setXLong 第一个参数是需要设置的寄存器,这里是x0寄存器,设置的内容是对象的hashcode;
- module.callFunction 这里我们返回的是我们hook中java的形式,没有hook是走的我们的c的形式,对此,我们number的读取也应该是java的形式;
String str = ctx.getXPointer(0).getString(0); 内存读取字符串
Hook替换(hookzz.replace)
1.1 new ReplaceCallback
1.2 替换以后依然可以调用原函数
1.3 不调用原函数的返回设置 HookStatus.LR(emulator, value);void callFunc() {final IHookZz hookZz = HookZz.getInstance(emulator);hookZz.replace(module.findSymbolByName("Java_com_xiaojianbang_ndk_NativeHelper_md5"), new ReplaceCallback() {@Overridepublic HookStatus onCall(Emulator<?> emulator, long originFunction) {//方法一 return super.onCall(emulator, 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);}
方法一和方法二效果是一样的
- HookStatus.LR 直接把原函数得到的值给修改了,这里修改成为的是100,随之我们调用的方法也应该变为callStaticJniMethodInt
