1 什么是Mach-O
Mach-O其实是Mach Object文件格式的缩写,是mac以及iOS上可执行文件的格式, 类似于windows上的PE格式 (Portable Executable ), linux上的elf格式 (Executable and Linking Format)。
如果我们想对 Mach -O 文件有所了解,可以将我们打包好的 ipa 文件后缀改成 .zip,然后解压生成 Payload 文件,在其中就可以找到 exec 文件。或者找一个动态库的 framework 在其中也可以找到 exec 文件。
常见的MachO文件:
- 目标文件:.o
- 库文件:.a .dylib Framework
- 可执行文件:dyld .dsym
如何查看文件格式
file [文件路径/文件名]
2 MachOView
官网直接下载的MachOView.app打开后不停闪退,因此最好是直接去下载源码然后编译出来使用。
MachOView GitHub地址
下载后记得修改源码:
3 MachO文件结构
使用MachOView打开两个Xcode编译后生成的app可执行文件,文件结构分别如下:
根据图可以看出,同时支持多种指令集的MachO文件会针对每个指令集生成一个Executable,每一个Executable里的文件结构又是一样的。下面我们针对Executable文件来分析,先看一张官网的MachO文件结构图,可看出MachO文件主要由三部分组成:
- Header
- Load Commons
-
Data
3.1 Mach Header
选取了Mach64 Header来分析,图中未红字表明的flags表示的是标示位,主要与系统的加载、链接相关:
3.2 Load Commands
Load commands是一张包含很多内容的表,内容包括区域的位置、符号表、动态符号表等。这里就是二进制文件加载进内存要执行的一些指令。这里的指令主要在负责我们 APP 对应进程的创建和基本设置(分配虚拟内存,创建主线程,处理代码签名/加密的工作),然后对动态链接库(.dylib 系统库和我们自己创建的动态库)进行库加载和符号解析的工作。
字段名称及意义分别如下:
名称 | 含义 |
---|---|
LC_SEGMENT_64 | 将文件中(32位或64位)的段映射到进程地址空间中 |
LC_DYLD_INFO_ONLY | 动态链接相关信息 |
LC_SYMTAB | 符号地址 |
LC_DYSYMTAB | 动态符号表地址 |
LC_LOAD_DYLINKER | 使用谁加载,我们使用dyld |
LC_UUID | 文件的UUID |
LC_VERSION_MIN_MACOSX | 支持最低的操作系统版本 |
LC_SOURCE_VERSION | 源代码版本 |
LC_MAIN | 设置程序主线程的入口地址和栈大小 |
LC_LOAD_DYLIB | 依赖库的路径,包含三方库 |
LC_FUNCTION_STARTS | 函数起始地址表 |
LC_CODE_SIGNATURE | 代码签名 |
首先看下Load Commands目录结构:
从上图可知 Load Commands 主要包含了有多个 Segment 段,每个中又包含了多个 Section 段。每一部分都是系统执行指令。其中 LC_SEGMENT 包含空指针陷阱
__TEXT段主要包含程序代码和只读的常量,这个段的内容如果是系统动态库的内容那么所有进程公用
__DATA 段主要包含全局变量和静态变量,这个段的内容每个进程单独进行维护
__LINKEDIT 主要包含链接器使用的符号和其他的表(比如函数名称、地址等) 这个段的内容也是可以多进程公用的。
此外还需介绍下和 SEGMENT 并列的一些比较重要的指令:
LC_LOAD_DYLINKER 该字段标明我们的MachO是被谁加载进去的。
可以理解为LC_LOAD_DYLINKER指向的地址是微信APP加载小程序的引擎,而我们的MachO是小程序。在上图中可以看到我们的Demo1的LC_LOAD_DYLINKER指向的地址就是dyld
,dyld
确实是用来加载我们app的。LC_LOAD_DYLIB
该字段标记了所有动态库的地址,只有在LC_LOAD_DYLIB中有标记,我们MachO外部的动态库(如:Framework)才能被dyld
正确的引用,否则dyld
不会主动加载。(因此很多通过这个知识点来做代码注入)LC_MAIN 是在所有的库都加载完成后,有其中的指令启动程序的主线程。我们的程序也是在这个函数之后才开始执行 main() 函数的。
LC_CODE_SIGNATURE 我想每个 iOSer 都知道代码签名的机制,其实代码签名的校验也是在这个指令下进行。实际上指令会把整个文件进行 hash 化处理并签名,在运行时去验证签名的正确性。
3.3 Data
Data 通常是对象文件中最大的部分,包含Segement的具体数据,如静态C字符串,带参数/不带参数的OC方法,带参数/不带参数的C函数。
可以看到,全局静态C字符,方法里面的字符串都被保存在data段的cstring里了,哪怕是%d,%s等等这样的参数类型字符串也被保存在内,但所有同样的字符串只会被保存一次:
同样所有的OC方法都被保存在methname里了:
4 结语
暂时通过MachOView初步窥探了一下MachO文件的结构,理解MachO文件结构是对后续学习MachO文件加载、dyld等建立知识基础。好多知识对于自己目前的知识水平感觉真的是看着像都懂了,但一问细节却又什么都不懂,书读百遍其义自见吧。
参考文章:
iOS逆向(5)-不知MachO怎敢说自己懂DYLD
理解 Mach-O 并提高程序启动速度