Mach-O
文件简介Mach-O
是一种文件格式,是Mach Object
文件格式的缩写。 它一般应用于可执行文件,目标代码,动态库,内核转储等中。sass
Mach-O
做为大部分基于Mach
核心的操做系统所使用。 如NeXTSTEP,Darwin
和Mac OS X
等系统使用这种格式做为其原生可执行文件,库和目标代码的格式。bash
在 NeXTSTEP 和 Mac OS X 中,能够将多个Mach-O
文件组合进一个多重架构二进制文件中,以用一个单独的二进制文件支持多种架构的指令集。这种称为胖二进制文件(即:Fat binary 文件)。架构
Mach-O
文件类型众多,常见的一些Mach-O
文件类型以下:app
.o
结尾的文件/usr/lib/xxx.dylib
XXX.app.dSYM
Mach-O
文件结构布局Mach-O
主要有三部分组成:iphone
Header 部分主要描述当前Mach-O
文件什么架构,是否 Fat 二进制文件,CPU 类型等。编辑器
Load commands 部分主要描述:函数
Mach-O
文件中在虚拟内存中空间是如何分配的,从哪一个内存地址开始到哪一个内存地址结束。Mach-O
文件中的位置,大小分布。Data 部分是描述内存如何被分配的内容。包括__TEXT, __DATA
等。 工具
Fat_Arch
的 header
结构图以下:布局
若是只有一种架构,那么 Fat Header 的地方直接就是 mac_headerui
为了方便管理,程序被添加到内存后是分段管理的,而每一个段是如何从本地加载到内存是在LC_Type
中进行描述的。 Segment
的加载命令描述结构以下:
struct segment_command { /* for 32-bit architectures */
uint32_t cmd; /* LC_SEGMENT */
uint32_t cmdsize; /* includes sizeof section structs */
char segname[16]; /* segment name */
uint32_t vmaddr; /* memory address of this segment */
uint32_t vmsize; /* memory size of this segment */
uint32_t fileoff; /* file offset of this segment */
uint32_t filesize; /* amount to map from the file */
vm_prot_t maxprot; /* maximum VM protection */
vm_prot_t initprot; /* initial VM protection */
uint32_t nsects; /* number of sections in segment */
uint32_t flags; /* flags */
};
复制代码
Mach-O
文件中不一样的 Segment 段__TEXT, __DATA
等在虚拟内存中的内存分布是用VM Address
和VM Size
来描述的; 在Mach-O
本地文件中的空间分布是用File Offset
和File Size
来描述的;
而同一个 Segment 信息在这两个维度中的空间分配策略,是不彻底相同的。 如:
arm64
架构 size
时0x100000000
,在armv7
架构size
时0x4000
。它会在虚拟内存中隔离出一大块 低地址区的内存空间。而在Mach-O
文件中占据的大小为 0,并无实际的内容。当设置一个指针变量的值为 NULL 时,其实就是将指针指向了__PAGEZERO 段这块区域。Mach-O
文件中占据的大小。缘由是在内存中须要预留一部分多余的空间给能够修改的全局变量或者所占空间能够变化的对象或容器使用。#define SEG_PAGEZERO "__PAGEZERO"
#define SEG_TEXT "__TEXT" /* the tradition UNIX text segment */
#define SEG_DATA "__DATA" /* the tradition UNIX data segment */
复制代码
SEG_PAGEZERO:
SEG_TEXT:
SEG_DATA:
根据 __TEXT 段的加载描述, 获得 __DATA 段内容的偏移地址以下:
Mach-O
文件利用两种空间描述,来表达本身在Mach-O
文件和虚拟内存中不一样空间的分配方式 每一个 app 都有本身独立的虚拟内存,这个虚拟内存只存在与本身的Mach-O
文件的load commands
的描述中。 当 app 执行时,会被系统映射到实际的物理内存中。
程序一旦编译完成,函数就安静的放在了__TEXT
段, 全局变量就安静的放在了__DATA
段上。等用户点击后,才被加载到内存。 在 iOS 逆向中,经过Hopper
工具反汇编能够看到函数的虚拟内存地址,可是Hopper
中展现的内存地址是没有ASLR的。 想要获得函数的真正函数地址,还须要获得当前Mach-O
文件在内存中的ASLR
偏移值。
当 app 被加载到内时,系统会自动进行ASLR
,在__PAGEZERO
端的上面随机多出一段空间做为偏移,使得Mach-O
文件的整个虚拟内存向下总体(包括堆,栈,共享库映射等线性布局)偏移。从而可让生成的函数内存地址不断变更。这样能够提升黑客的破解难道。
那如何获得当前Mach-O
文件的ASLR
偏移值呢?
经过lldb
调试器能够查到。
经过lldb
调试器的Mach-O
文件列表查询命令,-o
查询全部使用的Mach-O
文件包括dyld
连接编辑器、app 的Mach-O
文、dylib
库。 能够查询到 app 文件对应Mach-O
的所包含的全部Mach-O
文件。
lldb
调试器命令,打印app文件对应Mach-O
文件的所包含的全部Mach-O
文件。
image list -o -f
复制代码
结果以下:
(lldb) image list -o -f
[ 0] 0x0000000000558000 /Users/zhoufei/Library/Developer/Xcode/DerivedData/SorterAndFilter-gcqrjckyrquurscwtbwpiaeebgzj/Build/Products/Release-iphoneos/SorterAndFilter.app/SorterAndFilter
[ 1] 0x0000000100800000 /Users/zhoufei/Library/Developer/Xcode/iOS DeviceSupport/11.4 (15F79)/Symbols/usr/lib/dyld
[ 2] 0x0000000001190000 /Users/zhoufei/Library/Developer/Xcode/iOS DeviceSupport/11.4 (15F79)/Symbols/System/Library/Frameworks/Foundation.framework/Foundation
[ 3] 0x0000000001190000 /Users/zhoufei/Library/Developer/Xcode/iOS DeviceSupport/11.4 (15F79)/Symbols/System/Library/Frameworks/UIKit.framework/UIKit
[ 4] 0x0000000001190000 /Users/zhoufei/Library/Developer/Xcode/iOS DeviceSupport/11.4 (15F79)/Symbols/usr/lib/libobjc.A.dylib
[ 5] 0x0000000001190000 /Users/zhoufei/Library/Developer/Xcode/iOS DeviceSupport/11.4 (15F79)/Symbols/usr/lib/libSystem.B.dylib
[ 6] 0x0000000001190000 /Users/zhoufei/Library/Developer/Xcode/iOS DeviceSupport/11.4 (15F79)/Symbols/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation
[ 7] 0x0000000001190000 /Users/zhoufei/Library/Developer/Xcode/iOS DeviceSupport/11.4 (15F79)/Symbols/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics
[ 8] 0x0000000001190000 /Users/zhoufei/Library/Developer/Xcode/iOS DeviceSupport/11.4 (15F79)/Symbols/System/Library/Frameworks/QuartzCore.framework/QuartzCore
[ 9] 0x0000000001190000 /Users/zhoufei/Library/Developer/Xcode/iOS DeviceSupport/11.4 (15F79)/Symbols/usr/lib/libarchive.2.dylib
复制代码
第 0 个结果0x0000000000558000
就是要找的ASLR
值
如何证实所有变量在Mach-O
中,局部变量在栈中,对象在堆空间中?
根据打印的内存地址能够进行说明
经过 log 的内容,拿到 app 的Mach-O
文件路径:
[ 0] 0x0000000000558000 /Users/zhoufei/Library/Developer/Xcode/DerivedData/SorterAndFilter-gcqrjckyrquurscwtbwpiaeebgzj/Build/Products/Release-iphoneos/SorterAndFilter.app/SorterAndFilter
复制代码
其对应的偏移地址0x0000000000558000
,就是Mach-O
文件从本地添加到内存时,系统自动添加的ASLR
内存空间布局随机化值。
那Mach-O
文件SorterAndFilter.app/SorterAndFilter
的大小是多少呢?
经过终端,进行查询
1.cd /Users/zhoufei/Library/Developer/Xcode/DerivedData/SorterAndFilter-gcqrjckyrquurscwtbwpiaeebgzj/Build/Products/Release-iphoneos/SorterAndFilter.app/
2.size -l -m -x SorterAndFilter
复制代码
的到结果以下:
SorterAndFilter (for architecture arm64):
Segment __PAGEZERO: 0x100000000 (vmaddr 0x0 fileoff 0)
Segment __TEXT: 0x1c000 (vmaddr 0x100000000 fileoff 0)
Section __text: 0xfde0 (addr 0x100005740 offset 22336)
Section __stubs: 0x1bc (addr 0x100015520 offset 87328)
Section __stub_helper: 0x1d4 (addr 0x1000156dc offset 87772)
Section __const: 0x64 (addr 0x1000158b0 offset 88240)
Section __objc_methname: 0x36b4 (addr 0x100015914 offset 88340)
Section __ustring: 0x134 (addr 0x100018fc8 offset 102344)
Section __cstring: 0xd06 (addr 0x1000190fc offset 102652)
Section __objc_classname: 0x28c (addr 0x100019e02 offset 105986)
Section __objc_methtype: 0x19f2 (addr 0x10001a08e offset 106638)
Section __gcc_except_tab: 0xd8 (addr 0x10001ba80 offset 113280)
Section __unwind_info: 0x4a4 (addr 0x10001bb58 offset 113496)
total 0x168bc
Segment __DATA: 0xc000 (vmaddr 0x10001c000 fileoff 114688)
Section __got: 0x60 (addr 0x10001c000 offset 114688)
Section __la_symbol_ptr: 0x128 (addr 0x10001c060 offset 114784)
Section __const: 0x9f0 (addr 0x10001c188 offset 115080)
Section __cfstring: 0x9a0 (addr 0x10001cb78 offset 117624)
Section __objc_classlist: 0x90 (addr 0x10001d518 offset 120088)
Section __objc_catlist: 0x28 (addr 0x10001d5a8 offset 120232)
Section __objc_protolist: 0x58 (addr 0x10001d5d0 offset 120272)
Section __objc_imageinfo: 0x8 (addr 0x10001d628 offset 120360)
Section __objc_const: 0x55d8 (addr 0x10001d630 offset 120368)
Section __objc_selrefs: 0x9c0 (addr 0x100022c08 offset 142344)
Section __objc_classrefs: 0x138 (addr 0x1000235c8 offset 144840)
Section __objc_superrefs: 0x68 (addr 0x100023700 offset 145152)
Section __objc_ivar: 0xd0 (addr 0x100023768 offset 145256)
Section __objc_data: 0x5a0 (addr 0x100023838 offset 145464)
Section __data: 0x430 (addr 0x100023dd8 offset 146904)
Section __bss: 0x48 (addr 0x100024208 offset 0)
total 0x8250
Segment __LINKEDIT: 0x24000 (vmaddr 0x100028000 fileoff 163840)
total 0x10004c000
复制代码
在虚拟内存中,Mach-O
文件SorterAndFilter
的总大小是total 0x10004c000
。
因为Mach-O
文件在虚拟内存中的__PAGEZERO段的大小是0x100000000
因此在本地文件中Mach-O
文件的大小是:0x10004c000 - 0x100000000
等于0x4c000
由于系统自动添加的ASLR
值是0x558000
,因此在虚拟内存中,Mach-O
文件SorterAndFilter
的空间分布是: 0x558000 -> 0x1005A4000 (0x10004c000 + 0x558000 )
根据上面内存地址信息 log 的值:
2020-01-12 16:47:32.388332+0800 SorterAndFilter[1717:366886] 全局变量: 0x10057bf58, 局部变量: 0x16f8a57fc, 局部变量—对象指针:0x16f8a57f0, 堆空间-对象地址:0x100aace30
复制代码
动态连接器dyld的虚拟内存地址
[ 1] 0x0000000100800000 /Users/zhoufei/Library/Developer/Xcode/iOS DeviceSupport/11.4 (15F79)/Symbols/usr/lib/dyld
复制代码
Mach-O
文件SorterAndFilter全部使用的到的image(image都是Mach-O
类型文件)文件内存分布,获得虚拟内存中的内存分布以下:
这里有个很奇怪的地方,系统共享库的虚拟内存地址0x1190000
,是在Mach-O
文件SorterAndFilter
的虚拟内存空间范围内 我感受比较合理的解释是打印出来的0x1190000
是 app 中 stub 代码区地址。
那何为 stub 代码区呢?
Stub
代码区是本地源代码调用系统库代码的链接点,当 app 工程中有调用系统函数的代码时,在 app 编译后,那个调用系统函数的处的内存地址便指向了stub
代码区 。
app 从本地加载到内存时,使用的系统函数 API 在 app 的可执行文件文件中并无函数实现,须要dyld
动态连接器将系统 API 与系统函数地址进行绑定。 而根据绑定时机不一样,绑定分为 app 加载时binding
和函数调用时lazy_binding
。
下面以lazy_binding
绑定方式为例:
stub
代码区,stub
代码区的实现中,又将函数指针指向了懒动态符号表。stub_helper
代码区。stub_helper
代码区经过dyld_stub_binder
函数将真实的系统函数内存地址更新到懒动态符号表中。具体动态绑定流程图以下:
Mach-O
类型文件工具Mach-O
类型文件工具备不少,常见的以下:
系统自带工具
Mach-O
的文件类型Mach-O
文件的符号表Mach-O
特定部分和段的内容Mach-O
不一样段的虚拟内存分布Mach-O
文件的处理查看通用二进制文件包含的架构
lipo -info test
瘦身通用二进制文件,到包含指定架构(armv7)的瘦二进制文件
lipo test -thin armv7 -output test_armv7
合并两个瘦二进制文件到一个通用二进制文件
lipo -create test_armv7 test_arm64 -output test2
复制代码
** Xcode 自带工具**
lldb
调试器命令,打印全部 app 文件对应Mach-O
的所包含的全部Mach-O
文件。
image list -o -f
复制代码
第三方工具
// 产生头文件
class-dump -H test -o Headers
复制代码
Mach-O
文件Mach-O
文件中的函数和全局变量的信息。有了工具的使用,在分析Mach-O
文件时会变的轻松不少。