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

作者: Joy___ | 来源:发表于2016-09-04 22:21 被阅读8026次

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

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

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

    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.out

    192:Test Joy$ gcc -g main.c
    

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

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

    32位结构

    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位架构

    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:对应的具体类型,比如arm64、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、逆向工程等,加油吧

    参考链接

    相关文章

      网友评论

      • 巩固2022:执行a.out时提示:dyld: mach-o, but built for simulator (not macOS)。如何解决这个问题?谢谢
      • BeyondChao:期待,忘楼主持续更新!共勉
      • R0b1n_L33:相见恨晚
      • 萌小菜:不明觉厉 :wink:
      • changbenhe::relieved:看不懂,但是顶一个!
      • 判若两人丶:感觉很HTTP请求头请求体有点像啊,我找到一个错别字:"cmd:就是就是Load commands的类型"哈哈!
        Joy___:@判若两人丶 谢谢 😄

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

      本文链接:https://www.haomeiwen.com/subject/bltrettx.html