常见可执行文件

EXECUTABLE FORMATMAGICUSED FOR
PE32/PE32+MZPortable executables: The native format in Win- dows and Intel’s Extensible Firmware Interface (EFI) binaries. Although OS X does not support this format, its boot loader does and loads boot.efi.
ELF\x7FELFExecutable and Library Format: Native in Linux and most UNIX flavors. ELF is not supported on OS X.
Script#!UNIX interpreters, or script: Used primarily for shell scripts, but also common for other inter- preters such as Perl, AWK, PHP, and so on. The kernel looks for the string following the #!, and executes it as a command. The rest of the file is passed to that command via standard input (stdin).
Universal (fat) binaries0xcafebabe (Little-Endian)
0xbebafeca (Big-Endian)
Multiple-architecture binaries used exclusively in OS X.
Mach-O0xfeedface (32-bit)
0xfeedfacf (64-bit)
OS X native binary format.

引用《深入MAX OSX & iOS操作系统》里的一张表

在上面这些可执行文件格式中,OS X现在支持最后面三种:解释器脚本格式、通用二进制格式和Mach-O格式。解释器脚本格式实际上是一种特殊形式的二进制格式,因为它们只是指向“真正的”二进制文件的脚本,脚本最终会被二进制执行。这里就只需讨论两种格式,那就是通用二进制格式和Mach-O格式。

PE32/PE32+

ELF

Script

通用二进制

由于设备架构的不断变化,由以前的32位到64位,还有arm指令集升级,单一的一种二进制文件无法同时兼容32位和64设备,也不能同时对多种硬件架构进行优化,因此产生了通用二进制文件这种可执行文件格式,可以兼容所有设备。之所以能够兼容是因为含有多个不同架构的独立二进制文件,由于同时包含多个架构的可执行文件,因此通用二进制体积较大,所有又称为胖二进制(fat binaries)。执行时操作系统只会加载针对当前架构最优的二进制文件到内存中,其它不相关架构的二进制代码并不会被加载。

Mach-O二进制

Mach-O 是苹果公司自己维护的且独有的二进制格式,它是 Mach Object File Format 的简写,这是一个源于 NeXTSTEP 的项目。苹果官方对于 Mach-O 相关的文档很少,不过好在有开源的 XNU 源码,里面可以找到一些 Mach-O 相关的信息,如果感兴趣可以阅读一下

通用二进制格式

如何识别二进制文件是通用二进制?

通用二进制文件最前面都有固定的结构信息(fat_header),包含了二进制文件的基本信息,比如包含架构数,单个架构的二进制文件的大小,文件起始偏移量,适应的处理器架构版本等等。通用二进制文件进行了内存边界对齐,内核加载二进制文件的第一个页面就可以读取文头出相关信息。

fat_header

通用二进制文件格式定义在 <mach-o/fat.h>

#define FAT_MAGIC	0xcafebabe
#define FAT_CIGAM	0xbebafeca	/* NXSwapLong(FAT_MAGIC) */

struct fat_header {
	uint32_t	magic;		/* FAT_MAGIC */
	uint32_t	nfat_arch;	/* number of structs that follow */
};

struct fat_arch {
	cpu_type_t	cputype;	/* cpu specifier (int) */
	cpu_subtype_t	cpusubtype;	/* machine specifier (int) */
	uint32_t	offset;		/* file offset to this object file */
	uint32_t	size;		/* size of this object file */
	uint32_t	align;		/* alignment as a power of 2 */
};

magic

魔数,固定为 0xcafebabe0xbebafeca,这个值用来标记这个是一个通用二进制文件

nfat_arch

通用二进制文件中包含的架构数量,单个架构的二进制文件信息用fat_arch结构表示,fat_arch紧跟在fat_header后面

struct fat_arch {
	cpu_type_t	cputype;	/* cpu specifier (int) */
	cpu_subtype_t	cpusubtype;	/* machine specifier (int) */
	uint32_t	offset;		/* file offset to this object file */
	uint32_t	size;		/* size of this object file */
	uint32_t	align;		/* alignment as a power of 2 */
};
字段说明
cputypecpu类型,定义在 <mach/machine.h> 文件中
cpusubtypecpu子类型,定义在 <mach/machine.h> 文件中
offset二进制代码在整个通用二进制文件中的偏移量
size单个二进制代码的大小
align对齐边界大小,早期的OSX和iOS系统中,物理和虚拟内存都按照4K来分页的,基于A7、A8处理器(32位)的系统,物理内存按照4K来分页,虚拟内存按照16K来分页。基于A9及更高版本处理器(64位)后的系统,物理和虚拟内存都是16K来进行分页的。

示例

包含 ARM 和 ARM64 两种架构的通用二进制文件。

header结构:
fat-binaries

二进制数据:
macho-hex

指令集

指令集对应设备

指令集支持设备
armv6iPhone, iPhone 3G, iPod 1G/2G
armv7iPhone 3GS, iPhone 4, iPhone 4S, iPod 3G/4G/5G, iPad, iPad 2, iPad 3, iPad Mini
armv7siPhone 5, iPhone 5c, iPad 4
arm6464位设备,iPhone X,iPhone 8(Plus),iPhone 7(Plus),iPhone 6(Plus),iPhone 6s(Plus), iPhone 5s, iPad Air(2), Retina iPad Mini(2,3)
arm64eA12处理器,iPhone XS\XR\XS Max,iPhone 11 \11 Pro\11 Pro Max
i38632位模拟器
x86_6464位模拟器

如今app已经不再支持32位了,只需要64位即可,即arm64。机器对指令集是向下兼容的,arm64指令集也是可以运行在A12及更新的处理器上的,只是效率没那么高。如果考虑到包体积大小,现在直接选择arm64指令集就可以了,如果要考虑发挥A12处理器的性能,那么arm64和arm64e就足够。

查看二进制架构信息

  • lipo 命令
lipo -detailed_info Test         
Fat header in: Test
fat_magic 0xcafebabe
nfat_arch 2
architecture armv7
    cputype CPU_TYPE_ARM
    cpusubtype CPU_SUBTYPE_ARM_V7
    offset 16384
    size 71696
    align 2^14 (16384)
architecture arm64
    cputype CPU_TYPE_ARM64
    cpusubtype CPU_SUBTYPE_ARM64_ALL
    offset 98304
    size 71824
    align 2^14 (16384)
  • otool 命令
 otool -hv Test         
Mach header
      magic cputype cpusubtype  caps    filetype ncmds sizeofcmds      flags
   MH_MAGIC     ARM         V7  0x00     EXECUTE    29       2848   NOUNDEFS DYLDLINK TWOLEVEL PIE
Mach header
      magic cputype cpusubtype  caps    filetype ncmds sizeofcmds      flags
MH_MAGIC_64   ARM64        ALL  0x00     EXECUTE    29       3368   NOUNDEFS DYLDLINK TWOLEVEL PIE    

Mach-O二进制格式

基本组成

Mach-O文件有它固定的文件格式,结构主要包含以下三个区域:

  • Header
    头部,是一个mach_header类型数据结构,这个结构体指明了Mach-O文件的相关元信息,比如架构、CPU信号、文件大小等等信息

  • Load commands
    加载命令,指定文件的逻辑结构和文件在虚拟内存中的布局,简单来说,就是如何加载每个段的数据

  • Data
    数据区,包含多个段(Segment),比如__TEXT、__DATA等,每个段又包含多个区(Section),包含加载命令中定义的段的原始数据。

Mach-O文件的最前面是头部信息(Header),紧接着的是加载命令(Load commands),加载命令通常有很多个,再后面就是数据区域,数据区(Data)包含多个段(Segment),而每一个段中又可能包含多个区(Section)。引用一张经典的Mach-O结构图:
macho_arch

查看Segment信息

size命令查看Segment&Section信息

size -x -l -m NewDriver4iOS

Segment __PAGEZERO: 0x100000000 (vmaddr 0x0 fileoff 0)
Segment __TEXT: 0x48cc000 (vmaddr 0x100000000 fileoff 0)
	Section __text: 0x403a87c (addr 0x100004000 offset 16384)
	Section __stubs: 0x504c (addr 0x10403e87c offset 67364988)
	Section __stub_helper: 0x465c (addr 0x1040438c8 offset 67385544)
	Section __cstring: 0x25faaa (addr 0x104047f30 offset 67403568)
	Section __objc_methname: 0x1c2a09 (addr 0x1042a79da offset 69892570)
	Section __ustring: 0x3172e (addr 0x10446a3e4 offset 71738340)
	Section __const: 0x20e428 (addr 0x10449bb20 offset 71940896)
	Section __objc_classname: 0x37f0e (addr 0x1046a9f48 offset 74096456)
	Section __objc_methtype: 0x3d3bd (addr 0x1046e1e56 offset 74325590)
	Section __gcc_except_tab: 0x11b0c4 (addr 0x10471f214 offset 74576404)
	Section __dof_RACSignal: 0x37b (addr 0x10483a2d8 offset 75735768)
	Section __dof_RACCompou: 0x2e8 (addr 0x10483a653 offset 75736659)
	Section __unwind_info: 0x8ec84 (addr 0x10483a93c offset 75737404)
	Section __eh_frame: 0x848 (addr 0x1048c95c0 offset 76322240)
	total 0x48c5deb
Segment __DATA: 0xdbc000 (vmaddr 0x1048cc000 fileoff 76333056)
	Section __got: 0x19d8 (addr 0x1048cc000 offset 76333056)
	Section __la_symbol_ptr: 0x3588 (addr 0x1048cd9d8 offset 76339672)
	Section __mod_init_func: 0x930 (addr 0x1048d0f60 offset 76353376)
	Section __mod_term_func: 0x8 (addr 0x1048d1890 offset 76355728)
	Section __const: 0xdb980 (addr 0x1048d18a0 offset 76355744)
	Section __cfstring: 0x11ddc0 (addr 0x1049ad220 offset 77255200)
	Section __objc_classlist: 0x10a88 (addr 0x104acafe0 offset 78426080)
	Section __objc_nlclslist: 0x288 (addr 0x104adba68 offset 78494312)
	Section __objc_catlist: 0x1298 (addr 0x104adbcf0 offset 78494960)
	Section __objc_nlcatlist: 0x148 (addr 0x104adcf88 offset 78499720)
	Section __objc_protolist: 0x1bb8 (addr 0x104add0d0 offset 78500048)
	Section __objc_imageinfo: 0x8 (addr 0x104adec88 offset 78507144)
	Section __objc_const: 0x95ce98 (addr 0x104adec90 offset 78507152)
	Section __objc_selrefs: 0x6ac38 (addr 0x10543bb28 offset 88324904)
	Section __objc_protorefs: 0x358 (addr 0x1054a6760 offset 88762208)
	Section __objc_classrefs: 0xe540 (addr 0x1054a6ab8 offset 88763064)
	Section __objc_superrefs: 0x8fe0 (addr 0x1054b4ff8 offset 88821752)
	Section __objc_ivar: 0x2a3a8 (addr 0x1054bdfd8 offset 88858584)
	Section __objc_data: 0xa6950 (addr 0x1054e8380 offset 89031552)
	Section __data: 0xb7a28 (addr 0x10558ecd0 offset 89713872)
	Section YMMModules: 0x130 (addr 0x1056466f8 offset 90466040)
	Section YMMServices: 0x270 (addr 0x105646828 offset 90466344)
	Section __bss: 0x358d0 (addr 0x105646aa0 offset 0)
	Section __common: 0x97c0 (addr 0x10567c400 offset 0)
	total 0xdb9b20
Segment __LINKEDIT: 0x2ac8000 (vmaddr 0x105688000 fileoff 90472448)

示例

这里使用 MachOView 查看一个 ARM64 的可执行文件,可以直观的看到 Mach-O 文件的格式布局:
macho-view

mach_header

在 Mach-O文件的最前面是头部信息(Header),头部信息里面包含有 Mach-O 可执行文件的详细文件属性,包括文件类型、系统架构、加载命令数量与大小、重要标志属性等。在苹果官方的源码 <mach-o/loader.h> 里可以看到详细的 header 结构,现在都已64位架构作为示例:

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 */
};

magic

魔数,固定值。用来标记Mach-O二进制文件是32位、64位、大端还是小端。

  • 在32位系统下定义为:
#define MH_MAGIC	0xfeedface
#define MH_CIGAM	0xcefaedfe
  • 在 64 位下定义为:
#define MH_MAGIC_64 0xfeedfacf 
#define MH_CIGAM_64 0xcffaedfe

其中 MH_CIGAMMH_CIGAM_64 表示是小端序列,在 64位 MacOS/iOS 环境下,magic 的值是 0xfeedfacf

cputype

cpu类型,标识 Mach-O 文件对应的目标平台架构,定义在<mach/machine.h>文件中,常见以下几个类型:

#define CPU_TYPE_I386           CPU_TYPE_X86
#define CPU_TYPE_X86_64         (CPU_TYPE_X86 | CPU_ARCH_ABI64)
#define CPU_TYPE_ARM            ((cpu_type_t) 12)
#define CPU_TYPE_ARM64          (CPU_TYPE_ARM | CPU_ARCH_ABI64)

目前iPhone手机的CPU架构都是ARM架构,在64位手机系统上,cputype值为 CPU_TYPE_ARM64

cpusubtype

CPU子类型,表示具体的cpu指令集版本,定义在<mach/machine.h>文件中

iPhone家族中常见的有:

/*
 *	ARM subtypes
 */
#define CPU_SUBTYPE_ARM_V7              ((cpu_subtype_t) 10) 
#define CPU_SUBTYPE_ARM_V7S             ((cpu_subtype_t) 11) /* Swift */
#define CPU_SUBTYPE_ARM64_ALL           ((cpu_subtype_t) 0)
#define CPU_SUBTYPE_ARM64E              ((cpu_subtype_t) 2)

filetype

Mach-O文件类型,同一种二进制格式有不同的应用场景,因此也区分多种不同的目标文件类型(.o, .dylib, .framework, dSYM等),因此需要一个字段来标记具体的文件类型。文件的结构布局取决于文件的类型标识,除了MH_OBJECT类型外的所有文件类型,分段数据都被填充并对齐到段(segment)边界上,用来实现有效的分页需求。MH_EXECUTE、MH_FVMLIB、MH_DYLIB、MH_DYLINKER 和 MH_BUNDLE 文件类型的Header信息都包含在Mach-O的第一个段(segment)中。Mach-O的具体类型定义在文件<mach-o/loader.h>中,常见的类型如下:

#define	MH_OBJECT       0x1		/* relocatable object file */
#define	MH_EXECUTE      0x2		/* demand paged executable file */
#define	MH_FVMLIB       0x3		/* fixed VM shared library file */
#define	MH_CORE         0x4		/* core file */
#define	MH_PRELOAD      0x5		/* preloaded executable file */
#define	MH_DYLIB        0x6		/* dynamically bound shared library */
#define	MH_DYLINKER     0x7		/* dynamic link editor */
#define	MH_BUNDLE       0x8		/* dynamically bound bundle file */
#define	MH_DYLIB_STUB   0x9		/* shared library stub for static */
#define	MH_DSYM         0xa		/* companion file with only debug */
#define	MH_KEXT_BUNDLE  0xb		/* x86_64 kexts */
类型用途
MH_OBJECT编译器对源代码编译得到的(中间)结果,也可以是32位系统的内核扩展文件
MH_EXECUTE可执行的二进制文件,可以被系统直接执行,有一个 main() 函数入口。
MH_DSYM辅助的符号文件以及调试信息,DWARF(Debugging with Attribute Record Formats)
MH_DYLIB动态库(.dylib, .framework)
MH_DYLINKER动态连接器,例如 /usr/lib/dyld
MH_FVMLIB
MH_CORE核心转储文件,核心转储时生成的
MH_PRELOADMH_PRELOAD 是一种可执行格式,用于在内核(proms、stand alones、kernels等)下执行的内容。该格式可以在内核下执行,但可能要求在执行之前对其进行分页,而不是在执行前进行预加载。
MH_BUNDLE非独立的二进制文件,需要加载至其他二进制才能发挥作用
MH_DYLIB_STUB
MH_KEXT_BUNDLE内核扩展程序

ncmds

ncmds 指的是加载命令(load commands)的数量,详细的加载命令解释在后文中。

sizeofcmds

sizeofcmds 指的是所有的加载命令占用的空间字节大小,加载命令区域是紧跟在 header 后的。

reserved

64位下的保留字段,暂未使用

flags

一切重要的标志,dyld过程中的一些参数。同样定义在 <mach-o/loader.h> 中:

#define MH_NOUNDEFS                 0x1
#define MH_INCRLINK                 0x2
#define MH_DYLDLINK                 0x4
#define MH_BINDATLOAD               0x8
#define MH_PREBOUND                 0x10
#define MH_SPLIT_SEGS               0x20
#define MH_LAZY_INIT                0x40
#define MH_TWOLEVEL                 0x80
#define MH_FORCE_FLAT               0x100
#define MH_NOMULTIDEFS              0x200
#define MH_NOFIXPREBINDING          0x400
#define MH_PREBINDABLE              0x800
#define MH_ALLMODSBOUND             0x1000
#define MH_SUBSECTIONS_VIA_SYMBOLS  0x2000
#define MH_CANONICAL                0x4000
#define MH_WEAK_DEFINES             0x8000
#define MH_BINDS_TO_WEAK            0x10000
#define MH_ALLOW_STACK_EXECUTION    0x20000
#define MH_ROOT_SAFE                0x40000           
#define MH_SETUID_SAFE              0x80000         
#define MH_NO_REEXPORTED_DYLIBS     0x100000
#define MH_PIE                      0x200000
#define MH_DEAD_STRIPPABLE_DYLIB    0x400000 
#define MH_HAS_TLV_DESCRIPTORS      0x800000
#define MH_NO_HEAP_EXECUTION        0x1000000
#define MH_APP_EXTENSION_SAFE       0x02000000

标志位太多了就不再一一解释了,挑选几个常见的说明一下:

标志位作用
MH_NOUNDEFS表示文件没有未定义的符号,这些目标文件大部分都是静态二进制文件,没有进一步的链接依赖关系
MH_DYLDLINK对象文件是针对基本文件的增量链接的输出,不能再编辑链接
MH_TWOLEVEL两级名称空间绑定
MH_ALLOW_STACK_EXECUTION允许栈可执行,只有可执行文件才可以使用这个标志,如果开启了这个标识,当发生缓冲区溢出的时候吗,可执行栈会带来代码注入的风险
MH_PIE可执行文件启动地址空间布局随机化(Address Space Layout Randomization, ASLR)。
其它

示例

mach-header

Load command

加载命令(Load command)是紧跟在mach_header后面的,加载命令的数量和大小分别在mach_header中的ncmds和sizeofcmds给出,所有的加载命令都必须包含cmd和cmdsize两个字段。cmd字段表示该加载命令的类型,每个加载命令类型都有对应的命令结构。cmdsize表示该加载命令的大小,cmdsize用来定位加载命令结构在文件中的偏移位置,在32位系统架构下,加载命令的大小必须是4字节对齐的,在64位系统下,加载命令的大小是8字节对齐的。部分加载命令由内核加载器直接使用,部分集中命令是由动态链接器使用,加载命令在被调用的时候清晰的指导了内核加载器或者动态链接器如何正确的设置并加载二进制数据。

加载命令必备的结构字段如下:

struct load_command {
	uint32_t cmd;
	uint32_t cmdsize;	
};
  • cmd:加载命令类型;
  • cmdsize:加载命令的大小,可以计算出下一个cmd的偏移量;

加载命令类型

加载命令的类型定义在 <mach-o/loader.h> 中,一部分加载命令是由内核加载器使用,剩余的加载命令是由动态链接器加载解析的时候使用的。由于加载命令数量众多,涉及的功能众多,涉及到操作系统中的很多部分,没办法在这里进行完全详细的分析,这里就简单介绍下部分常见的加载命令。

LC_SEGMENT(LC_SEGMENT_64)

LC_SEGMENT和LC_SEGMENT_64是段加载命令,区别是32位和64位架构,是最主要的加载命令,这条命令指导内核正确的将Mach-O二进制文件中的段映射到进程地址空间中去。32位和64位的段加载命令的数据结构基本相同,就是一些字段宽度上的区别,这里就只列举64位的结构,它们包含了段布局的所有信息:

struct segment_command_64 { 
	uint32_t	cmd;		/* LC_SEGMENT_64 */
	uint32_t	cmdsize;	/* includes sizeof section_64 structs */
	char		segname[16];	/* segment name */
	uint64_t	vmaddr;		/* memory address of this segment */
	uint64_t	vmsize;		/* memory size of this segment */
	uint64_t	fileoff;	/* file offset of this segment */
	uint64_t	filesize;	/* amount to map from the file */
	vm_prot_t	maxprot;	/* maximum VM protection */
	vm_prot_t	initprot;	/* initial VM protection */
	uint32_t	nsects;		/* number of sections in segment */
	uint32_t	flags;		/* flags */
};

结构中字段表示的意思:

  • cmd
    加载命令类型,这里是 LC_SEGMENT_64 段加载命令。

  • cmdsize
    命令大小,这里还包含了段下面区(section) 结构的大小。

  • segname
    段的名称,常见的段有__PAGEZERO、__LINKEDIT、__TEXT、__DATA,关于这个几个段的信息在后面Segment一节单独说明。

  • vmaddr
    这个段的虚拟内存地址。

  • vmsize
    这个段分配的虚拟内存大小。

  • fileoff
    这个段在 Mach-O 文件中的偏移地址。

  • filesize
    这个段在 Mach-O 文件中字节数。

  • maxprot
    段的页面所最高内存保护级别,保护级别分别有可读、可写、可执行和无任何保护,这些权限值定义在文件 </osfmk/mach/vm_prot.h> 中。

  • initprot
    段的页面初始化的内存保护级别,同 maxport。

  • nsects
    段中区(section)的数量。

  • flags
    特殊标志位,这些标志位同样定义在<mach-o/loader.h> 文件中。

#define	SG_HIGHVM	0x1
#define	SG_FVMLIB	0x2
#define	SG_NORELOC	0x4		
#define SG_PROTECTED_VERSION_1 0x8

对于每一个段,段加载命令将 Mach-O 二进制文件中的内容加载到内存中,加载的规则是从偏移量 fileoff 处加载 filesize 字节到虚拟内存地址 vmaddr 处的 vmsize 字节空间里。每一段的页面都根据 initprot 定义的初始化内存保护级别进行初始化,段的内存保护级别是可以动态改变的,但是不能超过 maxprot 字段设定的级别。

LC_LOAD_DYLIB

动态库加载命令,系统dylib类型动态库或者framework动态库。动态库存在于应用目录Frameworks目录下。

注意framework是区分动态库还是静态库的,在创建的时候选择的Mach-O Type如果为Dynamic Library,就是动态库,它的文件类型为MH_DYLIB。如果选择的类为Static Library,就是静态库,它的文件类型为MH_OBJECT,实际上静态库就是.o文件格式的集合。

另外.a类型的静态库也是MH_OBJECT类型文件集合。

LC_SYMTAB

符号表信息(Symbol Table),记录了程序的符号表以及字符串表的偏移量及大小,符号表中记录了程序用到的函数以及全局变量的信息,符号表条目的数据结构定义在 <mach-o/nlist.h> 中。

/*
 * This is the symbol table entry structure for 64-bit architectures.
 */
struct nlist_64 {
    union {
        uint32_t  n_strx; /* index into the string table */
    } n_un;
    uint8_t n_type;        /* type flag, see below */
    uint8_t n_sect;        /* section number or NO_SECT */
    uint16_t n_desc;       /* see <mach-o/stab.h> */
    uint64_t n_value;      /* value of this symbol (or stab offset) */
};

LC_DYSYMTAB

动态符号表信息(Dynamic Symbol Table),包括动态链接过程中所需要的信息

LC_LOAD_DYLINKER

动态连接器加载命令,这个命令指导内核加载调用 dyld,当然也可以是其它的动态链接器。一个程序的大部分可执行文件都是动态链接的,库的加载和符号的解析工作都是通过动态链接器来完成的,也就是 LC_LOAD_DYLINKER 加载命令中指定路径的动态加载器,动态链接器的工作是在用户态的,内核在完成内核态相关的加载命令后,就将控制权转交给了动态链接器。

LC_LOAD_DYLINKER 对应的数据结构:

struct dylinker_command {
	uint32_t	cmd;
	uint32_t	cmdsize;
	union lc_str    name;
};
  • name:这个字段表示动态链接器的路径,这里是 /usr/lib/dyld,关于dyld的内容也很多,这里就不再详细说明。

LC_UUID

唯一UUID(128bit),标记二进制文件。静态链接器为其生成的文件所提供的唯一标识符

LC_MAIN

程序入口指令,这个指令只在 MH_EXECUTE 类型的文件中使用,也就是可执行文件主程序,用来指定 Mach-O 可执行文件 main() 函数的位置。

struct entry_point_command {
    uint32_t  cmd;	/* LC_MAIN only used in MH_EXECUTE filetypes */
    uint32_t  cmdsize;	/* 24 */
    uint64_t  entryoff;	/* file (__TEXT) offset of main() */
    uint64_t  stacksize;/* if not zero, initial stack size */
};
  • entryoff: main() 函数的偏移位置;
  • stacksize:主线程所需的堆栈大小;

LC_FUNCTION_STARTS

记录文件中每个函数的起始地址

LC_VERSION_MIN_IPHONEOS

支持最小的iOS操作系统版本

LC_CODE_SIGNATURE

是代码签名加载命令,描述了Mach-O的代码签名信息,如果不符合签名(或者在iOS当中不存在),进程会立即被内核用SIGKILL命令杀死。它属于链接信息,使用linedit_data_command结构体表示。

case LC_CODE_SIGNATURE:
    /* CODE SIGNING */
    if (pass != 1)
        break;
    /* pager -> uip ->
     load signatures & store in uip
     set VM object "signed_pages"
     */
    ret = load_code_signature(
                              (struct linkedit_data_command *) lcp,
                              vp,
                              file_offset,
                              macho_size,
                              header->cputype,
                              result,
                              imgp);
    if (ret != LOAD_SUCCESS) {
        printf("proc %d: load code signature error %d "
               "for file \"%s\"\n",
               p->p_pid, ret, vp->v_name);
        /*
         * Allow injections to be ignored on devices w/o enforcement enabled
         */
        if (!cs_process_global_enforcement())
            ret = LOAD_SUCCESS; /* ignore error */
        
    } else {
        got_code_signatures = TRUE;
    }
    
    if (got_code_signatures) {
        unsigned tainted = CS_VALIDATE_TAINTED;
        boolean_t valid = FALSE;
        vm_size_t off = 0;
        
        if (cs_debug > 10)
            printf("validating initial pages of %s\n", vp->v_name);
        
        while (off < alloc_size && ret == LOAD_SUCCESS) {
            tainted = CS_VALIDATE_TAINTED;
            
            valid = cs_validate_range(vp,
                                      NULL,
                                      file_offset + off,
                                      addr + off,
                                      PAGE_SIZE,
                                      &tainted);
            if (!valid || (tainted & CS_VALIDATE_TAINTED)) {
                if (cs_debug)
                    printf("CODE SIGNING: %s[%d]: invalid initial page at offset %lld validated:%d tainted:%d csflags:0x%x\n",
                           vp->v_name, p->p_pid, (long long)(file_offset + off), valid, tainted, result->csflags);
                if (cs_process_global_enforcement() ||
                    (result->csflags & (CS_HARD|CS_KILL|CS_ENFORCEMENT))) {
                    ret = LOAD_FAILURE;
                }
                result->csflags &= ~CS_VALID;
            }
            off += PAGE_SIZE;
        }
    }
    
    break;

LC_ENCRYPTION_INFO(LC_ENCRYPTION_INFO_64)

Segment

段,段在程序加载时会被映射到虚拟内存中,为了提高效率,所以每个段都是按照页对齐的(前面已经说了,32位上是4K对齐,64位上是16K对齐)。不同的段有不同的内存读写权限,每个段下面又包含多个区(section)

__PAGEZERO

空指针段,不可读、不可写、不可执行,如果程序试图访问__PAGEZERO段,那么将会引起系统的崩溃。当一个指针变量的值为NULL时,实际就是将指针指向了__PAGEZERO段。这个段在64架构大小为0x100000000,在32架构size时0x4000。它是在虚拟内存中空出一大块低地址区的内存空间。在实际Mach-O文件中占据的大小为0,并不会占据实际空间。

__TEXT

程序代码段,是可读可执行

__text

包含主程序代码段。这里存放的是汇编后的代码,当我们进行编译时,每个.m文件会经过预编译->编译->汇编形成.o文件,称之为目标文件。汇编后,所有的代码会形成汇编指令存储在.o文件的__TEXT.text区,__DATA.data也是类似。链接后,所有的.o文件会合并成一个文件,所有.o文件的_TEXT.text数据都会按链接顺序存放到应用二进制的_TEXT.text中

__stubs

用于Stub的占位代码,很多地方称之为桩代码,用来帮助dyld绑定符号
那何为stub代码区呢?

__stubs_helper

当Stub无法找到真正的符号地址后的最终指向,指向最终地址

__cstring

程序中硬编码的字符串

__const

用const修饰的常量

__objc_methname

代码中的所有objc方法名

__ustring

__objc_classname

代码中的所有的objc类名称

__objc_methtype

代码中所有的objc方法类型

__gcc_except_tab

__unwind_info

用于确定异常发生时栈所对应的信息,包括栈指针、返回地址、寄存器信息等,它同样包含相应的处理函数来支持像 catch、final 等特性;

__eh_frame

__DATA

程序数据段,可读写

__data

存储数据的section,static在进行非零赋值后会存储在这里,如果static 变量没有赋值或者赋值为0,那么它会存储在__DATA.__bss中

__got

存储引用的符号的实际地址

__la_symbol_ptr

lazy binding 的指针表,表中的指针一开始都指向 __stub_helper,__la_symbol_ptr Section 包含的符号指针则是在其第一次被程序使用时绑定。

__nl_symbol_ptr

非 lazy binding 的指针表,包含的符号指针需要在加载时绑定

__const

用const修饰的常量

__cfstring

程序中使用的 Core Foundation 字符串

__bss

存放没有初始化和初始化为0的全局变量

__common

没有初始化过的符号声明

__objc_classlist

所有的objc类,保存类的地址信息,与__objc_data中的信息相映射

__objc_data

objc数据,用于保存类需要的数据。最主要的内容是映射__objc_const地址,用于找到类的相关数据。

class objc_class_t {
    typedef typename P::uint_t pint_t;

    pint_t isa;
    pint_t superclass;
    pint_t method_cache;
    pint_t vtable;
    pint_t data;
}

__objc_const

objc常量,保存类、方法、协议、属性、实例变量、分类等相关信息

class objc_class_data_t {
    typedef typename P::uint_t pint_t;
    uint32_t flags;
    uint32_t instanceStart;
    // Note there is 4-bytes of alignment padding between instanceSize and ivarLayout
    // on 64-bit archs, but no padding on 32-bit archs.
    // This union is a way to model that.
    union {
        uint32_t                instanceSize;
        pint_t   pad;
    } instanceSize;
    pint_t ivarLayout;
    pint_t name;
    pint_t baseMethods;
    pint_t baseProtocols;
    pint_t ivars;
    pint_t weakIvarLayout;
    pint_t baseProperties;
}

分类

struct category_t {
    const char *name;
    classref_t cls;
    struct method_list_t *instanceMethods;
    struct method_list_t *classMethods;
    struct protocol_list_t *protocols;
    struct property_list_t *instanceProperties;
    // Fields below this point are not always present on disk.
    struct property_list_t *_classProperties;
};

__objc_catlist

分类列表,运行时决议

__objc_nlclslist

__objc_protolist

所有的协议

__objc_imageinfo

镜像信息版本信息

// Description of an Objective-C image.
// __DATA,__objc_imageinfo stores one of these.
typedef struct objc_image_info {
    uint32_t version; // currently 0
    uint32_t flags;

#if __cplusplus >= 201103L
  private:
    enum : uint32_t {
        // 1 byte assorted flags
        IsReplacement       = 1<<0,  // used for Fix&Continue, now ignored
        SupportsGC          = 1<<1,  // image supports GC
        RequiresGC          = 1<<2,  // image requires GC
        OptimizedByDyld     = 1<<3,  // image is from an optimized shared cache
        CorrectedSynthesize = 1<<4,  // used for an old workaround, now ignored
        IsSimulated         = 1<<5,  // image compiled for a simulator platform
        HasCategoryClassProperties  = 1<<6,  // class properties in category_t
        // not yet used = 1<<7

        // 1 byte Swift unstable ABI version number
        SwiftUnstableVersionMaskShift = 8,
        SwiftUnstableVersionMask = 0xff << SwiftUnstableVersionMaskShift,

        // 2 byte Swift stable ABI version number
        SwiftStableVersionMaskShift = 16,
        SwiftStableVersionMask = 0xffffUL << SwiftStableVersionMaskShift
    };
  public:
    enum : uint32_t {
        // Values for SwiftUnstableVersion
        // All stable ABIs store SwiftVersion5 here.
        SwiftVersion1   = 1,
        SwiftVersion1_2 = 2,
        SwiftVersion2   = 3,
        SwiftVersion3   = 4,
        SwiftVersion4   = 5,
        SwiftVersion4_1 = 6,
        SwiftVersion4_2 = 6,  // [sic]
        SwiftVersion5   = 7
    };

  public:
    bool isReplacement()   const { return flags & IsReplacement; }
    bool supportsGC()      const { return flags & SupportsGC; }
    bool requiresGC()      const { return flags & RequiresGC; }
    bool optimizedByDyld() const { return flags & OptimizedByDyld; }
    bool hasCategoryClassProperties() const { return flags & HasCategoryClassProperties; }
    bool containsSwift()   const { return (flags & SwiftUnstableVersionMask) != 0; }
    uint32_t swiftUnstableVersion() const { return (flags & SwiftUnstableVersionMask) >> SwiftUnstableVersionMaskShift; }
#endif
} objc_image_info;

__objc_protorefs

协议引用

__objc_selrefs

包含了所有被引用(使用)了的方法的

__objc_superrefs

超类引用

__objc_ivar

__mod_init_func

__mod_term_func

__LINKEDIT

链接器使用段,包含加载程序的元数据(比如函数名称和地址),将与动态链接相关的信息映射到虚拟地址空间,__LINKEDIT 段包括 rebase、bind、lazy bind 等信息。可能我们认为 Xcode 会把整个文件都做加密 hash 并用做数字签名。其实为了在运行时验证 Mach-O 文件的签名,并不是每次重复读入整个文件,而是把每页内容都生成一个单独的加密散列值,并存储在 __LINKEDIT 中。这使得文件每页的内容都能及时被校验并确保不被篡改。

LC_CODE_SIGNATURE, 
LC_SEGMENT_SPLIT_INFO,
LC_FUNCTION_STARTS, 
LC_DATA_IN_CODE,
LC_DYLIB_CODE_SIGN_DRS,
LC_LINKER_OPTIMIZATION_HINT,
LC_DYLD_EXPORTS_TRIE, 
LC_DYLD_CHAINED_FIXUPS. 

这些加载命令都是属于__LINKEDIT中的内容

附录〇:ASLR

内存空间布局随机化(Address space layout randomization)。当app加载到内存时,系统会自动进行ASLR,在__PAGEZERO段上随机移动一段空间作为偏移,使得Mach-O文件的整个虚拟内存向下整体(包括堆,栈,共享库映射等线性布局)偏移。从而可以让生成的函数内存地址不断变动,这样可以提高破解难度。

附录一:cputype

cpu类型,标识 Mach-O 文件对应的目标平台架构,定义在<mach/machine.h>文件中

#define CPU_TYPE_ANY		((cpu_type_t) -1)
#define CPU_TYPE_VAX		((cpu_type_t) 1)
#define CPU_TYPE_MC680x0	((cpu_type_t) 6)
#define CPU_TYPE_X86		((cpu_type_t) 7)
#define CPU_TYPE_I386		CPU_TYPE_X86
#define CPU_TYPE_X86_64		(CPU_TYPE_X86 | CPU_ARCH_ABI64)
#define CPU_TYPE_MC98000	((cpu_type_t) 10)
#define CPU_TYPE_HPPA       ((cpu_type_t) 11)
#define CPU_TYPE_ARM		((cpu_type_t) 12)
#define CPU_TYPE_ARM64      (CPU_TYPE_ARM | CPU_ARCH_ABI64)
#define CPU_TYPE_MC88000	((cpu_type_t) 13)
#define CPU_TYPE_SPARC		((cpu_type_t) 14)
#define CPU_TYPE_I860		((cpu_type_t) 15)
#define CPU_TYPE_POWERPC    ((cpu_type_t) 18)
#define CPU_TYPE_POWERPC64	(CPU_TYPE_POWERPC | CPU_ARCH_ABI64)

附录二:cpusubtype

cpu子类型标识符,定义在<mach/machine.h>文件中

/*
 *	ARM subtypes
 */
#define CPU_SUBTYPE_ARM_ALL             ((cpu_subtype_t) 0)
#define CPU_SUBTYPE_ARM_V4T             ((cpu_subtype_t) 5)
#define CPU_SUBTYPE_ARM_V6              ((cpu_subtype_t) 6)
#define CPU_SUBTYPE_ARM_V5TEJ           ((cpu_subtype_t) 7)
#define CPU_SUBTYPE_ARM_XSCALE          ((cpu_subtype_t) 8)
#define CPU_SUBTYPE_ARM_V7              ((cpu_subtype_t) 9)  /* ARMv7-A and ARMv7-R */
#define CPU_SUBTYPE_ARM_V7F             ((cpu_subtype_t) 10) /* Cortex A9 */
#define CPU_SUBTYPE_ARM_V7S             ((cpu_subtype_t) 11) /* Swift */
#define CPU_SUBTYPE_ARM_V7K             ((cpu_subtype_t) 12)
#define CPU_SUBTYPE_ARM_V8              ((cpu_subtype_t) 13)
#define CPU_SUBTYPE_ARM_V6M             ((cpu_subtype_t) 14) 
#define CPU_SUBTYPE_ARM_V7M             ((cpu_subtype_t) 15) 
#define CPU_SUBTYPE_ARM_V7EM            ((cpu_subtype_t) 16) 
#define CPU_SUBTYPE_ARM_V8M             ((cpu_subtype_t) 17) 

/*
 *  ARM64 subtypes
 */
#define CPU_SUBTYPE_ARM64_ALL           ((cpu_subtype_t) 0)
#define CPU_SUBTYPE_ARM64_V8            ((cpu_subtype_t) 1)
#define CPU_SUBTYPE_ARM64E              ((cpu_subtype_t) 2)

/*
 *	X86 subtypes.
 */
#define CPU_SUBTYPE_X86_ALL             ((cpu_subtype_t)3)
#define CPU_SUBTYPE_X86_64_ALL          ((cpu_subtype_t)3)
#define CPU_SUBTYPE_X86_ARCH1           ((cpu_subtype_t)4)
#define CPU_SUBTYPE_X86_64_H            ((cpu_subtype_t)8)  

附录三:Load Commands

/* Constants for the cmd field of all load commands, the type */
#define LC_SEGMENT	0x1	/* segment of this file to be mapped */
#define LC_SYMTAB	0x2	/* link-edit stab symbol table info */
#define LC_SYMSEG	0x3	/* link-edit gdb symbol table info (obsolete) */
#define LC_THREAD	0x4	/* thread */
#define LC_UNIXTHREAD	0x5	/* unix thread (includes a stack) */
#define LC_LOADFVMLIB	0x6	/* load a specified fixed VM shared library */
#define LC_IDFVMLIB	0x7	/* fixed VM shared library identification */
#define LC_IDENT	0x8	/* object identification info (obsolete) */
#define LC_FVMFILE	0x9	/* fixed VM file inclusion (internal use) */
#define LC_PREPAGE      0xa     /* prepage command (internal use) */
#define LC_DYSYMTAB	0xb	/* dynamic link-edit symbol table info */
#define LC_LOAD_DYLIB	0xc	/* load a dynamically linked shared library */
#define LC_ID_DYLIB	0xd	/* dynamically linked shared lib ident */
#define LC_LOAD_DYLINKER 0xe	/* load a dynamic linker */
#define LC_ID_DYLINKER	0xf	/* dynamic linker identification */
#define LC_PREBOUND_DYLIB 0x10	/* modules prebound for a dynamically */
				/*  linked shared library */
#define LC_ROUTINES	0x11	/* image routines */
#define LC_SUB_FRAMEWORK 0x12	/* sub framework */
#define LC_SUB_UMBRELLA 0x13	/* sub umbrella */
#define LC_SUB_CLIENT	0x14	/* sub client */
#define LC_SUB_LIBRARY  0x15	/* sub library */
#define LC_TWOLEVEL_HINTS 0x16	/* two-level namespace lookup hints */
#define	LC_PREBIND_CKSUM  0x17	/* prebind checksum */

/*
 * load a dynamically linked shared library that is allowed to be missing
 * (all symbols are weak imported).
 */
#define LC_LOAD_WEAK_DYLIB (0x18 | LC_REQ_DYLD)

#define LC_SEGMENT_64	0x19	/* 64-bit segment of this file to be mapped */
#define LC_ROUTINES_64	0x1a	/* 64-bit image routines */
#define LC_UUID		0x1b	/* the uuid */
#define LC_RPATH       (0x1c | LC_REQ_DYLD)    /* runpath additions */
#define LC_CODE_SIGNATURE 0x1d	/* local of code signature */
#define LC_SEGMENT_SPLIT_INFO 0x1e /* local of info to split segments */
#define LC_REEXPORT_DYLIB (0x1f | LC_REQ_DYLD) /* load and re-export dylib */
#define LC_LAZY_LOAD_DYLIB 0x20	/* delay load of dylib until first use */
#define LC_ENCRYPTION_INFO 0x21	/* encrypted segment information */
#define LC_DYLD_INFO 	0x22	/* compressed dyld information */
#define LC_DYLD_INFO_ONLY (0x22|LC_REQ_DYLD)	/* compressed dyld information only */
#define LC_LOAD_UPWARD_DYLIB (0x23 | LC_REQ_DYLD) /* load upward dylib */
#define LC_VERSION_MIN_MACOSX 0x24   /* build for MacOSX min OS version */
#define LC_VERSION_MIN_IPHONEOS 0x25 /* build for iPhoneOS min OS version */
#define LC_FUNCTION_STARTS 0x26 /* compressed table of function start addresses */
#define LC_DYLD_ENVIRONMENT 0x27 /* string for dyld to treat like environment variable */
#define LC_MAIN (0x28|LC_REQ_DYLD) /* replacement for LC_UNIXTHREAD */
#define LC_DATA_IN_CODE 0x29 /* table of non-instructions in __text */
#define LC_SOURCE_VERSION 0x2A /* source version used to build binary */
#define LC_DYLIB_CODE_SIGN_DRS 0x2B /* Code signing DRs copied from linked dylibs */
#define LC_ENCRYPTION_INFO_64 0x2C /* 64-bit encrypted segment information */
#define LC_LINKER_OPTION 0x2D /* linker options in MH_OBJECT files */
#define LC_LINKER_OPTIMIZATION_HINT 0x2E /* optimization hints in MH_OBJECT files */
#define LC_VERSION_MIN_TVOS 0x2F /* build for AppleTV min OS version */
#define LC_VERSION_MIN_WATCHOS 0x30 /* build for Watch min OS version */
#define LC_NOTE 0x31 /* arbitrary data included within a Mach-O file */
#define LC_BUILD_VERSION 0x32 /* build for platform min OS version */

附录四:内存保护级别

这些权限值定义在文件 </osfmk/mach/vm_prot.h>

#define	VM_PROT_NONE	((vm_prot_t) 0x00) // 无
#define VM_PROT_READ	((vm_prot_t) 0x01) // 读
#define VM_PROT_WRITE	((vm_prot_t) 0x02) // 写
#define VM_PROT_EXECUTE	((vm_prot_t) 0x04) // 执行

附录五:推荐文档