第18章:你好,Mach-O

Mach-O是在任何苹果操作系统上运行的编译程序所使用的文件格式。对该格式的了解对调试和逆向工程都很重要,因为Mach-O定义的布局适用于可执行文件在磁盘上的存储方式,以及可执行文件在内存中的加载方式。

知道一条指令所引用的内存区域在逆向工程方面是很有用的,但在探索Mach-O时,在调试方面也有许多有用的隐藏宝藏。比如说。

本章介绍了Mach-O的概念,而下一章,Mach-O的乐趣将展示利用这些知识可以实现的有趣的事情。请确保你为这一章准备了咖啡因,因为理论是第一位的,然后是下一章的乐趣。

术语

在深入了解你将要看到的所有不同的C结构之前,最好先对Mach-O布局做一个高层次的鸟瞰。

这是每个编译的可执行文件的布局;每个主程序、每个框架、每个内核扩展、所有在苹果平台上编译的东西。

在每个编译的苹果程序的开头是Mach-O头,它给出了这个程序可以在哪个CPU上运行的信息,它是哪种类型的可执行文件(一个框架? 一个独立的程序?)以及紧随其后的多少个加载命令。

加载命令是关于如何加载程序的指令,由C结构组成,其大小根据加载命令的类型而不同。

一些加载命令提供了关于如何加载段的指令。把段看作是具有特定类型的内存保护的内存区域。例如,可执行代码应该只有读取和执行的权限;它不需要写入权限。

程序的其他部分,如全局变量或单子,需要读和写的权限,但不需要可执行的权限。这意味着,可执行代码和全局变量的地址将生活在不同的段中。

区段可以有0个或更多的子组件,称为节。这些是更细化的区域,受其父段提供的相同内存保护的约束。再看一下上图。段落命令1指向可执行文件中的一个偏移量,该偏移量包含四个分段命令,而段落命令2指向一个包含0个分段命令的偏移量。最后,段命令3并不指向可执行文件中的任何偏移。

正是这些段对开发者和逆向工程人员具有深远的意义,因为它们对程序都有独特的作用。例如,有一个特定的部分来存储硬编码的UTF-8字符串,有一个特定的部分来存储对静态定义的变量的引用,等等。

这两章Mach-O的最终目标是在本章中向你展示一些有趣的加载命令,并在下一章中揭示一些有趣的部分。

在本章中,你会看到很多对系统头文件的引用。如果你看到像mach-o/stab.h这样的东西,你可以通过Xcode中的Open Quickly菜单来查看,按⌘ + Shift + O(默认),然后输入/usr/include/mach-o/stab.h。

我建议在搜索查询中加入/usr/include/,因为Xcode有时并不那么聪明。

如果你想在没有Xcode的情况下查看这个头文件,那么物理位置将是在:

其中${SYSTEM_PLATFORM}可以是MacOSX、iPhoneOS、iPhoneSimulator、WatchOS,等等。

现在你已经有了一个鸟瞰图,现在是时候深入到杂草中,查看所有可爱的C结构。

Mach-O头文件

在每个编译的苹果可执行文件的开头都有一个特殊的结构,表明它是否是一个Mach-O可执行文件。这个结构可以在mach-o/loader.h中找到。记住这个头文件的名字,因为本章中会经常提到它。这个结构有两个变体:一个用于32位操作系统(mach_header),另一个用于64位操作系统(mach_header_64)。本章将默认谈论64位系统,除非另有说明。

让我们看一下mach_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,是一个硬编码的32位无符号整数,表示这是Mach-O头的开始。这个magic数字的值是什么?在mach-o/loader.h头中再往下一点,你会发现以下内容。

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

这意味着每个64位的Mach-O可执行文件都将以0xfeedfacf开始,如果字节排序被调换,则以0xcffaedfe开始。在32位系统中,神奇的值是0xfeedface,如果是字节交换,则是0xcefaedfe。这个值可以让你快速确定该文件是否为Mach-O可执行文件,以及它是为32位还是64位架构编译的。

在这个神奇的数字之后是cputype和cpusubtype,它们表明这个Mach-O可执行文件允许在哪种类型的cpu上运行。同样,查阅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 */ 
... // there’s way more below but ommiting for brevity...

因此,对于一个主可执行文件(即不是一个框架),文件类型将是MH_EXECUTE。

在文件类型之后,头文件的下一个最有趣的方面是ncmds和sizeofcmds。加载命令表明了可执行文件的属性以及如何将其加载到内存中。

是时候从理论上休息一下了,通过在终端检查可执行文件的Mach-O头的原始字节,在野外看到这一点。

grep中的Mach-O头

打开一个终端窗口。我将选择grep可执行命令,但你可以选择任何符合你兴趣的终端命令。输入以下内容。

xxd -l 32 $(which grep)

这个命令说要把全路径的前32个原始字节转到grep可执行文件的位置。为什么是32字节?在mach_header_64结构的声明中,有8个变量,每个4字节长。

你会得到类似于下面的东西。

00000000: cffa edfe 0700 0001 0300 0080 0200 0000   ................
00000010: 1300 0000 4007 0000 8500 2000 0000 0000   ....@..... .....

现在是提醒自己的好时机,x86_64位英特尔系统使用的是小-endian架构。这意味着这些字节是相反的。

注意:尽管x86_64英特尔架构是小-endian,但苹果可以用大-endian或小-endian格式存储Mach-O信息,这部分是由于可追溯到PPC架构的历史原因。相比之下,磁盘上的Mach-O头排序在macOS上可以找到任何一种格式,但在内存中会是小标号。在本节后面,你会看到macOS的CoreFoundation模块,它的Mach-O头是以big-endian格式存储的。标准,嗯?

仔细看一下xxd输出的前4个字节。

cffa edfe

这可以被分割成各个字节...

cf fa ed fe

然后反过来,以字节为单位...

fe ed fa cf

现在,MH_MAGIC_64,也就是0xfeedfacf魔法变量,应该很明显了,表明这是为64位系统编译的。

幸运的是,xxd终端命令有一个特殊的选项,用于小-endian架构:-e选项。在你之前的终端命令中加入-e选项。

xxd -e -l 32 $(which grep)

你会得到与下面类似的东西。

00000000: feedfacf 01000007 80000003 00000002   ................ 
00000010: 00000013 00000740 00200085 00000000   ....@..... .....

让我们把所有这些值放到结构mach_header_64中。

struct mach_header_64 { 
   uint32_t magic = 0xfeedfacf 
   cpu_type_t cputype = 0x01000007 
   cpu_subtype_t cpusubtype = 0x80000003 
   uint32_t filetype = 0x00000002 
   uint32_t ncmds = 0x00000013 
   uint32_t sizeofcmds = 0x00000740 
   uint32_t flags = 0x00200085 
   uint32_t reserved = 0x00000000 
};

这里你可以看到第一个值的神奇数字是0xfeedfacf。这比在你的脑海中做颠倒字节的工作要容易一些!

在0xfeedfacf之后,有一个0x01000007。要计算出这个值,你必须查阅mach/machine.h,其中包含以下数值。

#define CPU_ARCH_ABI64  0x01000000  /* 64 bit ABI */
...
#define CPU_TYPE_X86  ((cpu_type_t) 7)

机器类型是CPU_ARCH_ABI64与CPU_TYPE_X86一起产生0x01000007(十六进制)(或16777223(十进制))的ORed。

注意:根据你的计算机型号,以及grep的版本,你可能会收到一些不同的输出,但Mach-O的格式将保持不变。将此作为参考,以确定你自己的独特值。

同样,cpusubtype的值0x80000003可以从同一个头文件中确定,CPU_SUBTYPE_LIB64和CPU_SUBTYPE_X86_64_ALL ORed在一起。

filetype的值为0x00000002,或者更准确地说,MH_EXECUTE。有19个加载命令(十六进制的0x00000013,其大小为0x00000740)。

标志值为0x00200085,包含了一系列的选项,但我会让你跳到mach-o/loader.h中自己去猜测这些。

如果你需要一个特殊的家庭作业,请找出flags变量中0x00200000值的意义。

最后,还有一个保留值,它只是一堆无聊的零,在这里没有任何意义。

胖头文件

有些可执行文件实际上是由一个或多个可执行文件 "粘 "在一起的。例如,许多应用程序同时编译一个32位和64位的可执行文件,并将它们放入一个 "胖 "可执行文件中。

这种将多个可执行文件 "粘在一起 "的做法由胖头表示,胖头也有一个独特的魔法值,将其与Mach-O头区分开来。

紧随胖头之后的是一些结构,它们表示CPU类型和胖头所在文件的偏移量。

查看mach-o/fat.h给出了以下结构。

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

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

...
#define FAT_MAGIC_64  0xcafebabf
#define FAT_CIGAM_64  0xbfbafeca  /* NXSwapLong(FAT_MAGIC_64) */

尽管有一个相当于64位的胖头,但在64位系统中仍然广泛使用32位。64位胖头实际上只在可执行片的偏移量大于4MB时才使用。这与你在上一节看到的Mach-O 64位变体头不同,它只在64位系统中使用。

胖头包含一些胖架构的结构,其值为nfat_arch,紧接着胖头。

下面是64位版本的fat架构。

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

这里是32位版本的fat架构。

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

胖头中的魔法值将指示使用这些32位或64位结构中的哪个。

想看看带有胖头的可执行文件的实际例子吗?请看macOS的CoreFoundation框架。在终端中,输入以下内容。

file /System/Library/Frameworks/CoreFoundation.framework/CoreFoundation

你会看到以下内容。

这说的是CoreFoundation由三种架构切片粘连而成:x86_64、i386、x86_64h。x86_64h架构是怎么回事?它代表Haswell,是2013年10月引入Macbook Pros的x86_64变体。在你自己的时间,你可以通过在加载Core Foundation模块的程序上使用LLDB来查看哪个架构被加载。

例如,在调试一个链接到CoreFoundation的macOS应用程序时,下面的方法会起作用。打开一个终端窗口,输入以下内容。

lldb $(which plutil)

这将为plutil应用程序打开一个LLDB会话,它是一个随macOS提供的小工具,用于操作属性列表。它恰好链接了Core Foundation,所以在这个例子中,它是一个很好的工具。

你需要运行一次该程序,以便它能够真正加载库。像这样输入运行。

(lldb) run 
Process 946 launched: ’/usr/bin/plutil’ (x86_64) 
No files specified.
plutil: [command_option] [other_options] file... 
... etc ...

然后在LLDB会话中,输入以下内容。

(lldb) image list -h CoreFoundation [ 0] 0x00007fff33cf6000

这将转储CoreFoundation模块的加载地址。在你获得加载地址后,转储包含Mach-O头的内存。

(lldb) x/8wx 0x00007fff33cf6000 
0x7fff33cf6000: 0xfeedfacf 0x01000007 0x00000008 0x00000006 
0x7fff33cf6010: 0x00000013 0x00001100 0xc2100085 0x00000000

在我的电脑上,cpusubtype包含值0x00000008,这相当于mach/machine.h中的以下内容。

#define CPU_SUBTYPE_X86_64_H subset */

因此,我可以知道,CoreFoundation的Haswell、x86_64h片断被加载到我的进程中。

跳回CoreFoundation的磁盘表示,转储原始字节。退出LLDB并输入以下内容。

xxd -l 68 -e /System/Library/Frameworks/CoreFoundation.framework/ CoreFoundation

这将产生类似于以下的输出。

00000000: bebafeca 03000000 07000001 03000000   ................ 
00000010: 00100000 30767400 0c000000 07000000   .....tv0........
00000020: 03000000 00907400 e0ca6700 0c000000   .....t...g......
00000030: 07000001 08000000 0060dc00 d0e67400   ..........`...t..
00000040: 0c000000                              ....

0xbebafeca或FAT_CIGAM是字节交换(big-endian)格式的32位脂肪头。这意味着-e是没有必要的。为什么使用68字节作为长度?让我们来算一算...

将上述终端命令的-e参数替换为字节大小参数(-g),表示以4字节分组显示输出。

xxd -l 68 -g 4 /System/Library/Frameworks/CoreFoundation.framework/ CoreFoundation

现在,原始的胖头数据应该更容易查看了。

00000000: cafebabe 00000003 01000007 00000003  ................
00000010: 00001000 00747630 0000000C 00000007  .....tv0........
00000020: 00000003 00749000 0067cae0 0000000C  .....t...g......
00000030: 01000007 00000008 00dc6000 0074e6d0  ..........`...t..
00000040: 0000000c                             ....

注意:如果fat头是64位的变体,-g 4选项就不会起作用,因为在struct fat_arch_64中有几个8字节的变量与4字节的变量混在一起。

前两个值--0xcafebabe和0x00000003--是结构fat_header,而剩下的字节将属于三个结构fat_arch中的一个。检查一下

第一个结构fat_arch,我们可以看到它是针对x86_64的,因为你之前看到的cputype 0x01000007和cpusubtype 0x00000003。到x86_64片开始的偏移量为0x00001000(4096),其大小为0x00747630。

为了证明x86_64片在文件开始的偏移量4096处,使用xxd的-s选项转储x86_64头。

xxd -l 32 -e -s 4096 /System/Library/Frameworks/CoreFoundation.framework/ CoreFoundation

正如你所猜测的,-s选项指定了一个开始的偏移量。你会看到x86_64分片的Mach-O头。

00001000: feedfacf 01000007 00000003 00000006  ................    
00001010: 00000015 00001120 02100085 00000000  .... ...........

现在已经讨论了头文件,是时候进入加载命令了。

加载命令

紧接着Mach-O头的是加载命令,提供了如何将可执行文件加载到内存中的指令,以及其他一些细节。这就是它的有趣之处。每个加载命令由一系列结构组成,每个结构的大小和参数各不相同。

幸运的是,对于每个加载命令结构,前两个变量始终是一致的,即cmd和cmdsize。cmd表示加载命令的类型,cmdsize表示结构的大小。这让你可以遍历加载命令,然后通过适当的cmdsize跳转。

Mach-O的作者预见到了这种情况,并提供了一个通用的加载命令结构,名为struct load_command。

struct load_command { 
  uint32_t cmd; /* type of load command */ 
  uint32_t cmdsize; /* total size of command in bytes */ 
};

这让你可以从每个加载命令开始,作为这个通用结构load_command。一旦你知道了cmd值,你就可以把内存地址投到适当的结构中。

那么,cmd可以有哪些值呢?我们再次对mach-o/loader.h表示信任。

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

如果你看到一个以LC开头的常数,那么你就知道那是一个加载命令。加载命令有64位和32位的对应关系,所以要确保你使用合适的命令。64位的加载命令在名称中会以"_64 "结尾。也就是说,64位系统仍然可以使用32位加载命令。例如,LC_UUID加载命令的名称中不包含_64,但在所有可执行文件中都包含。

LC_UUID是比较简单的加载命令之一,所以它是一个很好的开始例子。LC_UUID提供了通用唯一标识符,以识别一个可执行文件的特定版本。这个加载命令不提供任何特定的段信息,因为它都包含在LC_UUID结构中。

事实上,LC_UUID加载命令的结构是mach-o/loader.h中的uuid_command结构。

回到grep,你可以使用带有-l(加载命令)选项的otool命令查看grep的UUID。

otool -l $(which grep) | grep LC_UUID -A2

这将转储任何包含LC_UUID短语的点击后的两行。

  cmd LC_UUID 
cmdsize 24 
  uuid 3B067B3F-4F1F-39A3-A4B9-CFDD595F9289

otool将cmd从0x1b转换为LC_UUID,显示cmdsize为

sizeof(struct uuid_command)(又称24字节),并将UUID值显示为一个

的格式显示UUID值。如果你使用的是与我相同的macOS版本,那么你就会有相同的UUID!

段落

LC_UUID是一个简单的加载命令,因为它是独立的,不提供可执行文件的段/节的偏移量。现在是时候把你的注意力转向段了。

一个段是一个具有特定权限的内存分组。一个段可以有0个或更多的子组件,被称为段。

在讨论为段提供指令的加载命令结构之前,让我们先谈谈程序中通常会出现的一些段。

让我们通过看一个现实生活中的例子来锤炼这些信息。使用LLDB并附加到任何进程。是的,任何进程! 我将选择Simulator的SpringBoard作为例子。

启动Simulator后,键入以下内容。

lldb -n SpringBoard

连接后,键入以下内容。

(lldb) image dump sections SpringBoard

这将转储SpringBoard模块的部分(和段)。

同样,这也是不同区段和部分的内存分解。如果你想看磁盘上的Mach-O布局说明,你可以使用以下命令。

(lldb) image dump objfile SpringBoard

这将给你相当多的信息,因为它吐出了Mach-O加载指令以及模块中发现的所有符号。如果你滚动到顶部,你会发现所有的加载命令。重要的是要注意段和节的磁盘位置与段和节的实际内存位置之间的区别,因为一旦加载到内存中,它们将有不同的值。

注意:和往常一样,我写了一个LLDB命令,它以更漂亮的格式显示输出,并有更高级的选项来获取可执行文件中特定部分的信息。你可以在这里找到它:https://github.com/DerekSelander/LLDB/blob/master/lldb_commands/section.py

以编程方式找到段和节

在本章的演示部分,你将建立一个macOS的可执行程序,它可以遍历已加载的模块,并打印出每个模块中的所有段和节。

打开Xcode,创建一个新的项目,选择macOS然后选择命令行工具,并将这个程序命名为MachOSegments。确保选择Swift语言。

打开main.swift并将其内容替换为以下内容。

将此分解。

  1. 尽管Foundation将间接地导入MachO模块,但为了安全和代码的清晰,你明确地导入MachO模块。稍后你将会使用mach-o/loader.h中的几个结构。

  2. _dyld_image_count函数将返回进程中所有加载模块的总计数。你将用它在for循环中遍历所有的模块。

  3. _dyld_get_image_name函数将返回图像的完整路径。

  4. _dyld_get_image_header将返回Mach-O头的加载地址。(mach_header或mach_header_64)的加载地址,用于当前模块。

  5. CFRunLoopRun将防止应用程序退出。这很理想,因为我将让你在输出完成后用LLDB检查这个过程。

构建并运行该程序。你会看到一个模块的列表和它们的加载地址被吐出到控制台。这些加载地址是该模块的特定Mach-O头在内存中的位置。这几乎和在LLDB中做image list -b -h完全一样。如果你很好奇,想看一下这些值中的Mach-O头,可以复制其中的一个值,然后用LLDB来转储内存。

例如,在我的输出中,我看到以下内容。

8 CoreFoundation 0x00007fff33cf6000

你可以通过暂停执行并在LLDB中输入以下内容来查看CoreFoundations Mach-O Header的原始字节。

(lldb) x/8wx 0x00007fff33cf6000

然后你会看到与下面类似的内容。

0x7fff33cf6000: 0xfeedfacf 0x01000007 0x00000008 0x00000006 
0x7fff33cf6010: 0x00000013 0x00001100 0xc2100085 0x00000000

现在你有了MachOSegments程序中的基本输出,在for循环的末尾添加以下代码。

这就是Swift和C语言互操作性的丑陋之处,真正开始抬头。再来看看数字的分类。

  1. 加载命令在Mach-O头之后立即开始,所以头的地址要加到mach_header_64的加载地址的大小,以确定加载命令的开始位置。一个好的程序会通过确定魔法值来检查它是否在运行32位模式,但偶尔走走野路子也很有趣......

  2. 使用Swift的UnsafePointer将加载命令投给你之前看到的 "通用 "load_command结构。如果这个结构包含正确的cmd值,你将把这个内存地址投给适当的segment_command_64结构。

  3. 这里你知道load_command结构实际上应该是一个segment_command_64结构,所以我们再次使用Swift的UnsafePointer对象。

  4. 在每个循环结束时,我们需要将curLoadCommandIterator变量递增递增当前loadCommand的大小,这由cmdsize变量决定。

注意:当我看到LC_SEGMENT_64这个值时,我怎么知道要投出segment_command_64结构?在 mach-o/loader.h 头文件中,搜索所有对LC_SEGMENT_64。这里有定义LC_SEGMENT_64的声明,还有还有segment_command_64,它指出其cmd是LC_SEGMENT_64。找到所有对加载命令的引用将给你提供适当的C结构。

建立并运行。

在执行时,你会得到一些相当难看的输出,比如下面这个截断的输出。

0 MachOPOC 0x000000010000
(95, 95, 80, 65, 71, 69, 90, 69, 82, 79, 0, 0, 0, 0, 0, 0) 
(95, 95, 84, 69, 88, 84, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) 
(95, 95, 68, 65, 84, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) 
(95, 95, 76, 73, 78, 75, 69, 68, 73, 84, 0, 0, 0, 0, 0, 0)

这是因为Swift在使用C语言时真的很糟糕。segmentCommand.segname被声明为一个Int8的Swift元组。这意味着你需要建立一个辅助函数来将这些值转换为实际可读的Swift字符串。

跳到main.swift的顶端部分,声明以下函数。

使用Mirror对象,你可以取一个任何大小的元组并对其进行迭代。这比硬编码一个有16个Int8的Tuple类型的参数要干净得多。

跳回主体,用下面的内容替换print("\t(segmentCommand.segname)")。

let segName = convertIntTupleToString( name: segmentCommand.segname) 
print("\t\(segName)")

建立并运行。

0 MachOPOC 0x0000000100000000 
  __PAGEZERO 
  __TEXT 
  __DATA 
  __LINKEDIT 
1 libBacktraceRecording.dylib 0x0000000100ac7000 
  __TEXT 
  __DATA 
  __LINKEDIT 
2 libMainThreadChecker.dylib 0x0000000100ad7000 
  __TEXT 
  __DATA 
  __LINKEDIT 
  ...

好多了,对吗?现在每个模块都将打印出它所包含的分段。你就快成功了! 最后的障碍是打印出每个段的其余部分。

就在你刚刚创建的新打印命令下面,添加以下代码。

最后一轮的数字解释。

  1. 在每个结构segment_command_64中,都有一个成员规定了紧随其后的section_64命令的数量。你将使用另一个for循环来遍历每个片段中的所有部分。

  2. 开始时,你要抓取内存中第一个section_64结构的基址。

  3. 对于for循环中的每一次迭代,你将从偏移地址开始,然后加上由迭代器变量j乘以的section_64结构的大小。

  4. 一个section_64结构还有一个sectname变量,是另一个Int8的元组。你将抛出你先前创建的同一个函数,从其中抓出一个漂亮的Swift String。

代码就这样了。建立并运行。包括一个你将得到的输出的小片段。

0 MachOPOC 0x0000000100000000 
  __PAGEZERO 
  __TEXT 
    __text 
    __stubs 
    __stub_helper 
    __cstring 
    __objc_methname 
    __const 
    __swift4_types 
    __swift4_typeref 
    __swift4_reflstr 
    __swift4_fieldmd 
    __swift4_capture 
    __swift4_assocty 
    __swift4_proto 
    __swift4_builtin 
    __objc_classname 
    __objc_methtype 
    __swift4_protos 
    __ustring 
    __gcc_except_tab 
    __unwind_info 
    __eh_frame 
__DATA
  __nl_symbol_ptr 
  __got 
  __la_symbol_ptr 
  __mod_init_func 
  __const 
  __cfstring 
  __objc_classlist 
  __objc_nlclslist 
  __objc_catlist 
  __objc_protolist 
  __objc_imageinfo 
  __objc_const 
  __objc_selrefs 
  __objc_protorefs 
  __objc_classrefs 
  __objc_superrefs 
  __objc_ivar 
  __objc_data 
  __data 
  __crash_info 
  __thread_vars 
  __thread_bss 
  __bss 
  __common 
__LINKEDIT

正如你所看到的,只有主可执行文件有__PAGEZERO段,它有0个部分。有一大堆包含 swift4 的部分。在__DATA段中有一堆与Objective-C相关的部分,因为Swift在苹果平台上没有Objective-C就无法生存。

在下一章中,你会更仔细地研究其中的一些部分,并利用本章所学的知识做一些更有趣的事情。

从这里开始要去哪里?

如果我还没有间接暗示够的话,去看看mach-o/loader.h。我自己已经读了很多遍那个标题,每次读都能学到新东西。那里有很多东西,所以如果这一章把你打回原形,不要感到沮丧。

用你创建的结构玩所有的变量。查看其他加载命令,并将其与相应的结构相匹配。将这些命令添加到你创建的演示项目中,看看你能从中获得哪些信息。


上一章 目录 下一章