示例 Demo Code
继续使用上一篇文中的代码示例:
运行之后将其生成的执行文件拖入 MachOView 中。对 Mach-O 进行解析。
Linkedit Base Address 计算
从 MachOView 中 Load Commands -> LC_SEGMENT_64(__LINKEDIT) 中获取到以下值:
进行下列式(1)和式(2)的计算:
\[\begin{equation}
\left\{
\begin{array}{l@{\;=\;}l}
vmaddr=1000030000_{(hex)}\\
file\ offset=30000_{(hex)}
\end{array}
\right.
\end{equation}\]
\[\begin{equation}
\begin{aligned}
base\_address&=slide+vmaddr-file\ offset \\
&=slide+1000000000_{(hex)}
\end{aligned}
\end{equation}\]
符号表、字符表和跳转表
继续看上文的这张图片:
此时,我们已经获取到了 Base Address,根据之前的流程,需要找到 __LINKEDIT Section 中的 Symbols 表、Indirect Symbols 表和 String 字符串表,这些地址我们需要在 Load Commands 中来获取。找到 LC_SYMTAB 和 LC_DYSYMTAB 这两个 Commaneds。
\[\begin{equation}
\left\{
\begin{array}{l@{\;=\;}l}
symbol\_offset=symtab\_cmd\to symoff=12680_{(oct)}=3188_{(hex)}\\
indirect\_symbol\_offset=dysymtab\_cmd\to symoff =14008_{(oct)}=35B8_{(hex)}\\
string\_table\_offset=symtab\_cmd\to stringoff=13864_{(oct)}=3628_{(hex)}
\end{array}
\right.
\end{equation}\]
\[\begin{equation}
\left\{
\begin{array}{l@{\;=\;}l}
symbol\_base=base\_address+symbol\_offset=slide+100003188_{(hex)}\\
indirect\_symbol\_base=base\_address+indirect\_symbol\_offset=slide+1000035B8_{(hex)}\\
string\_table\_base=base\_address+string\_table\_offset=slide+100003628_{(hex)}
\end{array}
\right.
\end{equation}\]
跳转表 nl 和 la 绑定符号基址
我们在 MachOView 中验证了三个表的位置。之后继续跟随着 fishhook 的思路来进行验证。下面将进入二尺遍历 Load Command 流程。这一次需要拿出的 Command 是 LC_SEGMENT(__DATA),目的是为了找到 __nl_symbol_ptr
和 __la_symbol_ptr
这两个 Section 的位置。
\[\begin{equation}
\left\{
\begin{array}{l@{\;=\;}l}
nl\_indirect\_sym\_index=13_{(oct)}\\
la\_indirect\_sym\_index=15_{(oct)}\\
\end{array}
\right.
\end{equation}\]
\[\begin{equation}
\left\{
\begin{array}{l@{\;=\;}l}
nl\_sym\_base\_addr&=nl\_indirect\_sym\_index\times size+indirect\_symbol\_base\\
&=13_{(oct)}\times 4 + (1000035B8_{(hex)}+slide)=1000035EC_{(hex)}+C\\
la\_sym\_base\_addr&=la\_indirect\_sym\_index\times size+indirect\_symbol\_base\\
&=15_{(oct)}\times 4 + (1000035B8_{(hex)}+slide)=1000035F4_{(hex)}+C\\
\end{array}
\right.
\end{equation}\]
这里的 C 其实是上方 slide 常量的运算结果,由于在这个场景下 MachOView 验证时发现 slide = 0
,但不意味着 slide
是一个恒为 0 的值。vmaddr_slide
的取值其实是地址空间布局随机化(ASLR)机制的结果,这是一种针对缓冲区溢出的安全保护技术,这里不再赘述。
根据 Indirect Symbols 中的描述,发现下标标识的区域的起始位置与我们的计算相吻合,此处也再次验证成功。在 Indirect Symbols 的结构中,我们可以找到其 Lazy Symbol Pointer。例如,我们在代码中出现的 strlen
方法:
在符号表中获取全部信息
在 Lazy Symbol Pointers
这里,我们最终获取到了 0x10002068 -> _strlen
这个值。它起始是对于当前 _strlen
方法在符号表中的一个映射,我们可以简单的理解为它就是在符号表中对应方法的下标。
这个结论虽然至今笔者无法跟踪代码,将其结构实例化,但是可以通过 fishhook 的源码进行分析得出结论。
symtab[symtab_index].n_un.n_strx
从这个获取的方式来看,symtab_index
处的位置对应的是某个符号表的基址位,因此可以将类型转成 nlist_64
。
为什么在 fishhook 源码中可以将符号名称直接通过 char *symbol_name = strtab + strtab_offset;
直接取得呢?因为在 String Table 存储过程中会在每一个符号名的末尾增加不确定个 \0
,而在 C 中一次的字符数组获取会以 \0
为结束。在 MachOView 中已经将符号名进行了解析,我们可以清晰的在解析后的 nlist
结构中发现符号名:
此时我们已经找到了改符号表的位置,剩下的工作仅需将 Lazy Symbol Pointers
中对应的指针进行指向变更即可完成操作。我在 fishhook 中加入了部分输出验证代码,可以大致验证这一地址的更变,但是由于在运行时有着 ASLR 的参与,是无法精准的将地址完全与 MachOView 的解析结果精准比对,但是从地址的大致区域中可以看出这个操作的成功性,以下是加入代码和输出的 log:
0x1DA0
这个地址在原始的间接表指向的位置之前,所以我们大致断定它处在 _TEXT
段。并且__stub_helper
, __cstring
, __unwoind_info
这些 Session 是我们无法直接干预的位置,所以可以猜测 0x1DA0
落在我们的 __text
Session 中。我们掏出 Hopper 来验证一下这个猜想,发现正是如此:
尾声
fishhook 的原理探究至此就告一段落。通过这个源码探求,强化了对于 Mach-O 的学习和认识,也在之中学习到了 Facebook 对于 Hook C 方法这个很妙的技巧。最后不得不再惊叹一句,FB 真的是令技术人向往的地方。
参考