iOS 逆向 - Mach-O文件

前言

前幾篇文章中 :linux

shell 腳本自動重簽名與代碼注入shell

應用簽名原理及重簽名 (重籤微信應用實戰)vim

重籤應用調試與代碼修改 (Hook)windows

咱們對重簽名和代碼注入有了必定的瞭解 . 那麼這個過程當中咱們反覆提到一個最重要的文件 -- Mach-O .緩存

那麼說來講去 , 這個Mach-O 究竟是個什麼 . 既然它這麼重要 , 那麼咱們有必要去好好的瞭解一下它 .bash

( 對概念不太感興趣的同窗能夠直接跳到第二章節 Mach-O 的文件結構 ) 微信

MachO 文件

Mach-O 實際上是 Mach Object 文件格式的縮寫,是 mac 以及 iOS 上可執行文件的格式, 相似於 windows 上的 PE 格式 ( Portable Executable ) , linux 上的 elf 格式 ( Executable and Linking Format ) .架構

它是一種用於可執行文件、目標代碼、動態庫的文件格式。做爲 a.out 格式的替代,Mach-O 提供了更強的擴展性。函數

可是除了可執行文件外 , 其實還有一些文件也是使用的 Mach-O 的文件格式 .工具

屬於 Mach-O 格式的常見文件

  • 目標文件 .o
  • 庫文件
    • .a
    • .dylib
    • Framework
  • 可執行文件
  • dyld ( 動態連接器 )
  • .dsym ( 符號表 )

Tips : 使用 file 命令能夠查看文件類型

也就是說 Mach-O 並不是必定是可執行文件 , 它是一種文件格式 , 分爲 Mach-O Object 目標文件 、 Mach-O ececutable 可執行文件、 Mach-O dynamically 動態庫文件、 Mach-O dynamic linker 動態連接器文件、 Mach-O dSYM companion 符號表文件 , 等等 .

你們能夠本身經過 vim 幾個 .c , 而後 clang 生成 .o 目標文件和可執行文件來玩一下 , 以便更好地理解這幾種文件以及其編譯的模式 .

那麼上圖中咱們還看到一個 arm64 , 這個是什麼意思呢 ?

  • 在 release 模式下
  • 支持 iOS 11.0 系統版本如下

當知足這兩個條件時 , 咱們的應用打包出來的 Mach-O ececutable 可執行文件是包含 arm64 以及 arm_v7 的架構的 , iPhone 5C 以上機型都是 64 位系統了 .

那麼包含了支持多架構的 Mach-O ececutable 可執行文件被稱爲 : 通用二進制文件 , 即多種架構均可讀取運行 .

另外 Xcode 中經過編譯設置 Architectures 是能夠更改所生成的 Mach-O ececutable 可執行文件的支持架構的 .

編譯器在生成 Mach-O 文件會選擇 Architectures 以及 Valid Architectures 的交集 , 所以想要支持多架構的話 , 在Valid Architectures 中繼續添加就能夠了 , 編譯生成 Mach-O 以後 , 使用 file 命令能夠檢查下結果 .

通用二進制文件

  • 蘋果公司提出的一種程序代碼。能同時適用多種架構的二進制文件

  • 同一個程序包中同時爲多種架構提供最理想的性能。

  • 由於須要儲存多種代碼,通用二進制應用程序一般比單一平臺二進制的程序要大。

  • 可是因爲兩種架構有共通的非執行資源,因此並不會達到單一版本的兩倍之多。

  • 並且因爲執行中只調用一部分代碼,運行起來也不須要額外的內存。

通用二進制文件一般被稱爲 Universal binary , 在 MachOView 等 中叫作 Fat binary , 這種二進制文件是能夠徹底拆分開來 , 或者從新組合的 , 那麼接下來咱們來玩一下 .

Fat binary 的組合與拆分

1 - 新建工程 , 選擇支持系統版本 10.3 .

2 - 編輯運行模式

選擇 Release ( 測試完畢改回來 . 不然 run 太慢 )

3 - Build Settings

第一行是自帶的環境變量 , 你本身也能夠刪掉本身寫 , iOS 10.3 以上 + release 環境下會默認包含 arm64 + armv7 的架構 , 所以咱們本身加上 armv7sarm64e .

4 - 選擇真機 run

run 起來後找到 Mach-O 文件

能夠看到 , 咱們的 Fat binary 就已經生成好了 .

使用 lipo - info 命令也是能夠查看支持架構的

拆分 Fat binary

lipo macho文件名稱 -thin 要拆分哪一個架構 -output 拆分出來文件名
複製代碼

例:

lipo 通用二進制MachO_Test -thin armv7s -output macho_armv7s
複製代碼

而後咱們就看到文件夾多了一個 macho_armv7s , 查看一下 :

另外拆分後源文件並不會改變.

合併 Fat binary

lipo -create macho_arm64 macho_arm64e macho_armv7 macho_armv7s -output newMachO
複製代碼

合併後咱們來看下新生成的 和之前的文件的哈希值 .

如出一轍的 .

Tips:

這種方式在咱們合併靜態庫的時候會常常用到 , 由於靜態庫自己就是 Mach-O 文件嘛 , 另外咱們在逆向的時候 , 有時也常常會用這種方法拆分二進制文件 , 由於咱們只須要分析單一架構便可 , 無須肥大的二進制文件.

補充

另外稍微補充一點 , 多架構二進制文件組合成通用二進制文件時 , 代碼部分是不共用的 ( 由於代碼的二進制文件不一樣的組合在不一樣的 cpu 上可能會是不一樣的意義 ) . 而公共資源文件是會共用的 .

Mach-O 文件結構

Mach-O 的組成結構如圖所示包括了

  • Header 包含該二進制文件的通常信息

    • 字節順序、架構類型、加載指令的數量等。

    • 使得能夠快速確認一些信息,好比當前文件用於 32 位仍是 64 位,對應的處理器是什麼、文件類型是什麼

  • Load commands 一張包含不少內容的表

    • 內容包括區域的位置、符號表、動態符號表等。
  • Data 一般是對象文件中最大的部分

    • 包含 Segement 的具體數據

咱們來找一個 Mach-O 文件 使用 MachOView 或者 otool 命令去查看一下文件結構 .

那麼這個 Mach-O 到底這些部分存放的是什麼內容 , 加下來咱們就來一一探索一下 .

Mach Header

Header 中存儲的內容大體如上圖所示 , 那麼每一條到底對應着什麼呢 ? , 咱們打開源碼看一下, cmd + shift + o , 搜索 load.h , 找 mach_header_64 結構體.

struct mach_header_64 {
    uint32_t	magic;		/* 魔數,快速定位64位/32位 */
    cpu_type_t	cputype;	/* cpu 類型 好比 ARM */
    cpu_subtype_t	cpusubtype;	/* cpu 具體類型 好比arm64 , armv7 */
    uint32_t	filetype;	/* 文件類型 例如可執行文件 .. */
    uint32_t	ncmds;		/* load commands 加載命令條數 */
    uint32_t	sizeofcmds;	/* load commands 加載命令大小*/
    uint32_t	flags;		/* 標誌位標識二進制文件支持的功能 , 主要是和系統加載、連接有關*/
    uint32_t	reserved;	/* reserved , 保留字段 */
};
複製代碼

mach_header_64 相較於 mach_header , 也就是 32 位頭文件 , 只是多了一個保留字段 . mach_header 是連接器加載時最早讀取的內容 , 它決定了一些基礎架構 , 系統類型 , 指令條數等信息.

Load Commands

Load Commands 詳細保存着加載指令的內容 , 告訴連接器如何去加載這個 Mach-O 文件.

經過查看內存地址咱們發現 , 在內存中 , Load Commands 是緊跟在 Mach_header 以後的 .

那麼這些 Load Commands 對應了什麼呢 ? 咱們以 arm64 爲例.

其中 _TEXT 段和 _DATA 段 , 是咱們常常須要研究的 , MachOView 下面也有詳細列出.

_TEXT 段

咱們來看看 _TEXT 段裏都存放了什麼 , 其實真正開始讀取就是從 _TEXT 段開始讀取的 .

名稱 內容
_text 主程序代碼
_stubs , _stub_helper 動態連接
_objc_methodname 方法名稱
_objc_classname 類名稱
_objc_methtype 方法類型 ( v@: )
_cstring 靜態字符串常量

_DATA 段

_DATA 在內存中是緊跟在 _TEXT 段以後的.

名稱 內容
_got : Non-Lazy Symbol Pointers 非懶加載符號表
_la_symbol_ptr : Lazy Symbol Pointers 懶加載符號表
_objc_classlist 類列表

...

以及以一些數據源 就不一一列舉了 .

補充

另外有一點值得提一下的就是系統庫的方法 , 因爲是公用的 , 存放在共享緩存中 , 那麼咱們的 Mach-O 中調用系統方法 ,

例如 : 調用 NSLog("%@,@"haha");

這個方法的實現確定不在咱們的 Mach-O 裏 , 那麼它如何找到方法實現呢 ?

其實就是 dyld 在進行連接的時候 , 會將 Mach-O 裏調用存放在共享緩存中的方法進行符號綁定 , 而這個符號在 release 的時候是會被自動去掉的. 這也是咱們常用收集 bug 工具時須要恢復符號表的緣由. 而所以 fishhoohhook 系統函數的時候名字叫 reBind 的緣由 .

關於符號綁定這一點咱們在講 fishhook 的時候會詳細講述一下 .

至此 , 整個 Mach-O 文件結構咱們已經講述完了 . 後續在逆向的過程當中涉及到具體存儲內容咱們會繼續介紹 .

相關文章
相關標籤/搜索