MachO文件结构

接触iOS逆向已经有一段时间了,但是一直没有好好去了解一下最基础的MachO结构,本文主要记录一下对MachO文件结构的学习和理解。

MachO文件的基本格式

Mach-O是Mach Object文件格式的缩写,iOS以及Mac上可执行的文件格式,类似Window的PE,Linux上的ELF。
由下图可知,MachO文件的结构,主要包括Header、LoadCommand、Data三部分,Data部分由Section组成。

  • Header:保存了Mach-O的一些基本信息,包括了平台、文件类型、LoadCommands的个数等等。
  • LoadCommands:加载Mach-O文件时会使用这里的数据来确定内存的分布。
  • Data:每一个segment的具体数据都保存在这里,这里包含了具体的代码、数据等等。

这里介绍一个分析MachO文件结构的必备工具,MachOView

MachOView下载地址:http://sourceforge.net/projects/machoview/

MachOView源码地址:https://github.com/gdbinit/MachOView

效果如下图所示:

MachO头部

MachO头部结构可以从苹果开源的代码中找到,具体如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/*
* 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 */
};
* 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 */
};

从上面的代码中可以看出,它由以下部分组成。

  • magic:MachO文件的魔数,FAT为0xcafebabe,ARMv7位0xfeedface,ARM64为0xfeedfacf(Mac是小端模式)。
  • cputype、cpusubtype:CPU架构和子版本。
  • filetype:文件类型。常见的有MH_OBJECT(目标文件)、MH_EXECUTABLE(可执行二进制文件)、MH_DYLIB(动态库)。
  • mcmds:加载命令的数量。
  • sizeofcms:所有加载命令的大小
  • flags:dyld加载需要一些标记。其中,MH_PIE表示启用地址空间布局随机化。
  • reserved:64位的保留字段。

Load Command

load Command用于告诉操作系统应当如何加载文件中的数据,对系统内核加载器和动态链接器起指导作用。
load Command包含以下部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#define LC_SEGMENT_64 定义一个段,加载后被映射到内存中,包括里面的节
#define LC_SYMTAB 为文件定义符号表和字符串表
#define LC_DYSYMTAB 将符号表中给出符号的额外符号信息提供给动态链接器
#define LC_LOAD_DYLIB 依赖的动态库,包括动态库名称、当前版本号等等
#define LC_LOAD_DYLINKER 默认加载器路径
#define LC_UUID 用于表示MachO文件的ID
#define LC_RPATH rpath搜索的路径
#define LC_ENCRYPTION_INFO_64 文件是否加密的标志,加密内容的偏移和大小
#define LC_DYLD_INFO_ONLY 记录了有关链接的重要信息,包括__LINKEDIT中动态链接相关信息的偏移和大小。
#define LC_VERSION_MIN_IPHONEOS 系统要求最低版本
#define LC_FUNCTION_STARTS 函数起始地址表
#define LC_MAIN 程序入口,dyld获取改地址然后跳转到该处执行
#define LC_DATA_IN_CODE 定义在代码段内的非指令的表
#define LC_SOURCE_VERSION 构建二进制文件的源代码版本号
#define LC_DYLIB_CODE_SIGN_DRS 代码签名信息

LC_SEGMENT_64定义了一个64位的段,当文件加载后映射到地址空间,64位段的定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
struct segment_command_64 { /* for 64-bit architectures */
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 */
};

系统将fileoff偏移处的filesize大小的内容加载到虚拟内存的vmaddr,大小为vmsize,vmsize 并不等于 filesize,对于 4KB 大小的 VP,vmsize 是 4K 的倍数;换句话说,vmsize 一般大于 segment 的实际大小。
端页面的权限由initport进行初始化。但是不能超过 maxprot 中指定的值。
文件中主要包括PAGEZERO、TEXT、DATA、LINKEDIT四种段,段里可以包括不同的节。定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct section_64 { /* for 64-bit architectures */
char sectname[16]; /* name of this section */
char segname[16]; /* segment this section goes in */
uint64_t addr; /* memory address of this section */
uint64_t size; /* size in bytes of this section */
uint32_t offset; /* file offset of this section */
uint32_t align; /* section alignment (power of 2) */
uint32_t reloff; /* file offset of relocation entries */
uint32_t nreloc; /* number of relocation entries */
uint32_t flags; /* flags (section type and attributes)*/
uint32_t reserved1; /* reserved (for offset or index) */
uint32_t reserved2; /* reserved (for count or sizeof) */
uint32_t reserved3; /* reserved */
};

结构体section_64可以看做 section header,它描述了对应 section 的具体位置,以及要被映射的目标虚拟地址。
回头再看segment_command_64的cmdsize字段,它的数值并非是segment_command_64的 size 大小,还包括了紧接在 command 后面的所有section_64结构体的大小。
如果 segment 含有 5 个 section,那么对应的 segment_command_64 的 cmdsize 值为:72(segment_command_64本身大小) + 5 * 80(section_64的大小) = 472 bytes

_TEXT段和_DATA段

_TEXT

_TEXT段包含了多个节区,各区的作用如下:

  • _text节是主程序代码
  • _picsymbolstub4节和__syub_helper节用于动态链接
  • _cstring是代码里用到的字符串表
  • _objc_classname是objc类方法名
  • _objc_methname是objc的方法名称
  • _objc_methtype是objc的方法类型
  • _const是由const修饰的常量

_DATA

_DATA是数据段,里面主要存放数据,可读可写不可执行,部分节区作用如下:

  • _got非懒加载全局指针表
  • _la_symbol_ptr懒加载指针表
  • _cftring使用的字符串信息
  • _objc_classlist程序中类列表
  • _objc_protolist程序中协议列表
  • _objc_imageinfo程序中镜像信息
  • _objc_const是objc常量
  • _objc_superrefs是objc超类引用
  • _objc_ivar是objc类的实例变量

总结

以上是针对MachO文件结构的简单总结,为了更加形象的了解MachO文件结构及其在程序运行中的运用,下一节通过fishhook、class-dump等工具的原理深入了解MachO的工作原理。

有钱的捧个钱场
0%