Mach-O是Mach object的缩写,是Mac/iOS上用于存储程序、库的标准格式,这里简要解析Mach-O文件格式,结构和一些要点
Mach-O格式
Mach-O是一个以数据块分组的二进制字节流,这些数据块包含元信息,比如字节顺序、CPU类型、数据块大小等等
典型的Mach-O文件通常包含三个主要区域
Header
: 描述Mach-O文件的基本信息,如标识文件类型,目标架构类型,内存对齐大小,大小端序,加载命令数量,flags信息等Load commands
: 描述文件中每个段的信息(物理内存大小,虚拟内存等信息)Raw data
: 在Load command中定义的segment的原始数据
如下图
查看Mach-O文件结构
file
: 查看Mach-O文件类型1
2# Hugu为可执行文件
file Hugootool
:查看Mach-O特定部分和段的内容1
2
3
4
5
6
7
8
9
10# 查看mach-o header
otool -h Hugo
# 查看load command
otool -l Hugo
# 查看链接的动态库
otool -L Hugo
# otool 支持很多参数,查看不同的信息size
:查看段的内存分布1
size -l -m -x Hugo
输出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52Segment __PAGEZERO: 0x100000000 (vmaddr 0x0 fileoff 0)
Segment __TEXT: 0x1308000 (vmaddr 0x100000000 fileoff 0)
Section __text: 0x104f4cc (addr 0x100005150 offset 20816)
Section __stubs: 0x42d8 (addr 0x10105461c offset 17122844)
Section __stub_helper: 0x42c0 (addr 0x1010588f4 offset 17139956)
Section __objc_methname: 0xbf942 (addr 0x10105cbb4 offset 17157044)
Section __objc_classname: 0x130d2 (addr 0x10111c4f6 offset 17941750)
Section __objc_methtype: 0x1c635 (addr 0x10112f5c8 offset 18019784)
Section __cstring: 0xf26fd (addr 0x10114bc00 offset 18136064)
Section __ustring: 0xd3a0 (addr 0x10123e2fe offset 19129086)
Section __const: 0x2db07 (addr 0x10124b6a0 offset 19183264)
Section __gcc_except_tab: 0x21a80 (addr 0x1012791a8 offset 19370408)
Section __swift5_typeref: 0xc382 (addr 0x10129ac28 offset 19508264)
Section __swift5_fieldmd: 0xb028 (addr 0x1012a6fac offset 19558316)
Section __swift5_builtin: 0x76c (addr 0x1012b1fd4 offset 19603412)
Section __swift5_reflstr: 0x1417d (addr 0x1012b2740 offset 19605312)
Section __swift5_assocty: 0xd38 (addr 0x1012c68c0 offset 19687616)
Section __swift5_proto: 0x624 (addr 0x1012c75f8 offset 19691000)
Section __swift5_types: 0x998 (addr 0x1012c7c1c offset 19692572)
Section __swift5_capture: 0xb524 (addr 0x1012c85b4 offset 19695028)
Section __swift5_protos: 0x50 (addr 0x1012d3ad8 offset 19741400)
Section __dof_RACSignal: 0x37b (addr 0x1012d3b28 offset 19741480)
Section __dof_RACCompou: 0x2e8 (addr 0x1012d3ea3 offset 19742371)
Section __unwind_info: 0x31ce4 (addr 0x1012d418c offset 19743116)
Section __eh_frame: 0x2184 (addr 0x101305e70 offset 19947120)
total 0x1302e97
Segment __DATA: 0x560000 (vmaddr 0x101308000 fileoff 19955712)
Section __got: 0x1190 (addr 0x101308000 offset 19955712)
Section __la_symbol_ptr: 0x2c90 (addr 0x101309190 offset 19960208)
Section __mod_init_func: 0x40 (addr 0x10130be20 offset 19971616)
Section __const: 0x48950 (addr 0x10130be60 offset 19971680)
Section __cfstring: 0x6fea0 (addr 0x1013547b0 offset 20268976)
Section __objc_classlist: 0x68c8 (addr 0x1013c4650 offset 20727376)
Section __objc_nlclslist: 0x2c0 (addr 0x1013caf18 offset 20754200)
Section __objc_catlist: 0x1ef8 (addr 0x1013cb1d8 offset 20754904)
Section __objc_nlcatlist: 0x88 (addr 0x1013cd0d0 offset 20762832)
Section __objc_protolist: 0x11f8 (addr 0x1013cd158 offset 20762968)
Section __objc_imageinfo: 0x8 (addr 0x1013ce350 offset 20767568)
Section __objc_const: 0x3c85f8 (addr 0x1013ce358 offset 20767576)
Section __objc_selrefs: 0x2e430 (addr 0x101796950 offset 24734032)
Section __objc_protorefs: 0x4a8 (addr 0x1017c4d80 offset 24923520)
Section __objc_classrefs: 0x5c00 (addr 0x1017c5228 offset 24924712)
Section __objc_superrefs: 0x39d8 (addr 0x1017cae28 offset 24948264)
Section __objc_ivar: 0xc120 (addr 0x1017ce800 offset 24963072)
Section __objc_data: 0x59c10 (addr 0x1017da920 offset 25012512)
Section __data: 0x1c885 (addr 0x101834530 offset 25380144)
Section __swift_hooks: 0xb8 (addr 0x101850db8 offset 25497016)
Section __bss: 0x12e30 (addr 0x101850e70 offset 0)
Section __common: 0x1b88 (addr 0x101863ca0 offset 0)
total 0x55d825
Segment __LINKEDIT: 0x168000 (vmaddr 0x101868000 fileoff 25509888)
total 0x1019d0000也可以通过
MachOView
查看Segment相关信息还有一个功能非常强大的二进制编辑器
010Editor
,需要安装一下MachO模板,与MachOView类似,使用起来也非常方便
Header
我们通过machoview查看
名称 | 解释 |
---|---|
magic | Mach-O魔数,FAT:0xcafebabe, ARMv7:0xfeedface, ARM64:0xfeedfacf |
cputype、cpusubtype | CPU架构及子版本 |
filetype | mach-o文件类型 |
ncmds | 加载命令的数量 |
sizeofcmds | 所有加载命令的大小 |
flags | dyld加载需要的一些标记 |
reserved | 64位保留字段 |
在Header信息中,flags标志信息有一个标志为
MH_PIE
(在xnu项目的EXTERNAL_HEADERS/mach-o/loader.h
中可以找到),意思是开启ASLR
笔者尝试把MH_PIE
去掉,然后重签名跑到真机上,发现会闪退
Mach-O文件类型
在苹果开源的内核xnu
源码中EXTERNAL_HEADERS/mach-o/loader.h
可以找到Mach-O文件格式的定义
1 |
|
常见的类型有
- MH_OBJECT
- 目标文件(.o)
- 静态库文件(.a)(多个目标文件合并)
- MH_EXECUTE:
- 可执行文件,我们编译出来的App的主程序就是该类型
- MH_DYLIB:动态库
- dylib文件
- framework动态库(.framework/xx)
- MH_DYLINKER
/usr/lib/dyld
:动态链接器
- MH_DSYM
- .dSYM/Contents/Resources/DWARF/xx(符号表)
Universal Binary(通用二进制文件)
包含多个CPU架构的二进制文件,在运行时,只会加载对应架构的二进制
1 | $ file jcm |
Segment
在machoview的load commands可以看到所有段的
- LC_SEGMENT_64:记录一个段,加载后被映射到内存中
- LC_DYLD_INFO_ONLY:记录动态链接的重要信息,动态链接器要根据它来进行地址重定向
- LC_SYMTAB:文件所使用的符号表,符号数,字符串表的偏移量和大小
- LC_DYSYMTAB:动态链接器所使用的符号表,找到后获取间接符号表偏移量
- LC_LOAD_DYLINKER:默认的加载器路径(
/usr/bin/dylb
) - LC_UUID: Mach-O文件的唯一标识
- LC_MAIN:程序的入口,动态链接器获取该地址,然后程序跳转到该处运行
- LC_SOURCE_VERSION:构建二进制文件的源代码版本
- LC_VERSION_MIN_IPHONEOS:最低支持系统版本
- LC_ENCRYPTION_INFO_64:文件加密信息,我们判断是否脱壳,就是用了这里的信息
- LC_RPATH:链接器搜索路径列表,主要搜索framework
- LC_FUNCTION_STARTS: 函数其实地址表,调试器货其他程序能判断一个地址是否在该表范围内
- LC_DATA_IN_CODE: 定义在代码内的非指令表
- LC_CODE_SIGNATURE: 代码签名信息
LC_SEGMENT_64
图中可以看到,LC_SEGMENT_64
段有4个
1 | * LC_SEGMENT_64(`__PAGEZERO`) |
上面可以看出
- PAGEZERO段并不占用文件大小,当mach-o加载到内存时会占用8个字节(64位)
- TEXT段从0开始的,也就是header和loadcommand也属于TEXT段,加载到内存后,排在PAGEZERO后面
__TEXT段
TEXT段包含可执行的代码和一些只读数据,静态链接器设置该段位可读可执行,进程被允许执行这些代码,但不能修改
- __text:主程序代码
- __stubs: 帮助动态链接库绑定符号
- __const: const关键字修饰的常量
- __cstring: 只读c语言字符串
- __objc_methname: OC方法名
- __objc_classname:OC类名
- __objc_methtype:OC方法类型(方法签名)
- __unwind_info:编译器自动生成,用于确定异常发生时栈所对应的信息
__DATA段
包含了将会被更改的数据,静态链接器设置该段的虚拟内存权限位可读写
- __got: 全局非懒绑定符号指针表
- __la_symbol_ptr: 懒绑定符号指针表
- __mod_init_func:C++类的构造函数
- __const:未初始化过的常亮
- __cfstring:CoreFoundation字符串
- __objc_class_list:OC类列表
- __objc_nlclslist:实现了
+load
方法的OC类列表 - __objc_catlist:OC分类列表
- __objc_protolist:OC协议列表
- __objc_imageinfo:镜像信息,可用它区别Objective1.0与2.0
- __objc_const:OC初始化过的常量
- __objc_selrefs:OC选择器引用列表
- __objc_protorefs:OC协议引用列表
- __objc_classrefs:OC类引用列表
- __objc_superrefs: OC父类引用列表
- __objc_ivar: OC类的实例变量
- __objc_data: OC初始化过的变量
- __data:实际初始化数据段
- __common:未初始化过的符号声明
- __bss:未初始化的全局变量
__LINKEDIT
包含动态链接库的原始数据,如符号,字符串,重定向表条目等
dyld
dyld用于加载以下类型的Mach-O文件,在iOS中,App的可执行文件和动态库都是由dyld负责加载的
- MH_EXECUTE
- MH_DYLIB
- MH_BUNDLE
关于dyld如何加载mach-o文件,可以参考这里
ASLR(Address Space Layout Randomization)
iOS4.3开始引入了ASLR技术,地址空间布局随机化,系统在加载Mach-O文件的时候,会随机在头部添加一部分内存空间,从而让Mach-O文件在每次加载到内存时的地址都不相同,是一种针对缓冲区溢出的安全保护技术,通过对堆、栈、共享库映射等线性区布局的随机化,通过增加攻击者预测目的地址的难度,防止攻击者直接定位攻击代码位置,达到阻止溢出攻击的目的的一种技术
ASLR 会在程序每次载入的时候随机在原来的基础上添加随机的内存区域,已达到每次运行的程序地址都不一样,相当于__PAGEZERO
段的位置后移了
我们可以在lldb
调试器查看程序每个模块的偏移大小ASLR
偏移的大小
1 | # -o 表示虚拟地址偏移量,-f 表示路径 |
0x00000000003f8000
为主程序Test的内存偏移地址,这个偏移地址每次启动App都是随机的0x00000001003f8000
为主程序__TEXT段的起始地址,即ASLR偏移地址+__PAGEZERO的大小
由于MachO文件加载到内存中的数据和MachO文件的数据是一致的,连续的,所以,我们可以通过ASLR的偏移地址加上在MachO静态分析的地址,得到运行时的地址,我们再Hopper查看到的地址,其实就是没有算上ASLR,即上面的0x00000000003f8000
再Hopper搜索[ViewController test:]
方法,可以看到该函数的地址为0x0000000100005744
,根据上面得到的ASLR的偏移地址,就可以算出函数在内存中的地址,由于Hopper显示的地址是算上__PAGEZERO
段的,所以直接加上即可
1 | 0x00000000003f8000 + 0x0000000100005744 |
小结
了解Mach-O可以帮助我们理解dyld的加载Mach-O的过程以及与Mach-O相关的读取或操作,为逆向分析提供更好的思路