Mach-O
什么是Mach-O
Mach-O 為 Mach Object文件格式的縮寫,是用于 iOS 和 macOS 的可執(zhí)行文件,目標(biāo)代碼,動(dòng)態(tài)庫等等多種文件類型的的文件格式。
Mach-O文件格式
蘋果官方給了一張結(jié)構(gòu)圖:

我們編寫一個(gè)HelloWorld程序,將其編譯,然后通過MachOView來打開.out文件:

可以知道Mach-O由三部分組成:
-
Header:指明了CPU架構(gòu)、文件類型、Load Commands 個(gè)數(shù)等一些基本信息。 -
Load Commands:描述了怎樣加載每個(gè) Segment 的信息。在 Mach-O 文件中可以有多個(gè) Segment,每個(gè) Segment 可能包含零個(gè)、一個(gè)或多個(gè) Section。 -
Data:Segment 的具體數(shù)據(jù),包含了代碼和數(shù)據(jù)等。
Header
/*
* The 32-bit mach header appears at the very beginning of the object file for
* 32-bit architectures.
*/
struct mach_header {
uint32_t magic; /* mach magic number identifier */
cpu_type_t cputype; /* cpu specifier */
cpu_subtype_t cpusubtype; /* machine specifier */
uint32_t filetype; /* type of file */
uint32_t ncmds; /* number of load commands */
uint32_t sizeofcmds; /* the size of all the load commands */
uint32_t flags; /* flags */
};
/*
* The 64-bit mach header appears at the very beginning of object files for
* 64-bit architectures.
*/
struct mach_header_64 {
uint32_t magic; /* mach magic number identifier */
cpu_type_t cputype; /* cpu specifier */
cpu_subtype_t cpusubtype; /* machine specifier */
uint32_t filetype; /* type of file */
uint32_t ncmds; /* number of load commands */
uint32_t sizeofcmds; /* the size of all the load commands */
uint32_t flags; /* flags */
uint32_t reserved; /* reserved */
};
-
magic:魔數(shù),0xfeedface是32位,0xcefaedfe是64位 -
cputype:CPU類型 -
cpusubtype:CPU具體類型 -
filetype:文件類型,例如可執(zhí)行文件、庫文件等 -
ncmds:Load Commands的數(shù)量 -
sizeofcmds:Load Commands的總大小 -
flags:標(biāo)志位,用于描述該文件的詳細(xì)加載信息 -
reserved:64位才有的保留字段,暫時(shí)沒用
對(duì)于上面的HelloWorld程序來說,它的Header信息如下:

這里有一點(diǎn)值得注意:上面的定義中,flags是一個(gè)int值,但是怎么能夠存儲(chǔ)這么多類型?其實(shí)巧妙運(yùn)用了宏定義值的特殊性,使得它們本身和它們和的組合具有唯一性,例如這里的flags值為00200085:
#define MH_NOUNDEFS 0x1 /* the object file has no undefined
references */
#define MH_INCRLINK 0x2 /* the object file is the output of an
incremental link against a base file
and can't be link edited again */
#define MH_DYLDLINK 0x4 /* the object file is input for the
dynamic linker and can't be staticly
link edited again */
這里定義了0x1、0x2、0x4三個(gè)值,并且沒有定義0x5,flags最低位是5,那么表示同時(shí)有MH_NOUNDEFS和MH_DYLDLINK兩個(gè)標(biāo)志位。
Load Commands
struct load_command {
uint32_t cmd; /* type of load command */
uint32_t cmdsize; /* total size of command in bytes */
};
-
cmd類型:指定command類型 -
cmdsize:表示command大小,用于計(jì)算到下一個(gè)command的偏移量
| cmd | 作用 |
|---|---|
| LC_SEGMENT/LC_SEGMENT_64 | 將段內(nèi)數(shù)據(jù)加載映射到內(nèi)存中去 |
| LC_SYMTAB | 符號(hào)表信息 |
| LC_DYSYMTAB | 動(dòng)態(tài)符號(hào)表信息 |
| LC_DYLD_INFO_ONLY | 記錄地址重定向信息 |
| LC_LOAD_DYLINKER | 啟動(dòng)dyld |
| LC_UUID | 唯一標(biāo)識(shí)符 |
| LC_SOURCE_VERSION | 源代碼版本 |
| LC_MAIN | 程序入口 |
| LC_LOAD_DYLIB | 加載動(dòng)態(tài)庫 |
| LC_FUNCTION_STARTS | 函數(shù)符號(hào)表 |
| LC_DATA_IN_CODE | Data注入代碼地址 |
| LC_CODE_SIGNATURE | 代碼簽名信息 |
程序在構(gòu)建時(shí)會(huì)指定加載的基地址,但是無法保證基地址的唯一性,也無法保證映像的地址區(qū)間不重疊。
iOS采用了ASLR(Address space layout randomization)技術(shù),使得每個(gè)程序加載時(shí)的基地址隨機(jī)化。
這兩個(gè)原因?qū)е鲁绦蚣虞d到內(nèi)存時(shí),真實(shí)的基地址和構(gòu)建時(shí)指定的基地址是不同的,因此需要進(jìn)行地地址的重定向,LC_DYLD_INFO記錄的就是相關(guān)信息。
segment

首先看看segment的定義:
struct segment_command { /* for 32-bit architectures */
uint32_t cmd; /* LC_SEGMENT */
uint32_t cmdsize; /* includes sizeof section structs */
char segname[16]; /* segment name */
uint32_t vmaddr; /* memory address of this segment */
uint32_t vmsize; /* memory size of this segment */
uint32_t fileoff; /* file offset of this segment */
uint32_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 */
};
-
cmd:上面提到的Load Command類型 -
cmdsize:Load Command大小 -
segname[16]:段名稱

| segname | 含義 |
|---|---|
| __PAGEZERO | 可執(zhí)行文件捕獲空指針的段 |
| __TEXT | 代碼段和只讀數(shù)據(jù) |
| __DATA | 全局變量和靜態(tài)變量 |
| __LINKEDIT | 包含動(dòng)態(tài)鏈接器所需的符號(hào)、字符串表等數(shù)據(jù) |
-
vmaddr:段虛擬地址(未重定向),真實(shí)虛擬地址要加上ASLR的偏移量(隨機(jī)地址防御溢出攻擊) -
vmsize:段的虛擬地址大小 -
fileoff:段在文件內(nèi)的地址偏移 -
filesize:段在文件內(nèi)的大小 -
nsects:段內(nèi)section數(shù)量 -
flags:標(biāo)志位,用于描述詳細(xì)信息
將segment內(nèi)容加載到內(nèi)存的過程,就是從文件偏移fileoff處,將大小為filesize的段,加載到虛擬機(jī)vmaddr處。
程序在構(gòu)建時(shí)的基地址,可以從第一個(gè)__TEXT代碼段中的vmaddr獲取。而真實(shí)的的基地址,就是header指針指向的地址。
section
section的定義:
struct section { /* for 32-bit architectures */
char sectname[16]; /* name of this section */
char segname[16]; /* segment this section goes in */
uint32_t addr; /* memory address of this section */
uint32_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) */
};
-
sectname:section名稱 -
segname:所屬的segment名稱
(大寫的__TEXT代表segment,小寫的__text代表section)

| sectname | 含義 |
|---|---|
| __text | 源代碼對(duì)應(yīng)的機(jī)器指令 |
| __subs | 樁代碼 |
| __stub_helper | 用于動(dòng)態(tài)鏈接,啟動(dòng)dyld |
| __cstring | 硬編碼的C字符串 |
| __la_symbol_ptr | 延遲加載 |
| __data | 初始化的可變的變量 |
-
addr:section在內(nèi)存中的地址 -
size:section大小 -
offset:section在文件中的偏移 -
align:內(nèi)存對(duì)齊邊界 -
reloff:重定位入口在文件中的偏移(目前沒有找到實(shí)際用處) -
nreloc:重定位入口數(shù)量 -
flags:標(biāo)志位,記錄type(互斥)和attributes (多個(gè)共存) -
reserved:保留位,reserved1在下面非常有用
fishhook
什么是fishhook
fishhook 是一個(gè)由Facebook開源的框架,可以動(dòng)態(tài)修改鏈接 Mach-O 符號(hào)表。
demo
我們用這樣一段C代碼來演示:
#include <stdio.h>
#include <string.h>
#include "fishhook.h"
static int (*original_strlen)(const char *_s);
int new_strlen(const char *_s) {
return 666;
}
int main(int argc, const char * argv[]) {
struct rebinding strlen_rebinding = {
"strlen",
new_strlen,
(void *)&original_strlen
};
rebind_symbols((struct rebinding[1]){strlen_rebinding}, 1);
char *str = "Hello Fishhook!";
printf("%d\n", strlen(str));
return 0;
}
首先我們構(gòu)造了一個(gè)和原函數(shù)簽名相同的函數(shù)指針*original_strlen,然后重新實(shí)現(xiàn)了新的new_strlen函數(shù)。在main函數(shù)中,創(chuàng)建了一個(gè)rebinding結(jié)構(gòu)體,分別傳入了需要hook的函數(shù)名,新實(shí)現(xiàn)的函數(shù),與原函數(shù)簽名相同的函數(shù)指針。最后通過rebind_symbols進(jìn)行符號(hào)的重新綁定,運(yùn)行時(shí)就會(huì)輸出666。
源碼分析
結(jié)構(gòu)體定義
首先來看一看rebinding的定義:
struct rebinding {
const char *name; // 需要hook的函數(shù)名
void *replacement; // 新函數(shù)的實(shí)現(xiàn)
void **replaced; // 指向“原函數(shù)”的函數(shù)指針
};
這里通過函數(shù)名和一個(gè)函數(shù)簽名,可以確定需要hook的是哪個(gè)函數(shù),然后用新函數(shù)代替它,儲(chǔ)存這些信息的數(shù)據(jù)結(jié)構(gòu)就是一個(gè)rebinding。
struct rebindings_entry {
struct rebinding *rebindings; // 數(shù)組實(shí)例
size_t rebindings_nel; // 元素?cái)?shù)量
struct rebindings_entry *next; // 鏈表索引
};
// 全局靜態(tài)變量,記錄表頭
static struct rebindings_entry *_rebindings_head;
一個(gè)rebindings_entry可以理解為一次hook時(shí)的信息入口,它存儲(chǔ)了一個(gè)rebinding數(shù)組、數(shù)組元素的數(shù)量和下一個(gè)節(jié)點(diǎn)。所以這里維護(hù)的是一個(gè)rebindings_entry鏈表,有多少次hook,鏈表就有多少個(gè)節(jié)點(diǎn)。
rebind_symbols
直接調(diào)用的函數(shù)實(shí)現(xiàn):
int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel) {
// prepend_rebindings方法完成了rebindings_entry鏈表的初始化
int retval = prepend_rebindings(&_rebindings_head, rebindings, rebindings_nel);
// 返回-1則表示失敗
if (retval < 0) {
return retval;
}
// If this was the first call, register callback for image additions (which is also invoked for
// existing images, otherwise, just run on existing images
if (!_rebindings_head->next) {
// 若第一次進(jìn)行方法替換,則將此方法注冊(cè)到dyld中去,之后的每次替換都會(huì)調(diào)用該方法
_dyld_register_func_for_add_image(_rebind_symbols_for_image);
} else {
// 如果不是第一次替換符號(hào),則遍歷已經(jīng)加載的動(dòng)態(tài)庫
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是使用fishhook的入口函數(shù),它有兩個(gè)作用,一是調(diào)用prepend_rebindings進(jìn)行數(shù)據(jù)結(jié)構(gòu)的初始化,二是注冊(cè)了_rebind_symbols_for_image函數(shù)并在新映像加載時(shí)回調(diào)。如果調(diào)用_dyld_register_func_for_add_image時(shí),系統(tǒng)已經(jīng)加載了某些映像,則會(huì)分別調(diào)用它們注冊(cè)的回調(diào)函數(shù)。也就是說,在加載、卸載映像,以及為映像注冊(cè)回調(diào)函數(shù)時(shí),回調(diào)函數(shù)都會(huì)被調(diào)用,所以這個(gè)函數(shù)通常被用來監(jiān)控映像和統(tǒng)計(jì)系統(tǒng)數(shù)據(jù)。
prepend_rebindings 初始化
/*
該方法用于維護(hù)rebindings_entry
struct rebindings_entry **rebindings_head -> static *_rebindings_head
struct rebinding rebindings[] -> 傳入的方法符號(hào)數(shù)組
size_t nel -> 數(shù)組的元素?cái)?shù)量
*/
static int prepend_rebindings(struct rebindings_entry **rebindings_head,
struct rebinding rebindings[],
size_t nel) {
// 聲明rebindings_entry指針,分配空間
struct rebindings_entry *new_entry = (struct rebindings_entry *) malloc(sizeof(struct rebindings_entry));
// 分配空間失敗
if (!new_entry) {
return -1;
}
// 為鏈表元素的rebindings分配空間
new_entry->rebindings = (struct rebinding *) malloc(sizeof(struct rebinding) * nel);
// 分配空間失敗時(shí),釋放new_entry
if (!new_entry->rebindings) {
free(new_entry);
return -1;
}
// 將傳入的rebindings數(shù)組,copy到new_entry->rebindings中
memcpy(new_entry->rebindings, rebindings, sizeof(struct rebinding) * nel);
// 為new_entry->rebindings_nel賦值
new_entry->rebindings_nel = nel;
// 為new_entry->next賦值,以維護(hù)反向鏈表結(jié)構(gòu)
new_entry->next = *rebindings_head;
// 將static的*rebindings_head指針指向表頭
*rebindings_head = new_entry;
return 0;
}
prepend_rebindings初始化了一個(gè)新的rebindings_entry節(jié)點(diǎn)并插入鏈表頭部。如果在一個(gè)程序中多次調(diào)用rebind_symbols來hook函數(shù),就有多個(gè)rebinding數(shù)組需要維護(hù),rebindings_entry維護(hù)的是一個(gè)反向鏈表,每個(gè)節(jié)點(diǎn)都維護(hù)一個(gè)rebinding數(shù)組,通過鏈表可以判斷是否是第一次hook。

_rebind_symbols_for_image 準(zhǔn)備基址
// 入口方法,目的是滿足回調(diào)函數(shù)的簽名格式;intprt_t是符合平臺(tái)標(biāo)準(zhǔn)字長(zhǎng)的整型指針
static void _rebind_symbols_for_image(const struct mach_header *header,
intptr_t slide) {
// 真正調(diào)用的函數(shù)
rebind_symbols_for_image(_rebindings_head, header, slide);
}
調(diào)用_dyld_register_func_for_add_image進(jìn)行注冊(cè)時(shí),需要滿足特定的回調(diào)函數(shù)簽名格式。
// 準(zhǔn)備基址
static void rebind_symbols_for_image(struct rebindings_entry *rebindings,
const struct mach_header *header,
intptr_t slide) {
Dl_info info;
if (dladdr(header, &info) == 0) {
return;
}
// 聲明保留查找量:
segment_command_t *cur_seg_cmd;
segment_command_t *linkedit_segment = NULL;
struct symtab_command* symtab_cmd = NULL;
struct dysymtab_command* dysymtab_cmd = NULL;
// header = 0x100000000 -> 二進(jìn)制文件基址默認(rèn)偏移
// sizeof(mach_header_t) = 0x20 -> Mach-O Header 部分
// 初始化游標(biāo),跳過Mach-O Header
uintptr_t cur = (uintptr_t)header + sizeof(mach_header_t);
// 第一次遍歷Load Command,目的是找到上面的查找量,cur每次偏移Load Command的大小
// 可以計(jì)算出 Base Address、Symbol Table、Dynamic Symbol 和 String Table
for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) {
// 獲得當(dāng)前的Load Command
cur_seg_cmd = (segment_command_t *)cur;
if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) {
// Load Command的類型是LC_SEGMENT
if (strcmp(cur_seg_cmd->segname, SEG_LINKEDIT) == 0) {
// 找到SEG_LINKEDIT,即Load Command的name為"__LINKEDIT"
linkedit_segment = cur_seg_cmd;
}
} else if (cur_seg_cmd->cmd == LC_SYMTAB) {
// Load Command的類型是LC_SYMTAB
symtab_cmd = (struct symtab_command*)cur_seg_cmd;
} else if (cur_seg_cmd->cmd == LC_DYSYMTAB) {
// Load Command的類型是LC_DYSYMTAB
dysymtab_cmd = (struct dysymtab_command*)cur_seg_cmd;
}
}
// 判空容錯(cuò)
if (!symtab_cmd || !dysymtab_cmd || !linkedit_segment ||
!dysymtab_cmd->nindirectsyms) {
return;
}
// Find base symbol/string table addresses
// base = segment真實(shí)地址(已計(jì)算ASLR偏移) - segment偏移地址
// segment真實(shí)地址(已計(jì)算ASLR偏移) = segment虛擬地址(未計(jì)算ASLR偏移) + ASLR偏移量
// base = ALSR偏移量 + segment虛擬地址 - segment偏移地址
uintptr_t linkedit_base = (uintptr_t)slide + linkedit_segment->vmaddr - linkedit_segment->fileoff;
// symtab首地址 = base + symtab_cmd的symoff偏移地址
// 注意:LC_SYMTAB和LC_DYSYMTAB的中所記錄的Offset都是基于__LINKEDIT段的
nlist_t *symtab = (nlist_t *)(linkedit_base + symtab_cmd->symoff);
// strtab首地址 = base + symtab_cmd的stroff偏移地址
char *strtab = (char *)(linkedit_base + symtab_cmd->stroff);
// Get indirect symbol table (array of uint32_t indices into symbol table)
// indirect_symtab首地址 = base + dysymtab_cmd的indirectsymoff偏移地址
uint32_t *indirect_symtab = (uint32_t *)(linkedit_base + dysymtab_cmd->indirectsymoff);
// 游標(biāo)歸零復(fù)用
cur = (uintptr_t)header + sizeof(mach_header_t);
// 第二次遍歷,目的是找到LC_SEGMENT(__DATA)中 __nl_symbol_ptr和__la_symbol_ptr這兩個(gè)section
// 可以確定lazy binding指針表和non lazy binding指針表在Dynamic Symbol中對(duì)應(yīng)的位置
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) {
// Load Command的類型是LC_SEGMENT
if (strcmp(cur_seg_cmd->segname, SEG_DATA) != 0 &&
strcmp(cur_seg_cmd->segname, SEG_DATA_CONST) != 0) {
// 過濾name不是SEG_DATA或者SEG_DATA_CONST的segment
continue;
}
// 遍歷該segment下的section
for (uint j = 0; j < cur_seg_cmd->nsects; j++) {
// 獲取section
section_t *sect =
(section_t *)(cur + sizeof(segment_command_t)) + j;
// 和SECTION_TYPE取與,只保留后兩位,可判斷TYPE
if ((sect->flags & SECTION_TYPE) == S_LAZY_SYMBOL_POINTERS) {
// 如果section類型為S_LAZY_SYMBOL_POINTERS,則進(jìn)行rebingding重寫操作
perform_rebinding_with_section(rebindings, sect, slide, symtab, strtab, indirect_symtab);
}
if ((sect->flags & SECTION_TYPE) == S_NON_LAZY_SYMBOL_POINTERS) {
// 如果section類型為S_NON_LAZY_SYMBOL_POINTERS
perform_rebinding_with_section(rebindings, sect, slide, symtab, strtab, indirect_symtab);
}
}
}
}
}
這里對(duì)Load Commands進(jìn)行了兩次遍歷,第一次遍歷根據(jù)cmd找到了三個(gè)需要的segment:LC_SEGMENT__LINKEDIT、LC_SYMTAB、LC_DYSYMTAB,為第二次遍歷做好了地址計(jì)算的準(zhǔn)備;第二次遍歷找到了SECTION_TYPE為S_LAZY_SYMBOL_POINTERS和S_NON_LAZY_SYMBOL_POINTERS的section,并調(diào)用perform_rebinding_with_section對(duì)section中的符號(hào)進(jìn)行處理。

perform_rebinding_with_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) {
// 在Indirect Symbol Table中獲取符號(hào)表數(shù)組,利用了reserved1
uint32_t *indirect_symbol_indices = indirect_symtab + section->reserved1;
// 獲取函數(shù)指針列表 __DATA.__nl_symbol_ptr(或__la_symbol_ptr) section
void **indirect_symbol_bindings = (void **)((uintptr_t)slide + section->addr);
// 遍歷section
for (uint i = 0; i < section->size / sizeof(void *); i++) {
// 通過偏移“索引”獲取Indirect Address的Value
uint32_t symtab_index = indirect_symbol_indices[i];
// 過濾INDIRECT_SYMBOL_ABS和INDIRECT_SYMBOL_LOCAL
if (symtab_index == INDIRECT_SYMBOL_ABS || symtab_index == INDIRECT_SYMBOL_LOCAL ||
symtab_index == (INDIRECT_SYMBOL_LOCAL | INDIRECT_SYMBOL_ABS)) {
continue;
}
// 獲取符號(hào)名在符號(hào)表的偏移地址
uint32_t strtab_offset = symtab[symtab_index].n_un.n_strx;
// 獲取符號(hào)名
char *symbol_name = strtab + strtab_offset;
// 符號(hào)名長(zhǎng)度小于1時(shí)過濾
bool symbol_name_longer_than_1 = symbol_name[0] && symbol_name[1];
// 取出rebindings_entry鏈表頭部
struct rebindings_entry *cur = rebindings;
while (cur) {
// 遍歷rebindings_entry中的rebindings鏈表,匹配符號(hào)名和方法名
for (uint j = 0; j < cur->rebindings_nel; j++) {
if (symbol_name_longer_than_1 &&
strcmp(&symbol_name[1], cur->rebindings[j].name) == 0) {
if (cur->rebindings[j].replaced != NULL &&
indirect_symbol_bindings[i] != cur->rebindings[j].replacement) {
// 記錄跳轉(zhuǎn)地址
*(cur->rebindings[j].replaced) = indirect_symbol_bindings[i];
}
// 重寫跳轉(zhuǎn)地址
indirect_symbol_bindings[i] = cur->rebindings[j].replacement;
goto symbol_loop;
}
}
cur = cur->next;
}
symbol_loop:;
}
}
這個(gè)查找匹配的過程描述起來比較繞口,首先通過符號(hào)在__la_symbol_prt的index,加上Load Command中__la_symbol_prt的保留信息reserved1,得到了Indirect Symbols中位于index + reversed1 的數(shù)據(jù)index2,然后在Symbol Table中index2的位置拿到偏移地址offset,最后拿到String Table中offset處的數(shù)據(jù),這個(gè)數(shù)據(jù)就是函數(shù)名。如果函數(shù)名匹配,則更改__la_symbol_ptr表中的函數(shù)地址,完成hook。

總結(jié)
程序啟動(dòng)時(shí),會(huì)鏈接很多動(dòng)態(tài)庫,函數(shù)的調(diào)用就是通過指令跳轉(zhuǎn)到函數(shù)對(duì)應(yīng)的內(nèi)存地址。因?yàn)閯?dòng)態(tài)庫是運(yùn)行后開始鏈接,所以程序并不知道函數(shù)在哪里,所以這些函數(shù)放在__DATA,__la_symbol_prt表中。
例如我們現(xiàn)在要調(diào)用printf函數(shù),表中相應(yīng)內(nèi)容并不會(huì)直接指向printf,而是指向了dyld_stub_binder,它的作用就是計(jì)算真正的printf地址,并且將表中的指針指向修改,這樣下次就可以直接調(diào)用printf了,這就是懶加載。
而fishhook做的工作,就是在dyld綁定了地址之后再次做一個(gè)重綁定,200行左右的代碼,只有一行是在修改函數(shù)指針,最復(fù)雜的邏輯主要是在計(jì)算地址和匹配字符串。hook的局限性,就是只能修改__la_symbol_ptr表中的函數(shù)指向,也就是不能hook靜態(tài)庫中的函數(shù)和自定義的函數(shù)。安全方面,我們可以通過替換函數(shù)的地址是否在映像內(nèi),來判斷是否是惡意程序注入的hook。
