Mach-O 文件探索

之前在项目中使用 fishhook 来替换系统的 C 函数,其中涉及到很多和 iOS 系统相关的编译、链接等方面的知识,由于内容比较多,所以打算分几篇文章来进行讲解,本文主要是分析 Mach—O 文件。

在 macOS 以及 iOS 系统中,可执行文件的格式为 Mach-O,理解 Mach-O 文件格式对于我们探究操作系统的运作机制起着关键的作用。Mach-O 文件格式如下图所示,它由 Header、Load commands 以及 Data 三部分组成:

Header:记录 Mach-O 文件的基本信息,包括文件类型、支持的 CPU 类型以及加载命令的个数、大小等。

Load commands: 位于 Header 之后,向操作系统描述如何解析文件。

Data: 用于保存程序的 TEXT、DATA、LINKEDIT 等 segment 数据。

以下是人民群众喜闻乐见的一段代码:

#include <stdio.h>

int main(int argc, const char * argv[]) {
    printf("Hello, World!\n");
    return 0;
}

使用 clang main.c -o MachOExplore 命令编译上述代码,我们得到名为 MachOExplore 的目标文件,也就是本文将要探究的 Mach-O 文件。otool 是用来查看 Mach-O 文件的常用工具,但是本文会使用另一种工具 MachOVie�w 来完成任务。

Header

MachOView 查看 Header 的内容如下:

Header 中记录了 Mach-O 文件的属性信息,相关数据结构定义在 loader.h ����中,分为32位和64位两种:

/*
* 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) */

下表描述了各个字段的含义,mach_header_64 除了比 mach_header 多出一个 reserved 字段外,其他方面并无区别。

字段 说明
magic 表明文件适用于64位操作系统还是32位操作系统,因为大小端的存在,所以有 MH_MAGIC_64 和 MH_CIGAM_64 两种形式 。
cputype 描述文件所支持的 CPU 架构,包括 ARM64、X86_64、i386等。
cpusubtype 描述文件对应的具体 CPU 架构,例如 ARM_V7S、ARM64_V8、X86_ARCH1 等。
filetype 描述文件的类型,常见的类型有可执行文件、可重定位文件、共享库文件等。
ncmds 记录加载命令的个数。
sizeofcmds 记录所有加载命令的大小。
flags 描述文件在编译、链接等过程中的信息,示例中的 MH_NOUNDEFS 表示文件中不存在未定义的符号,MH_DYLDLINK 表示文件要交由 DYLD 进一步处理,MH_TWOLEVEL 表示文件使用两级命名空间,MH_PIE 表示启用地址空间布局随机化。

Load commands

Load commands 紧随在 Header 后,它包含了一系列的加载命令,目的是向操作系统描述如何处理 Mach-O 文件。示例中包含的加载命令如下:

LC_SEGMENT_64 命令表示将相应的 segment 映射到虚拟地址空间中,以 LC_SEGMENT_64(__PAGEZERO) 为例:

  • Command: 表示加载命令;

  • Command Size: 表示加载命令的大小;

  • Segment Name: 被加载的段的名字;

  • VM Address: 段所在的虚拟空间地址;

  • VM Size: 段所占用的虚拟空间的大小;

  • File Offset: 段在文件中的偏移量;

  • File Size: 段在文件中的大小;

  • Maximum VM protection: 表示与段相对应的最大操作权限;

  • Initial VM protection: 表示段的初始操作权限;

  • Number of Sections: 段包含多少个 Section;

  • Flags: 描述与段相关的加载信息,具体解释请参考 loader.h 文件;

所以上述命令是将 __PAGEZERO 段映射到虚拟地址 0x0 处,占用虚拟空间大小为 4GB,但是这4GB并不是真实的文件大小,它仅表明将虚拟地址空间的前4GB映射为不可读、不可写、不可执行,与 NULL 指针相对应。如果程序试图访问 __PAGEZERO 段,那么将会引起系统的崩溃。

接下来我们来看 LC_SEGMENT_64(__TEXT):

它将 __TEXT 段映射到虚拟地址空间 0x100000000 处,也就是紧随 __PAGEZERO 段,占用虚拟空间大小为 4096B,所对应的权限是可读、可执行、不可写入。我们的 __TEXT 段包含以下5个 Section:

  • __text: 包含程序的机器码;

  • __stubs__stub_helper: 用来帮助 DYLD 绑定符号;

  • __cstring: 记录了文件中的常量字符串信息(包含在双引号中),我们可以依据此信息找到字符串的地址;

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

LC_SEGMENT_64(__DATA) 的作用是将 __DATA 段映射到紧随 __TEXT 段的虚拟地址空间上,它包含两个 Section:

__nl_symbol_ptr Section 包含的符号指针需要在加载时绑定,而 __la_symbol_ptr Section 包含的符号指针则是在其第一次被程序使用时绑定。

LC_SEGMENT_64(__LINKEDIT) 则是将与动态链接相关的信息映射到虚拟地址空间,__LINKEDIT 段包括 rebase、bind、lazy bind 等信息。

LC_DYLD_INFO_ONLY 记录了有关链接的重要信息,它的数据结构如下:

struct dyld_info_command {
    uint32_t   cmd;     /* LC_DYLD_INFO or LC_DYLD_INFO_ONLY */
    uint32_t   cmdsize;     /* sizeof(struct dyld_info_command) */
    uint32_t   rebase_off;  /* file offset to rebase info  */
    uint32_t   rebase_size; /* size of rebase info   */
    uint32_t   bind_off;    /* file offset to binding info   */
    uint32_t   bind_size;   /* size of binding info  */
    uint32_t   weak_bind_off; /* file offset to weak binding info   */
    uint32_t   weak_bind_size;  /* size of weak binding info  */
    uint32_t   lazy_bind_off; /* file offset to lazy binding info */
    uint32_t   lazy_bind_size;  /* size of lazy binding infs */
    uint32_t   export_off;  /* file offset to lazy binding info */
    uint32_t   export_size; /* size of lazy binding infs */
};

根据它所记录的偏移量,我们便可以找到在 Dynamic Loader Info 中的相关信息。它的 ONLY 后缀表明这是程序运行所必须的,如果链接器不支持,那么加载过程就会终止。

LC_SYMTAB 记录了程序的符号表以及字符串表的偏移量及大小,符号表中记录了程序用到的函数以及全局变量的信息,符号表条目的数据结构定义在 nlist.h 中:

/*
 * Format of a symbol table entry of a Mach-O file for 32-bit architectures.
 */
struct nlist {
    union {
#ifndef __LP64__
        char *n_name;   /* for use when in-core */
#endif
        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 */
    int16_t n_desc;     /* see <mach-o/stab.h> */
    uint32_t n_value;   /* value of this symbol (or stab offset) */
};
/*
 * 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) */
};

数据结构中相关字段的含义都可以在 nlist.h 中找到,这里值得一说的是 n_un 字段,它用来记录符号的名字,但为什么是 uint32_t 类型呢?又为什么在注释中标明是 string table 的 索引呢?

这是因为在程序中,字符串的长度是不固定的,所以会将其放在 string table 中,然后存储它在 string table 中的偏移。如果其他部分想要引用某个字符串,那么他首先需要找到 string table 的起始地址,然后根据偏移量找到相应字符串的起始位置并向后读取字符,直到遇见 \0 才会停止读取过程,最后返回读到的字符串。

这也是 LC_SYMTAB 额外记录 string table 地址的原因,string table 通常用于记录 section 名、符号名等信息。

其他的加载命令如下表所示:

加载命令 描述
LC_DYSYMTAB 包括动态链接过程中所需要的信息
LC_LOAD_DYLINKER 指定动态链接器的地址
LC_LOAD_DYLIB 记录了程序所需要的动态库的相关信息
LC_UUID 静态链接器为其生成的文件所提供的唯一标识符
LC_MAIN 指定 main 函数的地址
LC_FUNCTION_STARTS 记录文件中每个函数的起始地址
LC_DATA_IN_CODE 记录那些写在程序二进制执行指令中的数据
LC_CODE_SIGNATURE 代码签名

Data

Data 包括文件所需的 segment 数据,除了 MachOView 工具,你也可以通过 size 工具来查看,运行 size -x -l -m MachOExplore 命令后可得到以下内容:

我们还可以通过输入 otool -s __DATA __la_symbol_ptr MachOExplore 命令来查看 __la_symbol_ptr Section 的数据:

也可以使用 otool -V -s __TEXT __text MachOExplore 命令来查看 __text Section 的反汇编代码:

同时 otool 也为一些常见的命令设置了缩写,例如 -t 就是 -s __TEXT __text 简称,而 -d 就是 -s __DATA __data 的简称,相关信息都可以通过 man otool 命令来查看。这些数据都可以在 MachOView 中看得很清楚,但是这些命令行工具记一下也无妨。

总结

以上便是 Mach-O 文件的探究(其实总结部分就是强行凑的(๑•̀ㅂ•́)و✧)。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容

  • 上一篇博客介绍了mach_header相关内容,Mach-O文件介绍之mach_header。这篇博客主要介绍Ma...
    Tomychen阅读 2,337评论 0 7
  • Mach-O 概述 和 部分命令介绍 我们知道Windows下的文件都是PE文件,同样在OS X和iOS中可执行文...
    青花瓷的平方阅读 14,860评论 2 52
  • 熟悉Linux和windows开发的同学都知道,ELF是Linux下可执行文件的格式,PE32/PE32+是win...
    Klaus_J阅读 3,900评论 1 10
  • 13. Hook原理介绍 13.1 Objective-C消息传递(Messaging) 对于C/C++这类静态语...
    Flonger阅读 1,400评论 0 3
  • 13.1 Objective-C消息传递(Messaging) 对于C/C++这类静态语言,调用一个方法其实就是跳...
    泰克2008阅读 1,975评论 1 6