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

Mach-O基本结构回顾

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

Mach-O的组成

Mach-O文件有三部分组成python

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

Mach-O深刻探究

Header

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

  • 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)中的信息为例bash

  • 最顶部的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段,会引发程序崩溃markdown

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技术app

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

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

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

而__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,就能够获得加载进内存以后的真实地址。
相关文章
相关标签/搜索