MachO文件概述

Mach-O其实是Mach Object文件格式的缩写,是macOS以及iOS上可执行文件的格式,类似于Windows上的PE格式(Portable Executable),Linux上的ELF格式(Executable and Linking Format

Mach-O文件格式

Mach-O是一种用于可执行文件、目标代码、动态库的文件格式。作为a.out格式的替代,Mach-O提供了更强的扩展性

属于MachO格式的常见文件

  • 目标文件.o
  • 库文件:.a.dylibFramework
  • 可执行文件
  • dyld
  • .dSYM

file命令

使用file命令,用来探测给定文件的类型

语法

  1. file (选项) (参数)

选项

  1. -b:列出辨识结果时,不显示文件名称;
  2. -c:详细显示指令执行过程,便于排错或分析程序执行的情形;
  3. -f<名称文件>:指定名称文件,其内容有一个或多个文件名称时,让file依序辨识这些文件,格式为每列一个文件名称;
  4. -L:直接显示符号连接所指向的文件类别;
  5. -m<魔法数字文件>:指定魔法数字文件;
  6. -v:显示版本信息;
  7. -z:尝试去解读压缩文件的内容。

参数

文件:要确定类型的文件列表,多个文件之间使用空格分开,可以使用Shell通配符匹配多个文件

案例1:

生成目标文件,查看文件格式

使用clang命令,将test.m生成目标文件

  1. clang -c test.m -o test.o

使用file命令,查看目标文件的文件类型

  1. file test.o
  2. -------------------------
  3. test.o: Mach-O 64-bit object x86_64

案例2:

生成可执行文件,查看文件格式

使用clang命令,将test.o生成可执行文件

  1. clang test.o

使用file命令,查看可执行文件的文件类型

  1. file a.out
  2. -------------------------
  3. a.out: Mach-O 64-bit executable x86_64

案例3:

使用clang命令,可以将目标文件生成可执行文件,也可以直接将.m源文件生成可执行文件

将目标文件生成test1可执行文件

  1. clang -o test1 test.o

.m源码文件生成test2可执行文件

  1. clang -o test2 test.m

不同方式生成的可执行文件,使用HASH验证它们的一致性

  1. md5 a.out
  2. md5 test1
  3. md5 test2
  4. -------------------------
  5. MD5 (a.out) = e3e459acc7a9aecee970ed12a0c1368d
  6. MD5 (test1) = e3e459acc7a9aecee970ed12a0c1368d
  7. MD5 (test2) = e3e459acc7a9aecee970ed12a0c1368d

在源码没有变化的时候,使用clang编译出的二进制文件是一致的

案例4:

将多个.m源码文件,生成一个可执行文件

创建test1.m文件,写入以下代码:

```

import

int main(){ return 1; }

  1. > 创建`test2.m`文件,写入以下代码:
  2. >

import

int test2(){ return 2; }

  1. > 将多个源码文件,生成一个可执行文件
  2. >

clang -o test1 test1.m test2.m

  1. > ![](https://upload-images.jianshu.io/upload_images/9297953-3012fc1078bf3afb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  2. > 案例5
  3. > 将多个目标文件,链接成一个可执行文件
  4. > 使用`clang`命令,将`test1.m``test2.m`生成目标文件
  5. >

clang -c test1.m test2.m

  1. > 将多个目标文件,链接成一个可执行文件
  2. >

clang -o test2 test2.o test1.o

  1. > ![](https://upload-images.jianshu.io/upload_images/9297953-d33b0b4f7636c9bb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  2. > 不同方式生成的可执行文件,使用`HASH`验证它们的一致性
  3. >

md5 test1

md5 test2

MD5 (test1) = 1c9ad179590712c5a2a8683af9173d6e MD5 (test2) = 40f53096fe90fab7a531cee885e0b8f1

  1. > 源码并没有发生变化,但两个可执行文件生成的`HASH`值不同。问题的本质,两次生成可执行文件时,编译源文件的顺序是不一样的
  2. > 使用`objdump`命令,分别查看`test1``test2`可执行文件的代码段
  3. >

objdump —macho -d test1

test1: (TEXT,text) section _main: 100003f80: 55 pushq %rbp 100003f81: 48 89 e5 movq %rsp, %rbp … _test2: 100003fa0: 55 pushq %rbp 100003fa1: 48 89 e5 movq %rsp, %rbp …

  1. >

objdump —macho -d test2

test2: (TEXT,text) section _test2: 100003f90: 55 pushq %rbp 100003f91: 48 89 e5 movq %rsp, %rbp … _main: 100003fa0: 55 pushq %rbp 100003fa1: 48 89 e5 movq %rsp, %rbp …

  1. > 两个可执行文件的代码截然不同
  2. > 可执行文件是一个或多个目标文件的集合,会受到文件编译顺序的影响。原理和`Xcode`中的`Compile Sources`一样<br />
  3. ![](https://upload-images.jianshu.io/upload_images/9297953-0f81f4f048089e5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  4. > 使用相同编译顺序,将多个目标文件,链接成一个可执行文件
  5. >

clang -o test3 test1.o test2.o

  1. > 使用`HASH`验证它们的一致性
  2. >

md5 test1

md5 test3

MD5 (test1) = 1c9ad179590712c5a2a8683af9173d6e MD5 (test3) = 1c9ad179590712c5a2a8683af9173d6e

  1. > 多个目标文件,当源码和编译顺序相同,使用`clang`编译出的二进制文件是一致的
  2. > 案例6
  3. > 查看`.a`的文件格式
  4. >

file libAFNetworking.a

libAFNetworking.a: current ar archive

  1. > 案例7
  2. > 查看`.dylib`的文件格式
  3. >

file libAFNetworking.dylib

libAFNetworking.dylib: Mach-O 64-bit dynamically linked shared library x86_64

  1. > 案例8
  2. > 查看`dyld`的文件格式
  3. > 使用`find`命令,找到`dyld`文件路径
  4. >

find /usr -name “dyld”

find: /usr/sbin/authserver: Permission denied /usr/lib/dyld /usr/share/file/magic/dyld

  1. > 查看文件格式
  2. >

file /usr/lib/dyld

/usr/lib/dyld: Mach-O universal binary with 2 architectures: [i386:Mach-O dynamic linker i386] [x86_64:Mach-O 64-bit dynamic linker x86_64] /usr/lib/dyld (for architecture i386): Mach-O dynamic linker i386 /usr/lib/dyld (for architecture x86_64): Mach-O 64-bit dynamic linker x86_64

  1. > - 动态链接器
  2. > 案例9
  3. > 查看`.dSYM`的文件格式
  4. > 右键`TestInject.app.dSYM`文件,显示包内容
  5. > 进入`Contents/Resources/DWARF`目录,找到`TestInject`文件
  6. > 查看文件格式
  7. >

file TestInject

TestInject: Mach-O 64-bit dSYM companion file x86_64

  1. > - 二进制符号文件,常用于分析`App`的崩溃信息
  2. #####可执行文件
  3. > 项目中,在`Build Settings``Mach-O Type`配置项,默认为`Executable`(可执行文件)<br />
  4. ![](https://upload-images.jianshu.io/upload_images/9297953-636a3da1dbc95f45.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  5. > `iOS11`及以上系统中,项目生成的可执行文件为`arm64`单一架构
  6. >

file WeChat

WeChat: Mach-O 64-bit executable arm64

  1. > 使用`iOS10.3`系统编译项目
  2. >

file WeChat

WeChat: Mach-O universal binary with 2 architectures: [arm_v7:Mach-O executable arm_v7] [arm64:Mach-O 64-bit executable arm64] WeChat (for architecture armv7): Mach-O executable arm_v7 WeChat (for architecture arm64): Mach-O 64-bit executable arm64

  1. > - 格式变为通用二进制文件,包含`armv7``arm64`两种架构
  2. > 项目中,`Build Settings`中包含架构的设置<br />
  3. ![](https://upload-images.jianshu.io/upload_images/9297953-acbfb72700d2fee1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  4. > - `Architectures`:设置支持的架构
  5. > - `Build Active Architecture Only`:只编译当前设备支持的架构,`Debug`模式默认`YES`
  6. > - `$(ARCHS_STANDARD)``Xcode`内置的环境变量,不同`Xcode`版本值不一样,当前版本下表示`armv7 + arm64`
  7. > 案例1
  8. > 增加一个兼容架构
  9. > `iOS`系统下,还有一个`armv7s`架构,支持`iPhone5``iPhone5c`
  10. > 打开项目,在`Build Settings`中的`Architectures`配置项,选择`other`,增加`armv7s`<br />
  11. ![](https://upload-images.jianshu.io/upload_images/9297953-e46d1f1dd3676a96.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  12. > 编译项目
  13. >

file WeChat

WeChat: Mach-O universal binary with 3 architectures: [arm_v7:Mach-O executable arm_v7] [arm_v7s:Mach-O executable arm_v7s] [arm64:Mach-O 64-bit executable arm64] WeChat (for architecture armv7): Mach-O executable arm_v7 WeChat (for architecture armv7s): Mach-O executable arm_v7s WeChat (for architecture arm64): Mach-O 64-bit executable arm64

  1. > - 同时包含三种架构,`armv7``armv7s``arm64`
  2. #####通用二进制文件
  3. > 通用二进制文件(`Universal Binary`
  4. > - 苹果公司提出的一种程序代码,能同时适用多种架构的二进制文件
  5. > - 同一个程序包中同时为多种架构提供最理想的性能
  6. > - 因为需要储存多种代码,通用二进制应用程序通常比单一平台二进制的程序要大
  7. > - 由于两种架构有共通的非执行资源(代码以外的),所以并不会达到单一版本的两倍之多
  8. > - 由于执行中只调用一部分代码,运行起来也不需要额外的内存
  9. > - 也被称为“胖二进制文件”(`Fat Binary`
  10. > `lipo`命令
  11. > 使用`lipo -info`,可以查看`MachO`文件包含的架构
  12. >

lipo -info MachO文件

  1. > 使用`lipo -thin`,拆分某种架构
  2. >

lipo MachO文件 -thin 架构 -output 输出文件路径

  1. > 使用`lipo -create`,合并多种架构
  2. >

lipo -create MachO1 MachO2 -output 输出文件路径

  1. > 案例1
  2. > 从通用二进制文件中拆分架构
  3. > 查看可执行文件
  4. >

lipo -info WeChat

Architectures in the fat file: WeChat are: armv7 armv7s arm64

  1. > - `WeChat`中包含`armv7``armv7s``arm64`三种架构
  2. > 使用`lipo`命令,拆分出`arm64`架构
  3. >

lipo WeChat -thin arm64 -output WeChat_arm64

  1. > 使用`lipo`命令,拆分出`armv7`架构
  2. >

lipo WeChat -thin armv7 -output WeChat_armv7

  1. > ![](https://upload-images.jianshu.io/upload_images/9297953-923ee8464710050d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  2. > 案例2
  3. > 合并架构
  4. > 使用`lipo`命令,将`arm64``armv7`架构合并
  5. >

lipo -create WeChat_arm64 WeChat_armv7 -output WeChat_64_v7

  1. > 查看`WeChat_64_v7`可执行文件
  2. >

lipo -info WeChat_64_v7

Architectures in the fat file: WeChat_64_v7 are: armv7 arm64

  1. > - 包含`armv7``arm64`两种架构
  2. #####MachO文件结构
  3. > 因为`MachO`文件本身是一种文件格式,所以我们一定需要了解其文件内部结构
  4. > `Mach-O`文件结构<br />
  5. ![](https://upload-images.jianshu.io/upload_images/9297953-b7037d3eda5bfae3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  6. > `Header`:包含该二进制文件的一般信息
  7. > - 字节顺序、架构类型、加载指令的数量等
  8. > - 使得可以快速确认一些信息,比如当前文件用于`32位`还是`64位`,对应的处理器是什么、文件类型是什么
  9. > `Load Commands`:一张包含很多内容的表
  10. > - 内容包括区域的位置、符号表、动态符号表等
  11. > `Data`:通常是对象文件中最大的部分
  12. > - 包含`Segement`的具体数据
  13. > 通用二进制文件,最上面是`Fat Header`,包含自身信息和架构信息。多架构之间,对应多套`Mach-O`的文件结构<br />
  14. ![](https://upload-images.jianshu.io/upload_images/9297953-fa5de29f36138016.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  15. > `otool`命令
  16. > 用来查看可执行文件的`MachO`信息
  17. >

Usage: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/otool [-arch arch_type] [-fahlLDtdorSTMRIHGvVcXmqQjCP] [-mcpu=arg] [—version] … -f print the fat headers -a print the archive header -h print the mach header -l print the load commands -L print shared libraries used -D print shared library id name -t print the text section (disassemble with -v) -x print all text sections (disassemble with -v) -p start dissassemble from routine name -s print contents of section -d print the data section -o print the Objective-C segment -r print the relocation entries -S print the table of contents of a library (obsolete) -T print the table of contents of a dynamic shared library (obsolete) -M print the module table of a dynamic shared library (obsolete) -R print the reference table of a dynamic shared library (obsolete) -I print the indirect symbol table -H print the two-level hints table (obsolete) -G print the data in code table -v print verbosely (symbolically) when possible -V print disassembled operands symbolically -c print argument strings of a core file -X print no leading addresses or headers -m don’t use archive(member) syntax -B force Thumb disassembly (ARM objects only) -q use llvm’s disassembler (the default) -Q use otool(1)’s disassembler -mcpu=arg use `arg’ as the cpu for disassembly -j print opcode bytes -P print the info plist section as strings -C print linker optimization hints —version print the version of /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/otool

  1. > 案例1
  2. > 查看可执行文件的`Header`信息
  3. >

otool -f WeChat

  1. > ![](https://upload-images.jianshu.io/upload_images/9297953-4f698ff964803658.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  2. > 案例2
  3. > 查看`App`所使用的动态库
  4. >

otool -L WeChat

  1. > ![](https://upload-images.jianshu.io/upload_images/9297953-604e69ce246de068.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  2. > 案例3
  3. > 查看`ipa`是否已砸壳
  4. >

otool -l WeChat | grep cryptid -A 1 -B 5

  1. > ![](https://upload-images.jianshu.io/upload_images/9297953-450ff8553ca378c1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  2. > - `cryptid``0`,表示已砸壳
  3. #####MachOView
  4. > 使用`MachOView`打开`WeChat`可执行文件<br />
  5. ![](https://upload-images.jianshu.io/upload_images/9297953-e77c63d545d672a8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  6. > - 包含一个`Fat Header`和三个架构的文件结构
  7. > - `cputype``CPU`类型,比如`ARM`
  8. > - `cpusubtype``CPU`的具体类型,`arm64``armv7`
  9. > - `offset`:偏移值
  10. > - `size`:当前架构大小
  11. > - `align`:对齐方式。`16384`,即:`16字节`
  12. > `armv7`架构的偏移值为`16384`,大小为`79104``armv7s`架构的偏移值等于`v7`架构的`偏移值 + 大小`,即`95488`,由于`16字节`对齐,即`98304``arm64`架构同理向后排列
  13. #####Header
  14. > `Header`的数据结构<br />
  15. ![](https://upload-images.jianshu.io/upload_images/9297953-f34e1d9ba57e273c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  16. >

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

  1. > - `magic`:魔数,快速定位属于`64位`还是`32位`
  2. > - `cputype``CPU`类型,比如`ARM`
  3. > - `cpusubtype``CPU`的具体类型,`arm64``armv7`
  4. > - `filetype`:文件类型,例如:可执行文件
  5. > - `ncmds``LoadCommands`的条数
  6. > - `sizeofcmds``LoadCommands`的大小
  7. > - `flags`:标志位,标识二进制文件支持的功能。主要是和系统加载、链接有关
  8. > - `reserved`:占位符
  9. #####Load Commands
  10. > `Load Commands`的数据结构<br />
  11. ![](https://upload-images.jianshu.io/upload_images/9297953-d80e845c64d69cb8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  12. > `__PAGEZERO`:空指针陷阱段,映射到虚拟内存空间的第一页,用于捕捉对`NULL`指针的引用<br />
  13. ![](https://upload-images.jianshu.io/upload_images/9297953-085836b6f65cda2b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  14. > - `VM Address`:虚拟内存地址
  15. > - `VM Size`:虚拟内存大小
  16. > - `File Offset`:数据在文件中偏移量
  17. > - `File Size`:数据在文件中的大小
  18. > `__PAGEZERO`:自身不占大小,仅占用`4G`虚拟内存大小。用于区分不同架构的系统,在`arm64`架构下为`0X100000000`,在非`arm64`架构下为`0X4000`
  19. > 可以使用`size`命令,查看`MachO`的内存分布
  20. >

size -l -m -x WeChat ```

iOS逆向实战--016:MachO - 图1

__TEXT:代码段,包含执行代码及其他只读数据。该段为只读数据段
iOS逆向实战--016:MachO - 图2

  • File Offset0,表示文件从代码段开始

__DATA:数据段,包括全局变量等。该段可读、可写
iOS逆向实战--016:MachO - 图3

__LINKEDIT:包含需要被动态链接器使用的信息
iOS逆向实战--016:MachO - 图4

  • 包括符号表、字符串表、重定位项表、签名等。该段和__PAGEZERO一样,末尾没有额外的Section信息。File Offset + File Size即为MachO文件末尾,等于文件的大小

LC_DYLD_INFO_ONLY:动态链接相关信息
iOS逆向实战--016:MachO - 图5

  • Rebase:用于重定向。Rebase Info Offset表示函数开始位置。Rebase Info Size表示从开始位置往后多少字节的数据需要重定向。当启动App加载MachO时,从Offset函数开始位置+ ASLR,后面的数据同理,一直加到Size大小的位置结束
  • Binding:绑定数据,外部符号需要将地址进行绑定。从开始位置,往后多少字节的数据需要绑定
  • Weak Binding:弱绑定数据
  • Lazy Binding:懒绑定数据,由dyld记录下来,使用的时候进行绑定
  • Export:对外开放的函数,提供给外部调用

LC_SYMTAB:符号表地址
iOS逆向实战--016:MachO - 图6

LC_DYSYMTAB:动态符号表地址
iOS逆向实战--016:MachO - 图7

LC_LOAD_DYLINKER:使用何种动态加载器,iOS系统上为dyld
iOS逆向实战--016:MachO - 图8

LC_UUIDMachO文件的唯一标识
iOS逆向实战--016:MachO - 图9

  • crash文件中也会有,用来检查crash文件与dYSM文件是否匹配

LC_VERSION_MIN_MACOSX:支持最低的系统版本
iOS逆向实战--016:MachO - 图10

LC_SOURCE_VERSION:源代码版本
iOS逆向实战--016:MachO - 图11

LC_MAIN:设置程序主线程的入口地址和栈大小
iOS逆向实战--016:MachO - 图12

LC_ENCRYPTION_INFO_64:加密信息
iOS逆向实战--016:MachO - 图13

LC_LOAD_DYLIB (XXX):依赖库的路径,包含三方库
iOS逆向实战--016:MachO - 图14

LC_RPATH@rpath路径
iOS逆向实战--016:MachO - 图15

LC_FUNCTION_STARTS:函数起始地址表
iOS逆向实战--016:MachO - 图16

LC_DATA_IN_CODE: 定义在代码段内的非指令的表
iOS逆向实战--016:MachO - 图17

LC_CODE_SIGNATURE: 代码签名
iOS逆向实战--016:MachO - 图18

Data

Load Commands以下,都属于数据部分,包含__TEXT__DATA__LINKEDIT
iOS逆向实战--016:MachO - 图19

__TEXT

Section 含义
__text 代码实现,文件从代码段开始
__stubs 符号桩,本质上就是一段会跳转到Lazy Binding表中,对应项指针指向的地址的代码
__stubs_helper 辅助函数,上述Lazy Binding表中没有找到符号地址都指向这里
__objc_methname OC方法名称
__objc_classname OC类名
__objc_methtype OC方法类型
__cstring cstring字符串常量
__unwid_info 用于存储异常情况信息

__DATA

Section 含义
__got Lazy Binding的符号表
__la_symbol_prt Lazy Binding符号表,每个表项中的符号一开始指向__stubs_helper
__cfstring CoreFoundation String
__objc_classlist OC类列表
__objc_protollist OC协议列表
__objc_imageinfo OC镜像信息
__objc_const OC类信息、方法列表、属性列表、变量列表
__objc_selfrefs OC类实例自引用(self
__objc_classfrefs OC类类自引用
__objc_superrefs OC类超类引用(super
__objc_ivar OC属性
__objc_data OCISA
_data 存放数据
总结

MachO概述:

  • MachO属于一种文件格式
  • 包含目标文件、可执行文件、静态库、动态库、dyld.dSYM
  • file命令,用来探测给定文件的类型

通用二进制文件

  • 集合多种架构,也被称为“胖二进制文件”
  • lipo命令,通过-thin拆分架构,通过-create合并架构

MachO文件结构

  • Header:用于快速确定该文件的CPU类型、文件类型
  • Load Commands:指示加载器如何设置并加载二进制数据
  • Data:存放代码、数据、字符串常量、类、方法等数据