趣探 Mach-O:文件格式分析

本文所读的源码,能够从这里找到,这是 Mach-O 系列的第一篇html

咱们的程序想要跑起来,确定它的可执行文件格式要被操做系统所理解,好比 ELFLinux下可执行文件的格式,PE32/PE32+windows的可执行文件的格式,那么对于OS XiOS 来讲 Mach-O 是其可执行文件的格式。windows

咱们平时了解到的可执行文件、库文件、Dsym文件、动态库、动态链接器都是这种格式的。Mach-O 的组成结构以下图所示包括了HeaderLoad commandsData(包含Segement的具体数据)bash

Header 的结构

Mach-O的头部,使得能够快速确认一些信息,好比当前文件用于32位仍是64位,对应的处理器是什么、文件类型是什么数据结构

能够拿下面的代码作一个例子架构

#include <stdio.h>

int main(int argc, const char * argv[]) {
    // insert code here...
    printf("Hello, World!\n");
    return 0;
}复制代码

在终端执行如下命令,能够生成一个可执行文件a.outapp

192:Test Joy$ gcc -g main.c复制代码

咱们能够使用MachOView(是一个查看MachO 格式文件信息的开源工具)来查看
.out文件的具体格式如何ide

看到这里确定有点懵比,不知道这是什么东西,下面看一下 header的数据结构工具

32位结构ui

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 */
};复制代码

64位架构this

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 */
};复制代码

32位和64位架构的头文件,没有太大的区别,只是64位多了一个保留字段罢了

  • magic:魔数,用于快速确认该文件用于64位仍是32位
  • cputype:CPU类型,好比 arm
  • cpusubtype:对应的具体类型,好比arm6四、armv7
  • filetype:文件类型,好比可执行文件、库文件、Dsym文件,demo中是2 MH_EXECUTE,表明可执行文件
* Constants for the filetype field of the mach_header
 */
#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 */
#define MH_DSYM 0xa /* companion file with only debug */
#define MH_KEXT_BUNDLE 0xb /* x86_64 kexts */复制代码
  • ncmds :加载命令条数
  • sizeofcmds:全部加载命令的大小
  • reserved:保留字段
  • flags:标志位,刚才demo中显示的都在这里了,其他的有兴趣能够阅读 mach o源码
#define MH_NOUNDEFS 0x1 // 目前没有未定义的符号,不存在连接依赖
#define MH_DYLDLINK 0x4 // 该文件是dyld的输入文件,没法被再次静态连接
#define MH_PIE 0x200000 // 加载程序在随机的地址空间,只在 MH_EXECUTE中使用
#define MH_TWOLEVEL 0x80 // 两级名称空间复制代码

随机地址空间

进程每一次启动,地址空间都会简单地随机化。

对于大多数应用程序来讲,地址空间随机化是一个和他们彻底不相关的实现细节,可是对于黑客来讲,它具备重大的意义。

若是采用传统的方式,程序的每一次启动的虚拟内存镜像都是一致的,黑客很容易采起重写内存的方式来破解程序。采用ASLR能够有效的避免黑客攻击。

dyld

动态连接器,他是苹果开源的一个项目,能够在这里下载,当内核执行LC_DYLINK(后面会说到)时,链接器会启动,查找进程所依赖的动态库,并加载到内存中。

二级名称空间

这是dyld的一个独有特性,说是符号空间中还包括所在库的信息,这样子就可让两个不一样的库导出相同的符号,与其对应的是平坦名称空间

Load commands 结构

Load commands紧跟在头部以后,这些加载指令清晰地告诉加载器如何处理二进制数据,有些命令是由内核处理的,有些是由动态连接器处理的。在源码中有明显的注释来讲明这些是动态链接器处理的。

这里列举几个看上去比较熟悉的....

// 将文件的32位或64位的段映射到进程地址空间
#define LC_SEGMENT 0x1 
#define LC_SEGMENT_64 0x19 

// 惟一的 UUID,标示二进制文件
#define LC_UUID 0x1b /* the uuid */

// 刚才提到的,启动动态加载链接器
#define LC_LOAD_DYLINKER 0xe /* load a dynamic linker */

// 代码签名和加密
#define LC_CODE_SIGNATURE 0x1d /* local of code signature */
#define LC_ENCRYPTION_INFO 0x21 /* encrypted segment information */复制代码

load command的结构以下

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

经过 MachOView来继续查看刚才Demo中的Load commands的一些细节,LC_SEGMENT_64LC_SEGMENT是加载的主要命令,它负责指导内核来设置进程的内存空间

  • cmd:就是Load commands的类型,这里LC_SEGMENT_64表明将文件中64位的段映射到进程的地址空间。LC_SEGMENT_64LC_SEGMENT的结构差异不大,下面只列举一个,有兴趣能够阅读源码
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 */
};复制代码
  • cmdsize:表明load command的大小
  • VM Address :段的虚拟内存地址
  • VM Size : 段的虚拟内存大小
  • file offset:段在文件中偏移量
  • file size:段在文件中的大小

将该段对应的文件内容加载到内存中:从offset处加载 file size大小到虚拟内存 vmaddr处,因为这里在内存地址空间中是_PAGEZERO段(这个段不具备访问权限,用来处理空指针)因此都是零

还有图片中的其余段,好比_TEXT对应的就是代码段,_DATA对应的是可读/可写的数据,_LINKEDIT是支持dyld的,里面包含一些符号表等数据

  • nsects:标示了Segment中有多少secetion
  • segment name:段的名称,当前是__PAGEZERO

Segment & Section

这里有个命名的问题,以下图所示,__TEXT表明的是Segment,小写的__text表明 Section

Section的数据结构

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) */
};复制代码
  • sectname:好比_textstubs
  • segname :section所属的segment,好比__TEXT
  • addr :section在内存的起始位置
  • size:section的大小
  • offset:section的文件偏移
  • align :字节大小对齐
  • reloff :重定位入口的文件偏移
  • nreloc: 须要重定位的入口数量
  • flags:包含sectiontypeattributes

发现不少底层知识都是以 Mach-O为基础的,因此最近打算花时间结合Mach-O作一些相对深刻的总结,好比符号解析、bitcode、逆向工程等,加油吧

参考连接

相关文章
相关标签/搜索