unidbg中的Hook

  1. unidbg支持dobby、hookzz、whale、xhook
  2. hookzz是dobby的前身,hookzz对32位支持较好,dobby对64位支持较好
  3. unidbg支持Unicorn自带的各种Hook(指令级Hook,块级Hook,内存读写Hook,异常Hook)以及unidbg封装后的console debugger
  4. 掌握原生unicorn hook以及console debugger即可
    原生unicorn hook不容易被检测
    console debugger可下多个断点,用于快速验证
  5. Hook的作用
    利用Hook来做算法分析,类似Frida的操作
    预先Hook一些系统函数,可以看出APP是否调用了某些方法来检测
    Hook掉一些检测函数或指令,来过反调试、反Hook等
    unidbg没法处理子线程中的操作,Hook相应位置,把子线程计算结果赋值给寄存器

    unidbg中的Hook-HookZz

  6. HookZz在32位更稳定

  7. 可以通过符号Hook,也可以通过地址Hook
  8. HookZz支持函数Hook、inline Hook(hookzz.wrap、hookzz.instrument)
    new WrapCallback()
    new InstrumentCallback()
    RegisterContext、Arm32RegisterContext、Arm64RegisterContext(RegisterContext是Arm32RegisterContext,Arm64RegisterContext的父类)
    HookZzArm32RegisterContext、HookZzArm64RegisterContext

    wrap-hook

    1. void callFunc() {
    2. IHookZz hookZz = HookZz.getInstance(emulator);
    3. hookZz.wrap(module.findSymbolByName("_Z9MD5UpdateP7MD5_CTXPhj"), new WrapCallback<Arm64RegisterContext>() { // inline wrap导出函数
    4. @Override
    5. public void preCall(Emulator<?> emulator, Arm64RegisterContext ctx, HookEntryInfo info) {
    6. md5_ctx = ctx.getPointerArg(0);//(这里的0代表第一个参数)
    7. Pointer plainText = ctx.getPointerArg(1);//(这里的1代表第二个参数)
    8. int length = ctx.getIntArg(2);//(这里的1代表第三个参数)
    9. Inspector.inspect(md5_ctx.getByteArray(0, 64), "preCall md5_ctx");
    10. Inspector.inspect(plainText.getByteArray(0, length), "plainText");
    11. }
    12. @Override
    13. public void postCall(Emulator<?> emulator, Arm64RegisterContext ctx, HookEntryInfo info) {
    14. Inspector.inspect(md5_ctx.getByteArray(0, 64), "postCall md5_ctx");
    15. }
    16. });
    17. StringObject md5Result = NativeHelper.callStaticJniMethodObject(emulator, "md5(Ljava/lang/String;)Ljava/lang/String;", new StringObject(vm, "a123456789"));
    18. System.out.println("md5Result: " + md5Result.getValue());
    19. }
    image.png
  • 我们hook的是md5中的Update函数,根据图片所示,我们传入的第一个参数是一个结构体,第二个是明文,第三个是长度
  • 传入的顺序如代码所示
  • 传入的类型根据结构体下方法所展示,例如 ctx.getIntArg(2)传入的第三个参数是一个整数型的;
  • 我们在此,必须调用了该函数,才能hook。如代码所示我们用callStaticJniMethodObject进行对md5函数的调用
  • wrap方法下有两个方法,一个是preCall,一个是postCall,对比我们Frida中是onEnter和onLeave

    inline Hook

    image.png

  • 我们针对汇编级别的hook,就需要要用到inline的hook,这里我们对add函数里面的w8,w9进行hook

    1. void callFunc() {
    2. IHookZz hookZz = HookZz.getInstance(emulator);
    3. hookZz.instrument(module.base + 0x1AEC, new InstrumentCallback<Arm64RegisterContext>() {
    4. @Override
    5. public void dbiCall(Emulator<?> emulator, Arm64RegisterContext ctx, HookEntryInfo info) {
    6. System.out.println("w8=0x" + Integer.toHexString(ctx.getXInt(8)) + ", w9=0x" + Integer.toHexString(ctx.getXInt(9)));
    7. }
    8. });
    9. int retval = NativeHelper.callStaticJniMethodInt(emulator, "add(III)I", 0x100, 0x200, 0x300); // 执行Jni方法
    10. System.out.println("retval: 0x" + Integer.toHexString(retval));
    11. }

    参数的获取

1.1 取出Java类型int number = emulator.getContext().getIntArg(1);vm.getObject(number);
1.2 以内存写入方式传递参数,取参数时就读内存
1.3 以addLocalObject方式传入Java类型参数,取参数时用getObject
1.4 Pointer下有getString方法,可以直接获取内存中的字符串

  1. void callFunc() {
  2. IHookZz hookZz = HookZz.getInstance(emulator);
  3. hookZz.wrap(module.findSymbolByName("_Z12jstring2cstrP7_JNIEnvP8_jstring"), new WrapCallback<Arm64RegisterContext>() {
  4. @Override
  5. public void preCall(Emulator<?> emulator, Arm64RegisterContext ctx, HookEntryInfo info) {
  6. int intArg = ctx.getIntArg(1);
  7. StringObject object = vm.getObject(intArg);
  8. }
  9. @Override
  10. public void postCall(Emulator<?> emulator, Arm64RegisterContext ctx, HookEntryInfo info) {
  11. //方法一
  12. byte[] byteArray = ctx.getPointerArg(0).getByteArray(0, 16);
  13. Inspector.inspect(byteArray,"byteArray");
  14. //方法二
  15. byte[] byteArray1 = ctx.getXPointer(0).getByteArray(0, 16);
  16. Inspector.inspect(byteArray1,"byteArray1");
  17. }
  18. });
  19. Number number = module.callFunction(emulator, "_Z12jstring2cstrP7_JNIEnvP8_jstring",
  20. vm.getJNIEnv(), vm.addLocalObject(new StringObject(vm, "123456")));
  21. long cstrAddr = number.longValue();
  22. byte[] bytes = emulator.getMemory().pointer(cstrAddr).getByteArray(0, 16);
  23. Inspector.inspect(bytes, "cstrAddr");
  24. }
  25. }
  • 我们是以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

    1. void callFunc() {
    2. IHookZz hookZz = HookZz.getInstance(emulator);
    3. hookZz.wrap(module.findSymbolByName("_Z12jstring2cstrP7_JNIEnvP8_jstring"), new WrapCallback<HookZzArm64RegisterContext>() {
    4. @Override
    5. public void preCall(Emulator<?> emulator, HookZzArm64RegisterContext ctx, HookEntryInfo info) {
    6. }
    7. @Override
    8. public void postCall(Emulator<?> emulator, HookZzArm64RegisterContext ctx, HookEntryInfo info) {
    9. String str = ctx.getXPointer(0).getString(0);
    10. int hashcode = vm.addLocalObject(new StringObject(vm,"hahah123456"));
    11. ctx.setXLong(0,hashcode);
    12. }
    13. });
    14. Number number = module.callFunction(emulator, "_Z12jstring2cstrP7_JNIEnvP8_jstring",
    15. vm.getJNIEnv(), vm.addLocalObject(new StringObject(vm, "123456")));
    16. int i = number.intValue();
    17. System.out.println(vm.getObject(i).getValue());
    18. }
  • 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);

    1. void callFunc() {
    2. final IHookZz hookZz = HookZz.getInstance(emulator);
    3. hookZz.replace(module.findSymbolByName("Java_com_xiaojianbang_ndk_NativeHelper_md5"), new ReplaceCallback() {
    4. @Override
    5. public HookStatus onCall(Emulator<?> emulator, long originFunction) {
    6. //方法一 return super.onCall(emulator, originFunction);
    7. //方法二 return HookStatus.RET(emulator,originFunction);
    8. return HookStatus.LR(emulator, 100);//方法三
    9. }
    10. });
    11. int md5Result = NativeHelper.callStaticJniMethodInt(emulator, "md5(Ljava/lang/String;)Ljava/lang/String;", new StringObject(vm, "xiaojianbang")); // 执行Jni方法
    12. System.out.println("md5Result: " + md5Result);
    13. }
  • 方法一和方法二效果是一样的

  • HookStatus.LR 直接把原函数得到的值给修改了,这里修改成为的是100,随之我们调用的方法也应该变为callStaticJniMethodInt