fishhook棘利,facebook開(kāi)源的一個(gè)可以動(dòng)態(tài)綁定Mach-O符號(hào)表的庫(kù)。在程序啟動(dòng)時(shí)與運(yùn)行時(shí)會(huì)通過(guò)dyld來(lái)綁定符號(hào)表(這里有非懶加載與懶加載之分)唉地,而fishhook可以修改符號(hào)表的綁定。
驗(yàn)證
- 非懶加載綁定
- 懶加載綁定
非懶加載綁定
先來(lái)說(shuō)說(shuō)非懶加載綁定,我們最熟悉的objc_msgSend
就是非懶加載綁定半夷。新建一個(gè)空的命令行項(xiàng)目:
int main(int argc, const char * argv[]) {
NSLog(@"Hello, World!");
return 0;
}
在NSLog(@"Hello, World!")
處打上斷點(diǎn)婆廊,并在Debug Workflow
中選中Always Show Disassembly
,運(yùn)行程序后執(zhí)行如下操作:
可以看到slide的值為0巫橄,似乎命令行項(xiàng)目與iOS不同淘邻,并沒(méi)有使用ASLR
技術(shù)。
通過(guò)MachOView可以看到湘换,objc_msgSend
在MachO中的偏移量為0x2008
宾舅,而由于slide為0,所以這里的虛擬地址0x100002008
就是程序運(yùn)行后的真實(shí)地址彩倚,在控制臺(tái)中繼續(xù)執(zhí)行如下操作:
- 通過(guò)
image lookup -a
可以看到0x100002008
地址對(duì)應(yīng)的確實(shí)是objc_msgSend
筹我,并且得到函數(shù)指針0x00007fff6b167040
,它存儲(chǔ)在Test.__DATA_CONST.__got
中 - 通過(guò)
image lookup -n
得到objc_msgSend
所在動(dòng)態(tài)庫(kù)libobjc.A.dylib
及偏移量0x0000000000006040
- 通過(guò)
image list -o -f
得到libobjc.A.dylib
動(dòng)態(tài)庫(kù)首地址0x00007fff6b161000
- 將
libobjc.A.dylib
動(dòng)態(tài)庫(kù)首地址加上objc_msgSend
偏移量得到objc_msgSend
存儲(chǔ)地址0x00007fff6b167040
這與用image lookup -a
命令看到的地址完全吻合帆离。
懶加載綁定
NSLog
的偏移量為0x3000
崎溃,同理,由于slide為0盯质,這里的虛擬地址0x100003000
就是真實(shí)地址袁串。
可以看到NSLog
符號(hào)存儲(chǔ)在Test.__DATA.__la_symbol_ptr
中,但是這里得到的地址0x0000000100001eac
與真實(shí)的NSLog
存儲(chǔ)地址0x00007fff37702332
并不相同呼巷,顯然在0x0000000100001eac
處必定進(jìn)行了綁定操作囱修。
在0x0000000100001eac
處打上斷點(diǎn),繼續(xù)執(zhí)行程序王悍,得到如下所示匯編代碼:
這里會(huì)跳轉(zhuǎn)到0x100001e9c
處破镰,繼續(xù)加斷點(diǎn)跟進(jìn):
這里調(diào)用了dyld_stub_binder
,NSLog
的符號(hào)綁定就是通過(guò)這個(gè)函數(shù)完成的压储,繼續(xù)跟進(jìn)并在dyld_stub_binder
對(duì)應(yīng)的匯編代碼末尾處打上斷點(diǎn)鲜漩,繼續(xù)運(yùn)行程序后做如下操作:
- 此時(shí)
r11
存儲(chǔ)的值為0x00007fff37702332
- 通過(guò)
image lookup -a
得到這個(gè)值就是NSLog
的存儲(chǔ)地址 - 再次查看
0x100003000
處信息,此時(shí)已經(jīng)變成NSLog
(之前為(void *)0x0000000100001eac
)
以上就是符號(hào)的非懶加載綁定與懶加載綁定集惋,順便說(shuō)一句孕似,懶加載綁定后已經(jīng)存在符號(hào)到函數(shù)的映射信息,再次調(diào)用相同函數(shù)時(shí)不存在這個(gè)綁定過(guò)程刮刑,而是直接調(diào)用喉祭。
fishhook
- fishhook的簡(jiǎn)單使用
- fishhook源碼解析
fishhook的簡(jiǎn)單使用
接著上文的NSLog
繼續(xù)說(shuō):
void (*sys_log)(NSString *format, ...);
void my_log(NSString *format, ...) {
sys_log([format stringByAppendingFormat:@"---hello fishhook---"]);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"before");
struct rebinding context;
context.name = "NSLog";
context.replacement = &my_log;
context.replaced = (void *)&sys_log;
struct rebinding contexts[1] = {context};
rebind_symbols(contexts, 1);
NSLog(@"after");
}
return 0;
}
運(yùn)行程序后輸出如下:
顯然已經(jīng)成功的hook了NSLog
函數(shù),下面來(lái)看fishhook是怎么做到的雷绢。
在rebind_symbols(contexts, 1)
與NSLog(@"after")
處打上斷點(diǎn)泛烙,運(yùn)行程序并作如下操作:
由于在此之前已經(jīng)調(diào)用過(guò)NSLog
,所以懶加載符號(hào)綁定已經(jīng)完成翘紊,此時(shí)0x100003000
對(duì)應(yīng)的就是NSLog
的函數(shù)指針蔽氨。繼續(xù)運(yùn)行程序執(zhí)行rebind_symbols
,在此查看0x100003000
,此時(shí)此時(shí)綁定的地址變成了自定義的my_log
函數(shù)鹉究,所以當(dāng)再次調(diào)用NSLog
時(shí)中捆,通過(guò)符號(hào)表找到的是my_log
,從而實(shí)現(xiàn)對(duì)NSLog
的hook
fishhook源碼解析
先了解一下數(shù)據(jù)結(jié)構(gòu):
struct rebinding {
const char *name;
void *replacement;
void **replaced;
};
struct rebindings_entry {
struct rebinding *rebindings;
size_t rebindings_nel;
struct rebindings_entry *next;
};
static struct rebindings_entry *_rebindings_head;
接著看Demo中調(diào)用的關(guān)鍵函數(shù)rebind_symbols
:
rebind_symbols
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 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) {
_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;
}
這里首先調(diào)用了prepend_rebindings
坊饶,這個(gè)函數(shù)是整個(gè)數(shù)據(jù)結(jié)構(gòu)的關(guān)鍵:
prepend_rebindings
static int prepend_rebindings(struct rebindings_entry **rebindings_head,
struct rebinding rebindings[],
size_t nel) {
struct rebindings_entry *new_entry = (struct rebindings_entry *) malloc(sizeof(struct rebindings_entry));
if (!new_entry) {
return -1;
}
new_entry->rebindings = (struct rebinding *) 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;
}
這里生成一個(gè)新的結(jié)構(gòu)體指針new_entry
泄伪,將rebindings
與nel
扔到結(jié)構(gòu)體對(duì)應(yīng)的成員變量中,并將next指針指向上文提到的_rebindings_head
處匿级,而此時(shí)的_rebindings_head
值為NULL
蟋滴,最后又將new_entry
賦值給_rebindings_head
钾挟。
顯然慎王,如果rebind_symbols
函數(shù)調(diào)用多次,最終_rebindings_head
是個(gè)單鏈表破讨,prepend_rebindings
函數(shù)的作用就是將新的結(jié)構(gòu)體指針添加到鏈表頭部孤页,而這個(gè)單鏈表的尾部是NULL
尔苦。
當(dāng)然,這里涉及到一些內(nèi)存申請(qǐng)操作行施,如果申請(qǐng)失敗返回-1
允坚。
繼續(xù)看prepend_rebindings
函數(shù)之后的代碼:
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));
}
}
如果_rebindings_head->next
為NULL
也就是說(shuō)rebind_symbols
函數(shù)是首次調(diào)用,此時(shí)注冊(cè)image
的回調(diào)函數(shù)_rebind_symbols_for_image
_dyld_register_func_for_add_image
這個(gè)函數(shù)是dyld提供給我們的鏡像加載回調(diào)函數(shù)蛾号,對(duì)應(yīng)注釋如下:
/*
* The following functions allow you to install callbacks which will be called
* by dyld whenever an image is loaded or unloaded. During a call to _dyld_register_func_for_add_image()
* the callback func is called for every existing image. Later, it is called as each new image
* is loaded and bound (but initializers not yet run). The callback registered with
* _dyld_register_func_for_remove_image() is called after any terminators in an image are run
* and before the image is un-memory-mapped.
*/
顯然稠项,在調(diào)用_dyld_register_func_for_add_image
及有新鏡像加載時(shí),這個(gè)注冊(cè)的回調(diào)函數(shù)會(huì)被每個(gè)已加載的鏡像回調(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);
}
回調(diào)函數(shù)直接調(diào)用rebind_symbols_for_image
rebind_symbols_for_image
這是fishhook的核心函數(shù):
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;
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;
}
}
if (!symtab_cmd || !dysymtab_cmd || !linkedit_segment ||
!dysymtab_cmd->nindirectsyms) {
return;
}
...
}
先通過(guò)dladdr
函數(shù)查看是否可以加載到mach_header *
對(duì)應(yīng)的info展运,如果加載不到,直接return精刷。
這里說(shuō)一下Dl_info
的數(shù)據(jù)結(jié)構(gòu):
typedef struct dl_info {
const char *dli_fname; /* Pathname of shared object */
void *dli_fbase; /* Base address of shared object */
const char *dli_sname; /* Name of nearest symbol */
void *dli_saddr; /* Address of nearest symbol */
} Dl_info;
比如拗胜,Demo對(duì)應(yīng)的info信息如下:
接著定義了4個(gè)結(jié)構(gòu)體指針,以64位架構(gòu)為例:
-
segment_command_t
就是segment_command_64
-
symtab_command
為Load Commands
中LC_SYMTAB
對(duì)應(yīng)的結(jié)構(gòu)體 -
dysymtab_command
為Load Commands
中LC_DYSYMTAB
對(duì)應(yīng)的結(jié)構(gòu)體
然后跳過(guò)MachO Header怒允,開(kāi)始遍歷Load Commands
埂软,將對(duì)應(yīng)的LC分別放到linkedit_segment
、symtab_cmd
误算、dysymtab_cmd
這三個(gè)結(jié)構(gòu)體指針中仰美。
完成循環(huán)后對(duì)結(jié)構(gòu)體指針判空,如果有滿足為空條件的儿礼,直接return。
static void rebind_symbols_for_image(struct rebindings_entry *rebindings,
const struct mach_header *header,
intptr_t slide) {
...
// Find base symbol/string table addresses
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);
// Get indirect symbol table (array of uint32_t indices into symbol table)
uint32_t *indirect_symtab = (uint32_t *)(linkedit_base + dysymtab_cmd->indirectsymoff);
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_DATA) != 0 &&
strcmp(cur_seg_cmd->segname, SEG_DATA_CONST) != 0) {
continue;
}
for (uint j = 0; j < cur_seg_cmd->nsects; j++) {
section_t *sect =
(section_t *)(cur + sizeof(segment_command_t)) + j;
if ((sect->flags & SECTION_TYPE) == S_LAZY_SYMBOL_POINTERS) {
perform_rebinding_with_section(rebindings, sect, slide, symtab, strtab, indirect_symtab);
}
if ((sect->flags & SECTION_TYPE) == S_NON_LAZY_SYMBOL_POINTERS) {
perform_rebinding_with_section(rebindings, sect, slide, symtab, strtab, indirect_symtab);
}
}
}
}
}
- 獲取當(dāng)前鏡像
SEG_LINKEDIT
經(jīng)ASLR
偏移后的首地址 - 獲取符號(hào)表地址
- 獲取字符串表地址
- 獲取動(dòng)態(tài)符號(hào)表地址
- 再次遍歷
Load Commands
找到__DATA
與__DATA_CONST
中對(duì)應(yīng)的__nl_symbol_ptr
與__la_symbol_ptr
進(jìn)行重新綁定
perform_rebinding_with_section
的功能就是庆寺,遍歷當(dāng)前符號(hào)表是否有與鏈表中每個(gè)結(jié)構(gòu)體指針對(duì)應(yīng)的name
字段匹配蚊夫,如果有則重新綁定。至此懦尝。fishhook的rebind就完成了知纷。
再回到rebind_symbols
壤圃,來(lái)看對(duì)應(yīng)的else
部分代碼:
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 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) {
_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;
}
上文說(shuō)過(guò),_dyld_register_func_for_add_image
只有在調(diào)用或者有新鏡像加載時(shí)才會(huì)調(diào)用注冊(cè)的回調(diào)函數(shù)琅轧。對(duì)于同一個(gè)鏡像來(lái)說(shuō)伍绳,當(dāng)rebind_symbols
被多次調(diào)用時(shí),注冊(cè)的回調(diào)函數(shù)_rebind_symbols_for_image
已經(jīng)不會(huì)再被調(diào)用乍桂,此時(shí)先獲取當(dāng)前已加載鏡像冲杀,然后逐個(gè)手動(dòng)調(diào)用_rebind_symbols_for_image
即可。
Have fun!