| 作者:小可长江,目前在 bilibili 做音视频研发,业余时间喜欢研究好的源码和逆向

打开姿势很重要

早些时候,iOS中一提到“黑魔法”、HOOK,很多人第一时间想到的就是 AOP RunTime MethodSwizzling 这些不明觉厉的东西,它们的基本用法其实都不难,真正难的是如何在合适的地方用好它们。

任何事物都有两面性,越强大其可能带来的隐患也越具有毁灭性。苹果提供的运行时机制固然大有用处,但如果在项目中滥用(更不是用来当做面试提升逼格的),很多时候只会适得其反,详细误区请参考 iOS界的毒瘤-MethodSwizzling

关于 MethodSwizzling 的用法在之前的文章中也有过讲解,请参考 MethodSwizzling的几种姿势。该方式更多的用于性能监测、 crash 的兼容和上报、反破解防护等一些工具的开发中,而在逆向中,在面对有相应安全防护措施的应用时,其用武之地比较有限。

无独有偶,“黑魔法”可不只有 RunTime ,今天我们来聊聊在逆向中常用的另一种HOOK方式:fishhook。

fishhook 背后的故事

(一)实现原理

fishhook 是 FaceBook 开源的可以动态修改 MachO 符号表的工具。fishhook 的强大之处在于它可以 HOOK 系统的静态 C 函数。

大家都知道 OC 的方法之所以可以 HOOK 是因为它的运行时特性,OC 的方法调用在底层都是 msg_send(id,SEL) 的形式,这为我们提供了交换方法实现(IMP)的机会,但 C 函数在编译链接时就确定了函数指针的地址偏移量(Offset),这个偏移量在编译好的可执行文件中是固定的,而可执行文件每次被重新装载到内存中时被系统分配的起始地址(在 lldb 中用命令 image List 获取)是不断变化的。运行中的静态函数指针地址其实就等于上述 Offset + Mach0 文件在内存中的首地址:

既然 C 函数的指针地址是相对固定且不可修改的,那么 fishhook 又是怎么实现 对 C 函数的 HOOK 呢?其实内部/自定义的 C 函数 fishhook 也 HOOK 不了,它只能HOOK Mach-O 外部(共享缓存库中)的函数。fishhook 利用了 MachO 的动态绑定机制(不清楚的同学看这里:MachO 文件结构详解dyld背后的故事&源码分析
):苹果的共享缓存库不会被编译进我们的 MachO 文件,而是在动态链接时才去重新绑定。苹果采用了 PIC(Position-independent code)技术成功让 C 的底层也能有动态的表现:

  • 编译时在 Mach-O 文件 _DATA 段的符号表中为每一个被引用的系统 C 函数建立一个指针(8字节的数据,放的全是0),这个指针用于动态绑定时重定位到共享库中的函数实现。

  • 在运行时当系统 C 函数被第一次调用时会动态绑定一次,然后将 Mach-O 中的 _DATA 段符号表中对应的指针,指向外部函数(其在共享库中的实际内存地址)。

fishhook 正是利用了 PIC 技术做了这么两个操作:

  • 将指向系统方法(外部函数)的指针重新进行绑定指向内部函数/自定义 C 函数。

  • 将内部函数的指针在动态链接时指向系统方法的地址。

这样就把系统方法与自己定义的方法进行了交换,达到 HOOK 系统 C 函数(共享库中的)的目的。

(二)用汇编解析过程

为了更好的理解 fishhook 是如何 HOOK 系统的 C 函数,我们以 HOOK NSLog 为例,从汇编着手来一步步去分析,为大家扒开 fishhook 实现 HOOK 系统 NSLog 的全过程。

注:对于非懒加载符号表,dyld 会在动态链接时就链接动态库
对于懒加载符号表,dyld 会在运行时函数第一次被调用时动态绑定一次
NSLog 在懒加载表中

1.验证系统的动态绑定:

新建一个空工程,写下这两行代码:

编译一下工程,在目录 Products 下将 .app 内的可执行文件拷出用 MachOView 打开:

记下 0x3028 这个偏移值,这就是用于重定向到共享库中的那个指针相对于 MachO文件的偏移量。

在两个 NSLog 处分别加上断点,将工程 Run 起来,把 Debug -> Debug Workflow -> Always Show Disassembly 勾选上,用于查看汇编信息,断点断住后获取 MachO 在内存中的首地址:

0x3028+0x000000010b0f7000 就是用于重定向到共享库中的那个指针的内存地址。此时我们查看该地址是否已经被重定向:

  1. 拿到该指针当前保存的值,iOS 的 CPU 是小端序,当前机型为 64 位 CPU,所以倒序读 8 个字节就是指针的值:0x010b0f89a0

  2. dis -s 是反汇编命令,我们发现此时该指针指向的函数正在调用系统动态绑定的函数

  3. 进一步查看调用函数详细信息:libdyld.dylib`dyld_stub_binder

这是在干嘛?没错,这就是第一次调用 NSLog 时系统去重新绑定位懒加载符号表中 NSLog 对应的指针所指向的位置。

接下来我们过掉第一次断点,让断点断在第二个 NSLog 处,再次查看符号表中该指针(依然是 0x3028+0x000000010b0f7000 这个地址)所指向的地址,

我们发现,它指向的地址由之前的 0x010b0f89a0 变为 0x010b491276 了,对应的函数也由之前的 dyld_stub_binder 变为 NSLog ,这意味着该函数的动态绑定已经完成。以上,我们验证了 iOS 的动态绑定全过程。

2.验证 fishhook 的重绑定:

我们将 fishhook文件拖入工程,并添加一个简单的绑定:

注意:修改文件后重新编译的 MachO 文件,符号表里的指针偏移值可能会改变,重新运行的程序内存首地址也会发生变化,需要你重新拿到它们计算得出指针新的内存地址。

我们运行起来之后点击屏幕进入上图所示断点,查看符号表中原本指向系统 NSLog 的指针指向:

此时该指针的指向被修改为我们自定义的函数 myNslog 了,而将系统重定位的外部函数地址保存到了另一个自定义函数指针 sys_nslog 中:

以上,我们通过断点分析汇编信息,验证了 fishhook 实现 HOOK 系统外部函数的思路。接下来我们结合 fishhook 的官方说明看它是如何根据字符串(方法名)找到对应指针在符号表中的偏移值的。

(三)fishhook 是如何根据字符串找到对应指针在符号表中的偏移值的?

fishhook 官方给了这张图:

这张图其实就是讲根据一个字符串(比如 "NSLog") 如何一步步找到其在 MachO 文件里对应指针的偏移值,大致步骤如下:

1) 在 String Table 中找到该字符串在 Symbols Table -> Symbols 中的位置:

用 0x4F9F-0x4F04 = 0x9B

2) 在 Symbols Table -> Symbols 中找到Data = 0x9B 的符号,其对应的 offset 值 122 (0x7A) 就是该符号在 Dynamic Symbols Table -> Indirect Symbols 表中的 Data 值

3) 在 Dynamic Symbols Table -> Indirect Symbols 表中找到 Data 值为 0x7A 的符号,其位于该表中的位置(第一个)就是它在懒加载表中对应的位置。

4) 懒加载表中对应位置的 Offset 值就是该指针最终的偏移量:

总结

今天我们结合 iOS 的共享缓存库中采用的 PIC 技术,介绍了 fishhook 对系统外部函数实现 HOOK 基本原理和具体过程,并通过反汇编命令一一验证了 iOS 的动态绑定过程和 fishhook 的重新绑定机制,最后把 fishhook 在符号表中查找指针偏移量的步骤做了演示。 愿你有所收获! 水平有限,请多指教~

文章链接

  • iOS界的毒瘤-MethodSwizzling
    https://juejin.im/entry/5a1fceddf265da43310d9985

  • MethodSwizzling的几种姿势 
    https://juejin.im/post/5c616552f265da2dd53fa4e7#heading-3

  • MachO--文件结构详解
    https://juejin.im/post/5c67e7efe51d45164c75993b

  • dyld背后的故事&源码分析
    https://juejin.im/post/5c727262e51d457139116208

推荐阅读

移动开发唱衰,iOS开发者如何涅槃重生?

Cocoapod 1.6 概览

看完这个你们团队的代码也很规范

共享文件原理_fishhook 的实现原理浅析相关推荐

  1. AbstractQueuedSynchronizer 原理分析 - Condition 实现原理

    1. 简介 Condition是一个接口,AbstractQueuedSynchronizer 中的ConditionObject内部类实现了这个接口.Condition声明了一组等待/通知的方法,这 ...

  2. 电机编码器调零步骤_编码器原理、霍尔应用原理、调整步骤三个方面进行解读编码器调试...

    电机中若具备电子铭牌功能,在应用中就可以直接使用,不需要需要调整编码器:如雷赛交流伺服电机具有电子铭牌功能,能自动识别电机型号,参数并对应匹配参数就能发挥伺服优异性能.若不具备电子铭牌功能的电机,则需 ...

  3. [有限元]虚位移原理和虚力原理的证明的统一逻辑

    原来的可能位移/可能力的约束方程是: 力边界上 可能力=常数1 位移边界上 可能位移=常数2 体内 可能平衡方程=常数3 所以可能功原理的右边有三项 由定义, 虚位移=可能位移1-可能位移2 虚力=可 ...

  4. v-model双向绑定原理_【Vue原理】VModel 白话版

    ↑点击上方 "神仙朱" 一起研究Vue源码吧 专注 Vue 源码分享,为了方便大家理解,分为了白话版和 源码版,白话版让大家可以轻松理解工作原理,源码版让大家更清楚内部操作和 Vu ...

  5. 计算机输入输出设备说课稿,信息技术七年级西交大版 第三节 计算机系统的组成与工作原理计算机系统及工作原理说课稿 (共15张PPT)...

    <信息技术七年级西交大版 第三节 计算机系统的组成与工作原理计算机系统及工作原理说课稿 (共15张PPT)>由会员分享,可在线阅读,更多相关<信息技术七年级西交大版 第三节 计算机系 ...

  6. 计算机指纹识别的原理步骤,指纹识别原理和过程

    指纹识别概念 指纹识别是生物识别的一种.不过其所分析的对象是指纹特征.指纹特征是最早被发现和应用的,所以指纹识别的历史较之其它识别技术要悠久的多.出现自动化的指纹识别系统到现在,目前的指纹识别技术已经 ...

  7. 计算机原理的拼音,微机原理课程,the course of microcomputer principles,音标,读音,翻译,英文例句,英语词典...

    化工原理课程是化学工业技术和化学工程科学发展的必然产物.十九世纪九十年代国外高等学校相继设置化学工程系,开出的课程大都是针对不同化工行业编写各自的生产工艺学,直到二十世纪初才明确认识到各行各业通用的物 ...

  8. 支持向量机原理(四)SMO算法原理

    支持向量机原理(一) 线性支持向量机 支持向量机原理(二) 线性支持向量机的软间隔最大化模型 支持向量机原理(三)线性不可分支持向量机与核函数 支持向量机原理(四)SMO算法原理 支持向量机原理(五) ...

  9. java8 stream运行原理之并行流原理详解

    上一篇文章<java8 stream运行原理之顺序流原理详解>介绍了顺序流的执行原理,本文接着上一篇介绍并行流的执行原理. 一.如何创建并行流 调用parallel()方法可以创建并行流, ...

最新文章

  1. SAP Spartacus加载delivery国家列表的处理逻辑
  2. 布隆过滤器 redis_redis布隆过滤器
  3. x=a%pq与x=a%p,x=a%q的关系(pq互质)
  4. OpenSSL 创建自签名证书
  5. Silverlight显示滚动条
  6. 软件测试技术案例教程 李海生 cd 源码 source,软件测试技术案例教程
  7. 华为数通笔记-VRF
  8. JuiceFS 如何帮助趣头条超大规模 HDFS 降负载
  9. Word文档里面如何给内容进行注释添加
  10. 学习微服务最好的方式:阅读《微服务架构设计模式》
  11. 苹果手机换电池对手机有影响吗_电池寿命真的影响手机性能~iPhone手机更换电池后性能对比...
  12. 【原创】JS 数字转换成英文写法(包含小数)
  13. 你是否希望大前端做服务器合租代管服务?
  14. Type-C快充诱电方案
  15. UOS系统适配-常用开发工具安装
  16. 2023届嵌入式笔面经一位双非本科生的秋招日记
  17. 云速建站:关于企业版的几点说明
  18. Codeforces Round #354 (Div. 2)-Theseus and labyrint
  19. 【观察】生态赋能与聚链共赢背后,解读 SAP 产业集群策略新价值
  20. ApplePay应用内购(inapp)支付流程图

热门文章

  1. 日志中出现乱码_合宙Luat | 乱码搞得一团糟?开源神器帮你轻松修复
  2. Java:File.separator作用相当于 ‘ \ ‘
  3. SpringBoot:解决日期转换问题和日期展示问题
  4. 安装python37路径报错_Robot framework安装python3.7导入HttpLibrary.HTTP报错
  5. 10 - java 权限修饰符
  6. c语言利用文件体写在桌面上,在C语言中怎样新建一个文件夹?
  7. Linux版本Oracle工具,Linux下oracle可视化操作工具sqldeveloper安装与配置
  8. Git 初学札记(十)—— Reset 回退的三种状态解析
  9. Effective Java(一)———— 代替构造器和Setter的构建器模式
  10. Java面试日常总结大杂烩