Mach-O文件簡介
Mach-O是一種文件格式,是Mach Object文件格式的縮寫。
它通常應用於可執行文件,目標代碼,動態庫,內核轉儲等中。
Mach-O作為大部分基於Mach核心的操作系統所使用。
如:NeXTSTEP,Darwin和Mac OS X等系統使用這種格式作為其原生可執行文件,庫和目標代碼的格式。
在NeXTSTEP和Mac OS X中,可以將多個Mach-O文件組合進一個多重架構二進制文件中,以用一個單獨的二進制文件支持多種架構的指令集。這種稱為胖二進制文件(即:Fat binary文件)。
Mach-O文件類型眾多,常見的一些Mach-O文件類型如下:
MH_OBJECT 目標文件,平時.o結尾的文件
MH_EXECUTE 可執行文件,我們平時編譯后的包中的執行文件
MH_DYLIB 一些動態庫,該文件夾下很多/usr/lib/xxx.dylib
MH_DSYM 符號文件,編譯成功后XXX.app.dSYM
Mach-O文件結構布局
Mach-O主要有三部分組成:
Header部分主要描述當前Mach-O文件什么架構,是否Fat二進制文件,CUP類型等等;
Load commands部分主要描述:
1.Mach-O文件中在虛擬內存中空間是如何分配的,從哪個內存地址開始到哪個內存地址結束。
2.不同段在Mach-O文件中的位置,大小分布。
Data部分是描述內存如何被分配的內容。包括__TEXT, __DATA等。

Header結構描述
Fat_Arch的header結構圖如下:

如果只有一種架構,那么Fat Header的地方直接就是mac_header
Load command結構描述

為了方便管理,程序被添加到內存后是分段管理的,而每個段是如何從本地加載到內存是在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文件中不同的內容段__TEXT, __DATA等在虛擬內存中的內存分布是用VM Address和VM Size來描述的;
在Mach-O本地文件中的空間分布是用File Offset和File Size來描述的;
而同一個Segment信息在這兩個維度中的空間分配策略,是不完全相同的。
如:
__PAGEZERO段:在arm64架構size時0x100000000,在armv7架構size時0x4000。它會在虛擬內存中隔離出一大塊
低地址區的內存空間。而在Mach-O文件中占據的大小為0,並沒有實際的內容。當設置一個指針變量的值為NULL時,其實就是將指針指向了__PAGEZERO段這塊區域。
__DATA段:在虛擬內存中的分配的空間要大於Mach-O文件中占據的大小。原因是在內存中需要預留一部分多余的空間給可以修改的全局變量或者所占空間可以變化的對象或容器使用。
常見的SEG類型
#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:
數據段,可讀、可寫。
__PAGEZERO段描述

__TEXT段加載描述
__DATA段加載描述
根據__TEXT段的加載描述, 得到__DATA段內容的偏移地址如下:
ASLR:內存空間布局隨機化
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綁定方式為例:
1.點擊按鈕,觸發本地函數對系統函數的調用,此時被調用的系統函數指針是指向的stub代碼區,
2. 在stub代碼區的實現中,又將函數指針指向了懶動態符號表。
3.懶動態符號表又將函數指針指向了stub_helper代碼區。
4.最后stub_helper代碼區通過dyld_stub_binder函數將真實的系統函數內存地址更新到懶動態符號表中。
具體動態綁定流程圖如下:
_nl_symbol_ptr列表在加載時綁定。
_la_symbol_ptr列表在第一次使用時進行函數綁定。
_la_symbol_ptr懶動態符號列表的綁定過程如下:
Mach-O類型文件工具
Mach-O類型文件工具有很多,常見的如下:
系統自帶工具
file: 查看Mach-O的文件類型
nm:查看Mach-O文件的符號表
otool:查看Mach-O特定部分和段的內容
size -l -m -x: 查看Mach-O不同段的虛擬內存分布
lipo:常用用於多架構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:可以把未經加密的app的頭文件導出來。
//產生頭文件
class-dump -H test -o Headers
MachOView: GUI工具查看Mach-O文件
HopperDisassembler: 反匯編查看Mach-O文件中的函數和全局變量的信息。
有了工具的使用,在分析Mach-O文件時會變的輕松很多。