移动端开发

Mach-O文件格式分析

2019-06-16  本文已影响33人  小凉介

最近用纯Swift参照非常知名的Aspects写了个Aspect,是基于Runtime进行方法交换,正好之前听说过可以通过fishhook动态修改 C 语言函数,所有就研究了一下,但是要想看懂fishhook,需要先了解Mach-O,这一块一直是我的知识盲点,这一次索性花些时间一并消化一下。苹果源码查看这里

Mach-O简介

Mach-O,是Mach object文件格式的缩写,是一种可执行文件、目标代码、共享程序库、动态加载代码和核心dump。是a.out格式的一种替代。Mach-O提供了更强的扩展性,并提升了符号表中信息的访问速度。

*
 * 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 */
                    /*  linking only, no section contents */
#define MH_DSYM     0xa     /* companion file with only debug */
                    /*  sections */
#define MH_KEXT_BUNDLE  0xb     /* x86_64 kexts */

我们看到Mach-O有多种文件类型,常见的格式:

  1. 可执行文件

  2. objcet

    • o 文件(目标文件)
    • .a 静态库文件.其实就是N个.o文件的集合
  3. DYLIB: 动态库文件

    • dylib
    • framework
  4. 动态连接器,dynamic linker

  5. DSYM:分析APP崩溃信息

C 文件 —> 可执行文件

非常推荐 Mach-O 文件一 ,这个也是从这篇文章拿来的。

  1. test.c 的 C 文件

     int main(){
         return 0;
     }
    
  2. 编译一下 clang -c test.c,生成 test.o 文件

  3. 通过 file 命令查看一下 file test.o,可以看到,test.o 为 Mach-O 文件,object 文件 test.o: Mach-O 64-bit object x86_64

  4. 通过 clang 链接一下目标文件test.o clang test.o,text.c 就转变成一个 a.out 的可执行文件

  5. 执行./a.out,转换执行过程

  6. 执行clang -o test1 test.o,链接 test.0 目标文件,生成 test1 的可执行文件

  7. 执行clang -o test2 test.c,直接一次性将源文件生成 test2 的可执行文件

Screen Shot 2019-06-16 at 2.55.46 PM.png Screen Shot 2019-06-16 at 2.56.43 PM.png

Mach-O结构

852671-9fde036a1ce9d902.jpg

通过上图,可以看出Mach-O主要由以下三部分组成:

 来用MachOView验证一下该示例的MachO文件结构:

d5833e6da709a578cfffed4352783f2a926ed76b.png

简单浏览mach-o可执行文件,具体可以分为几个部分:

mach_header_64

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

Load commands

Load commands跟在mach_header之后。所有命令的总大小由mach_header中的sizeofcmds字段给出。oad commands必须有前两个字段cmd和cmdsize。cmd字段以该命令类型的常量填充。每个命令类型都有专门针对它的结构。cmdsize字段是特定加载命令结构的字节大小加上跟随它的任何一部分,这是加载命令(即节结构、字符串等)的一部分。为了前进到下一个加载命令,cmdsize可以被添加到当前加载命令的偏移量或指针中。32位架构的cmdsize必须是4字节的倍数,对于64位架构必须是8字节的倍数(这些永远是任何加载命令的最大对齐)。填充的字节必须为零。目标文件中的所有表也必须遵循这些规则,以便文件可以进行内存映射。否则,这些表的指针在某些机器上无法正常工作或根本无法正常工作。所有padding归零像对象将比较逐字节。

/*
 * The segment load command indicates that a part of this file is to be
 * mapped into the task's address space.  The size of this segment in memory,
 * vmsize, maybe equal to or larger than the amount to map from this file,
 * filesize.  The file is mapped starting at fileoff to the beginning of
 * the segment in memory, vmaddr.  The rest of the memory of the segment,
 * if any, is allocated zero fill on demand.  The segment's maximum virtual
 * memory protection and initial virtual memory protection are specified
 * by the maxprot and initprot fields.  If the segment has sections then the
 * section structures directly follow the segment command and their size is
 * reflected in cmdsize.
 */
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 */
};

Section数据

部分的 Segment (主要指的 __TEXT 和 __DATA)可以进一步分解为 Section。之所以按照 Segment -> Section 的结构组织方式,是因为在同一个 Segment 下的 Section,可以控制相同的权限,也可以不完全按照 Page 的大小进行内存对其,节省内存的空间。而 Segment 对外整体暴露,在程序载入阶段映射成一个完整的虚拟内存,更好的做到内存对齐。

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),而section的命名则是两个下划线紧跟着小写字母(__text)。

下面列出段中可能包含的section:

  • __TEXT段:
    __text, __cstring, __picsymbol_stub, __symbol_stub, __const, __litera14, __litera18;
  • __DATA段

__data, __la_symbol_ptr, __nl_symbol_ptr, __dyld, __const, __mod_init_func, __mod_term_func, __bss, __commom;

  • __IMPORT段

__jump_table, __pointers;

其中__TEXT段中的__text是实际上的代码部分;__DATA段的__data是实际的初始数据。

关于Mach-o文件格式就讲完了,如果对程序从加载到执行过程感兴趣可以看Mach-O文件格式和程序从加载到执行过程趣探 Mach-O:加载过程,讲的很详细。

MachO 文件结构详解
Mach-O 文件一
Mach-O文件格式和程序从加载到执行过程
iOS逆向基础Mach-O文件(1)
Mach-O 文件格式探索

上一篇 下一篇

猜你喜欢

热点阅读