fishhook 源码分析

引用

  • Mac OS X ABI Mach-O File Format Reference

前言

fishhook 是什么?

fishhook 是用于 Mach-O 的符号动态绑定的 facebook 开源维护的的第三方库。当 Mach-O 加载第三方库的时候,可以用 fishhook 进行hook。

具体的参考 fishhook 在 GitHub 上的 readme .

原理

readme 上已经说的很清楚了。但是想要搞清楚原理,还有很多信息需要补充。

一句话概括就是:fishhook 找到目标函数地址,然后替换成自己的函数地址,达到 hook 的目的。

Mach-O

让我们来看看 Mach-O 的文件格式。Mach-O 是Mac OS X 上程序的标准格式。

这里有张图,来表示 Mach-O 的基本格式。

fishhook 源码分析_第1张图片
Figure 1 Mach-O file format basic structure

Mach-O 文件包含三个主要区域:

  • header structure
  • load commands
  • segment

其中,segment 是一段一段的。

每个 segment 包含 0 个或 多个 section。每个 section 包含 code 或者 特殊类型的 data。每个 segment 定义了 一片虚拟内存 ,其中动态链接器映射到程序的地址空间。

Mach-O 在内存中的布局结构:

fishhook 源码分析_第2张图片
Mach-O 内存布局

fishhook 要做的事情就是找到 segment: DATA ,然后找到 section:__la_symbol_ptr。最后,找到其中的目标函数地址,替换目标函数地址为自己的函数地址。

具体分为2个步骤:

  • 找到目标函数地址
  • 找到函数名,一一比对。成功后,替换目标函数地址为自己的函数地址。

其中,找到函数名的过程,有点曲折。string table 是一个数组,里面包含了函数名。根据偏移 offset 能找到需要的函数名。

分析

进行符号绑定前,我们能获得的信息有:

  • mach_header *header,header 结构 指针
  • intptr_t slide,ALSR 偏移

接着 header 的是 load commands。 load commans 里面包含了 LG_SEGMENT。 LG_SEGMENT 有4种:

  • __PAGEZERO
  • __TEXT
  • __DATA
  • __LINKEDIT

PAGEZERO 是可执行程序的第一个段。总是位于虚拟内存中的最开始的位置。它的大小是根据架构类型来的。在X64里面的,大小是 0x0000000100000000 。

TEXT 里面存储的是 code 和 只读数据。我们这里用不到。

DATA 里面包含了可读写数据。里面的 section header 正是我们需要的信息。最重要的是找到 section header 里的 __nl_symbol_ptr__la_symbol_ptr

LINKEDIT 里面记录了动态连接器的一些信息,fishhook 主要通过它找到基地址,一般得到的地址就是 PAGEZERO 之后的地址,也就是 0x0000000100000000。

LG_SEGMENT(__DATA)LG_SEGMENT(__TEXT) 里面都包含了 section header。我们主要用到 DATA 里的 section header ( 主要是 __nl_symbol_ptr__la_symbol_ptr )。

LG_SEGMENT(__DATA) 包含如下 section header :

  • __nl_symbol_ptr
  • __got
  • __la_symbol_ptr
  • __objc_imageinfo
  • __bss

__nl_symbol_ptr is an array of pointers to non-lazily bound data (these are bound at the time a library is loaded) and __la_symbol_ptr is an array of pointers to imported functions that is generally filled by a routine called dyld_stub_binder during the first call to that symbol (it's also possible to tell dyld to bind these at launch)

我们先来看看如何调用 fishhook

static void (*orig_foo)(int);

void my_foo(int x) {
printf("real func: %d\n",x);
}

int main(int argc, const char * argv[]) {
@autoreleasepool {
rebind_symbols((struct rebinding[1]){"foo",my_foo,(void *)&orig_foo}, 1);
foo(20);
}
return 0;
}

函数内部调用:

  • rebind_symbols_image
  • _rebind_symbols_for_image
  • rebind_symbols_for_image
  • perform_rebinding_with_section

rebind_symbols 里大体上做了如下操作:

  • 开辟链表 rebindings_entry 空间,并填入信息
  • 判断是不是第一次调用。第一次调用的话,对添加的 image 注册回调:_dyld_register_func_for_add_image(_rebind_symbols_for_image);
  • 非第一次的话,在已有的 image 里调用 _rebind_symbols_for_image

_rebind_symbols_for_image 里只是调用: rebind_symbols_for_image

rebind_symbols_for_image

rebind_symbols_for_image 大体上做如下动作:

  • 在 load commands 里找到 linkedit_segment、symtab_cmd、dysymtab_cmd
  • 通过 linkedit_segment 和 symtab_cmd 找到 symtab、strtab
  • 在 load commands 里找到 __DATA,__la_symbol_ptr__DATA,__nl_symbol_ptr
  • 调用 perform_rebinding_with_section

查找 linkedit_segment、symtab_cmd、dysymtab_cmd。在 MachOView 里,查看事例程序 TestMacOS(参见下面链接)的内存布局:

fishhook 源码分析_第3张图片
TestMacOs MachOView 查看结构

mach_header_64

struct mach_header_64
{
uint32_t magic;
cpu_type_t cputype;
cpu_subtype_t cpusubtype;
uint32_t filetype;
uint32_t ncmds;
uint32_t sizeofcmds;
uint32_t flags;
uint32_t reserved;
};
  • ncmds
  • load commands 的数量

segment_command_64

struct segment_command_64
{
uint32_t cmd;
uint32_t cmdsize;
char segname[16];
uint64_t vmaddr;
uint64_t vmsize;
uint64_t fileoff;
uint64_t filesize;
vm_prot_t maxprot;
vm_prot_t initprot;
uint32_t nsects;
uint32_t flags;
};
  • cmd
  • 数字从0x1开始,代表不同的 load command
  • cmdsize
  • 当前 cmd 的大小
  • segname
  • segment 的名字,大写的。如:__TEXT__DATA
  • vmaddr
  • 段在虚拟内存的开始地址
  • fileoff
  • vmaddr 的偏移
#define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT_64

//获得 load commands 的起始地址
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;
//查找LG_SEGMENT_64->__LINKEDIT
if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) {
if (strcmp(cur_seg_cmd->segname, SEG_LINKEDIT) == 0) {
linkedit_segment = cur_seg_cmd;
}
}
//查找 LG_SYMTAB
else if (cur_seg_cmd->cmd == LC_SYMTAB) {
symtab_cmd = (struct symtab_command*)cur_seg_cmd;
}
//查找 LG_DYSYMTAB
else if (cur_seg_cmd->cmd == LC_DYSYMTAB) {
dysymtab_cmd = (struct dysymtab_command*)cur_seg_cmd;
}
}

找到 linkedit_base

// Find base symbol/string table addresses
uintptr_t linkedit_base = (uintptr_t)slide + linkedit_segment->vmaddr - linkedit_segment->fileoff;

找到 symtab、strtab、dysymtab_command:

symtab_command

struct symtab_command
{
uint_32 cmd;
uint_32 cmdsize;
uint_32 symoff;
uint_32 nsyms;
uint_32 stroff;
uint_32 strsize;
};
  • symoff
  • symbol table 的偏移
  • stroff
  • string table 的偏移

dysymtab_command

struct dysymtab_command
{
uint32_t cmd;
uint32_t cmdsize;
uint32_t ilocalsym;
uint32_t nlocalsym;
uint32_t iextdefsym;
uint32_t nextdefsym;
uint32_t iundefsym;
uint32_t nundefsym;
uint32_t tocoff;
uint32_t ntoc;
uint32_t modtaboff;
uint32_t nmodtab;
uint32_t extrefsymoff;
uint32_t nextrefsyms;
uint32_t indirectsymoff;
uint32_t nindirectsyms;
uint32_t extreloff;
uint32_t nextrel;
uint32_t locreloff;
uint32_t nlocrel;
};
  • indirectsymoff
  • indirect symbol table 的偏移

nlist_64

struct nlist_64
{
union {
uint32_t n_strx;
} n_un;
uint8_t n_type;
uint8_t n_sect;
uint16_t n_desc;
uint64_t n_value;
};

  • n_un
  • 函数名在 string table 的 index
//注意:这里的偏移都是基于基地址( linkedit_base )的偏移
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);

获取 section header,并调用 perform_rebinding_with_section:

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);
}
}
}
}

perform_rebinding_with_section

section_64

struct section_64
{
char sectname[16];
char segname[16];
uint64_t addr;
uint64_t size;
uint32_t offset;
uint32_t align;
uint32_t reloff;
uint32_t nreloc;
uint32_t flags;
uint32_t reserved1;
uint32_t reserved2;
};
  • addr
  • section 的虚拟内存地址,类型是 integer 的
  • reserved1
  • 对于 symbol pointer sections 和 stubs sections 来说,reserved1 表示 indirect table 数组的 index。用来索引 section's entries.
uint32_t *indirect_symbol_indices = indirect_symtab + section->reserved1;
//找到库函数的地址: section->addr <==> section(__DATA,__la_symbol_ptr)
void **indirect_symbol_bindings = (void **)((uintptr_t)slide + section->addr);
for (uint i = 0; i < section->size / sizeof(void *); i++) {
//查找indirect_symbol_indices数组,获取其中的内容,得到 symtab_index 
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;
}
//找到函数名位于 string table 的偏移
uint32_t strtab_offset = symtab[symtab_index].n_un.n_strx;
//找到 函数名
char *symbol_name = strtab + strtab_offset;

if (strnlen(symbol_name, 2) < 2) {
continue;
}
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) {
//把函数地址保存到 rebindings.replaced 里
*(cur->rebindings[j].replaced) = indirect_symbol_bindings[i];
}
//替换内容为自定义函数地址
indirect_symbol_bindings[i] = cur->rebindings[j].replacement;
goto symbol_loop;
}
}
cur = cur->next;
}
symbol_loop:;
}

这里有张图,仿照着 fishhook 的 readme 画的。

fishhook 源码分析_第4张图片
查找 string table 的函数名

附录Demo

Demo

你可能感兴趣的:(fishhook 源码分析)