對Mach-O文件的初步探索

Mach-O是Mac/iOS上的可執行文件格式,相似於UNIX中的ELF(Extensible Firmware InInterface)。Mach-O文件的的Magic Number爲0xfeedface(32位)和0xfeedfacf(64位)。html

macOS上的可執行文件有三類:c++

可執行文件 Magic Number 用途
shell腳本 \x7FELF shell腳本,perl,awk等。
通用二進制格式 0xcafebabe,0xbebafeca 包含多種架構支持,也叫Fat Binary。
Mach-O 0xfeedface(32位), 0xfeedfacf(64位) iOS和macOS上使用。

通用二進制格式

lipo是管理Fat Binary的工具,可查看平臺列表,提取特定平臺的Binary等。通常用於macOS平臺上多架構通用二進制文件的處理。git

關於lipo命令的示例請看下文。github

Mach-O文件類型

iOS, macOS平臺上有如下幾種類型:shell

  • 二進制文件 .o
  • 靜態連接庫 .a文件,即多個.o合併在一塊兒
  • 動態連接庫 .dylib文件
  • Bundle,不能被連接的dylib,只能在運行時使用dlopen()來加載
  • 可重定向文件類型
  • nib 本質就是dylib文件
  • dSYM release版本
  • 存儲二進制文件符號信息的文件 .dSYM/Contents/Resources/DWARF/xx, 經常使用於分析APP的崩潰信息。

注意:可執行文件的格式與mach-o文件不能等同。bootstrap

在iOS 12.2/usr/include/mach-o/header.h中有相關定義。swift

使用MachOView工具來查看Mach-O文件,如查看該main文件:瀏覽器

在該工具中,能夠清晰地看到Mach-O的內部格式,包括Magic Number、CPU Type等,以及各個段:Load Command、__TEXT, __DATA等。緩存

Mach-O的結構

  • Header 文件類型,目標架構類型,加載命令等通常信息。由系統內核負責讀取。
  • Load commands 描述文件在虛擬內存中的邏輯結構,佈局等。內容包含了符號表等,數據的具體組織結構。加載命令,告訴loader如何設置並加載二進制數據。
  • Data 存放數據:代碼、字符常量、類、方法等。可擁有多個segment,每一個segment有多個section,每一個段都有一段虛擬內存映射到進程的地址空間。
  • Loader Info 連接信息,一個完整的用戶級MachO文件的末端是一系列連接信息,博阿憨了dyld用來連接可執行文件或依賴所需使用的符號表、字符串表等。
  • Raw segment data 原始數據區。文件中最大的部分,包含了Segement的具體數據。(在Load commands中定義的Segment的原始數據)。每個segment中有一個或多個Section,用來存放數據和代碼。

Load Commands

Load Commands展現了Mach-O文件的具體結構,不一樣的結構會採用不一樣的加載命令。sass

  • LC_SEGMENT_64, 將文件中的段映射到進程地址空間中,即加載命令
  • LC_DYLD_INFO_ONLY,動態連接庫相關(重定向地址、弱引用綁定、懶綁定、開放函數等的偏移等信息)
  • LC_SYMTAB,符號表
  • LC_DYSYMTAB,動態符號表
  • LC_LOAD_DYLNKER,加載器/usr/bin/dyld
  • LC_UUID,文件的惟一UUID
  • LC_MAIN, 程序主線程的入口地址和棧大小
  • LC_LOAD_DYLIB(Foundation) 動態庫的地址;只有標記了,才能被dyld加載。
  • LC_FUNCTION_STARTS 函數起始地址表
  • LC_DATA_IN_CODE 數據段
  • LC_CODE_SIGNATURE 代碼簽名

這些命令都是load_command結構,cmd字段標記了命令類型,而cmdsize則是命令的長度,即命令二進制的長度。

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

幾種常見的Segment或Section

section是相同或類似信息的集合,如.text、.data、.bss section都是不一樣的section。而segment是由多個屬性相同的section組成的。咱們一般說的代碼段和數據段指的其實就是segment。

__PAGEZERO

#define SEG_PAGEZERO "__PAGEZERO" /* the pagezero segment which has no */
					/* protections and catches NULL */
					/* references for MH_EXECUTE files */
複製代碼

__PAGEZERO是空指針陷阱段,映射到虛擬內存空間的第一頁,用於捕捉對NULL指針的引用。

__PAGEZERO的大小爲 4GB。這 4GB 並非文件的真實大小,可是規定了進程地址空間的前 4GB 被映射爲 不可執行、不可寫和不可讀。這就是爲何當讀寫一個 NULL 指針或更小的值時會獲得一個 EXC_BAD_ACCESS 錯誤。這是操做系統在嘗試防止引發系統崩潰。

__TEXT

代碼區,這一部分是App中的不可變部分,即Read only的。

#define SEG_TEXT "__TEXT" /* the tradition UNIX text segment */
#define SECT_TEXT "__text" /* the real text part of the text */
					/* section no headers, and no padding */

#define SECT_FVMLIB_INIT0 "__fvmlib_init0" /* the fvmlib initialization */
						/* section */
#define SECT_FVMLIB_INIT1 "__fvmlib_init1" /* the section following the */
					        /* fvmlib initialization */
						/* section */                    
複製代碼

__TEXT包含了不少其餘的Section:

Section 解釋
__TEXT, __text 主程序代碼段。The compiled machine code for the executable
__TEXT, __stubs Stub能夠理解爲一段佔位空間,placeholder,用於符號的lazy binding。
__TEXT, __stubs_helper Stub helper
__TEXT, __cstring C語言字符串。Literal string constants (quoted strings in source code)
__TEXT, __entitlements __entitlements
__TEXT, __unwind_info C語言字符串
__TEXT, __const 常量段(const修飾)。The general constant data for the executable
__TEXT, __objc_classname OC的類名
__TEXT, __objc_methname OC方法名稱
__TEXT, __objc_methtype OC方法類型,即方法簽名

__stubs和__stubs_helper是給dyld使用的,使得符號能夠被懶綁定(lazy binding),即在首次使用而非加載時就去綁定其符號地址。

注意,objc相關的有__objc_classname,__objc_methname,__objc_methtype,這三部分均爲不可變的。

__DATA

#define SEG_DATA "__DATA" /* the tradition UNIX data segment */
#define SECT_DATA "__data" /* the real initialized data section */
					/* no padding, no bss overlap */
#define SECT_BSS "__bss" /* the real uninitialized data section*/
					/* no padding */
#define SECT_COMMON "__common" /* the section common symbols are */
					/* allocated in by the link editor */
複製代碼
Section 解釋
__DATA_CONST, __got __got
__DATA, __got __got
__DATA, __data 已初始化的全局變量。Initialized global variables (for example int a = 1; or static int a = 1;).
__DATA, __bss 未初始化的靜態變量。Uninitialized static variables (for example, static int a;).
__DATA, __const Constant data needing relocation (for example, char * const p = "foo";).
__DATA, __cfstring Core Foundation字符串(CFStringRefs),沒有ARC
__DATA, __common 未初始化的外部全局變量。Uninitialized external globals (for example, int a; outside function blocks).
__DATA, __la_symbol_ptr 懶綁定的符號指針表。「Lazy」 symbol pointers. Symbol pointers for each undefined function called by the executable.
__DATA, __nl_symbol_ptr 非懶綁定的符號指針表。「Non lazy」 symbol pointers. Symbol pointers for each undefined data symbol referenced by the executable.
__DATA, __objc_classlist OC的類列表,存儲一個個指向objc_class結構體的指針
__DATA, __objc_nlclslist OC的類列表,+load相關?
__DATA, __objc_catlist OC的category列表,存儲一個個指向__objc_category結構體的指針
__DATA, __objc_protolist OC的協議列表,存儲一個個指向protocol_t結構體的指針
__DATA, __objc_imginfo OC的image信息
__DATA, __objc_selrefs 哪些SEL對應的字符串被引用了
__DATA, __objc_classrefs 類的引用,即msg_objSend相關
__DATA, __objc_superrefs super引用,記錄了super方法調用的類。如ViewController中的viewDidLoad中調用了[super viewDidLoad],則ViewController class即被記錄。也能夠理解爲objc_msgSendSuper相關。
__DATA, __objc_protorefs 協議引用
__DATA, __objc_ivar 成員變量
__DATA, __objc_const 這裏的const跟__TEXT的const徹底不一樣。__objc_const指的是OC內存佈局中的不可變部分,即class_ro_t類型。
__DATA, __objc_data 保存類所需的數據?

__la_symbol_ptr是懶綁定(lazy binding)的符號指針,在加載的時候,並未直接肯定符號地址,而是在第一次調用該函數的時候,經過PLT(Procedure Linkage Table)進行一次懶綁定。而__nl_symbol_ptr則不會進行懶綁定。

__OBJC

OC及runtime相關的

#define SEG_OBJC "__OBJC" /* objective-C runtime segment */
#define SECT_OBJC_SYMBOLS "__symbol_table" /* symbol table */
#define SECT_OBJC_MODULES "__module_info" /* module information */
#define SECT_OBJC_STRINGS "__selector_strs" /* string table */
#define SECT_OBJC_REFS "__selector_refs" /* string table */
複製代碼

__ICON

icon,tiff資源等。

#define SEG_ICON "__ICON" /* the icon segment */
#define SECT_ICON_HEADER "__header" /* the icon headers */
#define SECT_ICON_TIFF "__tiff" /* the icons in tiff format */
複製代碼

__LINKEDIT

包含須要被動態連接器dyld使用的符號和其餘表,包括符號表、字符串表等。

#define SEG_LINKEDIT "__LINKEDIT" /* the segment containing all structs */
					/* created and maintained by the link */
					/* editor. Created with -seglinkedit */
					/* option to ld(1) for MH_EXECUTE and */
					/* FVMLIB file types only */
複製代碼

__UNIXSTACK

#define SEG_UNIXSTACK "__UNIXSTACK" /* the unix stack segment */
複製代碼

__IMPORT

#define SEG_IMPORT "__IMPORT" /* the segment for the self (dyld) */
					/* modifing code stubs that has read, */
					/* write and execute permissions */
複製代碼

Dynamic Loader Info

Symbol Table

符號表是Mach-O中的符號映射。

Dynamic Symbol Table

動態符號表是加載動態庫時的函數表,是符號表的子集。動態符號表的符號 = 符號在原所屬符號表中的offset + 原所屬符號表在動態符號表中的offset + 動態符號表的基地址base。在動態符號表中查找到的這個符號的值,又等於該符號在符號表中的offset。

參考:Hook 原理之 fishhook 源碼解析

對比彙編代碼來查看mach-o

使用otool來查看對main文件進行反彙編後的彙編代碼:

main:
(__TEXT,__text) section
_main:
0000000000000000	pushq	%rbp
0000000000000001	movq	%rsp, %rbp
0000000000000004	subq	$0x20, %rsp
0000000000000008	movl	$0x0, -0x4(%rbp)
000000000000000f	movl	%edi, -0x8(%rbp)
0000000000000012	movq	%rsi, -0x10(%rbp)
0000000000000016	leaq	0x2e(%rip), %rdi
000000000000001d	movb	$0x0, %al
000000000000001f	callq	0x24
0000000000000024	xorl	%ecx, %ecx
0000000000000026	movl	$0x1, -0x14(%rbp)
000000000000002d	movl	$0x2, -0x18(%rbp)
0000000000000034	movl	-0x14(%rbp), %edx
0000000000000037	addl	-0x18(%rbp), %edx
000000000000003a	addl	$0x3, %edx
000000000000003d	movl	%edx, -0x1c(%rbp)
0000000000000040	movl	%eax, -0x20(%rbp)
0000000000000043	movl	%ecx, %eax
0000000000000045	addq	$0x20, %rsp
0000000000000049	popq	%rbp
000000000000004a	retq
複製代碼

相關數據結構

在iOS 12.2/usr/include/mach-o/fat.h中:

struct fat_header {
	uint32_t	magic;		/* FAT_MAGIC or FAT_MAGIC_64 */
	uint32_t	nfat_arch;	/* number of structs that follow */
};

struct fat_arch_64 {
	cpu_type_t	cputype;	/* cpu specifier (int) */
	cpu_subtype_t	cpusubtype;	/* machine specifier (int) */
	uint64_t	offset;		/* file offset to this object file */
	uint64_t	size;		/* size of this object file */
	uint32_t	align;		/* alignment as a power of 2 */
	uint32_t	reserved;	/* reserved */
};
複製代碼

在iOS 12.2/usr/include/mach-o/loader.h

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 */
    //表示二進制文件所支持的一些功能,和系統加載有關係。描述文件在編譯、連接等過程當中的信息,示例中的 MH_NOUNDEFS 表示文件中不存在未定義的符號,MH_DYLDLINK 表示文件要交由 DYLD 進一步處理,MH_TWOLEVEL 表示文件使用兩級命名空間,MH_PIE 表示啓用地址空間佈局隨機化。
	uint32_t	flags;		/* flags */
	uint32_t	reserved;	/* reserved */
};
複製代碼

header描述了Mach-O文件的基本信息,注意其中的CPU類型cputype,文件類型filetype,加載命令個數ncmds,全部加載命令的大小sizeofcmds。

不一樣的segment,會使用不一樣的加載命令。

struct dyld_info_command {
    uint32_t   cmd;     /* LC_DYLD_INFO or LC_DYLD_INFO_ONLY */
    uint32_t   cmdsize;     /* sizeof(struct dyld_info_command) */
    uint32_t   rebase_off;  //重定向的偏移值。(ASLR)
    uint32_t   rebase_size; //重定向的大小。
    uint32_t   bind_off;    //綁定。可執行文件讀到內存中會綁定一些數據。weak綁定,lazy綁定。
    uint32_t   bind_size;   /* size of binding info */
    uint32_t   weak_bind_off; /* file offset to weak binding info */
    uint32_t   weak_bind_size;  /* size of weak binding info */
    uint32_t   lazy_bind_off; /* file offset to lazy binding info */
    uint32_t   lazy_bind_size;  /* size of lazy binding infs */
    uint32_t   export_off;  //對外開發的函數。
    uint32_t   export_size; /* size of lazy binding infs */
};
複製代碼
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 */
};
複製代碼
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 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) */
	uint32_t	reserved3;	/* reserved */
};
複製代碼
  1. Mach-O 瞭解一下
  2. Mach-O 文件格式探索

一些相關工具

class-dump

在.app包內,執行命令查看可執行文件:

plutil -p Info.plist | grep CFBundleExecutable
# 結果 "CFBundleExecutable" => "XXXX"
複製代碼

class-dump即利用OC的runtime特性,將mach-o文件中的@interface和@prototol信息提取出來,生成對應的.h文件。

class-dump -H XXXX -o xxxx_headers
複製代碼

-H表示要生成頭文件,-o指定頭文件的存放目錄。

若是是Swift混編,則會出錯:

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid parameter not satisfying: [cursor offset] != 0'
libc++abi.dylib: terminating with uncaught exception of type NSException
複製代碼

從AppStore下載的App都是通過簽名加密的,class-dump會失敗。所以須要先砸殼,不然dump出來的頭文件是空的。 使用AppCrackr。

lipo

lipo是管理Fat File的工具,可查看平臺列表,提取特定平臺,從新打包。通常用於多架構mach-o文件的處理。

查看架構信息:

# lipo -info main
Non-fat file: main is architecture: x86_64
# lipo -info XXXX
Non-fat file: XXXX is architecture: arm64
複製代碼

查看到了該mach-o文件指定的平臺信息。若是有指定了多個平臺,則能夠提取單個平臺的mach-o文件。

提取特定架構:

lipo -thin armv7 MAMapKit -output MAMapKit.armv7
複製代碼

如單獨提取armv7結果的mach-o文件,體積要小不少。而多個mach-o之間會有共享的一些資源。

合併多個架構:

lipo -create WeChat_arm64 WeChat_armV7 -output WeChat_64_V7
複製代碼
lipo -detailed_info LuckyClient
複製代碼

等同於

otool -f -V LuckyClient
複製代碼

nm

nm用來顯示一個程序包的符號表,默認會顯示入口地址、符號類型、符號名。

nm -j MAMapKit.armv7 | grep png > symbols
複製代碼

能夠得到全部的libpng導出符號,存入的symbols文件。-j只輸出符號名。

strip

strip用來刪除程序裏的符號表。-R指定要刪除的符號列表。-S保留其餘符號。

strip -S -R symbols MAMapKit.armv7 -o MAMapKit.armv7.strip
複製代碼

otool

查看mach-o特定部分(segment或者section)的內容。

otool -L XXXX
複製代碼

可查看當前App須要鏈接的全部framework。主要都是一些Apple的庫,以及swift相關。

otool能夠查看mach-o的信息:

-f fat headers
-a archive header
-h mach header
-l load commands
-L shared libraries used
-D shared library id name
-t text section (disassemble with -v)
-p <routine name> start disassemble from routine name
-s <segname> <sectname> contents of se
複製代碼

如:

# 查看頭文件
otool -v -h main
# 查看__TEXT __text
otool -s __TEXT __text main
# 查看__TEXT __cstring
otool -s __TEXT __cstring main
複製代碼

MachOView

使用MachOView工具來查看Mach-O文件的具體細節,這裏就很少說了。

MachO-Kit

MachO-Kit是一個解析Mach-O文件的C語言庫。

其中使用了libMachO來進行Mach-O文件解析。

動態庫dylib

查看系統的全部動態庫:

find /usr/lib -name "*.dylib"
複製代碼

iOS系統常見的dylib有:

  • libobjc objc和runtime
  • libdispatch GCD
  • libsystem_c C語言庫
  • libsystem_blocks Block
  • libcommonCrypto 加密庫

dyld

dyld是Apple的動態連接庫加載器,內核作好App的初始準備後,交個dyld負責。做用以下:

  • 從內核留下的原始調用棧引導和啓動本身
  • 將程序以來的dylib遞歸加載進內存,考慮緩存機制
  • non-lazy符號當即link到可執行文件,lazy的存表裏
  • Runs static initializers for the executable
  • 找到可執行文件的main函數,準備參數並調用
  • 程序執行中負責綁定lazy符號,提供runtime dynamic loading services,提供調試器接口
  • 程序main函數return後執行static terminator
  • 某些場景下main函數結束後調用libsystem的_exit函數

iOS 程序 main 函數以前發生了什麼

使用/usr/lib/dyld來加載動態庫,可加載dylib,bundle,execute等類型的文件。dyld會經過一個共享緩存來加速dylib的加載。

ImageLoader

image即mach-o,裏邊是被編譯過的符號、代碼等,做用是將這些文件遞歸加載進內存,且每一個文件對應一個ImageLoader實例來負責加載。

因爲runtime向dyld綁定了回調,當image加載到內存後,dyld會通知runtime進行處理。 runtime接手後調用map_images作解析和處理,接下來load_images中調用call_load_methods方法,遍歷全部加載進來的Class,按繼承層級依次調用Class的+load方法和其Category的+load方法。

iOS 程序 main 函數以前發生了什麼

dlopen

加載dylib文件

void *_revealLib;
_revealLib = dlopen([filePath cStringUsingEncoding:NSUTF8StringEncoding], RTLD_NOW);
// xxx
dlclose(_revealLib);
複製代碼

dyld

若是在+load方法中斷點,便可在Xcode中進行調試。查看調用堆棧:

+ (void)load方法
call_load_methods
_dyld_start
dyldbootstrap::start(macho_header const*, int, char const*, long, macho_header const*, unsigned long*)
複製代碼

call_load_methods中就能看到objc_autoreleasePoolPush與objc_autoreleasePoolPop。

參考資料

  1. Mach-O develop.apple.com
  2. Mach-O 可執行文件
  3. mach-o/loader
  4. Macho文件瀏覽器---MachOView
  5. 深刻剖析 iOS 編譯 Clang LLVM
  6. Mach-O文件格式
  7. iOS逆向(5)-不知MachO怎敢說本身懂DYLD
  8. Objective-C runtime機制(前傳2)——Mach-O格式和runtime
相關文章
相關標籤/搜索