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的內存布局。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM