上一篇文章: "乘風(fēng)破浪: iOS逆向HOOK的實(shí)踐"
1.Fishhook
上一篇文章,我們?cè)敿?xì)介紹了HOOK的原理仲锄,其中提到了Fishhook作為一種HOOK技術(shù)之一劲妙。
-
1.FISHHOOK
是 Facebook開源的一個(gè)非常小的重新綁定動(dòng)態(tài)符號(hào)的庫(kù).
其實(shí)就是動(dòng)態(tài)修改鏈接mach-O
文件的工具.
利用Mach-O
文件加載原理,通過修改懶加載和非懶加載
兩個(gè)表的指針達(dá)到C函數(shù)進(jìn)行HOOK的目的.
-
2.
FISHHOOK源碼
fishhook代碼加起來(lái)差不多200多行,代碼量很少,輕量級(jí)代碼.
fishhook.h
#ifndef fishhook_h
#define fishhook_h
#include <stddef.h>
#include <stdint.h>
#if !defined(FISHHOOK_EXPORT)
#define FISHHOOK_VISIBILITY __attribute__((visibility("hidden")))
#else
#define FISHHOOK_VISIBILITY __attribute__((visibility("default")))
#endif
#ifdef __cplusplus
extern "C" {
#endif //__cplusplus
/*
* 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);
/*
* Rebinds as above, but only in the specified image. The header should point
* to the mach-o header, the slide should be the slide offset. Others as above.
*/
FISHHOOK_VISIBILITY
int rebind_symbols_image(void *header,
intptr_t slide,
struct rebinding rebindings[],
size_t rebindings_nel);
#ifdef __cplusplus
}
#endif //__cplusplus
#endif //fishhook_h
fishhook.c
#include "fishhook.h"
#include <dlfcn.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <mach/mach.h>
#include <mach/vm_map.h>
#include <mach/vm_region.h>
#include <mach-o/dyld.h>
#include <mach-o/loader.h>
#include <mach-o/nlist.h>
#ifdef __LP64__
typedef struct mach_header_64 mach_header_t;
typedef struct segment_command_64 segment_command_t;
typedef struct section_64 section_t;
typedef struct nlist_64 nlist_t;
#define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT_64
#else
typedef struct mach_header mach_header_t;
typedef struct segment_command segment_command_t;
typedef struct section section_t;
typedef struct nlist nlist_t;
#define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT
#endif
#ifndef SEG_DATA_CONST
#define SEG_DATA_CONST "__DATA_CONST"
#endif
struct rebindings_entry {
struct rebinding *rebindings;
size_t rebindings_nel;
struct rebindings_entry *next;
};
static struct rebindings_entry *_rebindings_head;
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;
}
static vm_prot_t get_protection(void *sectionStart) {
mach_port_t task = mach_task_self();
vm_size_t size = 0;
vm_address_t address = (vm_address_t)sectionStart;
memory_object_name_t object;
#if __LP64__
mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64;
vm_region_basic_info_data_64_t info;
kern_return_t info_ret = vm_region_64(
task, &address, &size, VM_REGION_BASIC_INFO_64, (vm_region_info_64_t)&info, &count, &object);
#else
mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT;
vm_region_basic_info_data_t info;
kern_return_t info_ret = vm_region(task, &address, &size, VM_REGION_BASIC_INFO, (vm_region_info_t)&info, &count, &object);
#endif
if (info_ret == KERN_SUCCESS) {
return info.protection;
} else {
return VM_PROT_READ;
}
}
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) {
const bool isDataConst = strcmp(section->segname, "__DATA_CONST") == 0;
uint32_t *indirect_symbol_indices = indirect_symtab + section->reserved1;
void **indirect_symbol_bindings = (void **)((uintptr_t)slide + section->addr);
vm_prot_t oldProtection = VM_PROT_READ;
if (isDataConst) {
oldProtection = get_protection(rebindings);
mprotect(indirect_symbol_bindings, section->size, PROT_READ | PROT_WRITE);
}
for (uint i = 0; i < section->size / sizeof(void *); i++) {
uint32_t symtab_index = indirect_symbol_indices[I];
if (symtab_index == INDIRECT_SYMBOL_ABS || symtab_index == INDIRECT_SYMBOL_LOCAL ||
symtab_index == (INDIRECT_SYMBOL_LOCAL | INDIRECT_SYMBOL_ABS)) {
continue;
}
uint32_t strtab_offset = symtab[symtab_index].n_un.n_strx;
char *symbol_name = strtab + strtab_offset;
bool symbol_name_longer_than_1 = symbol_name[0] && symbol_name[1];
struct rebindings_entry *cur = rebindings;
while (cur) {
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) {
*(cur->rebindings[j].replaced) = indirect_symbol_bindings[I];
}
indirect_symbol_bindings[i] = cur->rebindings[j].replacement;
goto symbol_loop;
}
}
cur = cur->next;
}
symbol_loop:;
}
if (isDataConst) {
int protection = 0;
if (oldProtection & VM_PROT_READ) {
protection |= PROT_READ;
}
if (oldProtection & VM_PROT_WRITE) {
protection |= PROT_WRITE;
}
if (oldProtection & VM_PROT_EXECUTE) {
protection |= PROT_EXEC;
}
mprotect(indirect_symbol_bindings, section->size, protection);
}
}
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;
}
// 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);
}
}
}
}
}
static void _rebind_symbols_for_image(const struct mach_header *header,
intptr_t slide) {
rebind_symbols_for_image(_rebindings_head, header, slide);
}
int rebind_symbols_image(void *header,
intptr_t slide,
struct rebinding rebindings[],
size_t rebindings_nel) {
struct rebindings_entry *rebindings_head = NULL;
int retval = prepend_rebindings(&rebindings_head, rebindings, rebindings_nel);
rebind_symbols_for_image(rebindings_head, (const struct mach_header *) header, slide);
if (rebindings_head) {
free(rebindings_head->rebindings);
}
free(rebindings_head);
return retval;
}
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;
}
注意
如果需要下載源碼,只需訪問上面提供的鏈接即可查看儒喊。
-
3.
FISHHOOK如何工作?
dyld
通過更新Mach-O二進(jìn)制文件中特定的_DATA段
來(lái)綁定懶加載和非懶加載符號(hào).
fishhook
通過更新這些符號(hào)位置進(jìn)行重新綁定,然后進(jìn)行相應(yīng)的替換,從而重新綁定這些符號(hào).
-
4.
我們繼續(xù)使用TEST 項(xiàng)目
ViewController.m
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic,strong)UIButton *revealBtn;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
NSLog(@"hello");
[self.view addSubview:self.revealBtn];
}
-(UIButton*)revealBtn{
if (!_revealBtn) {
_revealBtn = [UIButton buttonWithType:UIButtonTypeCustom];
_revealBtn.frame = CGRectMake(100,100, 100,40);
_revealBtn.backgroundColor = [UIColor redColor];
[_revealBtn setTitle:@"測(cè)試" forState:UIControlStateNormal];
[_revealBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[_revealBtn addTarget:self action:@selector(revealClick) forControlEvents:UIControlEventTouchUpInside];
}
return _revealBtn;
}
-(void)revealClick{
NSLog(@"revealClick");
}
@end
4.1
我們首先編譯/打包項(xiàng)目,生成ipa包.我們找到ipa包中mach-O文件.
4.2
我們就可以找到通過MachOView打開TEST(Mach-O)文件
4.3
找到懶加載符號(hào)表,找到NSLog的偏移地址.
這里是Offset(Ox3020
)
4.4
我們通過在viewDidLoad進(jìn)行斷點(diǎn),查看 NSLog 的匯編代碼
4.5
我們通過上一節(jié)中LLDB的介紹,使用lldb 命令進(jìn)行查看MachO的偏移地址,再找到NSLog的偏移地址,就是通過DYLD動(dòng)態(tài)綁定,將MachO中的_DATA段的指針,指向外部函數(shù),也就是NSLog函數(shù).
-
5.
怎么使用fishhook
下載fishhook源碼,我們只需要fishhook.c,fishhook.h
放到TEST項(xiàng)目目錄下,我們對(duì)NSLog進(jìn)行綁定修改.
ViewController.m
#import "ViewController.h"
#import "fishhook.h"
@interface ViewController ()
@property (nonatomic,strong)UIButton *revealBtn;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[self.view addSubview:self.revealBtn];
struct rebinding logReBind;
logReBind.name = "NSLog";
logReBind.replacement = newNSlog;
logReBind.replaced = (void *)&OriginalNSLog;
struct rebinding rebs[] ={ logReBind };
rebind_symbols(rebs, 1);
}
-(UIButton*)revealBtn{
if (!_revealBtn) {
_revealBtn = [UIButton buttonWithType:UIButtonTypeCustom];
_revealBtn.frame = CGRectMake(100,100, 100,40);
_revealBtn.backgroundColor = [UIColor redColor];
[_revealBtn setTitle:@"測(cè)試" forState:UIControlStateNormal];
[_revealBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[_revealBtn addTarget:self action:@selector(revealClick) forControlEvents:UIControlEventTouchUpInside];
}
return _revealBtn;
}
-(void)revealClick{
NSLog(@"hello");
}
static void (*OriginalNSLog)(NSString *format, ...);
void newNSlog(NSString *format, ...){
OriginalNSLog([NSString stringWithFormat:@"newNSLog : %@",format]);
}
@end
結(jié)果: 2020-03-18 16:43:17.060093+0800 TEST[5261:198913] newNSLog : hello
/*
* A structure representing a particular intended rebinding from a symbol
* name to its replacement
*/
struct rebinding {
const char *name;
void *replacement;
void **replaced;
};
name -> hook函數(shù)名稱
replacement -> 替換新的函數(shù)
replaced 保存原始函數(shù)指針變量的指針
最后綁定符號(hào)表
-
6.
通過符號(hào)表查找字符串
6.1
我們這里分析一下如何查找字符串表.
我們打開MachOView
我們以NSLog為例
6.2
我們找到的是懶加載表,NSLog
6.3
通過懶加載表(NSLog
) 找到 Indirect Symbols,圖中紅色箭頭所指方向.
6.4
找到間接符號(hào)表,如上圖.
間接符號(hào)表 與 懶加載表一一對(duì)應(yīng)
6.5
我們通過間接表獲取的Data值進(jìn)行機(jī)制轉(zhuǎn)換,獲取NSLog在(Symbol Tables -> Symbols
)中的位置,如下圖
6.6
我們找到符號(hào)表中的NSLog的data值 Ox9C
注意
查找字符串,通過符號(hào)表偏移量 + 字符串表基地址進(jìn)行獲取.
字符串表及地址: 0x61F0
符號(hào)表偏移值 : 0x9C
計(jì)算得到是Ox628C
這樣我們通過符號(hào)表就查找到了字符串.
2.總結(jié)
通過本篇文章的學(xué)習(xí)镣奋,我們了解了Fishhook的作用、如何使用以及查找字符串的原理怀愧。
感謝您的關(guān)注和支持侨颈!