mach-o格式浅析(一)

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


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

偷一张苹果官网上面的图

Alt text

从图上我们可以大概的看出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的内存布局。

转载于:https://www.cnblogs.com/tieyan/p/4454359.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值