fishhook 源码分析

引用

前言

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 的基本格式。

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 在内存中的布局结构:

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(参见下面链接)的内存布局:

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 画的。

查找 string table 的函数名

附录Demo

Demo

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容

  • 最早了解到fishhook是看了下面两篇文章之后,顿时让我觉得这是一个非常好的东西。总共210行代码,收获了150...
    阿呆少爷阅读 6,514评论 1 8
  • 关注仓库,及时获得更新:iOS-Source-Code-AnalyzeFollow: Draveness · Gi...
    Draveness阅读 5,920评论 5 33
  • fishhook 用于替换 iOS 程序中动态库的符号,常被用来 hook 系统中的 C 函数。 fishhook...
    gbupup阅读 1,034评论 0 10
  • 关键时刻,第一时间送达! 问题种类 时间复杂度 在集合里数据量小的情况下时间复杂度对于性能的影响看起来微乎其微。但...
    C9090阅读 839评论 0 1
  • 模拟登陆 以模拟登录兄弟连猿代码为例 四个步骤无需赘述了:初始化,配置,发送请求获取数据,关闭连接。 配置的部分为...
    王宝花阅读 4,247评论 0 1