Unidbg中的hook部分

unidbg中的HookZz实现指令级hook

—————————————–
最近十分沉迷Unidbg这个项目,看一下里面的Hook部分

image_1dtdlefd4qc81ft7ppn1nm94pg1p.png-18.6kB

可以看到,里面有很多当红的hook框架,whale hook,hookzz,xhook这些,我看了下,决定先学习其中的hookzz部分,先看下怎么实现的,

image_1dtl5d4vqa1b7dhcornu44ll9.png-187.3kB

凯神(作者)应该是把hookzz编译出了两种库,ios的dylib和安卓的so,在创建一个hookzz对象时会把这个libhookzz这个so加载进模拟器的内存中(猜测,代码好多处看的迷迷糊糊),然后根据符号名称去so中获得hook功能函数的地址,unidbg这里主要使用的是hookzz导出的两个hook接口,一个是Replace,一个是wrap,当时不是很懂他们的区别,去翻了下源码

PUBLIC RetStatus ZzWrap(void *function_address, PRECALL pre_call, POSTCALL post_call){
DLOG("[*] Initialize 'ZzWrap' hook at %p\n", function_address);
  Interceptor *intercepter = Interceptor::SharedInstance();
  HookEntry *entry        = new HookEntry();
  entry->id               = intercepter->entries.size();
  entry->pre_call         = pre_call;
  entry->post_call        = post_call;
  entry->type             = kFunctionWrapper;
  entry->function_address = function_address;
  InterceptRouting *route = InterceptRouting::New(entry);
  route->Dispatch();
  intercepter->AddHookEntry(entry);
  route->Commit();
  DLOG("[*] Finalize %p\n", function_address);
  return RS_SUCCESS;
}
PUBLIC RetStatus ZzReplace(void *function_address, void *replace_call, void **origin_call) {
  DLOG("[*] Initialize 'ZzReplace' hook at %p\n", function_address);
  Interceptor *intercepter = Interceptor::SharedInstance();
  HookEntry *entry        = new HookEntry();
  entry->id               = intercepter->entries.size();
  entry->replace_call     = replace_call;
  entry->type             = kFunctionInlineHook;
  entry->function_address = function_address;
  InterceptRouting *route = InterceptRouting::New(entry);
  route->Dispatch();
  intercepter->AddHookEntry(entry);
  // SET BEFORE `route->Commit()` !!!
 // set origin call with relocated function
  *origin_call = entry->relocated_origin_function;
  route->Commit();
  DLOG("[*] Finalize %p\n", function_address);
  return RS_SUCCESS;

}

两者同样保存了被hook地址的一些信息,调用了precall以及postcall这些函数,唯一不同的是,replace多一个更改了原始被hook函数的地址变为新函数地址,这个允许hookzz可以不执行原函数,但是wrap就不行。关于hookzz的底层实现可以去jmpnews大神的博客里,确实不是很好懂(我读不懂orz)。直接看下在unidbg中的例子吧,先学会怎么用。以test事例中的TTEncrypt为例(并不知道是哪家app的),先看下wrap这个玩意儿,和replace比起来,wrap能做的很少,更多的是打印内存/寄存器的值,起到辅助作用

private void ttEncrypt() throws IOException {
    Symbol sbox0 = module.findSymbolByName("sbox0");
    Symbol sbox1 = module.findSymbolByName("sbox1");
    Inspector.inspect(sbox0.createPointer(emulator).getByteArray(0, 256), "sbox0");
    Inspector.inspect(sbox1.createPointer(emulator).getByteArray(0, 256), "sbox1");//inspector类。用于异步输出log
    IHookZz hookZz = HookZz.getInstance(emulator);
    hookZz.wrap(module.findSymbolByName("ss_encrypt"), new WrapCallback<RegisterContext>() {//找到加密函数地址
        @Override
        public void preCall(Emulator emulator, RegisterContext ctx, HookEntryInfo info) {
            Pointer pointer = ctx.getPointerArg(2);//取ctx的r2寄存器,r2存的是key的初始地址
            int length = ctx.getIntArg(3);//r3寄存器存缓冲区长度
            byte[] key = pointer.getByteArray(0, length);//偏移加长度读内存
            Inspector.inspect(key, "ss_encrypt key");
            System.out.println("peer "+((UnicornPointer) pointer).peer);
        }
        @Override
        public void postCall(Emulator emulator, RegisterContext ctx, HookEntryInfo info) {
            System.out.println("ss_encrypt.postCall R0=" + ctx.getLongArg(0));//读r0返回值
        }
    });
    hookZz.wrap(module.base + 0x00000F5C + 1, new WrapCallback<Arm32RegisterContext>() {
        @Override
        public void preCall(Emulator emulator, Arm32RegisterContext ctx, HookEntryInfo info) {
            System.out.println("R3=" + ctx.getLongArg(3) + ", R10=0x" + Long.toHexString(ctx.getR10Long()));
        }
    });

去看了下这个so,无壳无混淆

image_1dtlpkp8hauh9bh24spak1in313.png-66.7kB

可以看到有很多导出的符号,可以直接findSymbolByName去找到他在unicorn模拟器中的地址,还是十分方便的,对于未导出的函数,可以采用base+偏移的形式去定位,这个也很常见,定位到地址之后,可以注册precall(原函数执行前调用)和postcall(原函数后调用),这个和xp差不多,很好理解,precall的三个参数分别是模拟器实例,RegCtx(保存了寄存器上下文),以及hookinfo信息,关于RegCtx有一点我很奇怪,似乎只提供了读寄存器的getIntArg(寄存器编号)这个函数,并没有提供写寄存器这个功能

image_1dtlq380s1m6l1h5314guuba15hc1g.png-55.3kB

我看的实现是arm32的,也就是所有寄存器都是4字节长度,这里unidbg对寄存器的值的情况做了次分类,int型数据,long型数据,以及pointer型的地址指针,至于寄存器里值具体是哪种类型以及如何取出来使用,这种情况需要在调试的基础确认后再使用,比如在这个例子中,去看下ss_encrypt这个函数

image_1dtlqceu4at71fl9t8el3oeu41t.png-11.4kB

一开始调用了memset函数,并且memset函数的src地址是存在r2里的,因此r2存的是一个地址指针,用pointer类型读出来之后,再进行getByteArray去读内存,其中peer是unicorn在内存中的地址,pointer+unicorn.peer的地址就是你要读的地址

image_1dtlqk4k91q2dcop2l7iuh1f5r2a.png-22.6kB

其他的几种类型更好理解,基本上看注释就行了,再看看replace

    hookZz.enable_arm_arm64_b_branch();
hookZz.replace(module.findSymbolByName("ss_encrypted_size"), new ReplaceCallback() {
    @Override
    public HookStatus onCall(Emulator emulator, long originFunction) {
        System.out.println("ss_encrypted_size.onCall arg0=" + emulator.getContext().getIntArg(0) + ", originFunction=0x" + Long.toHexString(originFunction));
        return HookStatus.RET(emulator, originFunction);
    }
});
hookZz.disable_arm_arm64_b_branch();

这个demo没什么说服力,因为最后ret到了原始函数地址,等于没有用到replace的功能,等以后碰到实际需要的例子时,再抽出来单独讲解,因为hookzz的hook层次比较低,毕竟用的inline hook,因此常常用来hook一些未导出的函数,但对于导出函数和系统函数,采用got/plt hook的方式更为方便,实际上xhook似乎就是这样的(猜测

image_1dtlr4i7r1ggok9a1luv64kgns2n.png-124.9kB

用xhook可以hook系统的函数更为方便,明天看看xhook和桌桐巨巨在看雪更新的hook框架。