Mach-O相关概念-编程思维

一、什么是Mach-O

Mach-OMach Object的缩写,是Mac/iOS上用于存储程序、库的标准格式

二、属于Mach-O格式的文件类型

xnu内核源码-loader.h文件

#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 */
#define	MH_FILESET	0xc		/* set of mach-o's */

三、常见的Mach-O文件类型

  • MH_OBJECT

    • 目标文件(.o)
    • 静态库文件(.a),静态库文件其实就是N个.o合并在一起
  • MH_EXECUTE:可执行文件

  • MH_DYLB:动态库文件

    • .dylib
    • .framework/xx
  • MH_DYLINKER:动态链接编辑器

    • /usr/bin/dyld
  • MH_DSYM:存储着二进制文件符号信息的文件

    • .dSYM/Contents/Resources/DWARF/xx(常用于分析app的奔溃信息)

四、Universal Binary通用二进制文件

  • 通用二进制文件:同时适用于多种架构的二进制文件;

  • 由于通用二进制文件需要存储多种架构的代码,通用二进制文件通常比单一平台二进制的程序要大;

  • 由于两种架构有共同的一些资源,所以并不会达到单一架构的两倍之多;

  • 由于执行过程中只调用一部分代码,运行起来也不需要额外的内存;

  • 因为文件比原来的要大,也被成为“胖二进制文件”(Fat Binary);

  • 查看二进制支持的架构命令行

    lipo -info xxx
    
  • 胖二进制文件瘦身

    lipo 胖二进制文件路径 -thin 架构类型 -output 输出文件路径
    
  • 两种架构的二进制合并

    lipo -create 文件1路径 文件2路径 -output 输出文件路径
    

五、Mach-O基本结构

5.1 窥探Mach-O的结构

  • 命令行工具

    1、file:查看Mach-O的文件类型
    2、otool:查看Mach-O特定部分和段的内容
    3、lipo:常用于多架构Mach-O文件的处理

     查看架构信息:lipo -info 文件路径
     导出某种特定架构:lipo 文件路径 thin 架构类型 -output 输出文件路径
     合并多种架构:lipo 文件路径1 文件路径2 -output 输出文件路径
    
  • GUI工具

    MachOView

5.2 Mach-O文件包含3个主要区域

  • Header(头部) : 指明了cpu架构、大小端序、文件类型、Load commands个数等一些基本信息
  • Load commands(加载命令) : 描述文件在虚拟内存中的逻辑结构、布局
  • Raw segment data(数据区) : 在Load commands中定义的Segment的原始数据,包含了代码和数据等。

5.2.1 Header

字段
magic 很多类型的文件,其起始的几个字节的内容是固定的,根据这几个字节的内容就可以确定文件类型,因此这几个字节的内容被称为魔数 (magic number)。
cputype CPU类型以及子类型字段,该字段确保系统可以将适合的二进制文件在当前架构下运行
cpusubtype CPU指定子类型,对于inter,arm,powerpc等CPU架构,其都有各个阶段和等级的CPU芯片,该字段就是详细描述其支持CPU子类型
filetype 说明该mach-o文件类型(可执行文件,库文件,核心转储文件,内核扩展,DYSM文件,动态库)
ncmds 说明加载命令条数
sizeofcmds 表示加载命令大小
flags 标志位,该字段用位表示二进制文件支持的功能,主要是和系统加载,链接相关
reserved 保留字段
  • magic number

    苹果平台有以下几种magic类型:
    脚本 - \x7FELF,常用于shell及其他解释器,如 Perl, AWK 等
    通用二进制格式 - 0xcafebabe、0xbebafeca,包含多种架构支持的二进制格式,只在 macOS 上支持
    MachO格式 - 根据苹果xnu内核源码,OSX和iOS上分别有以下几种不同架构对应的Magic number:

MH_CIGAMMH_MAGIC的反写,表示在小端序(litter endian)环境下使用,所以MH_MAGIC是在大端序(big endian)环境下使用

    /* Constant for the magic field of the mach_header (32-bit architectures) */
    #define	MH_MAGIC	0xfeedface	/* the mach magic number */
    #define MH_CIGAM	NXSwapInt(MH_MAGIC)

    /* Constant for the magic field of the mach_header_64 (64-bit architectures) */
    #define MH_MAGIC_64	0xfeedfacf	/* the 64-bit mach magic number */
    #define MH_CIGAM_64	NXSwapInt(MH_MAGIC_64)

Magic number显示涉及到大小端字节序,在class-dump中也可以看到下列源码:

    _byteOrder = CDByteOrder_LittleEndian;

    CDDataCursor *cursor = [[CDDataCursor alloc] initWithData:data];
    _magic = [cursor readBigInt32];
    if (_magic == MH_MAGIC || _magic == MH_MAGIC_64) {
        _byteOrder = CDByteOrder_BigEndian;
    } else if (_magic == MH_CIGAM || _magic == MH_CIGAM_64) {
        _byteOrder = CDByteOrder_LittleEndian;
    } else {
        return nil;
    }
  • 常见flags:

    MH_NOUNDEFS 0x1     /* Target 文件中没有带未定义的符号,不存在链接依赖 */
    MH_DYLDLINK 0x4 该目标文件是dyld的输入文件
    MH_SPLIT_SEGS    0x20  /* Target 文件中的只读 Segment 和可读写 Segment 分开  */
    MH_TWOLEVEL    0x80        /* 该 Image 使用二级命名空间(two name space binding)绑定方案 */
    MH_FORCE_FLAT    0x100 /* 使用扁平命名空间(flat name space binding)绑定(与 MH_TWOLEVEL 互斥) */
    MH_WEAK_DEFINES    0x8000 /* 二进制文件使用了弱符号 */
    MH_BINDS_TO_WEAK 0x10000 /* 二进制文件链接了弱符号 */
    MH_ALLOW_STACK_EXECUTION 0x20000/* 允许 Stack 可执行 */
    MH_PIE 0x200000  /* 对可执行的文件类型启用地址空间 layout 随机化,系统加载进程后为其随机分配一个虚拟内存空间。 */
    MH_NO_HEAP_EXECUTION 0x1000000 /* 将 Heap 标记为不可执行,可防止 heap spray 攻击 */
    

    MH_WEAK_DEFINES表示可执行文件使用了弱符号。 弱符号是一种链接技巧,可以避免在未使用的支持代码中进行链接。 例如,编译器进行分析并确定应用程序仅为整数,并告知链接器不在浮点支持代码中链接。

5.2.2 Load commands

常见的command及作用

command 作用
LC_SEGMENT/LC_SEGMENT_64 将对应的段中的数据加载并映射到进程的内存空间去
LC_SYMTAB 符号表信息
LC_DYSYMTAB 动态符号表信息
LC_LOAD_DYLINKER 标明我们的MachO是被谁加载进去的,即动态加载连接器dyld
LC_UUID 标示该二进制文件唯一的 UUID,128bit
LC_VERSION_MIN_IPHONEOS/MACOSX 要求的最低系统版本(Xcode中的Deployment Target)
LC_MAIN 设置程序主线程的入口地址和栈大小
LC_ENCRYPTION_INFO 加密信息
LC_LOAD_DYLIB 加载的动态库,包括动态库地址、名称、版本号等
LC_FUNCTION_STARTS 函数地址起始表
LC_CODE_SIGNATURE 代码签名信息

LC_SEGMENT/LC_SEGMENT_64用于描述如何加载数据到进程,最为重要,常见的有:

常见Segment 含义
__TEXT 代码段/只读数据段
__PAGEZERO __PAGEZERO 是在可执行文件有的,动态库里没有。这个段开始地址为0(NULL指针指向的位置),是一个不可读、不可写、不可执行的空间,能够在空指针访问时抛出异常。
__DATA 数据段
__LINKEDIT 包含需要被动态链接器使用的信息,包括符号表、字符串表、重定位项表等。该段是只可读,不可写不可执行
__OBJC 包含会被Objective Runtime使用到的一些数据。

5.2.3 Section

  • 常见的section
Section 含义
__text 主程序可执行的机器码
__stubs 用于动态库链接的桩,本质上是一小段会直接跳入lazybinding的表对应项指针指向的地址的代码。
__stub_helper 动态库链接的桩的辅助函数。上述提到的lazybinding的表中对应项的指针在没有找到真正的符号地址的时候,都指向这。
__cstring 去重后的常量字符串符号表描述信息,通过该区信息,可以获得常量字符串符号表地址
_TEXT __const 初始化过的常量
__unwind_info 用于存储处理异常情况信息
__objc_methname 保存OC里面方法名
__objc_classname 保存OC类的名字
__objc_methtype 保存ObOCjc类的一些信息(函数签名)
__objc_classlist OC的类列表
__objc_nlclslist OC的 +load 函数列表,比 __mod_init_func 更早执行
__objc_catlist OC的category列表
__objc_protolist OC的协议列表
__objc_imageinfo 保存文件中OC执行代码的一些信息
__objc_selrefs 指向selectors的引用
__objc_protorefs 指向protocol的引用
__objc_classrefs 指向classes的引用
__objc_superrefs 指向super classes的引用
__mod_init_func 初始化的全局函数地址,在 main 之前被调用
__bss 未初始化的静态变量
_got 存储引用符号的实际地址,类似于动态符号表
__bss 未初始化的静态变量
__nl_symbol_ptr 非lazy-binding的指针表,每个表项中的指针都指向一个在装载过程中,被动态链机器搜索完成的符号
__la_symbol_ptr lazy-binding的指针表,每个表项中的指针一开始指向stub_helper
DATA.common 没有初始化过的符号声明

利用dyld调用__mod_init_func机制脱壳

5.2.4 _debug相关section

  • 作用

    用于记录调试信息

  • 原理

    Xcode使用.dSYM文件记录符号表信息,符号表文件.dSYM实际上是从Mach-O文件中抽取调试信息而得到的文件目录。实际用于保存调试信息的文件是DWARF,在Mach-O中对应_debug相关的section

  • 详细说明

    DWARF(Debugging With Arbitrary Record Formats),是ELFMach-O等文件格式中用来存储和处理调试信息的标准格式。.dSYM中真正保存符号表数据的是DWARF文件。DWARF中不同的数据都保存在相应的section(节)中,ELF文件里所有的section名称都以.debug_开头,Mach-O中关于section的命名和ELF稍有区别,把名称前的.换成了_,例如.debug_info变成了_debug_info

    不管是Xcode勾选生成.dSYM文件,还是手动生成调试文件,实际都调用dsymutil工具从Mach-O文件中提取出调试符号表文件。

    使用dwarfdump工具,可以解析查看_debug相关section数据。解析具体崩溃地址等:

    dwarfdump -e --debug-info YourPath/YourApp.dSYM/Contents/Resources/DWARF >  
    info-e.txt
    
    dwarfdump -e --debug-line YourPath/YourApp.dSYM/Contents/Resources/DWARF > 
    line-e.txt
    
  • 相关文档

    iOS平台调试方案的一个说明

    DWARF调试格式官方介绍

六、Mach-O加载过程

通过前面内容,我们知道Mach-O有多种文件类型,比如MH_DYLIB文件、MH_BUNDLE文件、MH_EXECUTE文件(这些需要dyld动态加载),MH_OBJECT(内核加载)等。所以一个进程往往不是只需要内核加载器就可以完成加载的,需要dyld来进行动态加载配合。

加载过程底层执行:

execve
__mac_execve
exec_activate_image
exec_mach_imgact
load_machfile
parse_machfile
load_dylinker

一、内核加载流程

  • 分配虚拟内存空间。
  • fork进程。
  • 加载 Mach-O 到进程空间。
  • 加载动态连接器 dyld 并将控制权交给 dyld 处理。

二、dyld处理流程

主要有以下步骤:Load dylibs -> Rebase -> Bind -> ObjC -> Initializers

  • 处理环境变量
    大部分可以在Xcode进行相关的配置,进行对应的操作(如Log相关信息)

  • 解析Mach-O执行文件

  • 加载共享动态库
    默认的动态库会合并成一个大缓存文件,放到/System/Library/Cache/com.apple.dyld/目录下,按不同的架构分别保存着。其中包括UIKitFoundation等基础库。

  • Rebase/Bind
    在系统动态加载Mach-O文件的时候,会经过Rebase以及Bind两个阶段,其中Rebase是将内部指针进行固定数值的偏移,而Bind则正式用于将外部符号转为实际指针的步骤。Rebase数据描述了哪些是对指向Mach-O内部的引用并将其修正,而Bind数据描述哪些是指向外部的引用并进行修正。rebasingbinding包括weak_bind以及lazy_bind,它们在__LINKEDIT段内数据流的编码协议基本相同,都是以操作数(opcode)、立即数(immediate)以及uleb128/sleb128编码的偏移组成。

    Rebase - 程序每次启动后地址都会随机变化,这样程序里所有的代码地址都是错的,需要重新对代码地址进行修复才能正常访问,这个操作就是Rebase
    rebasing的协议和操作相对简单,都是找到地址后给其值加上偏移即可。
    rebase协议:通过byte & 0xF0得到opcode(操作数),byte & 0x0F得到immediate(立即数),根据操作数(opcode)进行分支处理。

    Bind - 由于符号在不同的库里面,所以需要符号绑定(Bind)这个过程。
    binding相对rebasing较复杂一些,它多了查找依赖库的部分,不过总体协议是相似的。包含non-lazy bindinglazy bindingweak binding。在ObjC中,类继承关系以及protocol等是non-lazy的,启动时就需要开始绑定,而在函数里的调用外部函数等等都是lazy binding的,在第一次调用时才会进行绑定。

    binding协议:和rebasing相同,通过byte & 0xF0得到opcode(操作数),byte & 0x0F得到immediate(立即数),根据操作数(opcode)进行分支处理。每次binding是在 rebasing之后进行的,他们交替进行,每个Mach-O镜像加载完成后需要将内部的地址引用都修正为偏移之后的正确地址,然后执行binding来修改外部引用地址。

    Export - export数据描述了对外可见的符号,通过objdump命令可查看外部可见符号;

    在进行rebasing之前,内核只是将Mach-O数据映射到虚拟内存,还未加载到内存。当rebasing 阶段开始在__DATA段进行读取时,发现没有数据,产生了page fault内核异常,这个时候内核才会从磁盘将相应的页(page)读到内存继续进行rebasing.

  • 准备Objc环境
    dyld将主程序Mach-O基址指针和包含的ObjC相关类信息传递到libobjc
    ObjC Runtime__DATA段中获取ObjC类信息,由于ObjC是动态语言,可以通过类名获取其实例,所以Runtime维护了一个映射所有类的全局类名表。当加载的数据包含了类的定义,类的名字就需要注册到全局表中。
    获取 protocolcategory 等类相关属性并与对应类进行关联。ObjC 的调用都是基于 selector 的,所以需要对 selector 全局唯一性进行处理。

    以上步骤由 dyld 启动 libSystem.dylib 统一对基础库进行调用执行,这里面就包含了 libobjcRuntime,同时 Runtime 会在 dyld 绑定回调,当 dyld 处理完相关数据后就会调用 ObjC Runtime 执行 Setup 工作。

  • Initializers
    通过 ObjC Runtimedyld 注册的通知,当 Mach-O 镜像准备完毕后,dyld 会回调到 ObjC 中执行 +load 方法,包括以下步骤:

    (1)获取所有 non-lazy class 列表。
    (2)按继承以及 category 的顺序将类排入待加载列表。
    (3)对待加载列表中的类进行方法判断并调用 +load 方法。
    执行 C/C++ 初始化构造器,如通过 attribute((constructor)) 注解的函数。
    如果包含 C++,则 dyld 同样会回调到 libc++ 库中对全局静态变量、隐式初始化等进行调用。

版权声明:本文版权归作者所有,遵循 CC 4.0 BY-SA 许可协议, 转载请注明原文链接
https://www.cnblogs.com/scott-mr/p/16197411.html

二、应用脱壳-编程思维

目录前言一、检测是否脱壳1.1 使用otool检测1.2 使用MachOView检测二、Clutch2.1 安装Clutch2.2 Clutch脱壳实战三、dumpdecrypted3.1 编译dumpdecrypted3.2 dumpdecrypted脱壳实战四、bfinject4.1 安装bfinject4.2 b

ReactiveCocoa(二)-编程思维

前言 通过ReactiveCocoa(一)的学习,相信大家对ReactiveCocoa有了一些基本认识吧。下面就让我们来学习ReactiveCocoa的一些基本使用吧! ReactiveCocoa基本使用 代替代理 在ReactiveCocoa(一)中讲到可以通过RACSubject代替代理,我们也可以通过rac_si

ReactiveCocoa(一)-编程思维

前言 之前总听别人说什么Reactive Cocoa + MVVM,但是没有找到讲解Reactive Cocoa相关的资料。结果进入新公司,项目里面有部分代码使用到了Reactive Cocoa,所以笔者在这记录自己学习Reactive Cocoa的笔记,同时也希望大家通过阅读这篇文章能学到点什么。 Reactive

Git从入门到速成-编程思维

什么是Git Git是Linux发明者Linus开发的一款新时代的版本控制系统,那什么是版本控制系统呢?怎么理解?网上一大堆详细的介绍,但是大多枯燥乏味,对于新手也很难理解,这里我只举几个例子。 熟悉编程的都知道,我们在软件开发中源代码其实是最重要的,那么对源代码的管理变得异常重要: 为了防止代码的丢失,肯定本地机器与

二、应用脱壳-编程思维

目录前言一、检测是否脱壳1.1 使用otool检测1.2 使用MachOView检测二、Clutch2.1 安装Clutch2.2 Clutch脱壳实战三、dumpdecrypted3.1 编译dumpdecrypted3.2 dumpdecrypted脱壳实战四、bfinject4.1 安装bfinject4.2 b

mach-o inside: bss section-编程思维

1 BSS 起源 BSS(Block Started by Symbol)这个词最初是 UA-SAP 汇编器(United Aircraft Symbolic Assembly Program)中的一个伪指令,用于为符号预留一块内存空间。该汇编器由美国联合航空公司于 20 世纪 50 年代中期为 IBM 704 大型机