mach-o文件分析

熟悉Linuxwindows開發的同學都知道,ELFLinux下可執行文件的格式,PE32/PE32+windows下可執行文件的格式,下面我們要講的就OSXiOS環境下可執行文件的格式:mach-o

Mach-O 是 Mach object 文件格式的縮寫,它是一種用于記錄可執行文件、對象代碼、共享庫、動態加載代碼和內存轉儲的文件格式。作為 .out 格式的替代品,Mach-O 提供了更好的擴展性,并提升了符號表中信息的訪問速度(from wiki)。

分析工具

  1. MachOView工具可以在Mac平臺上查看MachO文件格式信息,是一種可視化的方式,能更直觀的方便我們查看;
  2. otool是命令行工具,該工具是Xcode自帶的,路徑是:/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/otool,具體操作可以使用man otool查看。

結構

從蘋果的官方文檔中我們可以知道,mach-o文件主要包括以下三個區域:

apple-doc.jpg
  1. Header頭部區域,主要包括mach-o文件的一些基本信息;
  2. LoadCommands加載命令,是由多個Segment command組成的;
  3. Data包含Segement的具體數據。

下面我們通過工具看下具體區域的結構。

Header

Header位于mach-o文件的開始位置,不管是在32位架構還是64位架構。根據??開源的代碼中我們可以看到以下數據結構(下面的代碼默認都是以64位為例):

/*
 * The 64-bit mach header appears at the very beginning of object files for
 * 64-bit architectures.
 */
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 */
};

首先我們通過otool工具查看Alipayheader結構:

?  ~ otool -hv /Alipay/Payload/AlipayWallet.app/AlipayWallet
Mach header
      magic cputype cpusubtype  caps    filetype ncmds sizeofcmds      flags
   MH_MAGIC     ARM         V7  0x00     EXECUTE    75       7524   NOUNDEFS DYLDLINK TWOLEVEL BINDS_TO_WEAK
Mach header
      magic cputype cpusubtype  caps    filetype ncmds sizeofcmds      flags
MH_MAGIC_64   ARM64        ALL  0x00     EXECUTE    75       8240   NOUNDEFS DYLDLINK TWOLEVEL BINDS_TO_WEAK PIE

二進制文件包含arm64armv7s兩個指令集,就是我們通常聽到的胖二進制結構或者多重指令集。下面我們對header的結構做一個大致的分析:

  • magic: 表示的是mach-o文件的魔數#define MH_MAGIC_64 0xfeedfacf
  • cputypecupsubtype: 標識的是cpu的類型和其子類型,具體定義可以查看mach/machine.h文件中的定義。
#define CPU_TYPE_I386       CPU_TYPE_X86        /* compatibility */
#define CPU_TYPE_X86_64     (CPU_TYPE_X86 | CPU_ARCH_ABI64)
#define CPU_TYPE_MC98000    ((cpu_type_t) 10)
#define CPU_TYPE_HPPA           ((cpu_type_t) 11)
#define CPU_TYPE_ARM        ((cpu_type_t) 12)
#define CPU_TYPE_ARM64          (CPU_TYPE_ARM | CPU_ARCH_ABI64)
  • filetype文件類型,比如可執行文件、庫文件、Dsym文件等,定義如下所示:
 * 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 */
                    /*  linking only, no section contents */
#define MH_DSYM     0xa     /* companion file with only debug */
                    /*  sections */
#define MH_KEXT_BUNDLE  0xb     /* x86_64 kexts */
  • ncmdsload commands的個數;
  • sizeofcmds:所有的load commands的大小。
  • flags:標志位,定義有很多,具體可以看下mach-o/load.h里面的定義,下面列出了上面Alipay打印出來的header的flag:
#define MH_NOUNDEFS 0x1     /* 該文件沒有未定義的符號引用 */
#define MH_DYLDLINK 0x4     /* 該文件是dyld的輸入文件,無法被再次靜態鏈接 */
#define MH_TWOLEVEL 0x80        /* 該鏡像文件使用2級名稱空間 */
#define MH_BINDS_TO_WEAK 0x10000    /* 最后鏈接的鏡像文件使用弱符號 */
#define MH_PIE 0x200000 /* 加載程序在隨機的地址空間,只在MH_EXECUTE中使用 */

二級名稱空間

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

Load Commands

load command結構緊跟著header結構的后面,并且它們在虛擬內存中指定文件的邏輯結構和文件的布局。 每個加載命令從指定命令類型和命令數據大小的字段開始。
load command的結構如下所示:

struct load_command {
    uint32_t cmd;       /* type of load command */
    uint32_t cmdsize;   /* total size of command in bytes */
};
  • cmd: load command的類型,具體支持的類型如下圖所示;
  • cmdsize: 所有command的大小。

下面列舉常見的command類型及用途:

命令類型 用途
LC_UUID 為鏡像文件或者dSYM文件指定一個128位的UUID
LC_SEGMENT 定義該文件的一段,以映射到加載該文件的進程的地址空間中。 它還包括該段所包含的所有sections部分
LC_SEGMENT_64 同上,映射到64位的segment
LC_SYMTAB 符號表,當鏈接文件時,靜態和動態鏈接器都使用此信息,還可以通過調試器將符號映射到生成符號的原始源代碼文件
LC_DYSYMTAB 動態鏈接器使用的附加符號表信息
LC_THREAD LC_UNIXTHREAD 對于一個可執行文件,LC_UNIXTHREAD定義了主線程初始化的線程狀態
LC_LOAD_DYLIB 定義此文件鏈接的動態共享庫的名稱
LC_ID_DYLIB 指定安裝的動態共享庫名字
LC_PREBOUND_DYLIB 對于共享庫,該可執行文件與Prebound對齊,指定共享庫中使用的模塊
LC_LOAD_DYLINKER 指定內核執行加載此文件的動態鏈接器
LC_ID_DYLINKER 標識這個文件為動態鏈接器
LC_ROUTINES 包含共享庫初始化例程的地址
LC_ROUTINES_64 同上,64位
LC_TWOLEVEL_HINTS 包含兩級命名空間查詢提示表
LC_SUB_FRAMEWORK 標識這個文件為一個umbrella frameworksubframework的實現,umbrella framework的名字存儲在字符串參數中
LC_SUB_UMBRELLA 標識這個文件是umbrella framework的一個subumbrella

這些加載命令在Mach-O文件加載解析時,被內核加載器或者動態鏈接器調用,指導如何設置加載對應的二進制數據段,具體定義可以查看/usr/include/mach-o/loader.h文件或者最后列出的??文檔。

可以使用命令otool -v -l AlipayWallet查看APP當前的所有的load command

段數據(Segments)

在上面load command中有定義LC_SEGMENTLC_SEGMENT_64兩種類型,它們實際上就是標識的segments,我們可以看下Alipay的結構:

macho-segment.png

數據結構定義:

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 */
};

每一個segment定義了一些Mach-O文件的數據、虛擬地址、文件偏移和內存保護等屬性,這些數據在動態鏈接器加載程序時被映射到了虛擬內存中。每個段都有不同的功能,主要包括如下幾種類型:

  • __PAGEZERO: 空指針陷阱段,映射到虛擬內存空間的第一頁,用于捕捉對NULL指針的引用,沒有保護標志位;
  • __TEXT: 包含了執行代碼以及其他只讀數據;
  • __DATA: 包含了程序數據,該段可讀寫;
  • __LINKEDIT: 含有為動態鏈接庫使用的原始數據,比如符號,字符串,重定位表條目等等, 該段可讀寫。
  • _RODATA:包含objc類名、方法等列表。

一般情況下__TEXT__DATA段又分為幾種section
section括號前面大寫的字母代碼segment的名稱,后面小寫字母代表section名稱。

下面列出__TEXT__DATA下的section:

__TEXT section 用途
__text 程序代碼
__stubs 用于動態鏈接的存根
__stub_helper 用于動態鏈接的存根
__const 程序中const關鍵字修飾的常量變量
__objc_methname objc方法名
__cstring 程序中硬編碼的ANSI的字符串
__objc_classname objc類名
__objc_methtype objc方法類型
__gcc_except_tab 異常處理相關
__ustring unicode字符串
__unwind_info 異常處理
__eh_frame 異常處理
__DATA section 用途
__nl_symbol_ptr 動態符號鏈接相關,指針數組
__got 全局偏移表, Global Offset Table
__la_symbol_ptr 動態符號鏈接相關,也是指針數組,通過dyld_stub_binder輔助鏈接
__mod_init_func 初始化的全局函數地址,會在main之前被調用
__const const修飾的常量
__cstring 程序中硬編碼的ANSI的字符串
__cfstring CF用到的字符串
__objc_classlist objc類列表
__objc_nlclslist objcload方法列表
__objc_catlist objc category列表
__objc_protolist objc protocol列表
__objc_imageinfo 鏡像信息
__objc_const objc的常量
__objc_selrefs objc引用的SEL列表
__objc_protorefs objc引用的protocol列表
__objc_classrefs objc引用的class列表
__objc_superrefs objc父類的引用列表
__objc_ivar objcivar信息
__objc_data class信息
__bss 未初始化的靜態變量區
__data 初始化的可變變量

隨機地址空間

進程每一次啟動,地址空間都會簡單地隨機化。該方式對APP的安全性具有重大的意義。如果采用傳統的方式,程序的每一次啟動的虛擬內存鏡像都是一致的,黑客很容易采取重寫內存的方式來破解程序。采用ASLR可以有效的避免黑客的中間人攻擊等。

dyld

dyld動態鏈接器,全名是dynamic link editer,當內核執行LC_DYLINK時,連接器會啟動,查找進程所依賴的所有的動態庫,并加載到內存中,當然,??內部對這個步驟做了很多優化,有興趣的可以查看WWDC相關的視頻。

用途

上面我們列舉了mach-o文件格式的組成部分及一些主要結構,通過分析我們的APP的可執行文件,可以幫助我們更深層次的了解APP的一些信息,也可以做一些hack的事情,比如微信之前有出一篇博文,講的就是通過LinkMap文件進行iOS APP瘦身。這里注意,如果不使用LinkMap文件而是APP可執行文件的話,一定要是debug編譯出來的,因為release編譯的話是已經優化過的二進制文件,沒有我們想要的一些信息。

查找無用selector

以往C++在鏈接時,沒有被用到的類和方法是不會編進可執行文件里。但Objctive-C不同,由于它的動態性,它可以通過類名和方法名獲取這個類和方法進行調用,所以編譯器會把項目里所有OC源文件編進可執行文件里,哪怕該類和方法沒有被使用到。

結合LinkMap文件的__TEXT.__text,通過正則表達式([+|-][.+\s(.+)]),我們可以提取當前可執行文件里所有objc類方法和實例方法(SelectorsAll)。再使用otool命令otool -v -s __DATA __objc_selrefs逆向__DATA.__objc_selrefs段,提取可執行文件里引用到的方法名(UsedSelectorsAll),我們可以大致分析出SelectorsAll里哪些方法是沒有被引用的(SelectorsAll-UsedSelectorsAll)。注意,系統API的Protocol可能被列入無用方法名單里,如UITableViewDelegate的方法,我們只需要對這些Protocol里的方法加入白名單過濾即可。

另外第三方庫的無用selector也可以這樣掃出來的。

查找無用oc類

查找無用oc類有兩種方式,一種是類似于查找無用資源,通過搜索"[ClassName alloc/new"、"ClassName *"、"[ClassName class]"等關鍵字在代碼里是否出現。另一種是通過otool命令逆向__DATA.__objc_classlist段和__DATA.__objc_classrefs段來獲取當前所有oc類和被引用的oc類,兩個集合相減就是無用oc類,具體分析如下:
通過命令行:otool -arch arm64 -o AlipayWallet > ~/Desktop/all.txt保存所有的信息。

打開文件可以看到類似下面的數據:

/// 所有的信息
AlipayWallet:
Contents of (__DATA,__objc_classlist) section
00000001039d6d60 0x10442e250
           isa 0x10442e228
    superclass 0x10442e7a0
         cache 0x0
        vtable 0x0
          data 0x1039f2378 (struct class_ro_t *)
                    flags 0x90
            instanceStart 8
             instanceSize 8
                 reserved 0x0
               ivarLayout 0x0
                     name 0x104c5d014 APSettingsCanSchemeHandleParse
              baseMethods 0x1039f2358 (struct method_list_t *)
           entsize 24
             count 1
              name 0x10473c037 doParseURL:
             types 0x104ca59c7 @24@0:8@16
               imp 
            baseProtocols 0x0
                    ivars 0x0
           weakIvarLayout 0x0
           baseProperties 0x0
Meta Class
           isa 0x0
    superclass 0x10442e778
         cache 0x0
        vtable 0x0
          data 0x1039f2310 (struct class_ro_t *)
                    flags 0x91 RO_META
            instanceStart 40
             instanceSize 40
                 reserved 0x0
               ivarLayout 0x0
                     name 0x104c5d014 APSettingsCanSchemeHandleParse
              baseMethods 0x0 (struct method_list_t *)
            baseProtocols 0x0
                    ivars 0x0
           weakIvarLayout 0x0
           baseProperties 0x0
 ......

debug編譯出來的可執行文件objc_classrefs段:

...
0000000101ba2340 0x101befcb0 _OBJC_CLASS_$_QQObjectPasteboard
0000000101ba2348 0x101befd28 _OBJC_CLASS_$_QQArrayPasteboard
0000000101ba2350 0x101befe68 _OBJC_CLASS_$_QQApiURLDecoder
0000000101ba2358 0x101bf0200 _OBJC_CLASS_$_QQWebViewKit
0000000101ba2360 0x101befe40 _OBJC_CLASS_$_QQApiURLEncoder
0000000101ba2368 0x101befff8 _OBJC_CLASS_$_GetMessageFromQQReq
0000000101ba2370 0x101bf0048 _OBJC_CLASS_$_GetMessageFromQQResp
0000000101ba2378 0x101bf0138 _OBJC_CLASS_$_ShowMessageFromQQReq
0000000101ba2380 0x101bf0188 _OBJC_CLASS_$_ShowMessageFromQQResp
...

release編譯出來的可執行文件objc_classrefs段:

...
00000001043dc060 0x0 _OBJC_CLASS_$_NSDictionary
00000001043dc068 0x0 _OBJC_CLASS_$_NSNotificationCenter
00000001043dc070 0x10442e6b0
00000001043dc078 0x10442e340
00000001043dc080 0x10442e5c0
00000001043dc088 0x10442e610
00000001043dc090 0x10442e660
00000001043dc098 0x10442e520
00000001043dc0a0 0x0 _OBJC_CLASS_$_NSBundle
...

其中__objc_classlistsection保存了所有的類信息,objc_classrefssection中保存了使用的類信息,objc_classrefs第二列是代表偏移量,和__objc_classlist中每個類開頭00000001039d6d60 0x10442e250第二列的值是相同的。所以我們可以通過腳本找出所有的類和所有使用的類,兩者相減就是沒有使用的類。

針對上面兩個小的功能,寫了一個小的ruby腳本,自動的找出沒有使用的selectorclass,具體的使用及代碼大家可以查看我的GitHub.

References

  1. OS X ABI Mach-O File Format Reference
  2. Mach-O file
  3. mach-o格式淺析(二)
  4. iOS微信安裝包瘦身
  5. Executing Mach-O Files
  6. Fat_binary
  7. loader.h
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,967評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,273評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,870評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,742評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,527評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,010評論 1 322
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,108評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,250評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,769評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,656評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,853評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,371評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,103評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,472評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,717評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,487評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,815評論 2 372

推薦閱讀更多精彩內容