一、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 文件进行操作
-
使用
查看文件架构.png 从上图可以看出该可执行文件是一个通用二进制文件,且包含2种架构:arm_v7 和 arm64。file Mach-O
命令查看 Mach-O 文件类型
-
使用
文件架构.pnglifo -info <Mach-O>
命令查看文件架构
-
使用
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.pngMach-O主要分为三个部分:Header、Load commands和Data。
- Header:包含字节顺序、架构类型、加载指令的数量等,使得系统可以快速确认一些信息,比如当前文件用于32位还是64位,对应的处理器是什么、文件类型是什么。
- Load commands:它是一张包含很多内容的表,内容包括区域的位置、符号表、动态符号表等。每个加载指令都包含一个元信息,比如指令类型、名称、在二进制文件中的位置等等。
- Data:通常是对象文件中最大的部分。主要包含代码、数据,例如符号表,动态符号表等等。Data 中包含若干个 segment (段),每个 segment 下又有若干个 section(节)。
1. Header
头文件就是该可执行文件的信息概要
该部分结构可以 打开 <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:被引用的类列表
网友评论