> For the complete documentation index, see [llms.txt](https://ryukiedev.gitbook.io/wiki/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://ryukiedev.gitbook.io/wiki/ni-xiang/08.hook.md).

# 08.Hook

## 一、常见的Hook技术

* Method Swizzle
  * 利用OC的Runtime特性，动态改变SEL和IMP的对应关系，达到OC方法调用流程的改变。主要用于OC方法。
* fishhook
  * Facebook提供的一个动态修改链接mach-O文件的工具。利用MachO文件加载原理，通过懒加载和非懒加载两个表的指针达到C函数Hook的目的。
* Cydia Substrate
  * 主要针对OC方法、C函数以及对函数地址进行Hook操作。不经针对iOS，安卓也可以用。

## 二、fishhook

[fishhook-Github](https://github.com/facebook/fishhook)

* 首先回顾一下OC的动态特性
  * 并不是直接调用方法的实现地址，而是通过一张selector和imp的映射表去找
  * 静态语言如C，是直接通过地址访问
    * 但外部C函数调用是通过符号绑定来的

### 2.1 rebind\_symbols

```C
/*
 * For each rebinding in rebindings, rebinds references to external, indirect
 * symbols with the specified name to instead point at replacement for each
 * image in the calling process as well as for all future images that are loaded
 * by the process. If rebind_functions is called more than once, the symbols to
 * rebind are added to the existing list of rebindings, and if a given symbol
 * is rebound more than once, the later rebinding will take precedence.
 */
FISHHOOK_VISIBILITY
int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel);
```

参数列表：

* 需要Hook的rebinding数组
* 长度

### 2.2 rebinding

```C
/*
 * A structure representing a particular intended rebinding from a symbol
 * name to its replacement
 */
struct rebinding {
  const char *name;
  void *replacement;
  void **replaced;
};
```

`name` 需要Hook的函数名称，C字符串 `replacement` 新函数的地址 `replaced` 原始函数的指针

### 2.3 使用 rebind\_symbols hook NSLog

通过查看接口我们需要创建一些数据。

```Swift
- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor blackColor];
    // 目标去Hook系统的NSLog函数
    
    struct rebinding nslog;
    nslog.name = "NSLog";
    nslog.replacement = hook_NSLog;
    // 保存NSLog函数的指针
    nslog.replaced = (void *)&sys_NSLog;
    
    struct rebinding bds[] = {nslog};
    
    rebind_symbols(bds, 1);
}

/// 旧函数的指针
static void (*sys_NSLog)(NSString *format, ...);

// 新函数
void hook_NSLog(NSString *format, ...) {
    format = [format stringByAppendingString:@"\n🌈"];
    // 再调用系统的
    sys_NSLog(format);
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"RyukieLog");
}

```

输出结果：

```
2021-05-05 15:51:44.230666+0800 Hook[1650:239418] RyukieLog
🌈
2021-05-05 15:51:44.430579+0800 Hook[1650:239418] RyukieLog
🌈
2021-05-05 15:51:44.679392+0800 Hook[1650:239418] RyukieLog
🌈
```

Hook成功！

### 2.4 试一试hook C函数？

定义一个这样的函数

```C
void logFun(const char * str) {
    NSLog(@"%s", str);
}
```

修改rebinding信息

```C
struct rebinding nslog;
nslog.name = "logFun";
nslog.replacement = new_logfun;
nslog.replaced = (void *)&old_logfun;

static void(*old_logfun)(const char * str);

void new_logfun(const char * str) {
    str = "🌈";
    // 再调用旧的
    old_logfun(str);
}
```

发现并没有成功！应为这个是通过地址直接访问的。

## 三、重绑定符号

一个问题：编译的时候，NSLog的真实地址知道么？不知道。

在MachO加载进内存时，dyld会先加载依赖的系统库进共享缓存。

NSLog在Foundation框架中，加载后dyld会将NSLog的真实地址告诉MachO。

验证一下fishhook的实现：

![hook](/files/-MZvr1FtpUSnxmUnNefZ)

在hook前后分别下断点，打开汇编断点。

第一个断点：

`-> 0x10233d234 <+188>: bl 0x10233e320 ; symbol stub for: NSLog`

下一步：

```
->  0x104cae320 <+0>: nop    
    0x104cae324 <+4>: ldr    x16, #0x5cdc              ; (void *)0x0000000104cae464
    0x104cae328 <+8>: br     x16

```

第二个断点：

`-> 0x10233d254 <+220>: bl 0x10233e320 ; symbol stub for: NSLog`

下一步：

```
->  0x104cae320 <+0>: nop    
    0x104cae324 <+4>: ldr    x16, #0x5cdc              ; (void *)0x0000000104cad44c: hook_NSLog at /Users/RyukieW/RyukieSamaProject/Study/逆向/Code/Hook/Hook/Hook/ViewController.m:75
    0x104cae328 <+8>: br     x16
```

分析：

`bl 0x10233e320 ; symbol stub for: NSLog`

这一段是到`0x10233e320`中去寻找符号绑定的地址

第一次的第二步找到了系统原本函数的地址

第二次走到第二步后发现已经是hookNSLog了

## 四、符号绑定的过程

* 间接符号表
  * MachO文件以外的，所有调用系统的都是外部的。
* 本地符号
  * 上架去符号，去的是本地符号
* 全局符号
  * 可以暴露给外界

### 4.1 桩

一块代码！

上面一段中看到的一段汇编指令

`bl 0x10233e320 ; symbol stub for: NSLog`

`0x10233e320`就是NSLog的桩

bl -> 桩 -> 通过符号表去执行代码

> 懒加载符号表全部都是通过桩去进行绑定的

## 五、总结

* 符号绑定过程
  * 外部函数调用是执行桩里的代码
    * 通过懒加载符号表里的地址去执行
  * 懒加载符号表里面默认保存的是binder的代码
    * bingder函数在非懒加载符号的表里面（程序运行就绑定好了）
