iOS安全和逆向系列教程第6篇 深入理解Mach-O文件格式
在上一篇文章中,我们详细介绍了iOS应用开发的基础知识和实战分析方法。正如预告所述,本篇文章我们将正式开始探索Mach-O文件格式,这是iOS应用的二进制格式,深入理解它对于静态分析至关重要。
Mach-O格式概述
Mach-O(Mach Object)文件格式是macOS和iOS操作系统上的可执行文件、目标代码和共享库的文件格式。作为静态分析的基础,理解Mach-O文件结构对逆向工程师来说至关重要。
Mach-O的历史背景
Mach-O源自NeXTSTEP操作系统,后来被Apple采用作为macOS(原OS X)和iOS的标准二进制格式。它基于Mach内核设计,具有高度灵活性和可扩展性。
Mach-O文件类型
Mach-O文件主要有以下几种类型:
- 执行文件(Executable):可直接运行的程序
- 目标文件(Object File):编译后但未链接的代码
- 动态库(Dynamic Library):运行时加载的共享代码(.dylib)
- 静态库(Static Library):链接时合并到程序中的代码(.a)
- Framework:包含头文件和资源的动态库包
- 动态链接器(Dynamic Linker):负责加载和链接动态库
Mach-O文件基本结构
Mach-O文件由三个主要部分组成:
- Header:包含文件类型、目标架构等基本信息
- Load Commands:指示动态链接器如何加载文件
- Segments:包含代码和数据的实际内容
让我们详细探讨每个部分。
1. Mach-O Header分析
Mach-O头部包含识别文件类型和目标架构的基本信息。头部结构定义在<mach-o/loader.h>
中:
struct mach_header_64 {
uint32_t magic; // 魔数,标识文件类型和位数
cpu_type_t cputype; // CPU类型
cpu_subtype_t cpusubtype; // CPU子类型
uint32_t filetype; // 文件类型
uint32_t ncmds; // Load Commands数量
uint32_t sizeofcmds; // Load Commands总大小
uint32_t flags; // 标志位
uint32_t reserved; // 64位保留字段
};
magic字段
magic
字段是一个32位无符号整数,用于标识文件是32位还是64位,以及字节序:
0xFEEDFACE
: 32位小端序Mach-O文件0xFEEDFACF
: 64位小端序Mach-O文件0xCEFAEDFE
: 32位大端序Mach-O文件0xCFFAEDFE
: 64位大端序Mach-O文件
cputype和cpusubtype字段
这两个字段标识目标架构:
CPU_TYPE_ARM64
: ARM64架构(iPhone 5s及更高版本)CPU_TYPE_ARM
: ARM架构(旧款iOS设备)CPU_TYPE_X86_64
: Intel 64位架构(iOS模拟器)
filetype字段
filetype
字段标识文件类型:
MH_EXECUTE
: 可执行文件MH_DYLIB
: 动态库MH_BUNDLE
: 插件BundleMH_OBJECT
: 目标文件
flags字段
flags
字段包含重要的标记信息:
MH_PIE
: 启用地址空间布局随机化(ASLR)MH_NOUNDEFS
: 不存在未定义的符号引用MH_DYLDLINK
: 文件由动态链接器加载
2. Load Commands解析
Load Commands指示动态链接器如何处理Mach-O文件。它们紧跟在Header之后,数量由Header中的ncmds
字段指定。
每个Load Command都以相同的基本结构开始:
struct load_command {
uint32_t cmd; // 命令类型
uint32_t cmdsize; // 命令大小
};
常见的Load Commands类型
-
LC_SEGMENT_64:定义64位内存段
struct segment_command_64 { uint32_t cmd; // LC_SEGMENT_64 uint32_t cmdsize; // 命令大小 char segname[16]; // 段名称(如__TEXT, __DATA) 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; // 标志位 };
-
LC_SYMTAB:符号表信息
struct symtab_command { uint32_t cmd; // LC_SYMTAB uint32_t cmdsize; // 命令大小 uint32_t symoff; // 符号表偏移 uint32_t nsyms; // 符号数量 uint32_t stroff; // 字符串表偏移 uint32_t strsize; // 字符串表大小 };
-
LC_DYLD_INFO_ONLY:动态链接信息
struct dyld_info_command { uint32_t cmd; // LC_DYLD_INFO或LC_DYLD_INFO_ONLY uint32_t cmdsize; // 命令大小 uint32_t rebase_off; // 重定位信息偏移 uint32_t rebase_size; // 重定位信息大小 uint32_t bind_off; // 符号绑定信息偏移 uint32_t bind_size; // 符号绑定信息大小 uint32_t weak_bind_off; // 弱符号绑定信息偏移 uint32_t weak_bind_size;// 弱符号绑定信息大小 uint32_t lazy_bind_off; // 延迟绑定信息偏移 uint32_t lazy_bind_size;// 延迟绑定信息大小 uint32_t export_off; // 导出信息偏移 uint32_t export_size; // 导出信息大小 };
-
LC_LOAD_DYLIB:加载动态库
struct dylib_command { uint32_t cmd; // LC_LOAD_DYLIB uint32_t cmdsize; // 命令大小 struct dylib dylib; // 动态库信息 };
-
LC_MAIN:主程序入口点
struct entry_point_command { uint32_t cmd; // LC_MAIN uint32_t cmdsize; // 命令大小 uint64_t entryoff; // 入口点偏移 uint64_t stacksize; // 初始线程栈大小 };
3. Segments与Sections详解
Segments是Mach-O文件的主要组成部分,每个Segment又可以包含多个Sections。
常见的Segments
-
__TEXT段:包含只读、可执行的代码和数据
- __text section:编译后的机器码
- __stubs section:动态链接器存根
- __stub_helper section:动态链接器辅助存根
- __cstring section:C字符串
- __const section:常量数据
-
__DATA段:包含可读写的数据
- __data section:初始化的全局变量
- __la_symbol_ptr section:延迟符号指针
- __nl_symbol_ptr section:非延迟符号指针
- __bss section:未初始化的全局变量
- __objc sections:Objective-C运行时数据
-
__LINKEDIT段:包含动态链接器使用的信息,如符号表、字符串表等
使用工具分析Mach-O文件
以下是几个常用的命令行工具,可以帮助分析Mach-O文件:
1. file命令
file /path/to/binary
这个基本命令能够识别文件类型,对于Mach-O文件,它还会显示架构信息。
2. otool命令
otool(object file displaying tool)是macOS上的一个强大工具:
# 显示header信息
otool -h /path/to/binary
# 显示load commands
otool -l /path/to/binary
# 显示__text section内容(反汇编)
otool -tv /path/to/binary
# 显示动态库依赖
otool -L /path/to/binary
3. MachOView
MachOView是一个图形界面工具,提供了Mach-O文件的可视化分析:
- 可以浏览所有Header、Load Commands和Segments
- 支持十六进制查看和编辑
- 提供符号表和字符串表的搜索功能
实战:分析一个简单的iOS应用
让我们使用上面介绍的知识和工具,分析一个简单的iOS应用。
步骤1:提取并查看基本信息
首先,我们需要从设备中提取应用二进制文件,并查看其基本信息:
# 查看文件类型和架构
file SampleApp
# 输出示例
# SampleApp: Mach-O 64-bit arm64 executable, flags:<NOUNDEFS|DYLDLINK|TWOLEVEL|PIE>
步骤2:分析Header
otool -h SampleApp
# 输出示例
# Mach header
# magic cputype cpusubtype caps filetype ncmds sizeofcmds flags
# 0xfeedfacf 16777228 0 0x00 2 38 4032 0x00200085
从输出可以看出:
magic
: 0xfeedfacf(64位Mach-O)cputype
: 16777228(ARM64)filetype
: 2(MH_EXECUTE,可执行文件)flags
: 0x00200085(包含PIE、DYLDLINK等标志)
步骤3:查看Load Commands
otool -l SampleApp | less
这将显示所有的Load Commands,我们可以查找感兴趣的信息,如:
Load command 0
cmd LC_SEGMENT_64
cmdsize 72
segname __PAGEZERO
vmaddr 0x0000000000000000
vmsize 0x0000000100000000
...
Load command 1
cmd LC_SEGMENT_64
cmdsize 552
segname __TEXT
vmaddr 0x0000000100000000
vmsize 0x0000000000004000
...
步骤4:分析入口点
我们可以找到LC_MAIN命令来确定程序的入口点:
otool -l SampleApp | grep -A5 LC_MAIN
# 输出示例
# cmd LC_MAIN
# cmdsize 24
# entryoff 16384
# stacksize 0
这表明入口点位于文件偏移16384处。
步骤5:查看动态库依赖
otool -L SampleApp
# 输出示例
# SampleApp:
# /System/Library/Frameworks/UIKit.framework/UIKit (compatibility version 1.0.0, current version 3698.138.4)
# /System/Library/Frameworks/Foundation.framework/Foundation (compatibility version 300.0.0, current version 1437.141.0)
# ...
这显示了应用依赖的所有动态库,这对理解应用功能很有帮助。
步骤6:查看符号表
如果二进制文件没有被strip,我们可以查看符号表:
nm -nm SampleApp | less
这会列出所有符号,包括类名、方法名和全局变量。
Mach-O文件格式与逆向工程
理解Mach-O文件格式对iOS逆向工程有以下重要意义:
- 定位关键函数:通过分析符号表和__objc_*sections,可以找到类和方法的定义
- 理解内存布局:知道段和节的内存分布,有助于动态分析和修改
- 识别保护措施:通过flags和段保护属性,了解应用使用的安全措施
- 依赖分析:通过查看依赖的库,可以推断应用的功能和可能的漏洞
进阶技巧
1. 修改Mach-O文件
使用十六进制编辑器可以修改Mach-O文件,但需注意:
- 修改后需要重新签名
- 不能随意更改文件大小,除非同时调整所有相关偏移
- 修改受保护段需要调整签名和校验和
2. FAT二进制文件分析
iOS应用可能是包含多个架构的FAT二进制:
# 检查是否为FAT二进制
file SampleApp
# 提取特定架构
lipo -thin arm64 SampleApp -output SampleApp_arm64
3. 加密检测与解密
商店应用通常是加密的,需要先解密:
# 检查是否加密
otool -l SampleApp | grep -A4 LC_ENCRYPTION_INFO
# 如果加密,需要使用工具进行解密
# 例如在越狱设备上使用dumpdecrypted或frida-ios-dump
总结
Mach-O文件格式是iOS应用静态分析的基础。通过理解其结构和使用适当的工具,我们可以:
- 获取应用基本信息和架构
- 分析加载命令和内存布局
- 发现代码段和数据段的组织方式
- 了解应用依赖和符号表
掌握Mach-O文件格式是iOS逆向工程路上的重要一步,它为我们提供了静态分析的基础,并为后续的动态分析做好准备。
在下一篇文章中,我们将深入探讨iOS应用的静态分析方法,包括常用工具的使用技巧、反汇编代码的分析方法以及如何从静态分析中发现应用的关键逻辑。
作者:自学不成才
本文为iOS逆向工程专栏的文章,版权所有,未经许可请勿转载。