關於 Mach-O 類型文件那點事

Mach-O文件簡介

Mach-O是一種文件格式,是Mach Object文件格式的縮寫。 它一般應用於可執行文件,目標代碼,動態庫,內核轉儲等中。sass

Mach-O做爲大部分基於Mach核心的操做系統所使用。 如NeXTSTEP,DarwinMac OS X等系統使用這種格式做爲其原生可執行文件,庫和目標代碼的格式。bash

在 NeXTSTEP 和 Mac OS X 中,能夠將多個Mach-O文件組合進一個多重架構二進制文件中,以用一個單獨的二進制文件支持多種架構的指令集。這種稱爲胖二進制文件(即:Fat binary 文件)。架構

Mach-O文件類型衆多,常見的一些Mach-O文件類型以下:app

  • MH_OBJECT 目標文件,平時.o結尾的文件
  • MH_EXECUTE 可執行文件,咱們平時編譯後的包中的執行文件
  • MH_DYLIB 一些動態庫,該文件夾下不少/usr/lib/xxx.dylib
  • MH_DSYM 符號文件,編譯成功後XXX.app.dSYM

Mach-O文件結構佈局

Mach-O主要有三部分組成:iphone

Header 部分主要描述當前Mach-O文件什麼架構,是否 Fat 二進制文件,CPU 類型等。編輯器

Load commands 部分主要描述:函數

  1. Mach-O文件中在虛擬內存中空間是如何分配的,從哪一個內存地址開始到哪一個內存地址結束。
  2. 不一樣段在Mach-O文件中的位置,大小分佈。

Data 部分是描述內存如何被分配的內容。包括__TEXT, __DATA等。 工具

Header 結構描述

Fat_Archheader 結構圖以下:佈局

若是隻有一種架構,那麼 Fat Header 的地方直接就是 mac_headerui

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文件中不一樣的 Segment 段__TEXT, __DATA等在虛擬內存中的內存分佈是用VM AddressVM Size來描述的; 在Mach-O本地文件中的空間分佈是用File OffsetFile Size來描述的;

而同一個 Segment 信息在這兩個維度中的空間分配策略,是不徹底相同的。 如:

  • __PAGEZERO 段:在 arm64 架構 size0x100000000,在armv7架構size0x4000。它會在虛擬內存中隔離出一大塊 低地址區的內存空間。而在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文件時會變的輕鬆不少。

相關文章
相關標籤/搜索