關(guān)注倉庫,及時獲得更新:iOS-Source-Code-Analyze
Follow: Draveness · Github
Objective-C 作為基于 Runtime 的語言魁索,它有非常強大的動態(tài)特性搂鲫,可以在運行期間自省躯砰、進行方法調(diào)劑辉阶、為類增加屬性、修改消息轉(zhuǎn)發(fā)鏈路翔脱,在代碼運行期間通過 Runtime 幾乎可以修改 Objecitve-C 層的一切類奴拦、方法以及屬性。
真正絕對意義上的動態(tài)語言或者靜態(tài)語言是不存在的届吁。
C 語言往往會給我們留下不可修改的這一印象错妖;在之前的幾年時間里,筆者確實也是這么認為的疚沐,然而最近接觸到的 fishhook 使我對 C 語言的不可修改有了更加深刻的理解暂氯。
在文章中涉及到一個比較重要的概念,就是鏡像(image)亮蛔;在 Mach-O 文件系統(tǒng)中痴施,所有的可執(zhí)行文件、dylib 以及 Bundle 都是鏡像尔邓。
fishhook 簡介
到這里晾剖,我們該簡單介紹一下今天分享的 fishhook;fishhook 是一個由 facebook 開源的第三方框架梯嗽,其主要作用就是動態(tài)修改 C 語言函數(shù)實現(xiàn)齿尽。
這個框架的代碼其實非常的簡單,只包含兩個文件:fishhook.c
以及 fishhook.h
灯节;兩個文件所有的代碼加起來也不超過 300 行循头。
不過它的實現(xiàn)原理是非常有意思并且精妙的,我們可以從 fishhook
提供的接口中入手炎疆。
從接口開始
fishhook 提供非常簡單的兩個接口以及一個結(jié)構(gòu)體:
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
數(shù)組卡骂,也就是重新綁定信息,還有就是 rebindings_nel
形入,也就是 rebindings
的個數(shù)全跨。
使用 fishhook 修改 C 函數(shù)
使用 fishhook 修改 C 函數(shù)很容易,我們使用它提供的幾個范例來介紹它的使用方法亿遂。
這里要修改的是底層的 open
函數(shù)的實現(xiàn)浓若,首先在工程中引入 fishhook.h
頭文件渺杉,然后聲明一個與原函數(shù)簽名相同的函數(shù)指針:
#import "fishhook.h"
static int (*origianl_open)(const char *, int, ...);
然后重新實現(xiàn) new_open
函數(shù):
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);
}
}
這里調(diào)用的 original_open
其實相當于執(zhí)行原 open
;最后挪钓,在 main 函數(shù)中使用 rebind_symbols
對符號進行重綁定:
// 初始化一個 rebinding 結(jié)構(gòu)體
struct rebinding open_rebinding = { "open", new_open, (void *)&original_open };
// 將結(jié)構(gòu)體包裝成數(shù)組是越,并傳入數(shù)組的大小,對原符號 open 進行重綁定
rebind_symbols((struct rebinding[1]){open_rebinding}, 1);
// 調(diào)用 open 函數(shù)
__unused int fd = open(argv[0], O_RDONLY);
在對符號進行重綁定之后碌上,所有調(diào)用 open
函數(shù)的地方實際上都會執(zhí)行 new_open
的實現(xiàn)倚评,也就完成了對 open
的修改。
程序運行之后打印了 Calling real open('/Users/apple/Library/Developer/Xcode/DerivedData/Demo-cdnoozusghmqtubdnbzedzdwaagp/Build/Products/Debug/Demo', 0)
說明我們的對 open
函數(shù)的修改達到了預(yù)期的效果馏予。
整個 main.m 文件中的代碼在文章的最后面 main.m
fishhook 的原理以及實現(xiàn)
在介紹 fishhook 具體實現(xiàn)原理之前天梧,有幾個非常重要的知識需要我們了解,那就是 dyld吗蚌、動態(tài)鏈接以及 Mach-O 文件系統(tǒng)腿倚。
dyld 與動態(tài)鏈接
dyld 是 the dynamic link editor 的縮寫(筆者并不知道為什么要這么縮寫)。至于它的作用蚯妇,簡單一點說敷燎,就是負責將各種各樣程序需要的鏡像加載到程序運行的內(nèi)存空間中,這個過程發(fā)生的時間非常早 --- 在 objc 運行時初始化之前箩言。
在 dyld 加載鏡像時硬贯,會執(zhí)行注冊過的回調(diào)函數(shù);當然陨收,我們也可以使用下面的方法注冊自定義的回調(diào)函數(shù)饭豹,同時也會為所有已經(jīng)加載的鏡像執(zhí)行回調(diào):
extern void _dyld_register_func_for_add_image(
void (*func)(const struct mach_header* mh, intptr_t vmaddr_slide)
);
對于每一個已經(jīng)存在的鏡像,當它被動態(tài)鏈接時务漩,都會執(zhí)行回調(diào) void (*func)(const struct mach_header* mh, intptr_t vmaddr_slide)
拄衰,傳入文件的 mach_header
以及一個虛擬內(nèi)存地址 intptr_t
。
以一個最簡單的 Hello World 程序為例:
#include <stdio.h>
int main(int argc, const char * argv[]) {
printf("Hello, World!\n");
return 0;
}
代碼中只引用了一個 stdio
庫中的函數(shù) printf
饵骨;我們?nèi)绻?Build 這段代碼翘悉,生成可執(zhí)行文件之后,使用下面的命令 nm
:
$ nm -nm HelloWorld
nm
命令可以查看可執(zhí)行文件中的符號(對 nm
不熟悉的讀者可以在終端中使用 man nm
查看手冊):
(undefined) external _printf (from libSystem)
(undefined) external dyld_stub_binder (from libSystem)
0000000100000000 (__TEXT,__text) [referenced dynamically] external __mh_execute_header
0000000100000f50 (__TEXT,__text) external _main
在可執(zhí)行文件中的符號列表中居触,_printf
這個符號是未定義(undefined)的妖混,換句話說,編譯器還不知道這個符號對應(yīng)什么東西轮洋。
但是制市,如果在文件中加入一個 C 函數(shù) hello_world
:
#include <stdio.h>
void hello_world() {
printf("Hello, World!\n");
}
int main(int argc, const char * argv[]) {
printf("Hello, World!\n");
return 0;
}
在構(gòu)建之后,同樣使用 nm
查看其中的符號:
(undefined) external _printf (from libSystem)
(undefined) external dyld_stub_binder (from libSystem)
0000000100000000 (__TEXT,__text) [referenced dynamically] external __mh_execute_header
0000000100000f30 (__TEXT,__text) external _hello_world
0000000100000f50 (__TEXT,__text) external _main
我們的符號 _hello_world
并不是未定義的(undefined)弊予,它包含一個內(nèi)存地址以及 __TEXT
段祥楣。也就是說手寫的一些函數(shù),在編譯之后,其地址并不是未定義的荣堰,這一點對于之后分析 fishhook 有所幫助床未。
使用 nm
打印出的另一個符號 dyld_stub_binder
對應(yīng)另一個同名函數(shù)。dyld_stub_binder
會在目標符號(例如 printf
)被調(diào)用時振坚,將其鏈接到指定的動態(tài)鏈接庫 libSystem
,再執(zhí)行 printf
的實現(xiàn)(printf
符號位于 __DATA
端中的 lazy 符號表中):
每一個鏡像中的 __DATA
端都包含兩個與動態(tài)鏈接有關(guān)的表斋扰,其中一個是 __nl_symbol_ptr
渡八,另一個是 __la_symbol_ptr
:
-
__nl_symbol_ptr
中的 non-lazy 符號是在動態(tài)鏈接庫綁定的時候進行加載的 -
__la_symbol_ptr
中的符號會在該符號被第一次調(diào)用時,通過 dyld 中的dyld_stub_binder
過程來進行加載
0000000100001010 dq 0x0000000100000f9c ; XREF=0x1000002f8, imp___stubs__printf
地址 0x0000000100000f9c
就是 printf
函數(shù)打印字符串實現(xiàn)的位置:
在上述代碼調(diào)用 printf
時传货,由于符號是沒有被加載的屎鳍,就會通過 dyld_stub_binder
動態(tài)綁定符號。
Mach-O
由于文章中會涉及一些關(guān)于 Mach-O 文件格式的知識问裕,所以在這里會簡單介紹一下 Mach-O 文件格式的結(jié)構(gòu)逮壁。
每一個 Mach-O 文件都會被分為不同的 Segments,比如 __TEXT
, __DATA
, __LINKEDIT
:
這也就是 Mach-O 中的 segment_command
(32 位與 64 位不同):
struct segment_command_64 { /* for 64-bit architectures */
uint32_t cmd; /* LC_SEGMENT_64 */
uint32_t cmdsize; /* includes sizeof section_64 structs */
char segname[16]; /* segment name */
uint64_t vmaddr; /* memory address of this segment */
uint64_t vmsize; /* memory size of this segment */
uint64_t fileoff; /* file offset of this segment */
uint64_t filesize; /* amount to map from the file */
vm_prot_t maxprot; /* maximum VM protection */
vm_prot_t initprot; /* initial VM protection */
uint32_t nsects; /* number of sections in segment */
uint32_t flags; /* flags */
};
而每一個 segment_command
中又包含了不同的 section
:
struct section_64 { /* for 64-bit architectures */
char sectname[16]; /* name of this section */
char segname[16]; /* segment this section goes in */
uint64_t addr; /* memory address of this section */
uint64_t size; /* size in bytes of this section */
uint32_t offset; /* file offset of this section */
uint32_t align; /* section alignment (power of 2) */
uint32_t reloff; /* file offset of relocation entries */
uint32_t nreloc; /* number of relocation entries */
uint32_t flags; /* flags (section type and attributes)*/
uint32_t reserved1; /* reserved (for offset or index) */
uint32_t reserved2; /* reserved (for count or sizeof) */
uint32_t reserved3; /* reserved */
};
你只需要對這幾個概念有一個簡單的了解粮宛,知道它們有怎樣的包含關(guān)系窥淆,當文章中挑出這個名字時,對它不是一無所知就足夠了巍杈,這里并不會涉及太多相關(guān)的知識忧饭。
fishhook 的原理
到目前為止,我們對 dyld 以及 Mach-O 有了一個初步的了解筷畦,而 fishhook 使用了前面章節(jié)提到的 _dyld_register_func_for_add_image
注冊了一個回調(diào)词裤,在每次加載鏡像到程序中執(zhí)行回調(diào),動態(tài)修改 C 函數(shù)實現(xiàn)鳖宾。
在具體分析其源代碼之前吼砂,先為各位讀者詳細地介紹它的實現(xiàn)原理:
dyld 通過更新 Mach-O 二進制文件 __DATA
段中的一些指針來綁定 lazy 和 non-lazy 的符號;而 fishhook 先確定某一個符號在 __DATA
段中的位置鼎文,然后保存原符號對應(yīng)的函數(shù)指針渔肩,并使用新的函數(shù)指針覆蓋原有符號的函數(shù)指針,實現(xiàn)重綁定漂问。
整個過程可以用這么一張圖來表示:
原理看起來還是很簡單的赖瞒,其中最復(fù)雜的部分就是從二進制文件中尋找某個符號的位置,在 fishhook 的 README 中蚤假,有這樣一張圖:
這張圖初看很復(fù)雜栏饮,不過它演示的是尋找符號的過程,我們根據(jù)這張圖來分析一下這個過程:
- 從
__DATA
段中的 lazy 符號指針表中查找某個符號磷仰,獲得這個符號的偏移量1061
袍嬉,然后在每一個section_64
中查找reserved1
,通過這兩個值找到 Indirect Symbol Table 中符號對應(yīng)的條目 - 在 Indirect Symbol Table 找到符號表指針以及對應(yīng)的索引
16343
之后,就需要訪問符號表 - 然后通過符號表中的偏移量伺通,獲取字符串表中的符號
_close
fishhook 的實現(xiàn)
上面梳理了尋找符號的過程箍土,現(xiàn)在,我們終于要開始分析 fishhook 的源代碼罐监,看它是如何一步一步替換原有函數(shù)實現(xiàn)的吴藻。
對實現(xiàn)的分析會 rebind_symbols
函數(shù)為入口,首先看一下函數(shù)的調(diào)用棧:
int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel);
└── extern void _dyld_register_func_for_add_image(void (*func)(const struct mach_header* mh, intptr_t vmaddr_slide));
static void _rebind_symbols_for_image(const struct mach_header *header, intptr_t slide)
└── static void rebind_symbols_for_image(struct rebindings_entry *rebindings, const struct mach_header *header, intptr_t slide)
└── static void perform_rebinding_with_section(struct rebindings_entry *rebindings, section_t *section, intptr_t slide, nlist_t *symtab, char *strtab, uint32_t *indirect_symtab)
其實函數(shù)調(diào)用棧非常簡單弓柱,因為整個庫中也沒有幾個函數(shù)沟堡,rebind_symbols
作為接口,其主要作用就是注冊一個函數(shù)并在鏡像加載時回調(diào):
int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel) {
int retval = prepend_rebindings(&_rebindings_head, rebindings, rebindings_nel);
if (retval < 0) return retval;
if (!_rebindings_head->next) {
_dyld_register_func_for_add_image(_rebind_symbols_for_image);
} else {
uint32_t c = _dyld_image_count();
for (uint32_t i = 0; i < c; i++) {
_rebind_symbols_for_image(_dyld_get_image_header(i), _dyld_get_image_vmaddr_slide(i));
}
}
return retval;
}
在 rebind_symbols
最開始執(zhí)行時矢空,會先調(diào)用一個 prepend_rebindings
的函數(shù)航罗,將整個 rebindings
數(shù)組添加到 _rebindings_head
這個私有數(shù)據(jù)結(jié)構(gòu)的頭部:
static int prepend_rebindings(struct rebindings_entry **rebindings_head,
struct rebinding rebindings[],
size_t nel) {
struct rebindings_entry *new_entry = malloc(sizeof(struct rebindings_entry));
if (!new_entry) {
return -1;
}
new_entry->rebindings = malloc(sizeof(struct rebinding) * nel);
if (!new_entry->rebindings) {
free(new_entry);
return -1;
}
memcpy(new_entry->rebindings, rebindings, sizeof(struct rebinding) * nel);
new_entry->rebindings_nel = nel;
new_entry->next = *rebindings_head;
*rebindings_head = new_entry;
return 0;
}
也就是說每次調(diào)用的 rebind_symbols
方法傳入的 rebindings
數(shù)組以及數(shù)組的長度都會以 rebindings_entry
的形式添加到 _rebindings_head
這個私有鏈表的首部:
struct rebindings_entry {
struct rebinding *rebindings;
size_t rebindings_nel;
struct rebindings_entry *next;
};
static struct rebindings_entry *_rebindings_head;
這樣可以通過判斷 _rebindings_head->next
的值來判斷是否為第一次調(diào)用,然后使用 _dyld_register_func_for_add_image
將 _rebind_symbols_for_image
注冊為回調(diào)或者為所有存在的鏡像單獨調(diào)用 _rebind_symbols_for_image
:
static void _rebind_symbols_for_image(const struct mach_header *header, intptr_t slide) {
rebind_symbols_for_image(_rebindings_head, header, slide);
}
_rebind_symbols_for_image
只是對另一個名字非常相似的函數(shù) rebind_symbols_for_image
的封裝屁药,從這個函數(shù)開始粥血,就到了重綁定符號的過程;不過由于這個方法的實現(xiàn)比較長酿箭,具體分析會分成三個部分并省略一些不影響理解的代碼:
static void rebind_symbols_for_image(struct rebindings_entry *rebindings,
const struct mach_header *header,
intptr_t slide) {
segment_command_t *cur_seg_cmd;
segment_command_t *linkedit_segment = NULL;
struct symtab_command* symtab_cmd = NULL;
struct dysymtab_command* dysymtab_cmd = NULL;
uintptr_t cur = (uintptr_t)header + sizeof(mach_header_t);
for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) {
cur_seg_cmd = (segment_command_t *)cur;
if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) {
if (strcmp(cur_seg_cmd->segname, SEG_LINKEDIT) == 0) {
linkedit_segment = cur_seg_cmd;
}
} else if (cur_seg_cmd->cmd == LC_SYMTAB) {
symtab_cmd = (struct symtab_command*)cur_seg_cmd;
} else if (cur_seg_cmd->cmd == LC_DYSYMTAB) {
dysymtab_cmd = (struct dysymtab_command*)cur_seg_cmd;
}
}
...
}
這部分的代碼主要功能是從鏡像中查找 linkedit_segment
symtab_command
和 dysymtab_command
复亏;在開始查找之前,要先跳過 mach_header_t
長度的位置七问,然后將當前指針強轉(zhuǎn)成 segment_command_t
蜓耻,通過對比 cmd
的值,來找到需要的 segment_command_t
械巡。
在查找了幾個關(guān)鍵的 segment 之后刹淌,我們可以根據(jù)幾個 segment 獲取對應(yīng)表的內(nèi)存地址:
static void rebind_symbols_for_image(struct rebindings_entry *rebindings, const struct mach_header *header, intptr_t slide) {
...
uintptr_t linkedit_base = (uintptr_t)slide + linkedit_segment->vmaddr - linkedit_segment->fileoff;
nlist_t *symtab = (nlist_t *)(linkedit_base + symtab_cmd->symoff);
char *strtab = (char *)(linkedit_base + symtab_cmd->stroff);
uint32_t *indirect_symtab = (uint32_t *)(linkedit_base + dysymtab_cmd->indirectsymoff);
...
}
在 linkedit_segment
結(jié)構(gòu)體中獲得其虛擬地址以及文件偏移量,然后通過一下公式來計算當前 __LINKEDIT
段的位置:
slide + vmaffr - fileoff
類似地讥耗,在 symtab_command
中獲取符號表偏移量和字符串表偏移量有勾,從 dysymtab_command
中獲取間接符號表(indirect symbol table)偏移量,就能夠獲得符號表古程、字符串表以及間接符號表的引用了蔼卡。
間接符號表中的元素都是
uint32_t *
,指針的值是對應(yīng)條目n_list
在符號表中的位置-
符號表中的元素都是
nlist_t
結(jié)構(gòu)體挣磨,其中包含了當前符號在字符串表中的下標struct nlist_64 { union { uint32_t n_strx; /* index into the string table */ } n_un; uint8_t n_type; /* type flag, see below */ uint8_t n_sect; /* section number or NO_SECT */ uint16_t n_desc; /* see <mach-o/stab.h> */ uint64_t n_value; /* value of this symbol (or stab offset) */ };
字符串表中的元素是
char
字符
該函數(shù)的最后一部分就開啟了遍歷模式雇逞,查找整個鏡像中的 SECTION_TYPE
為 S_LAZY_SYMBOL_POINTERS
或者 S_NON_LAZY_SYMBOL_POINTERS
的 section,然后調(diào)用下一個函數(shù) perform_rebinding_with_section
來對 section 中的符號進行處理:
static void perform_rebinding_with_section(struct rebindings_entry *rebindings, section_t *section, intptr_t slide, nlist_t *symtab, char *strtab, uint32_t *indirect_symtab) {
uint32_t *indirect_symbol_indices = indirect_symtab + section->reserved1;
void **indirect_symbol_bindings = (void **)((uintptr_t)slide + section->addr);
for (uint i = 0; i < section->size / sizeof(void *); i++) {
uint32_t symtab_index = indirect_symbol_indices[i];
uint32_t strtab_offset = symtab[symtab_index].n_un.n_strx;
char *symbol_name = strtab + strtab_offset;
struct rebindings_entry *cur = rebindings;
while (cur) {
for (uint j = 0; j < cur->rebindings_nel; j++) {
if (strcmp(&symbol_name[1], cur->rebindings[j].name) == 0) {
if (cur->rebindings[j].replaced != NULL &&
indirect_symbol_bindings[i] != cur->rebindings[j].replacement) {
*(cur->rebindings[j].replaced) = indirect_symbol_bindings[i];
}
indirect_symbol_bindings[i] = cur->rebindings[j].replacement;
goto symbol_loop;
}
}
cur = cur->next;
}
symbol_loop:;
}
}
該函數(shù)的實現(xiàn)的核心內(nèi)容就是將符號表中的 symbol_name
與 rebinding
中的名字 name
進行比較茁裙,如果出現(xiàn)了匹配塘砸,就會將原函數(shù)的實現(xiàn)傳入 origian_open
函數(shù)指針的地址,并使用新的函數(shù)實現(xiàn) new_open
代替原實現(xiàn):
if (cur->rebindings[j].replaced != NULL &&
indirect_symbol_bindings[i] != cur->rebindings[j].replacement) {
*(cur->rebindings[j].replaced) = indirect_symbol_bindings[i]; // 將原函數(shù)的實現(xiàn)傳入 original_open 函數(shù)指針的地址
}
indirect_symbol_bindings[i] = cur->rebindings[j].replacement; // 使用新的函數(shù)實現(xiàn) new_open 替換原實現(xiàn)
如果你理解了上面的實現(xiàn)代碼晤锥,該函數(shù)的其它代碼就很好理解了:
- 通過
indirect_symtab + section->reserved1
獲取indirect_symbol_indices *
掉蔬,也就是符號表的數(shù)組 - 通過
(void **)((uintptr_t)slide + section->addr)
獲取函數(shù)指針列表indirect_symbol_bindings
- 遍歷符號表數(shù)組
indirect_symbol_indices *
中的所有符號表中廊宪,獲取其中的符號表索引symtab_index
- 通過符號表索引
symtab_index
獲取符號表中某一個n_list
結(jié)構(gòu)體,得到字符串表中的索引symtab[symtab_index].n_un.n_strx
- 最后在字符串表中獲得符號的名字
char *symbol_name
到這里比較前的準備工作就完成了女轿,剩下的代碼會遍歷整個 rebindings_entry
數(shù)組箭启,在其中查找匹配的符號,完成函數(shù)實現(xiàn)的替換:
while (cur) {
for (uint j = 0; j < cur->rebindings_nel; j++) {
if (strcmp(&symbol_name[1], cur->rebindings[j].name) == 0) {
if (cur->rebindings[j].replaced != NULL &&
indirect_symbol_bindings[i] != cur->rebindings[j].replacement) {
*(cur->rebindings[j].replaced) = indirect_symbol_bindings[i];
}
indirect_symbol_bindings[i] = cur->rebindings[j].replacement;
goto symbol_loop;
}
}
cur = cur->next;
}
在之后對某一函數(shù)的調(diào)用(例如 open
)蛉迹,當查找其函數(shù)實現(xiàn)時傅寡,都會查找到 new_open
的函數(shù)指針;在 new_open
調(diào)用 origianl_open
時婿禽,同樣也會執(zhí)行原有的函數(shù)實現(xiàn)赏僧,因為我們通過 *(cur->rebindings[j].replaced) = indirect_symbol_bindings[i]
將原函數(shù)實現(xiàn)綁定到了新的函數(shù)指針上。
實驗
fishhook 在 dyld
加載鏡像時扭倾,插入了一個回調(diào)函數(shù),交換了原有函數(shù)的實現(xiàn)挽绩;但是 fishhook 能否修改非動態(tài)鏈接庫膛壹,比如開發(fā)人員自己手寫的函數(shù)呢?我們可以做一個非常簡單的小實驗唉堪,下面是我們的 main.m
文件:
#import <Foundation/Foundation.h>
#import "fishhook.h"
void hello() {
printf("hello\n");
}
static void (*original_hello)();
void new_hello() {
printf("New_hello\n");
original_hello();
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
struct rebinding open_rebinding = { "hello", new_hello, (void *)&original_hello };
rebind_symbols((struct rebinding[1]){open_rebinding}, 1);
hello();
}
return 0;
}
這里的函數(shù)實現(xiàn)非常的簡單模聋,相信也不需要筆者過多解釋了,我們直接運行這份代碼:
代碼中只打印了 hello
唠亚,說明 fishhook 對這種手寫的函數(shù)是沒有作用的链方,如果在下面這里打一個斷點:
代碼并不會進這里,因為 hello
這個函數(shù)是包含在當前鏡像的灶搜,它只是從其它代碼地址跳轉(zhuǎn)到了當前函數(shù)實現(xiàn)祟蚀,這與我們調(diào)用外部庫時有很大的不同,當調(diào)用外部庫時割卖,我們需要 dyld 解決函數(shù)地址的問題前酿,但是函數(shù)在當前鏡像中卻并不需要 issue #25。
小結(jié)
fishhook 的實現(xiàn)非常的巧妙鹏溯,但是它的使用也有一定的局限性罢维,在接觸到 fishhook 之前,從沒有想到過可以通過一種方式修改 C 函數(shù)的實現(xiàn)丙挽。
Reference
其它
main.m
#import <Foundation/Foundation.h>
#import "fishhook.h"
static int (*original_open)(const char *, int, ...);
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 original_open(path, oflag, mode);
} else {
printf("Calling real open('%s', %d)\n", path, oflag);
return original_open(path, oflag, mode);
}
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
struct rebinding open_rebinding = { "open", new_open, (void *)&original_open };
rebind_symbols((struct rebinding[1]){open_rebinding}, 1);
__unused int fd = open(argv[0], O_RDONLY);
}
return 0;
}
關(guān)注倉庫肺孵,及時獲得更新:iOS-Source-Code-Analyze
Follow: Draveness · Github