iOS Hook 原理(三)- InlinehHook (Dobby)

一、inlinehook概述

inlineHook(内联钩子):所谓InlineHook就是直接修改目标函数的头部代码。让它跳转到我们自定义的函数里面执行我们的代码,从而达到Hook的目的。这种Hook技术一般用在静态语言的HOOK上面。

Inline Hook 就是在运行的流程中插入跳转指令来抢夺运行流程的一个方法。大体分为三步:

  • 将原函数的前 N 个字节搬运到 Hook 函数的前 N 个字节;
  • 然后将原函数的前 N 个字节填充跳转到 Hook 函数的跳转指令;
  • Hook 函数末尾几个字节填充跳转回原函数 +N 的跳转指令;
    image.png
image.png

Dobby(原名:HOOKZz)。是一个全平台的inlineHook(内联钩子)框架,用起来就和fishhook一样。Dobby 是通过插入 __zDATA 段和__zTEXT 段到 Mach-O 中。

  • __zDATA 用来记录 Hook 信息(Hook 数量、每个 Hook 方法的地址)、每个 Hook 方法的信息(函数地址、跳转指令地址、写 Hook 函数的接口地址)、每个 Hook 的接口(指针)。
  • __zText 用来记录每个 Hook 函数的跳转指令。

Dobby 通过 mmap 把整个 Mach-O 文件映射到用户的内存空间,写入完成保存本地。所以 Dobby 并不是在原 Mach-O 上进行操作,而是重新生成并替换。
Doddy

二、 编译Dobby

首先clone工程

#depth用于指定克隆深度,为1即表示只克隆最近一次commit.
git clone https://github.com/jmpews/Dobby.git --depth=1 

由于Dobby是跨平台的,所以项目并不是一个Xcode工程,要使用cmake将这个工程编译成为Xcode工程。进入Dobby目录,创建一个文件夹,然后cmake编译工程:

cd Dobby && mkdir build_for_ios_arm64 && cd build_for_ios_arm64
cmake .. -G Xcode \
-DCMAKE_TOOLCHAIN_FILE=cmake/ios.toolchain.cmake \
-DPLATFORM=OS64 -DARCHS="arm64" -DCMAKE_SYSTEM_PROCESSOR=arm64 \
-DENABLE_BITCODE=0 -DENABLE_ARC=0 -DENABLE_VISIBILITY=1 -DDEPLOYMENT_TARGET=9.3 \
-DDynamicBinaryInstrument=ON -DNearBranch=ON -DPlugin.SymbolResolver=ON -DPlugin.Darwin.HideLibrary=ON -DPlugin.Darwin.ObjectiveC=ON

编译完成后,会生成一个Xcode工程(这里在build_for_ios_arm64目录中 )。接下来编译Xcode工程生成我们的Framework

新版本的Dobby多了很多功能,这里我们只编译DpbbyX或者dobby,区别就是一个是.Framework一个是.dylib

image.png

到这里Dobby库就已经准备好了。

和上一篇文章一样如果提示需要账户直接添加CODE_SIGNING_ALLOWED配置。

三、导入 DobbyX.Framework / libdobby.dylib

新建一个工程DoddyDemo,导入DobbyX.Framework
问题处理

  1. bitcode问题'/Users/zaizai/HookTest/DoddyDemo/DobbyX.framework/DobbyX' does not contain bitcode. You must rebuild it with bitcode enabled (Xcode setting ENABLE_BITCODE), obtain an updated library from the vendor, or disable bitcode for this target. file '/Users/zaizai/HookTest/DoddyDemo/DobbyX.framework/DobbyX' for architecture arm64
    两个方案:
    1.生成Framework/dylib的时候设置支持bitcode

    image.png

    2.项目关闭bitcode
    image.png

  2. 拷贝Frameworkdyld: Library not loaded: @rpath/DobbyX.framework/DobbyX Referenced from: /private/var/containers/Bundle/Application/BFDFC7CA-7045-4392-881D-1BFEA22E6BFA/DoddyDemo.app/DoddyDemo Reason: image not found

这里就是DobbyXMacho文件中已经有了,但是在Frameworks目录中没有。Xcode不会帮我们自动Copy,需要添加Copy

image.png

处理完成后运行工程输出以为内容表示成功:

[*] ================================
[*] Dobby
[*] ================================
[*] dobby in debug log mode, disable with cmake flag "-DDOBBY_DEBUG=OFF"

四、 DobbyDemo

dobby最核心的函数DobbyHook定义如下:

// replace function
int DobbyHook(void *address, void *replace_call, void **origin_call);
  • address:需要HOOK的函数地址。
  • replace_call:新函数地址。
  • origin_call:保留原始函数的指针的地址。

从函数的参数就能看出来和fishhook很相似。使用下试试:

#import 

+ (void)load {
    //Hook sum
    DobbyHook(sum, HP_sum, (void *)&sum_p);
}

//要Hook的函数
int sum(int a, int b){
    return  a + b;
}

//新函数
int HP_sum(int a,int b) {
    NSLog(@"before hook: %d + %d = %d",a,b,sum_p(a,b));
    return a - b;
}

//原函数指针
static int(*sum_p)(int a,int b);

调用:

NSLog(@"after hook: %d + %d = %d",10,20,sum(10, 20));

输出:

DoddyDemo[9679:6241363] before hook: 10 + 20 = 30
DoddyDemo[9679:6241363] after hook: 10 + 20 = -10

这里也就Hook了自定义C函数,弥补了fishhook不能hook自定义c函数的问题。

那么外部函数可以Hook么?HookNSLog试下:

//新函数
void HP_NSLog(NSString *format, ...) {
    NSLog_p([format stringByAppendingString:@"\nHook Success"]);
}

//原函数指针
static void(*NSLog_p)(NSString *format, ...);

+ (void)load {
    DobbyHook(NSLog, HP_NSLog, (void *)&NSLog_p);
}

调用:

NSLog(@"HotpotCat");

输出:

 HotpotCat
 Hook Success

可以看到也是可以的,由于是直接插入跳转指令来执行自己的代码肯定都可以。

那么Dobby是怎么做的呢?以sum函数为例,分别在Hook前后在sum函数中打断点看下汇编代码:

//before hook
DoddyDemo`sum:
    0x104ab9c3c <+0>:  sub    sp, sp, #0x10             ; =0x10 
    0x104ab9c40 <+4>:  str    w0, [sp, #0xc]
    0x104ab9c44 <+8>:  str    w1, [sp, #0x8]
->  0x104ab9c48 <+12>: ldr    w8, [sp, #0xc]
    0x104ab9c4c <+16>: ldr    w9, [sp, #0x8]
    0x104ab9c50 <+20>: add    w0, w8, w9
    0x104ab9c54 <+24>: add    sp, sp, #0x10             ; =0x10 
    0x104ab9c58 <+28>: ret    

//after hook
DoddyDemo`sum:
    0x104ab9c3c <+0>:  adrp   x17, 0
    0x104ab9c40 <+4>:  add    x17, x17, #0xc5c          ; =0xc5c 
    0x104ab9c44 <+8>:  br     x17
->  0x104ab9c48 <+12>: ldr    w8, [sp, #0xc]
    0x104ab9c4c <+16>: ldr    w9, [sp, #0x8]
    0x104ab9c50 <+20>: add    w0, w8, w9
    0x104ab9c54 <+24>: add    sp, sp, #0x10             ; =0x10 
    0x104ab9c58 <+28>: ret  

通过汇编对比可以看到前面3行发生了变化(⚠️第二个sum调用没有拉伸栈空间,这里也就确实修改后汇编的行数是不变的,栈平衡放到了后面的函数中),打断点查看x17的内容:

image.png

可以看到在sum的头部跳转到了HP_sum中执行HP_sum函数。这也就验证了inlinehookhook函数的头部进行跳转。在这里br x17后面的代码就不执行了,只有在HP_sum中调用sum_p的时候才执行原来的代码。

继续进入HP_sum

image.png

进入blr x9

image.png

可以看到这里拉伸了栈空间,也就是说只有调用原来的函数才开辟空间,在sumbr x17后面做栈回收。这样也就栈平衡了。

继续往下调用,最后会分别回到HP_sum->sum 也就是执行sum后面的一段汇编:

image.png

继续进入:

image.png

验证确实回到了sum中继续执行。
⚠️修改的__Text段实际上是替换。

五、 地址替换符号

在正常的逆向开发中,我们一般情况下拿到的都是地址,所以DobbyHook的第一个参数应该是一个地址。那么怎么将符号替换成地址呢?还是以sum函数为例。

image.png

在这里我们能算出函数的偏移值为:0x102dc5c70 - 0x0000000102dc0000 = 0x5C70,查看MachO也确实是sum函数。
image.png

那么在代码中直接通过偏移值 + ASLR就能得到函数执行时候的地址了。(一般我们并不知道这个地址是sum,这里只是演示。正常逆向是要分析代码逻辑找地址然后尝试的)。
修改sum函数hook为地址代码如下:

#import 
#import 

//sum 函数地址 偏移值: PAGEZERO(0x100000000) + offset(0x5C70),这里用 uintptr_t 类型是为了方便计算。这里只是64位,暂不考虑32位。如果最低版本从`iOS11`开始,就不用考虑32位的情况了。
static uintptr_t sum_address_offset = 0x100005C70;

+ (void)load {
    //地址hook
    //获取ASLR,相当于rebase
    uintptr_t slide = _dyld_get_image_vmaddr_slide(0);
    uintptr_t sum_address = sum_address_offset + slide;
    NSLog(@"sum_address_offset:%p\nslide:%p\nsum_address:%p",(void *)sum_address_offset,(void *)slide,(void *)sum_address);
    DobbyHook((void *)sum_address, HP_sum, (void *)&sum_p);
}

这个时候运行并没有hook成功:

sum_address_offset:0x100005c70
slide:0x4150000
sum_address:0x104155c70
hook: 10 + 20 = 30

因为我们在主工程添加了代码,MachO变了,所以对应的获取的到的0x5C70也发生了改变:

image.png

这里也就说明了一些app的插件在app更新后就不能用的原因。这里有两种方式处理偏移值改变的情况。重新获取偏移值,或者将Hook代码放入动态库中。
重新获取计算偏移值发现是0x5D08,直接将sum_address_offset修改:

static uintptr_t sum_address_offset = 0x100005D08;

再次运行工程发现Hook成功了。

sum_address_offset:0x100005d08
slide:0x2570000
sum_address:0x102575d08
before hook: 10 + 20 = 30
hook: 10 + 20 = -10

⚠️这里有个问题是sum_address_offset的值变了,为什么偏移值没有变。这里是因为这块改动很小,数据大小是没有变化的,不会影响到偏移值的变化。当然最好的方案当然是放入动态库中。

六、 Dobby注入

正常情况写分析其它App我们是要注入自己的动态库的,写个AppDemo试下下整个流程。
AppDemo的主页面有如下代码:

int sum(int a,int b) {
    return a + b;
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    NSLog(@"sum result: %d",sum(10, 20));
}

计算下偏移值为0x5E54。编译生成AppDemo.app。新建一个Hook工程HookAppDemo。利用HookAppDemo工程对AppDemo.app重签名和注入。
DobbyX.frameworkAppDemo.app拷贝到HookAppDemo工程的目录。
目录结构如下:

image.png

HPHook是自定义注入库。

这里HPHook依赖DobbyX有两种方式:

  1. DobbyX加入到主工程:
    image.png

然后在HPHook Target配置:

other linker flags

image.png

这个时候两个库都已经在Frameworks中了,并且注入成功了。

image.png

  1. DobbyX 加入到HPHook中:
    配置HPHook或者主工程 Copy Files(任意一个都可以):
    image.png
  • 配置HPHook则主工程和Framework MachO分别如下:
    HookAppDemo

    HPHook
  • 配置主工程MachO分别如下:
    HookAppDemo
HPHook

原理一样,层级不同。

七、 Hook自定义函数

HPHook中写Hook代码

#import "HPInject.h"
#import 
#import 

@implementation HPInject

//sum 函数地址 偏移值: PAGEZERO(0x100000000) + offset(0x5E54),这里用 uintptr_t 类型是为了方便计算。
static uintptr_t sum_address_offset = 0x100005E54;

+ (void)load {
    //获取ASLR,相当于rebase
    uintptr_t slide = _dyld_get_image_vmaddr_slide(0);
    uintptr_t sum_address = sum_address_offset + slide;
    NSLog(@"sum_address_offset:%p\nslide:%p\nsum_address:%p",(void *)sum_address_offset,(void *)slide,(void *)sum_address);
    DobbyHook((void *)sum_address, HP_sum, (void *)&sum_p);
}

//要Hook的函数
int sum(int a, int b){
    return  a + b;
}

//新函数
int HP_sum(int a,int b) {
    NSLog(@"Hook Success");
    return sum_p(a,b);
}

//原函数指针
static int(*sum_p)(int a,int b);

@end

输出:

Hook Success
sum result: 30

这个时候就Hook成功了。

八、Hook Swift

还是同样的逻辑,将AppDemosum改写为swift实现,创建一个Swift AppDemo,代码如下:

func sum(a: Int, b: Int) -> Int {
    return a + b
}

override func touchesBegan(_ touches: Set, with event: UIEvent?) {
    print(sum(a: 10, b: 20))
}

同样的方式计算偏移值0x6650

HookAppDemo中用SwiftAppDemo.app替换AppDemo.app,修改sum_address_offset值。运行工程:

Hook Success
30

这个时候Swift代码也Hook成功了。

DobbyTest

inlinehook(Dobby)运行时Hook:对目标函数的汇编代码进行修改,修改的是内存中的MachO的代码段(强制替换)。

修改目标函数后,函数栈平衡放到调用原函数的地方。这么做为了确认是否需要调用原始的方法,需要的话会找时机拉伸栈后返回执行后面的代码。

你可能感兴趣的:(iOS Hook 原理(三)- InlinehHook (Dobby))