Mach-O

103 阅读6分钟

格式

Mash-O格式.gif

1. header

header 包含了,cpu类型,加载command的数量,文件类型等信息

/*
* The 32-bit mach header appears at the very beginning of the object file for
* 32-bit architectures.
32位 架构数据结构类型
*/
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.
64位架构数据结构类型
*/
struct mach_header_64 {
  uint32_t	magic;		/* mach magic number identifier */
// 识CPU的架构 arm x86, i386
  cpu_type_t	cputype;	/* cpu specifier */
// 体的CPU类型,区分不同版本的处理器
  cpu_subtype_t	cpusubtype;	/* machine specifier */
// 文件类型
  uint32_t	filetype;	/* type of file */
//加载了多少command,每个LoadCommands代表了一种Segment的加载方式
  uint32_t	ncmds;		/* number of load commands */
//LoadCommand的大小,主要用于划分Mach-O文件的‘区域’
  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 /* 

经常遇见的Mach-O文件类型:

  • MH_OBJECT,这种类型的文件有目标文件(.o)、静态库文件(.a) (静态库文件就是N个.o文件合并在一起的)

  • MH_EXECUTE,可执行文件,例如上面说的Super文件

  • MH_DYLIB,动态库文件,包括.dylib、.framework

  • MH_DYLINKER,动态链接编辑器,例如:位于手机这里的Device/usr/lib/的dyld程序

MH_DSYM,存储二进制符号信息的文件,dsym文件常用于分析APP的崩溃信息

loadCommands

用来描述文件在虚拟地址中的布局结构,就是存储着各段数据的大小,分段,地址等信息

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

这些加载指令清晰地告诉加载器如何处理二进制数据,有些命令是由内核处理的,有些是由动态链接器处理的。在源码中有明显的注释来说明这些是动态连接器处理的。 根据cmd字段的类型不同,使用了不同的函数来加载.看一看在内核代码中不同的command类型都有哪些作用。

  1. LC-SEGMENT;LC-SEGMENT-64 在内核中由load-segment 函数处理(将segment中的数据加载并映射到进程的内存空间去)

  2. LC-LOAD-DYLINKER 在内核中由load-dylinker 函数处理(调用/usr/lib/dyld程序)

  3. LC-UUID 在内核中由load-uuid 函数处理 (加载128-bit的唯一ID)

  4. LC-THREAD 在内核中由load-thread 函数处理 (开启一个MACH线程,但是不分配栈空间)

  5. LC-UNIXTHREAD 在内核中由load-unixthread 函数处理 (开启一个UNIX posix线程)

  6. LC-CODE-SIGNATURE 在内核中由load-code-signature 函数处理 (进行数字签名)

  7. LC-ENCRYPTION-INFO 在内核中由 set-code-unprotect 函数处理 (加密二进制文件)

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

存放着各段的原始数据,就是Load commands区域描述的地址所指向的数据

注入dylib整体思路

1、读取Mach-O文件信息到内存中;

2、定义一个mach_header,将原来的mach_header写到新定义的mach_header中;

3、 在新定义的mach_header中依需将ncmds加1,sizeofcmds加上要注入的dylib库的大小;

4、将新定义并修改好的mach_header从Mach-O最开始部分覆盖原文件的mach_header;

5、指针跳过sizeofcmds大小内存;

6、定义一个dylib结构体并赋值,即注入的 dylib 信息;

7、回退(新mach_header中sizeofcmds已包含要注入dylib的大小)并覆盖、写入 path 信息。

命令数据结构用途
LC_UUIDuuid_command(page 20)指定图像或其对应的dSYM文件的128位UUID
LC_SEGMENTsegment_command加载此文件时,定义映射到进程地址空间中所需的文件段。而且每个段中包含了所有的节
LC_SYMTABsymtab_command指定了文件的符号表。静态链接器和动态连接器连接文件的时候都需要用到这些信息,还可以通过调试器将符号映射到生成符号的原始源代码文件。
LC_DYSYMTABdysymtab_command指定了动态连接器用到的附带符号表信息
LC_THREAD LC_UNIXTHREADthread_command对于可执行文件,LC_UNIXTHREAD命令定义了进程主线程的线程状态。LC_THREAD和LC_UNIXTHREAD一样,但是LC_THREAD不会引起内核分配堆栈
LC_LOAD_DYLIBdylib_command定义此文件链接的动态共享库的名称。
LC_ID_DYLIBdylib_command定义了动态共享库安装名称
LC_PREBOUND_DYLIBprebound_dylib_command对于此可执行文件链接预绑定的共享库,指定使用的共享库中的模块。
LC_LOAD_DYLINKERdylinker_command指定内核执行加载文件所需的动态连接器
LC_ID_DYLINKERdylinker_command标志这个文件可以作为动态连接器
LC_ROUTINESroutines_command包含共享库初始化例行程序的地址(由链接器的-init选项指定)。
LC_ROUTINES_64routines_command_64包含共享库64位初始化例行程序的地址(由链接器的-init选项指定)。
LC_TWOLEVEL_HINTStwolevel_hints_command包含两级命名空间查询提示表。
LC_SUB_FRAMEWORKsub_framework_command将此文件标识为伞形框架的子框架的实现。伞形框架的名称存储在字符串参数中。(伞形框架可以包含多个子框架,苹果不推荐这样使用)
LC_SUB_UMBRELLAsub_umbrella_command指定此文件作为伞框架的子伞
LC_SUB_LIBRARYsub_library_command标志这个文件可以作为伞框架的一个字库的实现。请注意,Apple尚未为子库定义受支持的位置。
LC_SUB_CLIENTsub_client_command子框架可以明确地允许另一个框架或包链接到它,方法是包含一个LC_SUB_CLIENT load命令,该命令包含框架的名称或包的客户端名称。

1、(__TEXT,__text)

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

2、(__DATA,__data)

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

3、Symbol Table

符号表,这个是重点中的重点,符号表是将地址和符号联系起来的桥梁。符号表并不能直接存储符号,而是存储符号位于字符串表的位置。

4、String Table

字符串表所有的变量名、函数名等,都以字符串的形式存储在字符串表中。

5、动态符号表

动态符号表存储的是动态库函数位于符号表的偏移信息。(__DATA,__la_symbol_ptr) section 可以从动态符号表中获取到该section位于符号表的索引数组。