关注仓库,及时获得更新:iOS-Source-Code-Analyze
Follow: Draveness · Github
Objective-C 作为基于 Runtime 的语言,它有非常强大的动态特性,可以在运行期间自省、进行方法调剂、为类增加属性、修改消息转发链路,在代码运行期间通过 Runtime 几乎可以修改 Objecitve-C 层的一切类、方法以及属性。
真正绝对意义上的动态语言或者静态语言是不存在的。
C 语言往往会给我们留下不可修改的这一印象;在之前的几年时间里,笔者确实也是这么认为的,然而最近接触到的 fishhook 使我对 C 语言的不可修改有了更加深刻的理解。
在文章中涉及到一个比较重要的概念,就是镜像(image);在 Mach-O 文件系统中,所有的可执行文件、dylib 以及 Bundle 都是镜像。
fishhook 简介
到这里,我们该简单介绍一下今天分享的 fishhook;fishhook 是一个由 facebook 开源的第三方框架,其主要作用就是动态修改 C 语言函数实现。
这个框架的代码其实非常的简单,只包含两个文件:fishhook.c
以及 fishhook.h
;两个文件所有的代码加起来也不超过 300 行。
不过它的实现原理是非常有意思并且精妙的,我们可以从 fishhook
提供的接口中入手。
从接口开始
fishhook 提供非常简单的两个接口以及一个结构体:
1 2 3 4 5 6 7 8 9 10 11 12 |
struct rebinding { const char *name; void *replacement; void **replaced; }; int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel); int rebind_symbols_image(void *header, intptr_t slide, struct rebinding rebindings[], size_t rebindings_nel); |
其中 rebind_symbols
接收一个 rebindings
数组,也就是重新绑定信息,还有就是 rebindings_nel
,也就是 rebindings
的个数。
使用 fishhook 修改 C 函数
使用 fishhook 修改 C 函数很容易,我们使用它提供的几个范例来介绍它的使用方法。
这里要修改的是底层的 open
函数的实现,首先在工程中引入 fishhook.h
头文件,然后声明一个与原函数签名相同的函数指针:
1 2 3 |
#import "fishhook.h" static int (*origianl_open)(const char *, int, ...); |
然后重新实现 new_open
函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
int new_open(const char *path, int oflag, ...) { va_list ap = {0}; mode_t mode = 0; if ((oflag & O_CREAT) != 0) { // mode only applies to O_CREAT va_start(ap, oflag); mode = va_arg(ap, int); va_end(ap); printf("Calling real open('%s', %d, %d)\n", path, oflag, mode); return orig_open(path, oflag, mode); } else { printf("Calling real open('%s', %d)\n", path, oflag); return orig_open(path, oflag, mode); } } |
这里调用的 original_open
其实相当于执行原 open
;最后,在 main 函数中使用 rebind_symbols
对符号进行重绑定:
1 2 3 4 5 6 7 8 |
// 初始化一个 rebinding 结构体 struct rebinding open_rebinding = { "open", new_open, (void *)&original_open }; // 将结构体包装成数组,并传入数组的大小,对原符号 open 进行重绑定 rebind_symbols((struct rebinding[1]){open_rebinding}, 1); // 调用 open 函数 __unused int fd = open(argv[0], O_RDONLY); |
在对符号进行重绑定之后,所有调用 open
函数的地方实际上都会执行 new_open
的实现,也就完成了对 open
的修改。
程序运行之后打印了 Calling real open('/Users/apple/Library/Developer/Xcode/DerivedData/Demo-cdnoozusghmqtubdnbzedzdwaagp/Build/Products/Debug/Demo', 0)
说明我们的对 open
函数的修改达到了预期的效果。
整个 main.m 文件中的代码在文章的最后面 main.m
fishhook 的原理以及实现
在介绍 fishhook 具体实现原理之前,有几个非常重要的知识需要我们了解,那就是 dyld、动态链接以及 Mach-O 文件系统。
dyld 与动态链接
dyld 是 the dynamic link editor 的缩写(笔者并不知道为什么要这么缩写)。至于它的作用,简单一点说,就是负责将各种各样程序需要的镜像加载到程序运行的内存空间中,这个过程发生的时间非常早 — 在 objc 运行时初始化之前。
在 dyld 加载镜像时,会执行注册过的回调函数;当然,我们也可以使用下面的方法注册自定义的回调函数,同时也会为所有已经加载的镜像执行回调:
1 2 3 |
extern void ="crayon-5812ce94244a4007712249-3">3 |
extern void ʠ属性、修改消息转发链路,在代码运行期间通过 Runtime 几乎可以修改 Objecitve-C 层的一切类、方法以及属性。
C 语言往往会给我们留下不可修改的这一印象;在之前的几年时间里,笔者确实也是这么认为的,然而最近接触到的 fishhook 使我对 C 语言的不可修改有了更加深刻的理解。
fishhook 简介到这里,我们该简单介绍一下今天分享的 fishhook;fishhook 是一个由 facebook 开源的第三方框架,其主要作用就是动态修改 C 语言函数实现。 这个框架的代码其实非常的简单,只包含两个文件: 不过它的实现原理是非常有意思并且精妙的,我们可以从 从接口开始fishhook 提供非常简单的两个接口以及一个结构体:
其中 使用 fishhook 修改 C 函数使用 fishhook 修改 C 函数很容易,我们使用它提供的几个范例来介绍它的使用方法。 这里要修改的是底层的
然后重新实现
这里调用的
在对符号进行重绑定之后,所有调用 程序运行之后打印了
fishhook 的原理以及实现在介绍 fishhook 具体实现原理之前,有几个非常重要的知识需要我们了解,那就是 dyld、动态链接以及 Mach-O 文件系统。 dyld 与动态链接dyld 是 the dynamic link editor 的缩写 在 dyld 加载镜像时,会执行注册过的回调函数;当然,我们也可以使用下面的方法注册自定义的回调函数,同时也会为所有已经加载的镜像执行回调:
|