Mach-O 介绍

一、Mach-O 相关概念简介

1.概念描述

Mach-O 为 Mach Object 文件格式的缩写,它是一种用于可执行文件、目标代码、动态库、内核转储的文件格式。作为 a.out 格式的替代,Mach-O 提供了更强的扩展性,并提升了 符号表 中信息的访问速度。

通用二进制
通用二进制代码有两种基本类型:

  • 一种类型就是简单提供两种独立的二进制代码,一个用来对应x86架构,一个用来对应PowerPC架构。
  • 另外一种类型就是只编写一个架构的代码,当另外一种处理环境时让系统自动调用模拟器运行,这会导致运行速度下降。

多重架构二进制(胖二进制)
在 NeXTSTEP ,OPENSTEP 和 Mac OS X 中,可以将多个Mach-O文件组合进一个多重架构二进制(胖二进制)文件中,以用一个单独的二进制文件支持多种架构的指令集。例如,一个Mac OS X中的多重架构二进制可以包含32位和64位的PowerPC代码,或PowerPC和x86的32位代码,甚至包含32位的PowerPC代码,64位PowerPC代码,32位x86代码和64位x86代码。

2.对 Mach-O 文件进行操作

  • 使用 file Mach-O 命令查看 Mach-O 文件类型

    查看文件架构.png
    从上图可以看出该可执行文件是一个通用二进制文件,且包含2种架构:arm_v7 和 arm64。

  • 使用 lifo -info 命令查看文件架构

    文件架构.png

  • 使用 lipo 命令拆分某种架构

lipo  -thin <架构名> -output <输出文件路径>
  • 使用 lipo 命令合并多种架构
lipo -create   -output <输出文件路径>

二、查看可执行文件

1.使用 otool 命令查看 Mach-O 文件

  • 查看可执行文件的动态链接库
otool -L WeChat
  • 查看头信息
otool -h WeChat
  • 查看是否加密
otool -l WeChat | grep crypt

Mach-O 介绍_第1张图片
查看是否加密.png

cryptid 为 0 时表示无加密,即已砸壳;
cryptid 为 1 时表示有加密,即未砸壳。

  • 查看头信息
otool -h DingTalk

2.使用 MachOView 软件查看

用 MachOView 打开可执行文件可以看到有胖二进制文件的结构如下图:


Mach-O 介绍_第2张图片
胖二进制.png

可以看到可执行文件包括三个部分:

  • Fat Header:包含架构数量及不同架构指令集的简单信息
  • Executable(ARM_V7):arm_v7 架构对应的指令集
  • Executable(ARM64_ALL):arm64 架构对应的指令集

三、Mach-O 文件结构

Mach-O 介绍_第3张图片
Mach-O.png

Mach-O主要分为三个部分: HeaderLoad commandsData

  • Header:包含字节顺序、架构类型、加载指令的数量等,使得系统可以快速确认一些信息,比如当前文件用于32位还是64位,对应的处理器是什么、文件类型是什么。
  • Load commands:它是一张包含很多内容的表,内容包括区域的位置、符号表、动态符号表等。每个加载指令都包含一个元信息,比如指令类型、名称、在二进制文件中的位置等等。
  • Data:通常是对象文件中最大的部分。主要包含代码、数据,例如符号表,动态符号表等等。Data 中包含若干个 segment (段),每个 segment 下又有若干个 section(节)。

1. Header

头文件就是该可执行文件的信息概要

Mach-O 介绍_第4张图片
头部.png

该部分结构可以 打开 查看

/*
 * 64位结构头
 */
struct mach_header_64 {
    uint32_t    magic;      // Mach-O 文件的
    cpu_type_t  cputype;    // CPU 架构
    cpu_subtype_t   cpusubtype; // CPU 架构子版本
    uint32_t    filetype;   // 文件类型。常见的有 MH_OBJECT(目标文件)、MH_EXECUTE(可执行文件)、MH_DYLIB(动态库)、MH_DYLINKER(动态链接器)
    uint32_t    ncmds;      // 加载指令数量
    uint32_t    sizeofcmds; // 加载指令大小
    uint32_t    flags;      // dyld 加载需要的一些标记
    uint32_t    reserved;   // 64 位的保留字段
};

2. Load commands

Load commands 作用是让系统知道如何加载文件中的信息,对系统内核加载器和动态链接器起引导作用。

Mach-O 介绍_第5张图片
部分加载指令.png

从上图可以看到,Load command 包含以下部分:

  • LC_SEGMENT_64:定义一个段,加载后被映射到内存中,包括里面的节。相当与一个数据索引,指明了不同类型数据的地址和大小
  • LC_DYLD_INFO_ONLY:记录了有关链接的重要信息,包括 __LINKEDIT 中动态链接相关信息的具体偏移和大小
  • LC_SYMTAB:为文件定义符号表和字符串表,在链接文件时被连接器使用,同时也用于调试器映射符号到源文件。
  • LC_DYSYMTAB:将符号表中给出符号的额外符号信息提供给动态链接器
  • LC_LOAD_DYLINKER:默认的加载器路径
  • LC_UUID:用于标识 Mach-O 文件的 ID,也用于奔溃堆栈和符号文件的对应解析
  • LC_VERSION_MIN_IPHONEOS:系统要求的最低版本
  • LC_SOURCE_VERSION:构建二进制文件的源代码版本号
  • LC_MAIN:程序的入口。dyld 获取改地址,然后跳转到该处执行
  • LC_ENCRYPTION_INFO_64:文件是否加密标志,加密内容的偏移和大小
  • LC_LOAD_DYLIB:依赖的动态库
  • LC_RPATH:Runpath Search Paths, @rpatch 搜索的路径
  • LC_FUNCTION_STARTS:函数起始地址表,使用调试器和其他程序能很容易看到一个地址是否在函数内
  • LC_DATA_IN_CODE:定义在代码段内的非指令的表
  • LC_CODE_SIGNATURE:代码签名信息

3. Data

LC_SEGMENT_64 加载指令映射的就是 Data 中的数据偏移和大小,该文件组要包含四个段:

  • __PAGEZERO:空指针陷阱段,映射到虚拟内容控件的第一页,用于捕捉对 NULL 指针的引用
  • __TEXT:代码段/只读数据段
  • __DATA:读取和写入数据的段
  • __LINKEDIT:动态链接器需要使用的信息,包括重定位信息、绑定信息、懒加载信息等

下面是 64 位 segment 段的数据结构

struct segment_command_64 { /* for 64-bit architectures */
    uint32_t    cmd;        // 指令类型
    uint32_t    cmdsize;    // 指令大小
    char        segname[16];// 段的名字
    uint64_t    vmaddr;     // 映射到虚拟地址的偏移
    uint64_t    vmsize;     // 映射到虚拟地址的大小
    uint64_t    fileoff;    // 对应当前架构文件的偏移
    uint64_t    filesize;   // 文件的大小
    vm_prot_t   maxprot;    // 段页面的最高内存保护
    vm_prot_t   initprot;   // 初始内存保护
    uint32_t    nsects;     // 包含的节的个数
    uint32_t    flags;      // 段页面的标志
};

段中包含的节的数据结构

struct section_64 { /* for 64-bit architectures */
    char        sectname[16];   // 节的名字
    char        segname[16];    // 所属段的名字
    uint64_t    addr;       // 映射到虚拟地址的偏移
    uint64_t    size;       // 节的大小
    uint32_t    offset;     // 节在当前架构文件中的偏移
    uint32_t    align;      // 节的字节对齐大小
    uint32_t    reloff;     // 重定位入口的文件偏移
    uint32_t    nreloc;     // 重定位入口的个性
    uint32_t    flags;      // 节的类型和属性
    uint32_t    reserved1;  // 保留位
    uint32_t    reserved2;  // 保留位
    uint32_t    reserved3;  // 保留位
};

__Text 段中包含的节

  • __text:程序可执行的代码区域
  • __stubs:间接符号存根,跳转到懒加载指针表
  • __stub_helper:帮助解决懒加载符号加载的辅助函数
  • __objc_methname:方法名
  • __objc_classname:类名
  • __objc_methtype:方法签名
  • cstring:只读的 C 风格字符串,包含 OC 的部分字符串和属性名

__Data 段中包含的节

  • __nl_symbol_ptr:非懒加载指针表,在 dylib 加载时立即绑定值
  • __la_symbol_ptr:懒加载指针表,第一次调用是才会绑定值
  • __got:非懒加载全局指针表
  • __mod_init_func:constructor 函数
  • __mod_term_func:destructor 函数
  • __cfstring:OC 字符串
  • __objc_classlist:程序中类的列表
  • __objc_nlclslist:程序中自己实现了+load 方法的类
  • __objc_protolist:协议列表
  • __objc_classrefs:被引用的类列表

你可能感兴趣的:(Mach-O 介绍)