mach-o格式浅析(一)

水平有限,错误在所难免,求指点。

Mach-O格式全称为Mach Object文件格式的缩写,是mac上可执行文件的格式, 类似于windows上的PE格式 (Portable Executable ), linux上的elf格式 (Executable and Linking Format)

偷一张苹果官网上面的图

![Alt text](https://developer.apple.com/library/mac/documentation/DeveloperTools/Conceptual/MachORuntime/art/mach_o_segments.gif)

从图上我们可以大概的看出Mach-O可以分为3个部分

  • Header
  • segment
  • section

按图中的指示,header后面是segment,然后再跟着section,而一个segment是可以包含多个section的。mac系统上提供了一个很强大的工具 otool 来方便我们程序猿检阅Mach-O格式, 以本机安装的python2.7.6 为例(-f参数表示解析文件头,也就是Header, 全文都以python2.7.6 为例)


yeweijundeMacBook-Pro:~ yeweijun$ python -V
Python 2.7.6
yeweijundeMacBook-Pro:~ yeweijun$ otool -f /usr/bin/python
Fat headers
fat_magic 0xcafebabe
nfat_arch 2
architecture 0
    cputype 16777223
    cpusubtype 3
    capabilities 0x80
    offset 4096
    size 25904
    align 2^12 (4096)
architecture 1
    cputype 7
    cpusubtype 3
    capabilities 0x0
    offset 32768
    size 25648
    align 2^12 (4096)

那么我们是否不依赖于工具,完全自己人肉解析呢,答案是可以的,只有我们根据apple提供的mach-o格式解析即可。
我们从第一部分 Header 结构体开始, Header的结构体在 /usr/include/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 */
};

用010 editor 以hex方式打开python,运行MachOtemplate模板,按fat_arch结构体的格式按偏移对应好。

Alt text

magic是魔鬼数字,占4个字节,nfat_arch也是4个字节,fat_header后面紧跟的就是fat_arch结构体。 从010 editor hex截图,从文件偏移0开始,直接套上结构体,可以知道

* magic 值是 0xcafebabe * nfat_arch 值为2, 表示后面跟着2个nfat_arch * cputype cpu类型 * cpu_subtype_t 机器标示 * offset 二进制偏移 * size 二进制大小 * 对于nfat_arch[0] cputype 为 1000007h (对应十进制16777223) cpusubtype 为 80000003h offset 为 1000h size 为 6530h align 为 Ch * 对于nfat_arch[1] cputype 为 7h cpusubtype 为 3h offset 为 8000h size 为 6430 align 为 Ch

对比可以看出,我们人肉分析的header的头其实和otool -f参数 打印出来的结构是一致的。

实际上mach-o格式是个复合文件,一个Bin文件当中包含了多套的指令集。mac系统在启动这个进程的时候会加载最合适的某一套指令集,cputype为对应指令集的标示,数值定义在/usr/include/mach/machine.h 中。

CPU框架信息相关函数


/* NXGetAllArchInfos() returns a pointer to an array of all known
 * NXArchInfo structures.  The last NXArchInfo is marked by a NULL name.
 */
extern const NXArchInfo *NXGetAllArchInfos(void);

/* NXGetLocalArchInfo() returns the NXArchInfo for the local host, or NULL
 * if none is known. 
 */
extern const NXArchInfo *NXGetLocalArchInfo(void);

nfat_arch结构体值定义


/*
 * Capability bits used in the definition of cpu_type.
 */
#define	CPU_ARCH_MASK	0xff000000		/* mask for architecture bits */
#define CPU_ARCH_ABI64	0x01000000		/* 64 bit ABI */

/*
 *	Machine types known by all.
 */
 
#define CPU_TYPE_ANY		((cpu_type_t) -1)

#define CPU_TYPE_VAX		((cpu_type_t) 1)
/* skip				((cpu_type_t) 2)	*/
/* skip				((cpu_type_t) 3)	*/
/* skip				((cpu_type_t) 4)	*/
/* skip				((cpu_type_t) 5)	*/
#define	CPU_TYPE_MC680x0	((cpu_type_t) 6)
#define CPU_TYPE_X86		((cpu_type_t) 7)
#define CPU_TYPE_I386		CPU_TYPE_X86		/* compatibility */
#define	CPU_TYPE_X86_64		(CPU_TYPE_X86 | CPU_ARCH_ABI64)

/* skip CPU_TYPE_MIPS		((cpu_type_t) 8)	*/
/* skip 			((cpu_type_t) 9)	*/
#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_MC88000	((cpu_type_t) 13)
#define CPU_TYPE_SPARC		((cpu_type_t) 14)
#define CPU_TYPE_I860		((cpu_type_t) 15)
/* skip	CPU_TYPE_ALPHA		((cpu_type_t) 16)	*/
/* skip				((cpu_type_t) 17)	*/
#define CPU_TYPE_POWERPC		((cpu_type_t) 18)
#define CPU_TYPE_POWERPC64		(CPU_TYPE_POWERPC | CPU_ARCH_ABI64)

nfat_arch[0]中cputype为7, 即CPU_TYPE_X86,nfat_arch[1]中cputype为1000007h, 即CPU_TYPE_X86_64。 也就是说python文件包含了x86和x64两套指令集。 用hopper打开python,也可以看到其识别了2套指令集。

以nfat_arch[0]包含的x64为例。其offset为1000h。此处对应的结构体信息是 mach_header_64(文件位于/usr/include/mach-o/loader.h)

Alt text


struct mach_header {
	uint32_t	magic;		/* mach magic number identifier */  魔鬼数字
	cpu_type_t	cputype;	/* cpu specifier */                 cpu类型和子类型
	cpu_subtype_t	cpusubtype;	/* machine specifier */         
	uint32_t	filetype;	/* type of file */                  文件类型
	uint32_t	ncmds;		/* number of load commands */        commands的个数
	uint32_t	sizeofcmds;	/* the size of all the load commands */     commands大小
	uint32_t	flags;		/* flags */
};

//如果是64位指令,对应的是这个,多了一个reserved
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 */
};

ncmds 为command的个数,值为12h(对应十进制18) , sizeofcmds为command的总大小。
command结构体紧跟着mach_header头后面,跳过mach_header_64的大小,也是从1000h+20h, 继续分析。

Alt text

mach_header_64中 filetype 为文件的类型,可能的值如下, 2为MH_EXECUTE,说明是个执行体。



#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 */
					/*  linking only, no section contents */
#define	MH_DSYM		0xa		/* companion file with only debug */

Alt text

command的结构体头都是load_command开始,根据类型的不同再强制解析成不同的结构体,可能的类型的取值如下。


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

/*
 * After MacOS X 10.1 when a new load command is added that is required to be
 * understood by the dynamic linker for the image to execute properly the
 * LC_REQ_DYLD bit will be or'ed into the load command constant.  If the dynamic
 * linker sees such a load command it it does not understand will issue a
 * "unknown load command required for execution" error and refuse to use the
 * image.  Other load commands without this bit that are not understood will
 * simply be ignored.
 */
#define LC_REQ_DYLD 0x80000000

//load_command中cmd可选值定义 省去部分。。。。

/* 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_SEGMENT_64	0x19	/* 64-bit segment of this file to be
				   mapped */

python的第一项是19h 也就是LC_SEGMENT_64。 那么强制转换为segment_command_64结构体进行解析


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

Alt text

* segname command名字 * vmaddr 虚拟地址 * vmsize 虚拟地址大小 * fileoff 这个段所在的文件偏移 * filesize 这个段大小 * maxprot 这个段锁需要的内存属性 * initprot 这个段初始化的内存属性 * nsects 这个段上有多少个section

依次提取出来的python 的 segment名字如下
__PAGEZERO
__TEXT
__DATA
__LINKEDIT

以主要的__TEXT, __DATA为例,展开section分析

__TEXT段所包含的sections个数为7, 对应的结构体信息为section_64

Alt text



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

套上结构体,得到对应的名字依次为
__text
__stubs
__stub_helper
__cstring
__const
__unwind_info
__eh_frame

__DATA段所包含的sections个数为6,

Alt text

套上结构体,得到对应的名字依次为

__got
__nl_symbol_ptr
__la_symbol_ptr
__cfstring
__data
__bss

python 源码是纯C写的,没有使用oc语言,所有解析出来的信息里面没有保存object-c class的信息,下篇找个使用oc的语言的程序进行解析,试着找出object-c class的内存布局。

posted @ 2015-04-24 19:53  tieyan  阅读(4163)  评论(0编辑  收藏  举报