Mach-O分析

Mach-O二进制格式

Mach-Object,简称Mach-O,是苹果自己独有的二进制文件格式,运行在OSX和iOS上。
一个Mach-O文件由3个模块组成,文件头(Header)、加载命令(Load Commands)、数据块(Data)。


Mach-O 格式
  • Header : 保存了Mach-O的一些基本信息,包括cpu类型、文件类型、loadCommands等。
  • LoadCommands : 加载命令,紧跟在Header后面。
  • Data : 保存每一个segment的具体数据,包含了具体的代码、数据等。
1、Mach-O文件头

我们可以在dyld源码中找到对应的定义,在<mach-o/loader.h>头文件中:

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

/* 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 /* NXSwapInt(MH_MAGIC_64) */

不论是32位还是64位,都可以用下图表示:


Mach-O 文件头
  • magic :魔数值,也可以说是特征值,加载器可以通过这个魔数值快速判断二进制文件运行在32位还是64位系统上。在源码中可以看出都是定义为固定数值的宏。一些可执行文件的魔数参见表1-1。
  • cputype/cpusubtype:CPU类型和子类型,定义在<mach/machine.h>头文件中。
  • filetype :文件类型。可执行文件、库文件、核心转储文件、内核扩展等,详见表1-2。
  • ncmds/sizeofcmds:用于加载器的加载命令,数量和大小。
  • flags:动态链接器(dyld)的标志,常见的详见表1-3。
  • Reserved:仅64位有,额外的预留字段,目前还没有使用。

表 1-1 文件类型和魔数:

文件类型 魔数 用途
Mach-O文件 0xfeedface(32位)/0xfeedfacf(64位) OS X原生二进制格式
通用二进制文件 0xcafebabe(小尾顺序)/0xbebafeca(大尾顺序) 包含多种架构支持的二进制格式,只在OS X上支持
解释器脚本 #! unix脚本和一些解释器脚本使用的格式:主要用于shell脚本,也常用于其他解释器,例如Perl、AWK、PHP等。内核寻找#!后面跟着的字符串,,然后执行这个字符串表示的命令。文件剩下的部分通过标准输入(stdin)传递给这个命令
ELF \x7FELF 可执行文件和库文件格式:Linux和大部分Unix的原生格式,OS X不支持ELF
PE32/PE32+ MZ 可移植的可执行格式:Windows和Intel的EFI(Extensible Firmware Interface)二进制文件的原生格式。尽管OS X不支持这个格式,但是引导器支持这个格式,可以加载boot.efi文件
  • 大端小端模式
    字节存储有大尾顺序和小尾顺序之分,也就是大端小端模式。
    例如数值0x1234使用两个字节储存:高位字节是0x12,低位字节是0x34。
    所谓的大端模式,是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中。高位字节在前,低位字节在后。
    高位在低地址,低位在高地址(0x1234)
    所谓的小端模式,是指数据的低位保存在内存的低地址中,而数据的高位保存在内存的高地址中。低位字节在前,高位字节在后。
    高位在高地址,低位在低地址(0x3412)
0x1234567 大小端存储
  • 解释器脚本
    解释器脚本格式实际上只是一种特殊形式的二进制文件格式,因为这些文件只不过是指向"真正"二进制的脚本,而这些被指向的文件才是真正得到执行的文件。

表 1-2 Mach-O文件类型:

文件类型 定义值 用途 备注
MH_OBJECT 0x1 可重定位的目标文件,编译器对源代码变异得到的(中间)结果,也可以是32位的内核扩展 通过gcc -c xxx.c 生成xxx.o文件
MH_EXECUTE 0x2 可执行二进制文件 应用程序的二进制文件
MH_FVMLIB 0x3 VM共享库文件 xxx
MH_CORE 0x4 核心转储文件 核心转储时生成
MH_PRELOAD 0x5 预加载执行文件 xxx
MH_DYLIB 0x6 动态库 /usr/lib中的库文件
MH_DYLINKER 0x7 动态链接器 dyld
MH_BUNDLE 0x8 插件:非独立的二进制文件,要加载至其他二进制文件才能发挥作用 通过gcc -bindle生成,Xcode里面可以创建bundle的Target
MH_DYLIB_STUB 0x9 静态共享库桩 xxx
MH_DSYM 0xa 辅助的符号文件以及调试信息 通过gcc -g生成,Xcode->Product->Archive打包之后,里面就会生成一个叫DSYM后缀名的文件夹
MH_KEXT_BUNDLE 0xb 内核扩展 64位的内核扩展,常见于/System/Library/Extensions下

表1-3 Mach-O头文件常见标志(flags):

标志 定义值 用途
MH_NOUNDEFS 0x1 表示目标文件没有带有未定义的符号。这些目标文件大部分都是静态二进制文件,没有进一步的链接依赖关系
MH_DYLDLINK 0x4 该目标文件是dyld的输入文件,无法被再次的静态链接
MH_TWOLEVEL 0x80 该镜像使用的是两级名称空间绑定
MH_WEAK_DEFINES 0x8000 二进制文件包含了弱符号
MH_BINDS_TO_WEAK 0x10000 二进制文件链接了弱符号
MH_ALLOW_STACK_EXECUTION 0x20000 允许栈执行。只有可执行文件(MH_EXECUTE)可以使用这个标志,但是通常不建议使用,当发生缓冲区溢出时,可执行的栈会给代码注入带来方便
MH_PIE 0x200000 对可执行文件启用地址空间随机化分布,仅MH_EXECUTE可用
MH_NO_HEAP_EXECUTION 0x1000000 将堆标记为不可执行。可以防止"堆喷"的攻击技术,使用这种技术的黑客盲目地用shellcode覆盖大量的堆空间,然后投机地跳转到这些地址中,企图能够跳转到自己的代码并且执行

代码注入常见方法是使用栈变量(即自动变量),因此默认情况下栈都标记为不可执行。而MH_ALLOW_STACK_EXECUTION(允许栈执行)这个标志可以覆盖这种行为,但是非常危险。堆则默认可执行,尽管完全可能被注入,但是通过堆注入代码相对困难一些。

我们可以用MachOView打开一个可执行文件,如下:


Mach Header

otool工具是可以查看Mach-O文件的原生工具。

$ otool -hV /bin/ls
Mach header
      magic cputype cpusubtype  caps    filetype ncmds sizeofcmds      flags
MH_MAGIC_64  X86_64        ALL LIB64     EXECUTE    18       1816   NOUNDEFS DYLDLINK TWOLEVEL PIE

Mach-O文件头的主要功能在于加载命令(Load Commands),加载命令紧跟在文件头之后。从文件头中可以知道加载命令的数量和大小(ncmds/sizeofcmds)。

2、Load Commands

dyld源码中定义了50多条Load Commands,这些指令在被调用时清晰地指导了如何设置并加载二进制数据。
所有加载命令的前两个字段必须是cmd和cmdsize:

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

cmd表示类型,cmdsize表示大小,32位体系架构的cmdsize必须是4字节的倍数,对于64位体系结构,必须是8字节的倍数(这永远是任何加载命令的最大对齐)。填充的字节必须为零,目标文件中的所有表也必须遵循这些规则,这样目标文件才能进行内存映射。

表1-4 LoadCommands类型:

命令 定义值 用途
LC_SEGMENT 0x1 将文件中(32位)的段映射到内存地址空间中
LC_SEGMENT_64 0x19 将文件中(64位的段映射到内存地址空间中
LC_LOAD_DYLINKER 0xe 加载一个动态链接器,即dyld
LC_LOAD_DYLIB 0xc 加载动态链接库,大部分可执行文件都是动态链接的,包括系统提供和第三方的库
LC_UUID 0x1b 一个唯一的128位ID,这个ID匹配一个二进制文件及其对应的符号
LC_THREAD 0x4 开启一个Mach线程,但是不分配栈(很少在核心转储文件之外使用)
LC_UNIXTHREAD 0x5 开启一个UNIX线程(初始化栈布局和寄存器)。设置程序主线程的入口点地址和栈大小,通常情况下,除了指令指针/程序计数器之外,所有的寄存器值都为0,Lion之后使用LC_MAIN
LC_CODE_SIGNATURE 0x1d 代码签名
LC_ENCRYPTION_INFO 0x21 加密的二进制文件
LC_DYLD_INFO_ONLY 0x22 压缩dyld信息
LC_SYMTAB 0x2 链接器桩的符号表信息
LC_DYSYMTAB 0xb 动态链接器符号表信息
LC_VERSION_MIN_IPHONEOS 0x25 手机的最低支持版本
LC_FUNCTION_STARTS 0x26 压缩表函数起始地址

LC_SEGMENT(或LC_SEGMENT_64)命令是最主要的加载命令,这条命令指导内核如何设置新运行的进程的内存空间。这些 segment直接从Mach-O二进制文件加载到内存中。
LC_SEGMENT、LC_SEGMENT_64结构定义

struct segment_command { /* for 32-bit architectures */
    uint32_t    cmd;        /* LC_SEGMENT */
    uint32_t    cmdsize;    /* includes sizeof section structs */
    char        segname[16];    /* segment name */
    uint32_t    vmaddr;     /* memory address of this segment */
    uint32_t    vmsize;     /* memory size of this segment */
    uint32_t    fileoff;    /* file offset of this segment */
    uint32_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 */
};

/*
 * The 64-bit segment load command indicates that a part of this file is to be
 * mapped into a 64-bit task's address space.  If the 64-bit segment has
 * sections then section_64 structures directly follow the 64-bit segment
 * command and their size is reflected in cmdsize.
 */
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 segment的名字
vmaddr segname的虚拟物理地址
vmsize segname分配的虚拟内存大小
fileoff segname在文件中的偏移量
filesize segname在文件中占用的字节数
maxprot segname的页面所需要的最高内存保护
initprot segname的页面最初始的内存保护
nsects segname中区(section)数量
flags 标志位

通过LC_SEGMENT命令,设置进程虚拟内存的过程就变成遵循LC_SEGMENT命令的简单操作。对于每一个段,将文件中相应的内容加载到内存中:从偏移量为fileoff处加载filesize字节到虚拟内存地址vmaddr处的vmsize字节。每一个段的页面都根据initprot进行初始化,initprot指定了如何通过读/写/执行位初始化页面的保护级别。段的保护设置可以动态改变,但是不能超过maxprot中指定的值。
_PAGEZERO段(空指针陷阱)、_TEXT段(程序代码)、_DATA段(程序数据)和_LINKEDIT(链接器使用的符号和其他表)段提供了LC_SEGMENT命令。


2-1 _PAGEZERO

从图2-1中可以看出,_PAGEZERO段的fileoff=0,filesize=0,vmaddr=0,vmsize=16384=0x4000,那么该段就是从偏移量为0处加载0个字节到虚拟地址为0处的16384字节,那么下一段的vmaddr地址应该就是从0x4000开始向后分配。如下图所示:


2-2 _TEXT

_TEXT段则是从偏移量为0处加载15335424个字节到虚拟地址为0x4000处的15335424字节。因此这一段的虚拟地址分配为0x4000+15335424=0xEA4000,也就是占用15335424字节大小,内存地址为0x4000到0xEA4000。这里刚好filesize和vmsize的大小相等。同理下一段的vmaddr起始位置为0xEA4000。如下所示:
2-3 _DATA

图2-3中可以看到filesize和vmsize的大小是不相等的,vmsize应该比filesize大,一个是文件实际大小,一个是分配的内存空间。

我们再看一看fileoff,_PAGEZERO的fileoff是0,filesize也是0,在_TEXT段中fileoff还是0,_TEXT中filesize是15335424,然后在_DATA段中fileoff是15335424。这样看来fileoff对应的就是字节的实际大小偏移量,_DATA段中filesize是3375104,那么在下一段中
fileoff就应该是15335424+3375104=18710528。vmaddr可以理解为虚拟内存地址偏移量,也就是每一个段的起始地址。


2-4 _LINKEDIT
3. Sections

段有时候也可以进一步分解为区(section):


LC_SEGMENT Sections

区的结构定义:

struct section { /* for 32-bit architectures */
    char        sectname[16];   /* name of this section */
    char        segname[16];    /* segment this section goes in */
    uint32_t    addr;       /* memory address of this section */
    uint32_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) */
};

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 主程序代码
__stub_helper、__stubs 用于动态链接的桩
__cstring 程序中硬编码的C语言字符串
__const 用const修饰的常量变量以及硬编码的常量
__objc_methname Objective-C方法名
__objc_methtype Objective-C方法类型
__objc_classname Objective-C类名称
__objc_classlist Objective-C类列表
__objc_protolist Objective-C原型
__objc_imginfo Objective-C镜像信息
__objc_const Objective-C常量
__objc_catlist Objective-C类别列表
__objc_selrefs Objective-C自引用(this)
__objc_protorefs Objective-C原型引用
__objc_classrefs Objective-C类引用
__objc_superrefs Objective-C超类引用
__nl_symbol_ptr 非懒加载符号指针表
__la_symbol_ptr 懒加载符号指针表
__mod_init_func 模块初始化函数
__objc_ivar 属性
__objc_data Objective-C数据
__cfstring 程序中使用的Core Foundation字符串(CFStrings)

segment_command flags类型定义

/* Constants for the flags field of the segment_command */
#define SG_HIGHVM   0x1 /* the file contents for this segment is for
                  the high part of the VM space, the low part
                  is zero filled (for stacks in core files) */
#define SG_FVMLIB   0x2 /* this segment is the VM that is allocated by
                  a fixed VM library, for overlap checking in
                  the link editor */
#define SG_NORELOC  0x4 /* this segment has nothing that was relocated
                  in it and nothing relocated to it, that is
                  it maybe safely replaced without relocation*/
#define SG_PROTECTED_VERSION_1  0x8 /* This segment is protected.  If the
                      segment starts at file offset 0, the
                      first page of the segment is not
                      protected.  All other pages of the
                      segment are protected. */
#define SG_READ_ONLY    0x10 /* This segment is made read-only after fixups */

这里主要有一个标志需要注意,SG_PROTECTED_VERSION_1表示这个段的页面是加密的,Finder就是使用这种加密方式。

$ otool -lV /System/Library/CoreServices/Finder.app/Contents/MacOS/Finder
/System/Library/CoreServices/Finder.app/Contents/MacOS/Finder:
Mach header
      magic cputype cpusubtype  caps    filetype ncmds sizeofcmds      flags
MH_MAGIC_64  X86_64        ALL LIB64     EXECUTE    80       9928   NOUNDEFS DYLDLINK TWOLEVEL BINDS_TO_WEAK PIE MH_HAS_TLV_DESCRIPTORS
Load command 0
      cmd LC_SEGMENT_64
  cmdsize 72
  segname __PAGEZERO
   vmaddr 0x0000000000000000
   vmsize 0x0000000100000000
  fileoff 0
 filesize 0
  maxprot ---
 initprot ---
   nsects 0
    flags (none)
Load command 1
      cmd LC_SEGMENT_64
  cmdsize 1112
  segname __TEXT
   vmaddr 0x0000000100000000
   vmsize 0x0000000000757000
  fileoff 0
 filesize 7696384
  maxprot rwx
 initprot r-x
   nsects 13
    flags PROTECTED_VERSION_1

Xcode包含了一个名为segedit的特殊工具,可以用于提取或替换Mach-O文件中的段。

https://samhuri.net/posts/2010/01/basics-of-the-mach-o-file-format

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容