内容简介:在上篇文章老规矩,片头先上福利:在开始正文之前,假设面试官问了一个问题:
在上篇文章 不知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的自定义组件和构造函数
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Powerful
Patty McCord / Missionday / 2018-1-25
Named by The Washington Post as one of the 11 Leadership Books to Read in 2018 When it comes to recruiting, motivating, and creating great teams, Patty McCord says most companies have it all wrong. Mc......一起来看看 《Powerful》 这本书的介绍吧!