趣探 Mach-O:文件格式分析

本文所讀的源碼,能夠從這裏找到,這是 Mach-O 系列的第一篇html

咱們的程序想要跑起來,確定它的可執行文件格式要被操做系統所理解,好比 ELFLinux下可執行文件的格式,PE32/PE32+windows的可執行文件的格式,那麼對於OS XiOS 來講 Mach-O 是其可執行文件的格式。windows

咱們平時瞭解到的可執行文件、庫文件、Dsym文件、動態庫、動態鏈接器都是這種格式的。Mach-O 的組成結構以下圖所示包括了HeaderLoad commandsData(包含Segement的具體數據)bash

Header 的結構

Mach-O的頭部,使得能夠快速確認一些信息,好比當前文件用於32位仍是64位,對應的處理器是什麼、文件類型是什麼數據結構

能夠拿下面的代碼作一個例子架構

#include <stdio.h>

int main(int argc, const char * argv[]) {
    // insert code here...
    printf("Hello, World!\n");
    return 0;
}複製代碼

在終端執行如下命令,能夠生成一個可執行文件a.outapp

192:Test Joy$ gcc -g main.c複製代碼

咱們能夠使用MachOView(是一個查看MachO 格式文件信息的開源工具)來查看
.out文件的具體格式如何ide

看到這裏確定有點懵比,不知道這是什麼東西,下面看一下 header的數據結構工具

32位結構ui

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 */
};複製代碼

64位架構this

struct mach_header_64 {
    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 */
    uint32_t    reserved;    /* reserved */
};複製代碼

32位和64位架構的頭文件,沒有太大的區別,只是64位多了一個保留字段罷了

  • magic:魔數,用於快速確認該文件用於64位仍是32位
  • cputype:CPU類型,好比 arm
  • cpusubtype:對應的具體類型,好比arm6四、armv7
  • filetype:文件類型,好比可執行文件、庫文件、Dsym文件,demo中是2 MH_EXECUTE,表明可執行文件
* Constants for the filetype field of the mach_header
 */
#define MH_OBJECT 0x1 /* relocatable object file */
#define MH_EXECUTE 0x2 /* demand paged executable file */
#define MH_FVMLIB 0x3 /* fixed VM shared library file */
#define MH_CORE 0x4 /* core file */
#define MH_PRELOAD 0x5 /* preloaded executable file */
#define MH_DYLIB 0x6 /* dynamically bound shared library */
#define MH_DYLINKER 0x7 /* dynamic link editor */
#define MH_BUNDLE 0x8 /* dynamically bound bundle file */
#define MH_DYLIB_STUB 0x9 /* shared library stub for static */
#define MH_DSYM 0xa /* companion file with only debug */
#define MH_KEXT_BUNDLE 0xb /* x86_64 kexts */複製代碼
  • ncmds :加載命令條數
  • sizeofcmds:全部加載命令的大小
  • reserved:保留字段
  • flags:標誌位,剛纔demo中顯示的都在這裏了,其他的有興趣能夠閱讀 mach o源碼
#define MH_NOUNDEFS 0x1 // 目前沒有未定義的符號,不存在連接依賴
#define MH_DYLDLINK 0x4 // 該文件是dyld的輸入文件,沒法被再次靜態連接
#define MH_PIE 0x200000 // 加載程序在隨機的地址空間,只在 MH_EXECUTE中使用
#define MH_TWOLEVEL 0x80 // 兩級名稱空間複製代碼

隨機地址空間

進程每一次啓動,地址空間都會簡單地隨機化。

對於大多數應用程序來講,地址空間隨機化是一個和他們徹底不相關的實現細節,可是對於黑客來講,它具備重大的意義。

若是採用傳統的方式,程序的每一次啓動的虛擬內存鏡像都是一致的,黑客很容易採起重寫內存的方式來破解程序。採用ASLR能夠有效的避免黑客攻擊。

dyld

動態連接器,他是蘋果開源的一個項目,能夠在這裏下載,當內核執行LC_DYLINK(後面會說到)時,鏈接器會啓動,查找進程所依賴的動態庫,並加載到內存中。

二級名稱空間

這是dyld的一個獨有特性,說是符號空間中還包括所在庫的信息,這樣子就可讓兩個不一樣的庫導出相同的符號,與其對應的是平坦名稱空間

Load commands 結構

Load commands緊跟在頭部以後,這些加載指令清晰地告訴加載器如何處理二進制數據,有些命令是由內核處理的,有些是由動態連接器處理的。在源碼中有明顯的註釋來講明這些是動態鏈接器處理的。

這裏列舉幾個看上去比較熟悉的....

// 將文件的32位或64位的段映射到進程地址空間
#define LC_SEGMENT 0x1 
#define LC_SEGMENT_64 0x19 

// 惟一的 UUID,標示二進制文件
#define LC_UUID 0x1b /* the uuid */

// 剛纔提到的,啓動動態加載鏈接器
#define LC_LOAD_DYLINKER 0xe /* load a dynamic linker */

// 代碼簽名和加密
#define LC_CODE_SIGNATURE 0x1d /* local of code signature */
#define LC_ENCRYPTION_INFO 0x21 /* encrypted segment information */複製代碼

load command的結構以下

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

經過 MachOView來繼續查看剛纔Demo中的Load commands的一些細節,LC_SEGMENT_64LC_SEGMENT是加載的主要命令,它負責指導內核來設置進程的內存空間

  • cmd:就是Load commands的類型,這裏LC_SEGMENT_64表明將文件中64位的段映射到進程的地址空間。LC_SEGMENT_64LC_SEGMENT的結構差異不大,下面只列舉一個,有興趣能夠閱讀源碼
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];    /* segment name */
    uint64_t    vmaddr;        /* memory address of this segment */
    uint64_t    vmsize;        /* memory size of this segment */
    uint64_t    fileoff;    /* file offset of this segment */
    uint64_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 */
};複製代碼
  • cmdsize:表明load command的大小
  • VM Address :段的虛擬內存地址
  • VM Size : 段的虛擬內存大小
  • file offset:段在文件中偏移量
  • file size:段在文件中的大小

將該段對應的文件內容加載到內存中:從offset處加載 file size大小到虛擬內存 vmaddr處,因爲這裏在內存地址空間中是_PAGEZERO段(這個段不具備訪問權限,用來處理空指針)因此都是零

還有圖片中的其餘段,好比_TEXT對應的就是代碼段,_DATA對應的是可讀/可寫的數據,_LINKEDIT是支持dyld的,裏面包含一些符號表等數據

  • nsects:標示了Segment中有多少secetion
  • segment name:段的名稱,當前是__PAGEZERO

Segment & Section

這裏有個命名的問題,以下圖所示,__TEXT表明的是Segment,小寫的__text表明 Section

Section的數據結構

struct section { /* for 32-bit architectures */
    char        sectname[16];    /* name of this section */
    char        segname[16];    /* segment this section goes in */
    uint32_t    addr;        /* memory address of this section */
    uint32_t    size;        /* size in bytes of this section */
    uint32_t    offset;        /* file offset of this section */
    uint32_t    align;        /* section alignment (power of 2) */
    uint32_t    reloff;        /* file offset of relocation entries */
    uint32_t    nreloc;        /* number of relocation entries */
    uint32_t    flags;        /* flags (section type and attributes)*/
    uint32_t    reserved1;    /* reserved (for offset or index) */
    uint32_t    reserved2;    /* reserved (for count or sizeof) */
};複製代碼
  • sectname:好比_textstubs
  • segname :section所屬的segment,好比__TEXT
  • addr :section在內存的起始位置
  • size:section的大小
  • offset:section的文件偏移
  • align :字節大小對齊
  • reloff :重定位入口的文件偏移
  • nreloc: 須要重定位的入口數量
  • flags:包含sectiontypeattributes

發現不少底層知識都是以 Mach-O爲基礎的,因此最近打算花時間結合Mach-O作一些相對深刻的總結,好比符號解析、bitcode、逆向工程等,加油吧

參考連接

相關文章
相關標籤/搜索