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

本文所读的源码,可以从这里找到,这是 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、逆向工程等,加油吧

参考链接

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

推荐阅读更多精彩内容

  • 熟悉Linux和windows开发的同学都知道,ELF是Linux下可执行文件的格式,PE32/PE32+是win...
    Klaus_J阅读 3,762评论 1 10
  • 13. Hook原理介绍 13.1 Objective-C消息传递(Messaging) 对于C/C++这类静态语...
    Flonger阅读 1,382评论 0 3
  • 上一篇博客介绍了mach_header相关内容,Mach-O文件介绍之mach_header。这篇博客主要介绍Ma...
    Tomychen阅读 2,280评论 0 7
  • 原文地址 写在之前 之前工作中对Mach-O文件有一定的接触, 原本早就想写一篇文章分享一下,但是奈何只是不够深入...
    南栀倾寒阅读 4,697评论 3 22
  • 13.1 Objective-C消息传递(Messaging) 对于C/C++这类静态语言,调用一个方法其实就是跳...
    泰克2008阅读 1,852评论 1 6