水平有限,错误在所难免,求指点。
Mach-O格式全称为Mach Object文件格式的缩写,是mac上可执行文件的格式, 类似于windows上的PE格式 (Portable Executable ), linux上的elf格式 (Executable and Linking Format)
偷一张苹果官网上面的图
从图上我们可以大概的看出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结构体的格式按偏移对应好。
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)
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, 继续分析。
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 */
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 */
};
- segname command名字
- vmaddr 虚拟地址
- vmsize 虚拟地址大小
- fileoff 这个段所在的文件偏移
- filesize 这个段大小
- maxprot 这个段锁需要的内存属性
- initprot 这个段初始化的内存属性
- nsects 这个段上有多少个section
依次提取出来的python 的 segment名字如下
__PAGEZERO
__TEXT
__DATA
__LINKEDIT
以主要的__TEXT, __DATA为例,展开section分析
__TEXT段所包含的sections个数为7, 对应的结构体信息为section_64
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,
套上结构体,得到对应的名字依次为
__got
__nl_symbol_ptr
__la_symbol_ptr
__cfstring
__data
__bss
python 源码是纯C写的,没有使用oc语言,所有解析出来的信息里面没有保存object-c class的信息,下篇找个使用oc的语言的程序进行解析,试着找出object-c class的内存布局。