0x00 摘要
人生无根蒂,飘如陌上尘。 分散逐风转,此已非常身。
— 陶渊明 《杂诗》
mach-o
格式是OS X系统上的可执行文件格式,类似于windows的PE
与linux的ELF
,如果不彻底搞清楚mach-o
的格式与相关知识,去做其他研究,无异于建造空中阁楼。
每个Mach-O文件斗包含一个Mach-O头,然后是载入命令(Load Commands),最后是数据块(Data)。
接下来就对整个Mach-O的格式做出详细的分析。
0x01 Mach-O格式简单介绍
Mach-O文件的格式如下图所示:
又如下几个部分组成:
- Header:保存了Mach-O的一些基本信息,包括了平台、文件类型、LoadCommands的个数等等。
- LoadCommands:这一段紧跟Header,加载Mach-O文件时会使用这里的数据来确定内存的分布。
- Data:每一个segment的具体数据都保存在这里,这里包含了具体的代码、数据等等。
0x02 Headers
2.1 数据结构
Headers的定义可以在开源的内核代码中找到。
1 |
|
根据mach_header
与mach_header_64
的定义,很明显可以看出,Headers的主要作用就是帮助系统迅速的定位Mach-O文件的运行环境,文件类型。
2.2 实例
使用工具分析一个mach-o文件来具体的看一下Mach-O Headers。
通过otool可以得到Mach header的具体的情况,但是可读性略微有一点差。
1 |
➜ bin otool -h git |
还有一个工具是MachOview可以看的更清楚一点。
- MagicNumber的值为0xFEEDFACF所以该文件是一个64位平台上的文件
- CPU Type和CPU SubType也很容易理解,运行在X86_64的CPU平台上
- File Type标示了该文件是一个可执行文件,后面具体分析
- Flags标示了这个MachO文件的四个特性,后面具体分析
2.3 具体参数
2.3.1 FileType
因为Mach-O文件不仅仅用来实现可执行文件,同时还用来实现了其他内容
- 内核扩展
- 库文件
- CoreDump
- …
他的源码定义如下:
解释一下一些常用到的文件类型。
File Type | 用处 | 例子 |
---|---|---|
MH_OBJECT | 编译过程中产生的*.obj文件 | gcc -c xxx.c 生成xxx.o文件 |
MH_EXECUTABLE | 可执行二进制文件 | /usr/bin/git |
MH_CORE | CoreDump | 崩溃时的Dump文件 |
MH_DYLIB | 动态库 | /usr/lib/里面的那些库文件 |
MH_DYLINKER | 连接器linker | /usr/lib/dyld文件 |
MH_KEXT_BUNDLE | 内核扩展文件 | 自己开发的简单内核模块 |
2.3.2 flags
Mach-O headers还包含了一些很重要的dyld的加载参数。代码中的定义如下:
1 |
#define MH_INCRLINK 0x2 |
同样简单的介绍几个比较重要的。
Flag Type | 含义 |
---|---|
MH_NOUNDEFS | 目标没有未定义的符号,不存在链接依赖 |
MH_DYLDLINK | 该目标文件是dyld的输入文件,无法被再次的静态链接 |
MH_PIE | 允许随机的地址空间 |
MH_ALLOW_STACK_EXECUTION | 栈内存可执行代码,一般是默认关闭的。 |
MH_NO_HEAP_EXECUTION | 堆内存无法执行代码 |
2.4 Headers小结
0x03 Load Commands
这是load_command的数据结构
1 |
struct load_command { |
Load Commands 直接就跟在Header后面,所有command占用内存的总和在Mach-O Header里面已经给出了。在加载过Header之后就是通过解析LoadCommand来加载接下来的数据了。我简单的看了一下内核中是如何解析macho数据的,抛开内核的实现细节,逻辑其实也十分简单。
1 |
static |
3.1cmdsize字段
这里主要看while循环刚刚进入的时候几行代码,来理解是如何通过load_command的cmd字段来解析Macho文件的数据。
3.2 cmd字段
1 |
switch(lcp->cmd) { |
从这一段代码可以看出,根据cmd字段的类型不同,使用了不同的函数来加载。简单的列出一张表看一看在内核代码中不同的command类型都有哪些作用。
Command类型 | 处理函数 | 用途 |
---|---|---|
LC_SEGMENT;LC_SEGMENT_64 | load_segment | 将segment中的数据加载并映射到进程的内存空间去 |
LC_LOAD_DYLINKER | load_dylinker | 调用/usr/lib/dyld程序 |
LC_UUID | load_uuid | 加载128-bit的唯一ID |
LC_THREAD | load_thread | 开启一个MACH线程,但是不分配栈空间。 |
LC_UNIXTHREAD | load_unixthread | 开启一个UNIX线程 |
LC_CODE_SIGNATURE | load_code_signature | 进行数字签名 |
LC_ENCRYPTION_INFO | set_code_unprotect | 加密二进制文件 |
0x04 Segment&Section
加载数据时,主要加载的就是LC_SEGMET活着LC_SEGMENT_64。其他的Segment的用途在上一节已经简单的介绍了,这里不做深究。
LCSEGMENT以及LC_SEGMENT_64的数据结构是这样的。
可以看出,这里大部分的数据是用来帮助内核将Segment映射到虚拟内存的。主要要关注的是nsects
字段,标示了Segment中有多少secetion。section是具体有用的数据存放的地方。
Section的数据结构如下:
除了同样有帮助内存映射的变量外,在了解Mach-O格式的时候,只需要知道不同的Section有着不同的作用就可以了。
Section | 作用 |
---|---|
__text | 代码 |
__cstring | 硬编码的字符串 |
__const | const 关键词修饰过的变量 |
__DATA.__bss | bss段 |
因为section类型已经是最小的分类了,还有更多复杂section段就不一一例举了,遇到没见过的section类型可以自行查找Apple文档。
0x05 小结
通过对Mach-O格式的仔细分析,可以更好的理解Mach-O文件的加载过程,为研究dyld或者其他OS X系统下的模块打好基础。
参考
1.mach-o文件加载的全过程(1)
2.Mach-O 可执行文件
3.iPhone Mach-O文件格式与代码签名
4.Dynamic Linking of Imported Functions in Mach-O
http://www.codeproject.com/Articles/187181/Dynamic-Linking-of-Imported-Functions-in-Mach-O
5.otool详解Mach-o文件头部
PS:
更多文章可以在我的学习分享博客http://BLOGIMAGE/