最新消息:

[iOS Hacker] Mach-O 文件格式解析

iOS/MacOSX/移动安全 exchen 383浏览 0评论

Mach-O 文件格式解析

Mach-O 文件格式是苹果的 iOS 和 macOS 使用的一套可执行文件的文件格式,类似在 Windows 平台使用 PE 文件格式,Linux 平台使用 ELF 文件格式。Mach-O 文件格式主要的分布是 Mach Header、LoadCommand、Segment、Seciton,如下图所示(该图片来源苹果官网):

当一个文件同时支持多个 CPU 平台,比如同时支持 ARMV7、ARM64,就相当是两个 Mach-O 文件,编译器会编译两个 Mach-O 文件,然后合成一个 Fat 文件,如下图所示:

操作系统运行的时候会根据自身平台运行相应的 Mach-O 文件。macOS 系统自带一个 file 命令可以查看可执行文件支持的平台,查看 python 支持的平台

Xcode 在编译 iOS 程序可以选择同时支持 ARMV7 和 ARM64,编译 macOS 程序也可以选择同时支持 x86 和 x86_64,但是如果一个程序需要同时支持 iOS 和 macOS 的时候,Xcode 不能自动生成,可以使用 lipo 命令手动对文件进行合并。

由于每个 CPU 平台都是单独的一个 Mach-O 文件,然后合成的 Fat 文件,所以体积会变大,比如某个程序我们只需要支持 ARM64,就可以把其他平台给移除掉,这样就能起到 “瘦身” 的作用,使用 lipo 命令移除其他平台。

一、Fat Header

Xcode 自带一个 otool 工具,可以查看 Fat 头信息

1.1 fat_header

1.1.1 magic

magic 是一个标记,表示是 Fat 的文件类型, 是固定的 0xcafebabe 或者是 0xbebafeca。

1.1.2 nfat_arch

nfat_arch 表示包含了多个少 Mach-O 文件。

1.2 fat_arch

fat_header 之后,紧接着就是 fat_arch

1.2.1 cputype

cputype 表示支持的 CPU 类型,一般有 ARMV7, ARM64, X86, X86_64 这几种类型。

1.2.2 cpusubtype

cpusubtype 表示子 CPU 类型,一般有以下几种类型

1.2.3 offset

offset 表示是 当前架构的 Mach-O 文件的数据相对于文件开头的偏移位置,比如 16384,就表示是比 0x4000 开始的。

1.2.4 sizeof

sizeof 表示数据的大小。

1.2.5 align

表示数据的内存对齐边界。

二、Mach Header

使用 otool 查看 Mach Header

2.1 mach_header

2.1.1 magic

magic 和 fat_hader 里的 magic 类似,也是一个标记,32 位的值是 MH_MAGIC,64位的值是 MH_CIGAM_64。

2.1.2 cputype

mach_header 里的 cputype 与 fat_arch 里的 cputype 的含义完全一样。

2.1.3 cpusubtype

mach_header 里的 cpusubtype 与 fat_arch 里的 cpusubtype 的含义完全一样。

2.1.4 filetype

filetype 表示 Mach-O 的具体文件类型,如果是可执行文件就是 MH_EXECUTE,如果是动态库就是 MH_DYLIB

2.1.5 ncmds

ncmds 表示 Mach-O 文件中 load command (加载命令)的个数。

2.1.6 sizeofcmds

sizeofcmds 表示load command (加载命令) 占用的字节总大小。

2.1.7 flags

flags 表示文件的标志信息,取值如下:

2.2 mach_header_64

mach_header64 与 mach_header 的结构定义差不多,只是多了一个 reserved 字段,目前为系统保留字段。

三、LoadCommands

mach_header 之后是 LoadCommands(加载命令),使用 otool 查看 LoadCommands

load_command 结构信息如下:

cmd 表示 LoadCommand (加载命令)的类型,类型的不同结构体也会有所不同,有如下一些类型:

3.1 LC_SEGMENT

一个程序一般会分为多个段,不同类型的数据放入不同的段中,代码是放入 __TEXT 段中,段结构信息如下:

3.1.1 segname

segname 表示是段的名称,最长 16 字节大小。常见的段名称有 __PAGEZERO、__LINKEDIT、__TEXT、__DATA。

3.1.1.1 __PAGEZERO

__PAGEZERO 是在可执行文件有的,动态库里没有。这个段开始地址为0(NULL指针指向的位置),是一个不可读、不可写、不可执行的空间,能够在空指针访问时抛出异常。这个段的大小,32位上是 0x4000,64位上是 4G。如图所示:

3.1.1.2 __TEXT

__TEXT 是代码段,里面主要是存放代码的,该段是可读可执行,但是不可写。

3.1.1.3 __DATA

__DATA 是数据段,里面主要是存放数据,该段是可读可写,但不可执行。

3.1.1.4 __LINKEDIT

__LINKEDIT 段用于存放签名信息,该段是只可读,不可写不可执行。

3.1.2 vmaddr

vmaddr 表示段要加载的虚拟内存地址。

3.1.3 vmsize

vmsize 表示段所占的虚拟内存的大小。

3.1.4 fileoff

fileoff 表示段数据所有的文件中的偏移地址。

3.1.5 filesize

filesize 表示段数据的大小。

3.1.6 maxprot

maxprot 表示页面所需要的最高内存保护。

3.1.7 initprot

initprot 表示页面初始的内存保护。

3.1.8 nsects

nsects 表示该段包含了多少个节区。

3.1.9 flags

flags 表示段的标志信息。

3.1.10 Section Header

节头的信息结构如下:

当一个段包含多个节区,节区头信息会以数组的形式存储在段加载命令后面,有些段的节区是 0,就那不会有 Section Header,比如 __TEXT 段包含了 10 个节区,节区头信息的存储如下:

3.1.10.1 sectname

sectname 表示节区的名称,最长 16 字节大小。

3.1.10.2 segname

segname 表示节区所在的段名。

3.1.10.3 addr

addr 表示节区所在的内存地址。

3.1.10.4 size

size 表示节区所在的大小。

3.1.10.5 offset

offset 表示节区所在文件偏移。

3.1.10.6 align

align 表示节区的内存对齐边界。

3.1.10.7 reloff

reloff 表示重定位信息的文件偏移。

3.1.10.8 nreloc

nreloc 表示重定位条目的个数。

3.1.10.9 flags

flags 表示节区的标志属性

标志属性如果是 SG_PROTECTED_VERSION_1,表示该段是经过加密的。

3.1.10.10 reserved1

3.1.10.11 reserved2

3.2 LC_LOAD_DYLIB

LC_LOAD_DYLIB 和 LC_ID_DYLIB、LC_LOAD_WEAK_DYLIB、LC_REEXPORT_DYLIB 这三种类型使用的结构信息是一样的

dylib_command 除了 cmd 和 cmdsize,只有一个 dylib 结构

3.2.1 name

name 表示动态库的完整路径,它是一个 lc_str 联合结构

offset 表示字符串从 dylib 结构体开始计算的偏移位置,ptr 表示是动态库路径字符串指针,比如 dylib 结构体大小为 52,lcstr 里的 offset 为 24,52 - 24 = 28,相当于从 24 开始偏移 28 位,这 28 位就是动态库路径字符串的大小,/usr/lib/libsqlite3.dylib 字符串的实际大小是 25,加上 \0 是 26,因为是 4 字节对齐,所以 lcstr 占用的空间是 28 位,如下图所示。

3.2.2 timestamp

timestamp 表示动态库构建时的时间戳。

3.2.3 current_version

current_version 表示当前版本号。

3.2.4 compatibility_version

compatibility_version 表示兼容的版本号。

3.3 LC_MAIN

3.3.1 entryoff

entryoff 表示 main() 函数的文件偏移,下图的 entryoff 为 103107, 十六进制为 0x192c3,

用 IDA 查看文件,将基址设为 0 ,然后跳转到 0x192c3 代码查看,果然是 main 函数的地址,如下图:

3.3.2 stacksize

stacksize 表示初始的堆栈大小。

3.4 LC_ENCRYPTION_INFO

3.4.1 cryptoff

cryptoff 表示加密的文件编移。

3.4.2 cryptsize

cryptsize 表示加密的大小

3.4.3 cryptid

cryptid 表示加密的类型,如果没有加密,cryptid 为 0。比如我们自己用 Xcode 编译的程序 cryptid 都是 0,如果是从 appstore 下载的应用, cryptid 为 1。

3.5 LC_CODE_SIGNATURE

3.1.5.1 dataoff

dataoff 表示 __LINKEDIT 段的在文件里的偏移位置。

3.1.5.2 datasize

datasize 表示数据的大小。

3.6 LC_ID_DYLIB

LC_ID_DYLIB 是动态库的标识,只有动态库才有这个 LoadCommand,结构和 LC_LOAD_DYLIB 的结构是一样的,名称就是动态库自己的名称。比如 AdSupport 模块的 LC_ID_DYLIB 信息的名称是 /System/Library/Frameworks/AdSupport.framework/AdSupport,如下图:

如果是我们自己写的 framework 动态库,LC_ID_DYLIB 信息的名称一般是 @rpath/test.framework/test。

3.7 LC_UUID

随机生成的 UUID。

3.8 LC_DYLD_INFO_ONLY

3.9 LC_SYMTAB

LC_SYMTAB 是符号表和字符串表的偏移信息,结构如下:

3.9.1 symoff

symoff 表示符号表的偏移。

3.9.2 nsyms

nsyms 表示符号表条目的个数。

3.9.3 stroff

stroff 表示字符串表在文件中的偏移。

3.9.4 strsize

stroff 表示字符串表的大小。

3.10 LC_DYSYMTAB

3.11 LC_RPATH

LC_RPATH 表示程序运行时的查找路径,比如加载 xxx.dylib,会去什么目录去找这个文件。结构信息如下:

lc_str 表示是路径名称,结构信息和 3.2.1 name 的说明是一样的。Xcode 的编译选项添加 LD_RUNPATH_SEARCH_PATHS 的路径则实际在可执行文件里添加了 LC_RPATH,如下图:

3.12 LC_VERSION_MIN_IPHONEOS

支持最低的 iOS 版本号

3.13 LC_LOAD_DYLINKER

四、Symbol Table & String Table

Symbol 表的头信息是在 LoadCommand 里的 LC_SYMTAB,其中 symoff 表示符号表的偏移,如下图

符号表的结构是一个连续的列表,其中的每一项都是一个 struct nlist

上图的符号表地址为 205592,十六进制是 0x32318,来看一下符号表的信息,如下图:

4.1 n_strx

n_strx 表示符号名在字符串表中的偏移量,相当于的函数名称。从上图看第一个符号表的 n_strx 的值是 2,跳转到字符串表地址 246052,十六进制为 0x3c124。

4.2 n_type

n_type 表示类型。

4.3 n_sect

n_sect 表示是节的索引。

4.4 n_desc

n_desc 表示描述信息。

4.5 n_value

n_value 表示函数对应的地址。上面的符号表有一个函数名是 [NSString base64StringFromData2:length:] n_value 是 44592,对应十六进制 0xAE30,用 IDA 打开文件,将基址设置为 0x4000,跳转到 0xAE30 地址,可以看到果然是 [NSString base64StringFromData2:length:] 如下图:

文档历史

exchen,2017-11-10 编写

转载请注明:exchen's blog » [iOS Hacker] Mach-O 文件格式解析

发表我的评论
取消评论
表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址