iOS hook 原理(fishhook)

2,351 阅读6分钟

1.HOOK概述

1.1 hook

HOOK,中文译为“挂钩”或者“钩子”。在iOS逆向中是指改变程序运行流程的一种技术。通过HOOK可以让别人的程序执行自己所写的代码。

1.2 iOS中的hook

  • Method Swizzle: 利用OCRuntime特性,动态改变SEL(方法编号)和IMP(方法实现)的对应关系,达到IC方法调用流程改变的目的。主要用于OC方法。
  • fishhook: 它是Facebook提供的一个动态修改链接mach-O文件的工具。利用MachO文件加载原理,通过修改懒加载和非懒加载两个表的指针达到C函数HOOK的目的。
  • Cydia Substrate: Cydia Substrate 原名Mobile Substrate ,它的主要作用是针对OC方法,C函数以及函数地址进行HOOK操作。当然它并不是仅仅针对iOS而设计的,Android一样可以用。官方地址:www.cydiasubstrate.com/

1.3 Method Swizzle

在OC中,SEL和IMP之间的关系,就好像一本书的目录。SEL是方法编号,就像标题一样。IMP是方法实现的真实地址,就像页码一样。他们是一一对应的关系。

Runtime提供了交换两个SEL和IMP对应关系的函数。通过这个函数交换两个SEL和IMP对应关系的技术,我们称之为Method Swizzle(方法欺骗)

1.4 Cydia Substrate(MobileHooker)

它定义一系列的宏和函数,底层调用objc的runtime和fishhook来替换系统或者目标应用的函数。

  • MSHookMessageEx: 主要作用于Objective-C方法
void MSHookMessageEx(Class class, SEL selector, IMP replacement, IMP result) 
  • MSHookFunction: 主要作用于C和C++函数,Logos语法的%hook 就是对此函数做了一层封装
void MSHookFunction(voidfunction,void* replacement,void** p_original)

2. fishHook

2.1 fishHook

它是Facebook提供的一个动态修改链接mach-O文件的工具。利用MachO文件加载原理,通过修改懒加载和非懒加载两个表的指针达到C函数HOOK的目的

2.2 获取地址

https://github.com/facebook/fishhook

2.3 关键函数

  • 结构体
/*
 * A structure representing a particular intended rebinding from a symbol
 * name to its replacement
 */
struct rebinding {
  const char *name; // 函数名称
  void *replacement;// 交换函数的地址
  void **replaced;  // 原函数的地址
};
  • 常用函数
/*
 * 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);
/*
参数一:存放着 rebinding 结构体数组,可以同时交换多个函数
参数二:rebindings 数组的长度 
*/

2.4 示例(替换NSLog)


- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.

    struct rebinding nsLog;
    
    nsLog.name = "NSLog";
    nsLog.replacement = myLog;
    nsLog.replaced = (void *)&sys_log;
    
    /// rebinding数组
    struct rebinding rebs[1] = {nsLog};
    /// 重新绑定
    rebind_symbols(rebs, 1);
}

///函数指针,用来保存原始的函数地址
static void (*sys_log)(NSString * format,...);

/// 新的函数,(注:在C语言张红函数的名称就是函数的指针!)
void myLog(NSString * format,...) {
    format = [format stringByAppendingFormat:@"Hook到了!"];
    // 调用系统的函数
    sys_log(format);
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"打印一下");
}
  • 打印结果: 打印一下Hook到了!

3.fishhook原理

3.1 Hook自定义函数

// 测试代码
struct rebinding func;
func.name = "func";
func.replacement = newFunc;
func.replaced = (void *)&funcP;
    
struct rebinding funcs[1] = {func};

rebind_symbols(funcs, 1);

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    func("打印一下");
}

//函数的实现地址在MachO本地文件。
void func(const char * str){
    NSLog(@"%s",str);
}

//原始的函数指针
static void(*funcP)(const char *);
//新函数
void newFunc(const char * str){
    NSLog(@"勾住了!");//指向的符号!!
    funcP(str);
}
  • 打印结果: 打印一下
  • 为什么没有勾住呢,说明fishhook不支持hook自定义的C函数。系统的C函数存在着动态的部分。 NSLog 存在于:动态库共享缓存(dyld shared cache)。func的函数实现地址在MachO本地,在编译的时候就确定了,没有动态的特性。

3.2 PIC

fishhook可以Hook C函数,由于函数是静态的,也就是说在编译的时候,编译器就知道了它的实现地址,这也是为什么C函数只写函数声明在调用的时候会报错。那么为什么fishhook能hook C 函数呢?首先要了解一下PIC技术。

PIC技术: iOS的执行文件是MachO,在编译完成后会生成一个符号表,在MachO的DATA段。应用加载的时候dyld会将符号与函数的真实实现地址进行绑定。当调用系统函数的时候会先找到符号,符号的值就是函数的实现地址(也就是找到了函数的真实实现地址)。然后就可以根据函数的真实地址进行调用了。这就是Apple的PIC技术。所以自定义函数就不能进行hook,自定义函数不需要PIC技术,所以也就不需要符号,找不到符号就不能进行重新绑定,所以就不能进行hook。

  • fishhook原理: 所以fishhook的原理就是重新给绑定符号的函数的实现地址,将符号的实现地址绑定到自己实现的地址(replacement),然后在保存函数的真实实现地址(replaced)。当调用函数时就可以调用我们自己实现的函数了。从而达到了hook效果。(即:通过符号查找字符串)详细解读请看:fishhook

4.验证符号绑定

4.1验证dyld和fishhook的符号绑定

测试代码:如图的两处设置断点。

编译好后使用MachOView查看MachO文件,找到NSLog的符号和偏移地址
由图中可以看出NSLog的偏移地址是:0xC000 断点后查看当前应用的进程起始地址(ASLR):0x0000000100db0000

上述两个地址相加得:0x100DBC000

依次过掉断点,读内存,查看汇编。

结论: 在第一个断点前,查看内存是乱数据,当执行完第一个断点后就看到了系统的Foundation 框架的 NSLog 所以说明 NSLog 是懒加载形式的绑定,所以存在于MachO文件里面的Lazy Symbol Pointer 里。在使用的时候dyld继续符号的绑定。在执行完fishhook的重新绑定后,读这个偏移量的内存已经替换成了我们自己的myLog。从而得知fishhook对我们的符号进行了重新的绑定。

5. fishhook查找符号

  • 通过 MachOView 查看 MachO 文件的 Lazy Symbol Pointers 找到NSLog

  • 上面NSLog对应 Dynamic Symbol Table 里面NSLog的位置(这里都是第一个)

  • 根据 Dynamic Symbol Table NSLog Data段的内容(这里是000000B7)换算成10进制就是183 去 Symbol Table 里面找到编号是#183的符号,也就找到了偏移地址(箭头所示):000000E4
  • 最后去String Table 里面根据首地址:000124F4 加偏移地址:000000E4 得到:0x115B8

自此通过读取MachO 通过传入的Name找到NSLog。

特别提醒!

原创文章,转载注明出处!