iOS逆向学习之九(深入研究Mach-O结构)

2020-04-13 11:35:03 蜻蜓队长

Mach-O基本结构回顾

在深入学习Mach-O文件之前,先来回顾一下之前学习的Mach-O的基本结构,可以到官网查看Mach-O文件的介绍

Mach-O的组成

Mach-O文件有三部分组成

  • Header中包含文件类型、目标架构类型等等基本信息
  • Load Commands是描述文件在虚拟内存中的逻辑结构和布局,相当于简介和目录索引
  • Raw Segment Data中存放了所有在Load Commands中定义的Segment所对应的原始数据

Mach-O深入探究

Header

在Mach-O文件中,Header部分存放了文件的基本描述信息,如下:

  • Magic Number代表当前Mach-O文件的架构是MH_MAGIC_64,所支持的架构是arm64架构
  • CPU Type、CPU SubType代表CPU的类型和子类型,在源码<mach/machine.h>中够可以看到具体的定义

  • File Type代表文件的类型,图中的文件类型表示可执行文件类型
  • Number of Load Commands 和 Size of Load Commands 表示Load Commands的数量和大小
  • Flags 代表动态链接器(dyld)的标志
  • Reserved 保留字段

Load Commands

Load Commands指定了文件在虚拟内存中的逻辑结构和布局,如下

在Load Commands中存储了各种段的基本信息,下面以LC_SEMENT_64(__PAGEZERO)中的信息为例

  • 最顶部的Command代表Load Command的类型是LC_SEMENT_64,具体含义是将文件中的段映射到进程地址空间
  • Command Size 表示当前Load Command本身的大小
  • Segment Name 是Load Command的名称,当前的Load Command名称为__PAGEZERO
  • VM Address 表示__PAGEZERO段加载到虚拟内存中的地址,从0x000000000开始
  • VM Size 表示__PAGEZERO段在虚拟内存中所占据的空间大小
  • File Offset 表示当前__PAGEZERO段在Mach-O文件中的位置。
  • File Size 表示__PAGEZERO段在Mach-O文件中的大小,此处File Size为0表示在Mach-O文件中并没有__PAGEZERO段,在Mach-O文件被加载进虚拟内存中,才会附加上__PAGEZERO段。
  • Maxinum VM Protection 表示当前段在虚拟内存中所需要的最高内存保护
  • Initial VM Protection 表示当前段的初始内存保护
  • Number of Sections 表示当前段中所包含的Section的数量
  • Flag 标志位

__PAGEZERO是Mach-O加载进内存之后附加的一块区域,它不可读,不可写,主要用来捕捉NULL指针的引用。如果访问__PAGEZERO段,会引起程序崩溃

Raw Segment Data

在Raw Segment Data中就存放了所有段的原始数据

  • __TEXT段中存放了所有函数代码
  • __DATA段中存放了所有全局变量信息

使用size 指令查看Mach-O内存分布

size -l -m -x Mach-O文件路径
复制代码

ASLR

什么是ASLR?

ASLR其实就是Address Space Layout Randomization,地址空间布局随机化。它是一种针对缓冲区溢出的安全保护技术,通过对堆、栈、共享库映射等线性区布局的随机化,通过增加攻击者预测目的地址的难度,防止攻击者直接定位攻击代码位置,达到阻止溢出攻击的目的的一种技术。在iOS 4.3开始引入ASLR技术

未使用ASLR技术时,Mach-O文件加载进内存后如何布局?

在未使用ASLR技术时,Mach-O被加载进内存后,是从地址0x000000000开始存放,前文说到,Mach-O文件本身是不存在__PAGEZERO的,在Mach-O文件被加载到虚拟内存之后,系统会给Mach-O文件分配一个__PAGEZERO,它的开始位置是0x000000000,结束位置是0x100000000。并且它的大小是固定的。

Mach-O本身的内容在虚拟内存中存放的开始位置从0x100000000开始,也就是紧接着__PAGEZERO的结束地址存放。而且在下图中,__TEXT段的File Offset为0,File Size为63062016,这代表着在Mach-O文件中,从0x000000000位置开始到0x003C24000为止存放的都是__TEXT段的内容。

__TEXT段在虚拟内存中存放的开始位置是0x100000000,终止位置是0x103C24000,这说明__TEXT段是原封不动的从Mach-O文件加载进虚拟内存中,紧接着__PAGEZERO存放的。

通过分析剩下的__DATA段、__LinkEDIT段等等可以得出以下结论

PS:在arm64架构中,__PAGEZERO段的终止位置是从0x100000000(8个0)而在非arm64架构中,__PAGEZERO段的终止位置是从0x4000(3个0)开始

使用了ASLR技术后,Mach-O文件加载进内存后如何布局?

在使用了ASLR技术之后,在Mach-O文件加载进内存之后,__PAGEZERO的开始位置就不是从0x000000000开始存放了,ASLR会随机产生一个地址偏移Offset,而__PAGEZERO的开始位置需要在0x000000000的基础上加上偏移量Offset的值,才是真正的存放地址。 假设随机偏移量Offset是0x000005000,那么__PAGEZERO的开始位置就是0x000005000,结束位置就是0x100005000。剩下的__TEXT段、__DATA段和__LINKEDIT段则依次偏移Offset即可,如下:

获取函数在虚拟内存中的真实内存地址

于Mach-O文件被加载进虚拟内存中时,由于使用了ASLR技术,导致内存地址产生Offset,所以要想获取函数的准确的内存地址,就需要知道当前具体的偏移量。然后使用以下公式就可得出函数在虚拟内存中的内存地址

函数的内存地址(VM Address) = File Offset + ASLR Offset + __PAGEZERO Size
复制代码
  • File Offset 表示当前函数在Mach-O文件中的存放位置
  • ASLR Offset 表示随机地址偏移量
  • __PAGEZERO Size 表示__PAGEZERO段的size

通常我们使用Hopper、IDA等工具查看Mach-O文件所看到的地址都是未使用ASLR的VM Address,要想获取函数的真实虚拟内存地址,就需要找到Mach-O加载进虚拟内存后的随机偏移量Offset

上图中函数test的起始地址是0x6558,也就是说它的File Offset为0x6558。这个是它在Mach-O文件中的地址偏移。

动态调试,获取程序ASLR的偏移量

运用上一章动态调试的知识,我们来一步一步获取ASLR的偏移量

  • 首先在Mac上使用tcprelay.py开启Mac端口号映射
python tcprelay.py -t 22:10088 9999:10089
复制代码
  • 然后通过SSH连接iPhone
ssh root@localhost -p 10088
复制代码
  • 在iPhone上使用启动Debugserver,将要动态调试的App附加到Debugserver上,此处以听云App为例
debugserver *:9999 -a ting
复制代码
  • 在Mac上启动LLDB,然后通过Mac的10089端口连接Debugserver服务
➜  ~ lldb
(lldb) process connect connect://localhost:10089
复制代码
  • 使用image list命令得到App可执行文件的路径
(lldb) image list -o -f grep | ting
[  0] 0x0000000000080000 /var/mobile/Containers/Bundle/Application/14C4F899-BD7B-41A4-BC1A-61892E7B943B/ting.app/ting(0x0000000100080000)
复制代码
  • 可以看出,0x0000000000080000就是听云可执行文件的起始地址,也就是ASLR的偏移量,然后,使用Hopper Disassmbler可以获取到未使用ASLR的地址,加上0x0000000000080000,就可以得到加载进内存之后的真实地址。

以上内容来自于网络,如有侵权联系即删除
相关文章

上一篇: 【译】Swift和函数式编程的精髓

下一篇: iOS逆向——shell重签名及代码注入

客服紫薇:15852074331
在线咨询
客户经理