图解 fishhook 原理

前言

虽然写 fishhook 原理的文章有很多,但是总觉得不够简单直观。大部分都是罗列大堆源码进行讲解,看得人云里雾里。

因此,本文将完全抛开源码,旨在简单清晰,直击要害,带你彻底弄清 fishhook 的原理。

hook 本质

函数是如何被调用的?

我们所写的代码最终都会编译为机器码。程序运行时,cpu 会顺序执行一条条的指令,根据不同类型的指令和数据,执行不同的操作。所有的符号都有自己的地址,包括数据和函数符号。

而函数调用是一条跳转指令,如下所示。在做完一些前置保存工作之后,就会跳转到函数所在地址的第一条指令开始执行。

// x86
call xxx

// arm64
br xxx

试想,如果我们想要 hook 某个函数,那是不是将其地址替换下就可以呢?没错,原理就是这么简单,将原函数地址替换为新函数地址

其实在使用 fishhook 时,我们也能看到一些影子。下面这段代码中,传入了待修改的函数名 NSLog、新函数 my_log 以及存放原函数的指针 orig_nslog

rebind_symbols((struct rebinding[1]){{"NSLog", my_log, (void *)&orig_nslog}}, 1);

从传入的几个参数来看,我们不妨先做个简单的猜想:它可能是在某个地方取到一些函数符号值,对比与传入的函数名是否相等,然后做替换。

当然,这个猜想并不完善,因为还涉及到一些未解的问题:

  • 函数符号值存在哪
  • 原函数地址又存在哪
  • 如何修改原函数地址

那,究竟是不是这个思路呢?下面,我们带着这些问题一一来解答。

Mach-O 结构

iOS 中,可执行文件是个 Mach-O 结构。国际惯例,先了解一下它的组成结构,请看下图。

image

Mach-O 大体上由如下几部分构成:

  • Header,描述 Macho-O 文件的一些元信息,比如魔数、支持的 cpu 类型等。

  • Load Commands,加载命令,也就是告诉系统如何去处理不同的加载信息。

    常用的命令有:

    • LC_SEGMENT,表示加载 segment,系统会将其映射到进程的虚拟地址空间中,比如 TEXT 代码段、DATA 数据段。
    • LC_DYLIB,表示加载动态库,会带有动态库的路径信息。
    • LC_LOAD_DYLINKER,表示动态链接器的信息,里面有 dyld 的路径。
    • ...
  • Sections,编译器对不同类型的资源在逻辑上的划分,比如 .text.data.symtab 等。

这里,我们只需要要关注几个特定的 Load CommandSection 就好,这对了解 fishhook 的原理就足够了。

如果想要详细了解 mach-o 的具体构成,可参考 osx-abi-macho-file-format-reference

Load Command

Load Command 表示加载命令,里面包含一些数据信息,不同的加载命令中包含的数据不太一样。

今天我们需要了解的只有如下几种:

  • LC_SEGMENT (__DATA_CONST),加载常量数据段。包括一系列的 section header,也就是 section 相关的信息,比如地址、大小、section 名称等。这里我们知道 __got section header 在里面就好。对应的结构为 segment_command

  • LC_SEGMENT (__DATA),加载可写数据段。同样包括一系列的 section header,这里我们只需记住有 lazy_symbol_ptr section header

  • LC_SEGMENT (__LINK_EDIT),加载 __LINK_EDIT 段。该段中包含了符号表、字符串表、重定位符号表等,主要供 dyld 使用。

  • LC_SYMTAB,包含了符号表、字符串表的偏移量,对应结构为 symtab_command

  • LC_DYSYMTAB,包含了间接表(动态库符号表)的偏移量,对应结构为 dysymtab_command

觉得文字啰里啰嗦的同学,可以直接看下面这张图。

image

Section

section 这部分,我们只要关注数据段__got__la_symbol_ptr 两个 section 即可。这两部分的结构一样,都是一个表,里面存储了动态库符号的地址。以下我们统称为 got 表。

__got 中存储数据符号地址,__la_symbol_ptr 中存储函数符号地址,下面我们只分析一种就好。

一看到符号地址这几个词,有没有突然间精神抖擞。到这里,我们又可以猜想一下,改变符号地址,就是修改 got 表中的值。

为什么符号地址会存放在单独的表中?这就与动态库共享有关。

动态库符号地址

为了做到动态库代码段共享,符号地址不能写死。因此,程序中使用 got 中间表来存放动态库符号地址,代码段中的符号地址都从 got 中获取,从而解决了符号地址固定的问题。

got 在数据段中,可读可写。那么,我们就可以利用这个特性,通过修改 got 表中符号的地址,以达到替换的目的。

关于 got 的说明,可以参看我之前写的文章 图解 Mach-O 中的 got ,对动态库符号地址及链接有个大概的了解。

PS:如果没空看的同学,也无大碍,记住 got 表中存储的是符号地址就好。

关键 Load Command 查找

前面我们提到过,__link_edit 中存放了一些重要的信息,比如符号表、字符串表、重定位表。那么如何获取到这些表呢?

Load Command 一节,我们介绍了几个需要留意的加载命令。现在再来回顾一下:

  • LC_SEGMENT (__LINK_EDIT)
  • LC_SYMTAB,存放符号表和字符串表的偏移。
  • LC_DYSYMTAB,存放间接符号表的偏移。

这里,出现了符号和字符串字眼,那么一定跟符号查找有关系。现在知道的信息是几张表的偏移量。那如果取到了真实的基址(加上 ASLR 随机偏移后),再加上偏移量不就可以获取到这几张表了吗?

所以,第一步做的事情就是从 Load Commands 中找到这些个命令,计算出 Mach-O 在虚拟地址空间的实际基址,然后得到以下三张表:

  • 符号表
  • 字符串表
  • 间接符号表

如下图所示,最右边的三张表。

image

此时已经知道了符号表信息,那么如何将 got 表中的地址跟它所对应的符号关联起来呢?

间接符号表

在说间接符号表之前,我们先了解一下符号表中存的是啥。

是具体符号值吗?🙅‍♂️,不不不,其实是符号在字符串表中的下标。由于字符串表存储了所有的符号值,那么自然的,符号表中只需存下标就可以,省时省心又省力。

间接符号表,也就是动态库符号表,存放了动态库符号的信息。间接,顾名思义,说明表中的数据不是字符串表的下标,而是该符号在符号表中的下标,中间多了一层。

如下图所示:

image

got 中的符号是动态库符号,肯定会涉及到间接符号表。那么它们究竟是如何对应上的呢?下面就请 got section header 闪亮登场。

got section header

由于 __got section headerlazy_symbol_ptr secton header 结构是完全一样的,下面以 lazy_symbol_ptr secton header 举例说明。

其结构如下图所示:

image

它里面包含了两个非常关键的字段:

  • address,表示它所属的 section 地址。由于 section 中存放是的 got 表,所以它也就是 got 表地址。
  • reserved1,表示 got 表中第一个符号在间接表中的下标。

第二点有点绕,举个🌰。

假设 got 中总共有 3 个符号,reserved1 = 2,那么说明第一个符号在间接表中的下标为 2,第二个符号的下标为 3,第三个符号的下标为 4。如下图所示:

image

这样,如果有了在间接表的下标 → 得到在符号表的下标 → 得到在字符串表的下标 → 得到字符串。

完整的动态符号查找路径如下图所示:

image

由于我们在使用 fishhook 的时候会传入函数名称,假设为 A,那么当查找到某项 got 符号字符串值 B 后,那么只需对比一下 A、B 是否相等即可。如果相等,更新该项符号在 got 中的地址即可。

注意 got 中的数据项实际上是个间接指针,也就是指向指针的指针,指针的值才是符号地址。

根据上图,我们可以推断出一个初始的替换雏形:

  • 根据 address 字段,获取 got 表地址。
  • 遍历 got 表,根据当前是第几项,查找当前符号在间接表中的下标
  • 间接表中获取到符号表下标
  • 符号表中获取到字符串表下标
  • 得到符号字符串,与待替换函数名称进行比较。若相等,则更新 got 表中的值。

举个🌰,假设将 NSLog 替换为 my_log 自定义实现。整个处理过程如下图所示:

image

注意:更新 got 第一项符号的地址时,是 *got[0] = my_log,因为表中每一项是二级指针。

至此,如果你能理解上述所讲的知识点,那么恭喜你,已经搞懂了 fishhook 的原理。

加点东西

rebinding 结构

以上只是一个简易模型。在具体实现中,fishhook 将所要替换的函数封装成了一个链表结构,每个节点中有个数组,存放了待修改的函数们。如下图所示:

image
  • rebindings_entry 表示链表节点。
  • 节点字段 rebindings 表示待替换函数数组。
  • struct rebinding 表示函数替换信息。

那么相应的,我们的雏形也得稍微改进一下:

  • 根据 address 字段,获取 got 表地址。
  • 遍历 got 表,根据当前是第几项,查找当前符号在间接表中的下标
  • 间接表中获取到符号表下标
  • 符号表中获取到字符串表下标
  • 得到符号字符串,与待替换函数名称进行比较。
    1. 遍历链表节点
    2. 遍历当前节点中的函数替换数组
    3. 若找到了相同的函数符号值,则更新 got

全局替换

由于动态库中的符号很可能被可执行文件或多个动态库使用。因此,对于每个加载的 image,都要做如上的操作。也就是说,这是一项全局性的替换。

打个比方,假设我们将 NSLog 替换为自己的实现。在 A、B、C 三个动态库(不论是系统库还是自己编写的)中都用到了 NSLog,那么,它们 got 表中的值都要被更新,以达到全局替换的目的。

限制

由于 fishhook 修改的是 got 中的符号地址,那么它也只能对动态库中的 c 函数进行替换。

总结

这篇文章中,我主要讲述了 fishhook 所涉及到的最小知识点,比如动态库符号地址在哪,如何查找符号值,以及如何匹配修改符号地址等。以图代码,期望以最简单直接的方式探究其本质原理。希望对你有帮助~

PS:如果你看懂了原理,再去看源码,应当是非常轻松了。

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

推荐阅读更多精彩内容

  • fishhook,facebook开源的一个可以动态绑定Mach-O符号表的库。在程序启动时与运行时会通过dyld...
    01_Jack阅读 1,184评论 0 2
  • 这是Mach-O系列的第三篇 阅读 FishHook源码之前,你可能需要对以下知识有个简单的了解 Mach-O文件...
    Joy___阅读 7,741评论 9 45
  • fishhook是Facebook提供的一个动态修改链接mach-O文件的工具。利用MachO文件加载原理,通过修...
    异想天不开_9950阅读 19,385评论 17 107
  • fishhook fishhook是Facebook提供的用于hook系统c函数的库。它能动态重新绑定运行在iOS...
    lattr阅读 1,052评论 0 1
  • 久违的晴天,家长会。 家长大会开好到教室时,离放学已经没多少时间了。班主任说已经安排了三个家长分享经验。 放学铃声...
    飘雪儿5阅读 7,454评论 16 22