Mach-O 简介
Mach-O
其实是Mach Object
文件格式的缩写,是MacOS
和iOS
上一种用于可执行文件、目标代码、动态库的文件格式。类似于Windows上的PE格式
(Protable Executable),Linux上的ELF格式
(Executable and Linking Format)。Mach-O
作为a.out格式的替代,提供了更强的扩展性。
Mach-O 文件类型
属于Mach-O
格式的常见文件:
- 目标文件 *.o
- 库文件
- *.a
- *.dylib
- *.framework
- 可执行文件
- dyld
- *.dsym
Mach-O 文件结构
由上图可以看出,Mach-O
主要分三大块:
- Header:保存
Mach-O
的一些基本信息,包括平台、文件类型、指令数、指令总大小、dyld标记Flags等。 - Load commands:加载
Mach-O
文件时会使用这部分数据来确定内存分布,对系统内核加载器和动态连接器起指导作用。 - Data:每个Segment的具体数据保存在这里,包含具体的代码、数据等。
Header
Header的数据结构如下:
/*
* The 32-bit mach header appears at the very beginning of the object file for
* 32-bit architectures.
*/
struct mach_header {
uint32_t magic; /* mach magic number identifier */
cpu_type_t cputype; /* cpu specifier */
cpu_subtype_t cpusubtype; /* machine specifier */
uint32_t filetype; /* type of file */
uint32_t ncmds; /* number of load commands */
uint32_t sizeofcmds; /* the size of all the load commands */
uint32_t flags; /* flags */
};
/* Constant for the magic field of the mach_header (32-bit architectures) */
#define MH_MAGIC 0xfeedface /* the mach magic number */
#define MH_CIGAM 0xcefaedfe /* NXSwapInt(MH_MAGIC) */
/*
* The 64-bit mach header appears at the very beginning of object files for
* 64-bit architectures.
*/
struct mach_header_64 {
uint32_t magic; /* mach magic number identifier */
cpu_type_t cputype; /* cpu specifier */
cpu_subtype_t cpusubtype; /* machine specifier */
uint32_t filetype; /* type of file */
uint32_t ncmds; /* number of load commands */
uint32_t sizeofcmds; /* the size of all the load commands */
uint32_t flags; /* flags */
uint32_t reserved; /* reserved */
};
/* Constant for the magic field of the mach_header_64 (64-bit architectures) */
#define MH_MAGIC_64 0xfeedfacf /* the 64-bit mach magic number */
#define MH_CIGAM_64 0xcffaedfe /* NXSwapInt(MH_MAGIC_64) */
各变量释义:
名称 | 定义 |
---|---|
magic | Mach-O魔数。如: FAT:0xcafebabe ARMv7:0xfeedface ARM64:0xfeedfacf |
cputype、cpusubtype | CPU架构及子版本 |
filetype | 文件类型,共有11种宏定义类型,如: MH_EXECUTABLE(可执行二进制文件) MH_OBJECT(目标文件) MH_DYLIB(动态库) |
ncmds | 加载命令的数量 |
sizeofcmds | 所有加载命令的大小 |
flags | dyld加载需要的一些标记,有28种宏定义,具体看源码,其中MH_PIE表示启用ASLR地址空间布局随机化 |
reserved | 64位保留字段 |
如何查看Mach-O文件的header
信息
- 使用
objdump
指令
Mac ~/testDemo.app objdump --macho -private-header testDemo
Mach header
magic cputype cpusubtype caps filetype ncmds sizeofcmds flags
MH_MAGIC_64 ARM64 ALL 0x00 EXECUTE 23 2888 NOUNDEFS DYLDLINK TWOLEVEL PIE
- 使用
otools
指令
Mac ~/testDemo.app otool -h testDemo
testDemo:
Mach header
magic cputype cpusubtype caps filetype ncmds sizeofcmds flags
0xfeedfacf 16777228 0 0x00 2 23 2888 0x00200085
- 使用MachOView查看
Load Commands
数据结构如下:
/*
* The load commands directly follow the mach_header. The total size of all
* of the commands is given by the sizeofcmds field in the mach_header. All
* load commands must have as their first two fields cmd and cmdsize. The cmd
* field is filled in with a constant for that command type. Each command type
* has a structure specifically for it. The cmdsize field is the size in bytes
* of the particular load command structure plus anything that follows it that
* is a part of the load command (i.e. section structures, strings, etc.). To
* advance to the next load command the cmdsize can be added to the offset or
* pointer of the current load command. The cmdsize for 32-bit architectures
* MUST be a multiple of 4 bytes and for 64-bit architectures MUST be a multiple
* of 8 bytes (these are forever the maximum alignment of any load commands).
* The padded bytes must be zero. All tables in the object file must also
* follow these rules so the file can be memory mapped. Otherwise the pointers
* to these tables will not work well or at all on some machines. With all
* padding zeroed like objects will compare byte for byte.
*/
struct load_command {
uint32_t cmd; /* type of load command */
uint32_t cmdsize; /* total size of command in bytes */
};
Load Commands
的数目和总大小已经在Header
中记录,见ncmds
和sizeofcmds
。所有加载指令都是以cmd、cmdsize开头,cmd字段表示该指令的类型,cmdsize字段以字节为单位,主要记录偏移量,以便于Load Commands
指针查找下一条加载指令。32位架构的cmdsize是以4字节的倍数,64位结构的cmdsize是以8字节的倍数,不够用0填充。
Load Commands
中常见的加载指令:
-
LC_SEGMENT_64
:定义一段(Segment),加载后被映射到进程的内存空间中,包括里面的节(Section) -
LC_DYLD_INFO_ONLY
:记录有关链接的信息,包括在__LINKEDIT中动态链接的相关信息的具体偏移与大小(重定位,绑定,弱绑定,懒加载绑定,导出信息等),ONLY表示该指令是程序运行所必需的。 -
LC_SYMTAB
:定义符号表和字符串表,链接文件时被dyld使用,也用于调试器映射符号到源文件。符号表定义的本地符号仅用于调试,而已定义和未定义的external符号被链接器使用 -
LC_DYSYMTAB
:将符号表中给出符号的额外信息提供给dyld -
LC_LOAD_DYLINKER
:dyld的默认路径 -
LC_UUID
:Mach-O唯一ID -
LC_VERSION_MIN_IPHONES
:系统要求的最低版本 -
LC_SOURCE_VERSION
:构建二进制文件的源代码版本号 -
LC_MAIN
:应用程序入口,dyld的_main函数获取该地址,然后跳转 -
LC_ENCRYPTION_INFO_64
:文件加密标志,加密内容偏移和大小 -
LC_LOAD_DYLIB
:依赖的动态库,含动态库名,版本号等信息 -
LC_RPATH
:@rpath搜索路径 -
LC_DATA_IN_CODE
:定义在代码段内的非指令的表 -
LC_CODE_SIGNATURE
:代码签名信息
如何查看Mach-O文件的Load Command
信息
- 使用
objdump
指令
objdump --macho --private-headers 【Mach-O路径】
- 使用
otool
指令
Mac ~/testDemo.app otool -l testDemo
- 使用MachOView查看
Data
由多个二进制组成,逐一排列,包含__TEXT代码、__DATA代码、符号表,存储实际的代码和数据
数据结构如下:
struct section_64 { /* for 64-bit architectures */
char sectname[16]; /* 节名 */
char segname[16]; /* 所属段名 */
uint64_t addr; /* 映射到虚拟地址的偏移 */
uint64_t size; /* 节的大小 */
uint32_t offset; /* 节在当前架构文件中的偏移 */
uint32_t align; /* 节的字节对齐大小n,2^n */
uint32_t reloff; /* 重定位入口的文件偏移 */
uint32_t nreloc; /* 重定位入口个数 */
uint32_t flags; /* 节的类型和属性*/
uint32_t reserved1; /* reserved (for offset or index) */
uint32_t reserved2; /* reserved (for count or sizeof) */
uint32_t reserved3; /* 保留位,以上两同理 */
};
Section节已经是最小的分类了,Data的大部分数据内容集中在__TEXT、__DATA这两段中。
__TEXT节 | 含义 |
---|---|
__text | 程序可执行代码区域 |
__stubs | 间接符号存根,用于跳转到懒加载指针表 |
__stubs_helper | 懒加载符号加载辅助函数 |
__cstring | 只读的C字符串,包含OC的部分字符串和属性名 |
__DATA节 | 含义 |
---|---|
__nl_symbol_ptr | 非懒加载指针表,dyld加载时立即绑定值 |
__la_symbol_ptr | 懒加载指针表,第1次调用才绑定值 |
__got | 非懒加载全局指针表 |
__mod_init_func | constructor函数 |
__cfstring | OC字符串 |
Mach-O 学习使用过程中用到的指令
file命令
功能:用于辨识文件类型
语法:
file [-bcLvz][-f <名称文件>][-m <魔法数字文件>...][文件或目录...]
-
参数:
-
-b
:列出辨识结果时,不显示文件名称 -
-c
:详细显示指令执行过程,便于排错或分析程序执行的情形 -
-f<名称文件>
:指定名称文件,其内容有一个或多个文件名称时,让file依序辨识这些文件,格式为每列一个文件名称 -
-F
:使用指定分隔符号替换输出文件名后的默认的":"分隔符 -
-L
:直接显示符号连接所指向的文件的类别 -
-m<魔法数字文件>
:指定魔法数字文件 -
-v
:显示版本信息 -
-z
:尝试去解读压缩文件的内容 -
-i
:输出mime类型的字符串 -
\[文件或目录...\]
:要确定类型的文件列表,多个文件之间使用空格分开,可以使用shell通配符匹配多个文件
-
示例:
//不加参数
[root@localhost ~]# file install.log
install.log: UTF-8 Unicode text
//-b:列出辨识结果时,不显示文件名称
[root@localhost ~]# file -b install.log
UTF-8 Unicode text
//-i:输出mime类型的字符串
[root@localhost ~]# file -i install.log
install.log: text/plain; charset=utf-8
//-F:使用指定分隔符号替换输出文件名后的默认的":"分隔符
[root@localhost ~]# file -F "--" install.log
install.log-- ASCII text
[root@localhost ~]# file -F "++" install.log
install.log++ ASCII text
otool命令
- 功能:用来查看可执行文件的Mach-O信息
- 语法:
otool [-arch arch_type] [-fahlLDtdorSTMRIHGvVcXmqQjCP] [-mcpu=arg] [--version] <object file> ...
- 参数:
-f print the fat headers
-a print the archive header
-h print the mach header
-l print the load commands
-L print shared libraries used
-D print shared library id name
-t print the text section (disassemble with -v)
-x print all text sections (disassemble with -v)
-p <routine name> start dissassemble from routine name
-s <segname> <sectname> print contents of section
-d print the data section
-o print the Objective-C segment
-r print the relocation entries
-S print the table of contents of a library (obsolete)
-T print the table of contents of a dynamic shared library (obsolete)
-M print the module table of a dynamic shared library (obsolete)
-R print the reference table of a dynamic shared library (obsolete)
-I print the indirect symbol table
-H print the two-level hints table (obsolete)
-G print the data in code table
-v print verbosely (symbolically) when possible
-V print disassembled operands symbolically
-c print argument strings of a core file
-X print no leading addresses or headers
-m don't use archive(member) syntax
-B force Thumb disassembly (ARM objects only)
-q use llvm's disassembler (the default)
-Q use otool(1)'s disassembler
-mcpu=arg use `arg' as the cpu for disassembly
-j print opcode bytes
-P print the info plist section as strings
-C print linker optimization hints
--version print the version of /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/otool
- 示例:
1、查看App所使用的动态库
Mac ~/testDemo.app otool -L testDemo
testDemo:
/System/Library/Frameworks/Foundation.framework/Foundation (compatibility version 300.0.0, current version 1774.101.0)
/usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1292.60.1)
/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation (compatibility version 150.0.0, current version 1774.101.0)
/System/Library/Frameworks/UIKit.framework/UIKit (compatibility version 1.0.0, current version 4006.0.0)
2、查看ipa是否已经砸壳
Mac ~/testDemo.app otool -l testDemo| grep crypt
cryptoff 16384
cryptsize 16384
cryptid 0
其中:cryptid
为0表示已经砸壳。cryptid
为1表示未砸壳。
3、 查看Mach-O头部信息
Mac ~/testDemo.app otool -h testDemo
testDemo:
Mach header
magic cputype cpusubtype caps filetype ncmds sizeofcmds flags
0xfeedfacf 16777228 0 0x00 2 23 2888 0x00200085
通用二进制文件
通用二进制(Universal binary)是苹果公司提出的一种程序代码,使程序能以本地程序的形式运行在使用PowerPC或者英特尔微处理器(x86)的麦金塔电脑上,在同一个程序包中同时为两种架构提供最理想的性能。硬件方面,苹果电脑公司已经将其产品线上的所有麦金塔电脑在2006年内转为英特尔处理器,相对应的软件方面,苹果最早是在2005年世界开发者大会(WWDC)上就发布了通用二进制的内容来适应这种转换。 当程序在操作系统中运行后,将自动检测通用二进制代码,根据使引用的架构自动选择合适的代码来执行,实现无损的本地程序运行速度
- 查看当前Mach-O的包含的架构信息
lipo -info 【MachO文件】
//示例
lipo -info WeChat
Architectures in the fat file: WeChat are: armv7 armv7s arm64
- 当Mach-O包含多种架构时,可对其进行架构拆分
lipo [MachO文件] -thin 架构 -output 输出文件路径
//示例
lipo WeChat -thin arm64 -output WeChat_arm64
lipo -info WeChat_arm64
Architectures in the fat file: WeChat_arm64 are: arm64
lipo WeChat -thin armv7 -output WeChat_armv7
lipo -info WeChat_armv7
Architectures in the fat file: WeChat_armv7 are: armv7
- 可对多种架构的Mach-O进行合并
lipo -create MachO1 MachO2 -output 输出文件路径
//示例
lipo -create WeChat_arm64 WeChat_armv7 -output WeChat_64_v7
lipo -info WeChat_64_v7
Architectures in the fat file: WeChat_64_v7 are: armv7 arm64
总结
-
Mach-O
是一种文件格式,包含目标文件、可执行文件、静态库、动态库等,可用file
命令来查看文件类型。 -
Mach-O
文件结构包含三部分:- Header:用来快速确定该文件的CPU类型、文件类型
- Load Commands:指示加载器如何设置并加载二进制数据
- Data:存放代码、数据、字符串常量、类、方法等数据
-
file
命令:用于辨识文件类型 -
otool
命令:用来查看可执行文件的Mach-O
信息 -
通用二进制
是集合多种架构,也称为”胖二进制“,可通过lipo
命令进行架构的查看、拆分、合并
等操作