Mach-O 介绍

作者: 绿叶竹林 | 来源:发表于2019-01-10 16:06 被阅读0次

    一、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 <Mach-O> 命令查看文件架构

      文件架构.png
    • 使用 lipo 命令拆分某种架构

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

    二、查看可执行文件

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

    • 查看可执行文件的动态链接库
    otool -L WeChat
    
    • 查看头信息
    otool -h WeChat
    
    • 查看是否加密
    otool -l WeChat | grep crypt
    
    查看是否加密.png
    cryptid 为 0 时表示无加密,即已砸壳;
    cryptid 为 1 时表示有加密,即未砸壳。
    • 查看头信息
    otool -h DingTalk
    

    2.使用 MachOView 软件查看

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


    胖二进制.png

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

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

    三、Mach-O 文件结构

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

    1. Header

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

    头部.png
    该部分结构可以 打开 <macho-o/loader.h> 查看
    /*
     * 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 作用是让系统知道如何加载文件中的信息,对系统内核加载器和动态链接器起引导作用。

    部分加载指令.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 介绍

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