探索Mach-O文件

Mach-O

Mach-O文件格式是 OS XiOS 系統(tǒng)上的可執(zhí)行文件格式,類似于windowsPE 文件 與 Linux(其他 Unix like)的 ELF 文件。

Mach-O 沒有類似于 XML、YAMLJSON 等諸如此類的特殊格式,它只是一個二進制字節(jié)流,被劃分為了有意義的數(shù)據(jù)塊。這些塊包含元信息,比如字節(jié)順序、cpu 類型、塊的大小等等。

它由3部分組成:

1、Header:保存了Mach-O的一些基本信息,包括了平臺、文件類型、LoadCommands的個數(shù)等等。
2、LoadCommands:這一段緊跟Header,加載Mach-O文件時會使用這里的數(shù)據(jù)來確定內(nèi)存的分布。
3、Data:每一個segment的具體數(shù)據(jù)都保存在這里,這里包含了具體的代碼、數(shù)據(jù)等等。

Mach-O文件的格式如下圖所示:

mach

OS X有兩種類型的目標文件:Mach-O 文件通用二進制文件,也叫作胖文件。它們之間的區(qū)別是:Mach-O 文件包含一種架構(i386、x86_64、arm64 等等)的對象代碼,而胖文件可能包含若干包含不同架構(i386、x86_64、arm、arm64 等等)對象代碼的對象文件。

Fat Header 文件

胖文件包含不同架構的數(shù)據(jù),不過每一個架構的結構跟Mach-O 文件一樣。

FAT二進制數(shù)據(jù)Header數(shù)據(jù)結構定義在 <mach-o/fat.h>里面。

#define FAT_MAGIC   0xcafebabe 32位 大端
#define FAT_CIGAM   0xbebafeca 32位 小端 /* NXSwapLong(FAT_MAGIC) */

struct fat_header {
    uint32_t    magic;      /* FAT_MAGIC or FAT_MAGIC_64 */
    uint32_t    nfat_arch;   /* 結構體實例的個數(shù) */
};

struct fat_arch {
    cpu_type_t  cputype;    /* cpu 說明符 (int) */
    cpu_subtype_t   cpusubtype; /* 指定 cpu 確切型號的整數(shù) (int) */
    uint32_t    offset;     /* CPU 架構數(shù)據(jù)相對于當前文件開頭的偏移值 */
    uint32_t    size;       /* 數(shù)據(jù)大小 */
    uint32_t    align;      /* 數(shù)據(jù)內(nèi)潤對其邊界,取值為 2 的冪 */
};

/*
 * then the 64-bit fat file format is used.
 */
#define FAT_MAGIC_64    0xcafebabf 64位 大端
#define FAT_CIGAM_64    0xbfbafeca 64位 小端   /* NXSwapLong(FAT_MAGIC_64) */

struct fat_arch_64 {
    cpu_type_t  cputype;    /* cpu 說明符 (int) */
    cpu_subtype_t   cpusubtype;  /* 指定 cpu 確切型號的整數(shù) (int) */
    uint64_t    offset;     /* CPU 架構數(shù)據(jù)相對于當前文件開頭的偏移值 */
    uint64_t    size;       /* 數(shù)據(jù)大小 */
    uint32_t    align;       /* 數(shù)據(jù)內(nèi)潤對其邊界,取值為 2 的冪 */
    uint32_t    reserved;   /* 保留位 */
};
大小端

struct fat_header里說 magic 只能取FAT_MAGIC 或者 FAT_MAGIC_64 , 但是為什么這里又來了一個FAT_CIGAM, 這是什么情況?
如果心細的話, 會發(fā)現(xiàn)CIGAM其實就是 MAGIC 反過來寫了而已. 因此這里需要了解一個概念, 就是大小端.
數(shù)據(jù)在內(nèi)存中存儲有2種形式, 一種是高數(shù)值在低內(nèi)存, 另外一種相反則是低數(shù)值在低內(nèi)存, 說起來比較抽象, 直接以 0xabcdef12 來比較大小端存儲情況:

// 大端:
// | 0x00000 | ab | <-- 最大的數(shù)字在低位
// | 0x00008 | cd |
// | 0x00010 | ef |
// | 0x00018 | 12 |

// 小端:
// | 0x00000 | 12 | <-- 最小的數(shù)字在低位
// | 0x00004 | ab |
// | 0x00008 | cd |
// | 0x0000c | ef |

在計算機系統(tǒng)中,我們是以字節(jié)為單位的,每個地址單元都對應著一個字節(jié),一個字節(jié)為8bit。但是在C語言中除了8bit的char之外,還有16bit的short型,32bit的long型(要看具體的編譯器)。另外,對于位數(shù)大于8位的處理器,例如16位或者32位的處理器,由于寄存器寬度大于一個字節(jié),那么必然存在著一個如果將多個字節(jié)安排的問題。因此就導致了大端存儲模式和小端存儲模式。

大小端在不同的架構上是不一樣的。

大端模式,是指數(shù)據(jù)的高字節(jié)保存在內(nèi)存的低地址中,而數(shù)據(jù)的低字節(jié)保存在內(nèi)存的高地址中,這樣的存儲模式有點兒類似于把數(shù)據(jù)當作字符串順序處理:地址由小向大增加,而數(shù)據(jù)從高位往低位放;
小端模式,是指數(shù)據(jù)的高字節(jié)保存在內(nèi)存的高地址中,而數(shù)據(jù)的低字節(jié)保存在內(nèi)存的低地址中,這種存儲模式將地址的高低和數(shù)據(jù)位權有效地結合起來,高地址部分權值高,低地址部分權值低,和我們的邏輯方法一致。

fat實例

Mach Header 文件

Mach-O 文件Header數(shù)據(jù)結構定義在 <mach-o/loader.h>里面。

/* 32-bit architectures */
struct mach_header {
    uint32_t    magic;      /* mach magic number identifier */
    cpu_type_t  cputype;    /* cpu specifier */
    cpu_subtype_t   cpusubtype; /* machine specifier */
    uint32_t    filetype;   /* type of file */
    uint32_t    ncmds;      /* number of load commands */
    uint32_t    sizeofcmds; /* the size of all the load commands */
    uint32_t    flags;      /* flags */
};

/* Constant for the magic field of the mach_header (32-bit architectures) */
#define MH_MAGIC    0xfeedface  /* the mach magic number */
#define MH_CIGAM    0xcefaedfe  /* NXSwapInt(MH_MAGIC) */

/* 64-bit architectures */
struct mach_header_64 {
    uint32_t    magic;      /* mach magic 標識符 */
    cpu_type_t  cputype;    /* CPU 類型標識符,同通用二進制格式中的定義 */
    cpu_subtype_t   cpusubtype; /* CPU 子類型標識符,同通用二級制格式中的定義 */
    uint32_t    filetype;   /* 文件類型 */
    uint32_t    ncmds;      /* 加載器中加載命令的條數(shù) */
    uint32_t    sizeofcmds; /* 加載器中加載命令的總大小 */
    uint32_t    flags;      /* dyld 的標志 */
    uint32_t    reserved;    /* 64 位的保留字段 */
};

/* Constant for the magic field of the mach_header_64 (64-bit architectures) */
#define MH_MAGIC_64 0xfeedfacf /* the 64-bit mach magic number */
#define MH_CIGAM_64 0xcffaedfe /* NXSwapInt(MH_MAGIC_64) */

由于 Mach-O 支持多種類型文件,所以此處引入了 filetype 字段來標明,這些文件類型定義在 loader.h 文件中同樣可以找到。

#define    MH_OBJECT    0x1        /* Target 文件:編譯器對源碼編譯后得到的中間結果 */
#define    MH_EXECUTE    0x2        /* 可執(zhí)行二進制文件 */
#define    MH_FVMLIB    0x3        /* VM 共享庫文件(還不清楚是什么東西) */
#define    MH_CORE        0x4        /* Core 文件,一般在 App Crash 產(chǎn)生 */
#define    MH_PRELOAD    0x5        /* preloaded executable file */
#define    MH_DYLIB    0x6        /* 動態(tài)庫 */
#define    MH_DYLINKER    0x7        /* 動態(tài)連接器 /usr/lib/dyld */
#define    MH_BUNDLE    0x8        /* 非獨立的二進制文件,往往通過 gcc-bundle 生成 */
#define    MH_DYLIB_STUB    0x9        /* 靜態(tài)鏈接文件(還不清楚是什么東西) */
#define    MH_DSYM        0xa        /* 符號文件以及調(diào)試信息,在解析堆棧符號中常用 */
#define    MH_KEXT_BUNDLE    0xb        /* x86_64 內(nèi)核擴展 */

另外在loader.h中還可以找到 flags 中所取值的全部定義,這里只介紹常用的:

#define    MH_NOUNDEFS    0x1        /* Target 文件中沒有帶未定義的符號,常為靜態(tài)二進制文件 */
#define MH_SPLIT_SEGS    0x20  /* Target 文件中的只讀 Segment 和可讀寫 Segment 分開  */
#define MH_TWOLEVEL    0x80        /* 該 Image 使用二級命名空間(two name space binding)綁定方案 */
#define MH_FORCE_FLAT    0x100 /* 使用扁平命名空間(flat name space binding)綁定(與 MH_TWOLEVEL 互斥) */
#define MH_WEAK_DEFINES    0x8000 /* 二進制文件使用了弱符號 */
#define MH_BINDS_TO_WEAK 0x10000 /* 二進制文件鏈接了弱符號 */
#define MH_ALLOW_STACK_EXECUTION 0x20000/* 允許 Stack 可執(zhí)行 */
#define    MH_PIE 0x200000  /* 對可執(zhí)行的文件類型啟用地址空間 layout 隨機化 */
#define MH_NO_HEAP_EXECUTION 0x1000000 /* 將 Heap 標記為不可執(zhí)行,可防止 heap spray 攻擊 */

Mach-O 文件頭主要目的是為加載命令提供信息。加載命令過程緊跟在頭之后,并且ncmdssizeofcmds 字段將會用在加載命令的過程中。

mach-o

LoadCommands

mach_header之后的是Load Command加載命令,這些加載命令在Mach-O文件加載解析時,被內(nèi)核加載器或者動態(tài)鏈接器調(diào)用,基本的加載命令的數(shù)據(jù)結構如下:

struct load_command {
    uint32_t cmd;       /* type of load command */
    uint32_t cmdsize;   /* total size of command in bytes */
};

此結構對應的成員只有2個:cmd字段代表當前加載命令的類型。cmdsize字段代表當前加載命令的大小。
cmd的類型不同,所代表的加載命令的類型就不同,它的結構體也會有所不一樣,對于不同類型的加載命令,它們都會在load_command結構體后面加上一個或多個字段來表示自己特定的結構體信息。

macOS系統(tǒng)在進化的過程中,加載命令算是比較頻繁被更新的一個數(shù)據(jù)結構體,截止到macOS 10.13系統(tǒng),加載命令的類型cmd的取值共有53種。

通過查看XNU-4570代碼可以發(fā)現(xiàn)內(nèi)核會處理9個命令,其中還有LC_SEGMENT、LC_ENCRYPTION_INFO,它們是32位的處理方式。

Command類型 處理函數(shù) 用途
LC_SEGMENT_64 load_segment 將segment數(shù)據(jù)加載映射到進程的內(nèi)存空間
LC_UNIXTHREAD load_unixthread 開啟一個UNIX線程
LC_MAIN load_main 加載main函數(shù)
LC_LOAD_DYLINKER load_dylinker 調(diào)用/usr/lib/dyld程序
LC_UUID load_uuid 加載128-bit的唯一ID
LC_CODE_SIGNATURE load_code_signature 進行數(shù)字簽名
LC_ENCRYPTION_INFO_64 set_code_unprotect 加密二進制文件

至于內(nèi)核dyld加載mach-o的流程,這里不多介紹(水平不夠 ??),可以自行查閱相關資料,或者看相關源碼。

LoadCommands結構如下圖:

image

根據(jù)偏移量找到每一個段區(qū)第一個變量cmd的地址,根據(jù)它的value類型,轉(zhuǎn)成相應的結構體,所以每一個段區(qū)的結構體前面2位都是cmdcmdsize

Segment

LC_SEGMENT 意味著這部分文件需要映射到進程的地址空間去,它是是最常見的段結構,這里看下他們的數(shù)據(jù)結構。

段名一般為大寫,節(jié)名一般為小寫,名字前面加2個下劃線__

struct segment_command_64 { /* for 64-bit architectures */
    uint32_t    cmd;        /* LC_SEGMENT_64 */
    uint32_t    cmdsize;    /* includes sizeof section_64 structs */
    char        segname[16];    /* 一個16字節(jié)大小的空間,用來存儲段的名稱 */
    uint64_t    vmaddr;     /* 段要加載的虛擬內(nèi)存地址 */
    uint64_t    vmsize;     /* 段所占的虛擬內(nèi)存的大小 */
    uint64_t    fileoff;    /* 段數(shù)據(jù)所在文件中偏移地址 */
    uint64_t    filesize;   /* 段數(shù)據(jù)實際的大小 */
    vm_prot_t   maxprot;    /* 頁面所需要的最高內(nèi)存保護 */
    vm_prot_t   initprot;   /* 頁面初始的內(nèi)存保護 */
    uint32_t    nsects;     /* 段所包含的節(jié)區(qū)(section) */
    uint32_t    flags;      /* 段的標志信息 */
};

</br>
一個程序編譯后,可執(zhí)行的程序分成了多個段,不同的類型的數(shù)據(jù)放入了不同的段中,如代碼段放入__TEXT。segment_command有幾個常用的段,分別起到不同的作用,定義如下:

宏定義 名字 用途 內(nèi)存保護
SEG_PAGEZERO __PAGEZERO 捕捉NULL指針的引用,映射到虛擬內(nèi)存的第一頁 0
SEG_TEXT __TEXT 包含了執(zhí)行代碼以及其他只讀數(shù)據(jù) 5
SEG_DATA __DATA 程序數(shù)據(jù) 3
SEG_LINKEDIT __LINKEDIT 鏈接器使用的符號以及其他表 1

</br>

segment_command中的maxprotinitprot是內(nèi)存保護相關的,分別有:

  • VM_PROT_READ(可讀 1)
  • VM_PROT_WRITE(可寫 2)
  • VM_PROT_EXECUTE(可執(zhí)行 4)

每個段具體的值都是由他們組合相加得到的,但LC_SEGMENT_64(__PAGEZERO)段比較特殊,2個字段的值都是VM_PROT_NONE(0)。

section

通過注釋A segment is made up of zero or more sections可知,segment里面還可以包含sections,下面先看下sections的數(shù)據(jù)結構:

struct section_64 { /* for 64-bit architectures */
    char        sectname[16];   /* name of this section */
    char        segname[16];    /* segment this section goes in */
    uint64_t    addr;       /* memory address of this section */
    uint64_t    size;       /* size in bytes of this section */
    uint32_t    offset;     /* file offset of this section */
    uint32_t    align;      /* Section 的內(nèi)存對齊邊界 (2 的次冪) */
    uint32_t    reloff;     /* 重定位入口的文件偏移 */
    uint32_t    nreloc;     /* 需要重定位的入口數(shù)量 */
    uint32_t    flags;      /* flags (section type and attributes)*/
    uint32_t    reserved1;  /* reserved (for offset or index) */
    uint32_t    reserved2;  /* reserved (for count or sizeof) */
    uint32_t    reserved3;  /* reserved */
};

segment_command結構中nsects字段標識含有多少個section,它是具體有用的數(shù)據(jù)存放的地方。下面列舉幾個常見的section

segment Section 用戶
__TEXT __text 主程序代碼
__TEXT __cstring C 語言字符串
__TEXT __const const 關鍵字修飾的常量
__TEXT __stubs 用于 Stub 占位代碼,很多地方稱之為樁代碼。
__TEXT __stubs_helper 當 Stub 無法找到真正的符號地址后的最終指向
__TEXT __objc_methname Objective-C 方法名稱
__TEXT __objc_methtype Objective-C 方法類型
__TEXT __objc_classname Objective-C 類名稱
__DATA __data 初始化過的可變數(shù)據(jù)
__DATA __la_symbol_ptr lazy binding 的指針表,表中的指針一開始都指向 __stub_helper
__DATA __nl_symbol_ptr 非 lazy binding 的指針表,每個表項中的指針都指向一個在裝載過程中,被動態(tài)鏈機器搜索完成的符號
__DATA __const 沒有初始化過的常量
__DATA __cfstring 程序中使用的 Core Foundation 字符串(CFStringRefs)
__DATA __bss BSS,存放為初始化的全局變量,即常說的靜態(tài)內(nèi)存分配
__DATA __common 沒有初始化過的符號聲明
__DATA __objc_classlist Objective-C 類列表
__DATA __objc_protolist Objective-C 原型
__DATA __objc_imginfo Objective-C 鏡像信息
__DATA __objc_selfrefs Objective-C self 引用
__DATA __objc_protorefs Objective-C 原型引用
__DATA __objc_superrefs Objective-C 超類引用
__DATA __objc_ivar Objective-C 成員變量
__DATA __objc_catlist Objective-C 分類
__DATA __objc_nlclslist Objective-C 重寫+load方法的類
__DATA __objc_nlcatlist Objective-C 重寫+load方法的分類
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • Mach-O 概述 和 部分命令介紹 我們知道Windows下的文件都是PE文件,同樣在OS X和iOS中可執(zhí)行文...
    青花瓷的平方閱讀 15,317評論 2 52
  • 1 dyld 1.1 dyld簡介 在iOS系統(tǒng)中,幾乎所有的程序都會用到動態(tài)庫,而動態(tài)庫在加載的時候都需要用d...
    Kevin_Junbaozi閱讀 12,620評論 4 44
  • 組成 Mach-O通常有三部分組成頭部 (Header): Mach-O文件的架構 比如Mac的 PPC, PPC...
    充滿活力的早晨閱讀 1,755評論 0 5
  • Mach-O類型的文件 Mach-O是一種文件的格式; 是iOS/Mac OS上存儲程序以及庫的標準格式Mach ...
    其字德安閱讀 5,545評論 0 13
  • 有時被放棄 像人類無視自己的影子 有時被研究 如何在這個三維的籠子里來來去去 有時被疏解 學會蓋印和濾色 有時被存...
    篩篩閱讀 367評論 0 1

友情鏈接更多精彩內(nèi)容