内容简介:在上篇文章老规矩,片头先上福利:在开始正文之前,假设面试官问了一个问题:
在上篇文章 不知MachO怎敢说自己懂DYLD 中已经详细介绍了MachO,并且由MachO引出了 dyld
,再由 dyld
讲述了App的启动流程,而在App的启动流程中又说到了一些关键的名称如: LC_LOAD_DYLINKER
、 LC_LOAD_DYLIB
以及 objc
的回调函数 _dyld_objc_notify_register
等等。并且在末尾提出了MachO中还有一些符号表,而有哪些符号表,这些符号表又有些什么用呢?笔者在这篇文章就将一一道来。
老规矩,片头先上福利: 点击下载demo ,demo中有笔者给fishhook每句代码加的详细注释!!! 这篇文章会用到的 工具 有:
在开始正文之前,假设面试官问了一个问题:
都知道Objective-C最大的特性就是runtime,大家可以用使用runtime对OC的方法进行hook,那么C函数能不能hook?
有兴趣回答的朋友可以先行在评论区回答,答完之后再继续阅读或者预先偷窥一下文末的答案,看看这被炒了无数次冷饭的runtime自己是否真的了然于胸。
本将从以下几方面回答上面所提的问题:
- Runtime的Hook原理
- 为什么C不能hook
- 如何利用MachO“玩坏”系统C函数
- fishhook源码分析
- 绑定系统C函数过程验证
一、Runtime的Hook原理
Runtime,从名称上就知道是运行时,也是它造就了OC运行时的特性,而要想彻底明白什么是运行时,那么就需要将之与 C语言 有相比较。
今天咱们就从汇编的角度看一看OC和C在调用方法(函数)上有什么区别。
注:笔者使用的是iPhone 7征集调试,所有一下汇编都是基于arm64,所以以下所有汇编默认为基于arm64。
新建一个工程取名为:FishhookDemo
敲入两个OC方法 mylog
和 mylog2
,挂上断点,如图:
开启汇编断点,如图:
运行工程,会跳转到如下图的汇编断点:
从上图可以看的出来调用了两个 objc_msgSend
,这两个很像是 我们的 mylog
和 mylog2
,但现在还不能确定。
想一想 objc_msgSend
的定义:
OBJC_EXPORT void objc_msgSend(void /* id self, SEL op, ... */ ) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); 复制代码
第一个参数是 self
,第二个参数是 SEL
,所以可以知道SEL是放在x1的寄存器里面(什么是x1?继续关注作者,之后的文章会有相关的汇编的专门篇章)。
马不停蹄,挂上两个汇编断点,查看一下两个x1中存放的到底是什么,如图:
这也就验证了咱们OC方法都是消息转发(objc_msgSend)。而同一个C函数的地址又都是一样的(笔者这次运行的地址就是 0x1026ce130
) 。
所以在每次调用OC方法的时候就让我们有了一次改变消息转发「目标」的机会。
这里稍微提一下runtime的源码分析流程: Step 1、方法查找 ① 汇编快速查找缓存 ② C/C++慢速查找: self
-> super
-> NSObject
->找到换缓存起来 Step 2、动态方法解析: _class_resolveMethod
① _class_resolveInstanceMethod
② _class_resolveClassMethod
Step 3、消息转发 ① _forwardingTargetForSelector
② _methodSignatureForSelector
③ _forwardInvocation
④ _doesNotRecognizeSelector
二、为什么C不能hook
同样我们从汇编的角度切入。
敲入代码一些C函数,挂上断点,如图:
运行工程:
会看到断点断到如下汇编:
可以看到每个 NSLog
对应跳转的地址都是 0x10000a010
,每个 printf
对应跳转的地址都是 0x10000a184
,也就是说每个C的函数都是一一对应着一个真实的地址空间。每次在调用一个C函数的时候都是执行一句汇编 bl 0xXXXXXXXX
。
所以上面讲述到的消息转发的机会没有了,也就是没有了利用runtime来Hook的机会了。
三、如何利用MachO“玩坏”系统C函数
既然如此,那么是否C函数就真的那么牢不可破,无法对他进行Hook呢?
答案肯定是否定的!
想要从根上理解这个问题,首先要了解:我们的C函数分为系统C函数和我们自定义的C函数。
1、自定义的C函数
在上面的步骤中我们已经了解到所有C函数的调用都是跳转到一个「固定的地址」,那么就可以推断得出这个「固定的地址」其实是在编译期已经被生成好了,所以才能快速、直接的跳转到这个地址,实现函数调用。
C语言被称之为是静态语言也就是这么个理。
2、系统的C函数
在上篇文章 不知MachO怎敢说自己懂DYLD 已经提到了在dyld启动app的第二个步骤就是加载共享缓存库,共享缓存库包括Foundation框架, NSLog
是被包含在Foundation框架的。那么就可以确定一件事情,在我们将自己工程打包出的MachO文件中是不可能预先确定 NSLog
的地址的。
但是又因为C语言是静态的特性,没法在运行的时候实时获取共享缓存库中 NSLog
的地址。而共享缓存库的存在好处太大,既能节省大量内存,又能加快启动速度提升性能,不能弃之而不用。
为了解决这个问题,Apple使用了PIC( Position-independent code )技术,在第一次使用对应函数( NSLog
)的时候,从系统内存中将对函数( NSLog
)的内存地址取出,绑定到APP中对应函数( NSLog
)上,就可以实现正常的C函数( NSLog
)调用了。
既然有这么个过程,iOS系统可以动态的绑定系统C函数的地址,那么咱们就也能。
四、fishhook源码分析
1、fishhook的总体思路
Facebook的开源库 fishhook 就可以完美的实现这个任务。
先上一张官网原理图:
总体来说,步骤是这样的:
- 先找到四张表Lazy Symbol Pointer Table、Indirect Symbol Table、Symbol Table、String Table。
- MachO有个规律:Lazy Symbol Pointer Table中第index行代表的函数和Indirect Symbol Table中第index行代表的函数是一样的。
- Indirect Symbol Table中value值表示Symbol Table的index。
- 找到Symbol Table的中对应index的对象,其data代表String Table的偏移值。
- 用String Table的基值,也就是第一行的pFile值,加上Symbol Table的中取到的偏移值,就能得到Indirect Symbol Table中value(这个value代表函数的偏移值)代表的函数名了。
1、验证NSLog地址
下面就来验证一下在NSLog的地址是不是真的就存在Indirect Symbol Table中。 同样在NSLog处下好断点,打开汇编断点,运行代码。会发现断点断在如下入位置:
注:笔者的工程重新build了,MachO也重新生成,所以此处的截图和上文中断住NSLog的截图的地址不一样,这是正常情况。
可以发现NSLog的地址是 0x104d36010
,先记住这个值。
然后查看我们APP在内存中的偏移值。
利用 image list
命令列出所有image,第一个image就是我们APP的偏移值,也就是内存地址。
可以看到APP在内存中的偏移值为 0x104d30000
。
接着打开MachOView查看MachO中的Indirect Symbol Table中的value,如图:
其值为 0x100006010
,去除最高位得到的 0x6010
就是 NSLog
在MachO中的偏移值。 最后将 NSLog
在MachO中的偏移值于APP在内存中的偏移值相加就得到 NSLog
真实的内存地址:
0x6010
+ 0x104d30000
= 0x104d36010
最终证明,在Indirect Symbol Table的value中的值就是其对应的函数的地址!!!
2、根据MachO的表查找对应的函数名和函数地址
咱们还是用 NSLog
来距离查找。
1、Indirect Symbol Table
取出其data值 0000010A
,用10进制表示,结果为 266
,如图:
2、Symbol Table
在Symbol Table中找到下标(offset)为266的的对象,取出其data 0x124
,如图:
2、String Table
将在Symbols中得到的偏移值 0x124
加上String Table的首个地址 DC6C
,得到值 DD90
,然后找到pFile为 DD90
的值,如下两图:
上述就是根据MachO的表查找对应的函数名和函数地址全过程了。
3、源码分析
fishhook的源码总共只有250行左右,所以结合MachO慢慢看,其实一点也不费劲,在笔者的 demo 中有对其每一句函数的详细注释。当然也有对fishhook使用的demo。
所以笔者就不在此处对fishhook做太过详细的介绍了。只对其中一些关键参数和关键函数做介绍。
- fishhook为维护一个链表,用来储存需要hook的所有函数
// 给需要rebinding的方法结构体开辟出对应的空间 // 生成对应的链表结构(rebindings_entry),并将新的entry插入头部 static int prepend_rebindings(struct rebindings_entry **rebindings_head, struct rebinding rebindings[], size_t nel) 复制代码
- 根据linkedit的基值,找到对应的三张表:symbol_table、string_table和indirect_symtab :
// 找到linkedit的头地址 uintptr_t linkedit_base = (uintptr_t)slide + linkedit_segment->vmaddr - linkedit_segment->fileoff; // 获取symbol_table的真实地址 nlist_t *symtab = (nlist_t *)(linkedit_base + symtab_cmd->symoff); // 获取string_table的真实地址 char *strtab = (char *)(linkedit_base + symtab_cmd->stroff); // Get indirect symbol table (array of uint32_t indices into symbol table) // 获取indirect_symtab的真实地址 uint32_t *indirect_symtab = (uint32_t *)(linkedit_base + dysymtab_cmd->indirectsymoff); 复制代码
- 最核心的一个步骤,查找并且替换目标函数:
// 在四张表(section,symtab,strtab,indirect_symtab)中循环查找 // 直到找到对应的rebindings->name,将原先的函数复制给新的地址,将新的函数地址赋值给原先的函数 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) 复制代码
五、绑定系统C函数过程验证
上面说了这么多,那么咱们来验证一下系统C函数是不是真的会这样被绑定起来,并且看一看,是在什么时候绑定的。
同样,在第一次敲入 NSLog
函数的地方加上断点,在第二个 NSLog
处也加上断点:
运行工程后,使用 dis -s
命令查看该函数的汇编代码,并且继续查看其中第一次 b
指令,也就是函数调用的汇编,如图:
从上图就可以看到,在我们第一次调用 NSLog
的时候,系统确实会默认的调用 dyld_stub_binder
函数对 NSLog
进行绑定。
继续跳过这个断点,进入下一个 NSLog
的汇编断点处,同样利用 dis -s
命令查看该汇编:
得到答案:
系统确实会在第一次调用系统C函数的时候对其进行绑定!
还记得正文开始的时候的那个问题吗?
那么是不是系统C函数可以hook,而自定义的C函数就绝对不能hook了呢?
很显然,国内外大神那么多,肯定是能做到的,有兴趣的读者可以自行查阅Cydia Substrate。
这篇文章利用了一些LLDB命令行看了许多我们想看的内容,如 image list
, register read
还有 dis -s
,在我们正向开发中,LLDB就是一把利器,而在我们玩逆向的时候,LLDB就成为了我们某些是后的唯一途径了!所以,在下一篇文章中,笔者将会对LLDB进行更加详细的讲解,让大家看到LLBD的伟大。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 函数运行环境系统动态链接库版本太低?函数计算 fun 神助力分忧解难
- JavaScript图形实例:迭代函数系统生成图形
- PHP中exec()函数执行系统命令失败
- (译)使用渲染函数构建一个设计系统的排版布局
- 使用 deprecated 声明防止开发人员使用危险的系统函数
- 【golang-GUI开发】struct tags系统(二)qt的自定义组件和构造函数
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
GWT in Action
Robert Hanson、Adam Tacy / Manning Publications / 2007-06-05 / USD 49.99
This book will show Java developers how to use the Google Web Toolkit (GWT) to rapidly create rich web-based applications using their existing skills. It will cover the full development cycle, from ......一起来看看 《GWT in Action》 这本书的介绍吧!