1. 程式人生 > IOS開發 >iOS逆向學習之四(初識Mach-O)

iOS逆向學習之四(初識Mach-O)

什麼是Mach-O檔案?

Mach-O是Mach object的縮寫,是Mac\iOS上用來儲存程式、庫的標準格式

Mach-O檔案型別

  • 可以點選下載xnu原始碼,在原始碼中的EXTERNAL_HEADERS/mach-o/loader.h 檔案中,我們可以看到Mach-O格式的所有檔案型別

xun是蘋果MacOS\iOS等作業系統的核心

  • 常見的Mach-O檔案型別
Mach-O型別 示例檔案
MH_OBJECT 目標檔案(.o)
靜態庫檔案(.a)注:
MH_EXECUTE 可執行檔案,存放App的所有原始碼資訊,在.app/xx
MH_DYLIB 動態庫檔案.dylib 或者 .framework/xx
MH_DYLINKER 動態連結編輯器,也就是之前所說的/usr/lib/dyld工具
MH_DSYM 此檔案中儲存這二進位制檔案符號資訊,在開發中,我們經常使用此檔案來分析App的崩潰資訊

Mach-O的基本結構

可以點選官網檢視Mach-O的介紹。

Mach-O組成

Mach-O由3個部分組成

  • Header,包含檔案型別、目標架構型別等等
  • Load commands,是描述檔案在虛擬記憶體中的邏輯結構和佈局,相當於一份目錄索引
  • Raw segment data,在Load commands中所定義的Segment,在這裡都能找到原始資料。

Raw segment data存放了所有的原始資料,而Load commands相當於Raw segment data的索引目錄

窺探Mach-O的結構

常用工具

  • 命令列工具,通過file命令檢視Mach-O檔案的基本資訊
file 檔案路徑
複製程式碼
  • otool,檢視Mach-O特定部分和段的內容
#檢視Mach-O檔案的header資訊
otool -h 檔案路徑

#檢視Mach-O檔案的load commands資訊
otool -l 檔案路徑
複製程式碼

更多使用方法,終端輸入otool -help檢視

  • lipo,用來處理多架構Mach-O檔案,常用命令如下
#檢視架構資訊
lipo -info 檔案路徑

#匯出某種型別的架構
lipo 檔案路徑 -thin 架構型別 -output 輸出檔案路徑

#合併多種架構型別
lipo 檔案路徑1 檔案路徑2 -output 輸出檔案路徑 複製程式碼

Universal Binary(通用二進位制檔案)

通用二進位制檔案就是同時適用於多種架構的二進位制檔案,它包含了多種不同架構的獨立的二進位制檔案,它有以下特點

  • 因為需要儲存多種架構的程式碼,所以通用二進位制檔案要比單架構二進位制檔案要大
  • 因為兩種種架構之間可以共用一些資源,所以兩種架構的通用二進位制檔案大小不會達到單一架構版本的兩倍。
  • 執行過程中只會呼叫其中的部分程式碼,所以執行起來不會佔用額外的記憶體
  • 通用二進位制檔案通常也被稱為“胖二進位制檔案(Fat binary)”

dyld和Mach-O

dyld是iOS中用來載入可執行檔案、動態庫的工具,其實它本身也是一個Mach-O檔案。

什麼是dyld?
  • dyld 動態載入器(又叫做動態連結編輯器)
  • dyld的原始碼可以點選此處下載
dyld的作用。

dyld可以用來載入以下三種類型的Mach-O檔案

  • MH_EXECUTE
  • MH_DYLIB
  • MH_BUNDLE

通過檢視dyld的原始碼可以看到載入檔案時的型別校驗

從編碼到App安裝到手機

想要了解Mach-O檔案,首先要了解從編寫程式碼,開發App到App打包並安裝到手機上的整個過程。

  • 首先我們編寫完成程式碼之後,會通過LLVM編譯器預處理我們的程式碼,比如將巨集放在指定的位置
  • 預處理結束之後,LLVM會對程式碼進行詞法分析和語法分析,生成AST。AST是抽象語法樹,主要用來進行快速遍歷,實現靜態程式碼檢查的功能。
  • AST會生成IR,IR是一種更加接近機器碼的語言,通過IR可以生成不同平臺的機器碼。對於iOS平臺,IR生成的可執行檔案就是Mach-O.
  • 然後通過連結器將符號和地址繫結在一起,並且將專案中的多個Mach-O檔案合併成一個Mach-O檔案。
  • 最後通過簽名等操作生成.app檔案,然後對.app檔案進行壓縮就生成了我們可以安裝的ipa包。
  • 當然,ipa包的安裝途徑有兩種:
    • 通過開發者賬號上傳到App Store,然後在App Store上下載安裝。
    • 通過PP助手、iFunBox、Xcode等工具來安裝

逆向App,我們需要做哪些工作?

初步瞭解了什麼是Mach-O檔案,以及App從開發到安裝的過程,我們就可以來學習如何逆向一款App

  • 介面分析 通過之前的學習,我們已經可以使用Cycript和Reveal對App的介面進行分析
  • 程式碼分析 iOS開發中,所有的程式碼最後都會經過編譯生成Mach-o檔案,所以我們需要對Mach-O檔案進行靜態分析

靜態分析的工具有MachOView、class-dump、Hopper Disassembler、ida等等,後面會一一學習

  • 動態除錯 除了靜態分析,我們還需要執行目標App,對App進行動態除錯

動態除錯的工具有debugserver、LLDB等等

  • 程式碼編寫、注入 進行完介面分析、程式碼分析和動態除錯之後,我們可以在特定位置注入我們自己寫的程式碼,必要時可以重新簽名並且打包ipa

除錯工具

calss-dump

class-dump的作用就是把Mach-O檔案的class資訊給匯出來,生成對應的.h標頭檔案

  • 可以點選官網下載class-dump工具包
  • 下載完成之後將其中的class-dump可執行檔案複製到Mac上的/usr/local/bin目錄中,這樣在終端就能識別class-dump命令了

在Mac中,終端執行的所有指令都會去/usr/bin目錄和/usr/local/bin目錄下尋找

  • class-dump的常用命令如下
# -H表示需要生成標頭檔案  -o用於指定標頭檔案的存放目錄
class-dump -H Mach-O檔案路徑 -o 標頭檔案存放目錄
複製程式碼

Hopper Disassmbler

Hopper Disassmbler可以將Mach-O檔案的機器語言反編譯成彙編程式碼、OC虛擬碼或者是Swift虛擬碼。最常使用的快捷鍵有

#找出哪裡引用了這個方法
Shift + Option + X
複製程式碼

下載地址:pan.baidu.com/s/1yP_VcBlQ…


靜態庫和動態庫

在iOS開發中,有很多功能都是現成可用的,不關你的App在用,其它的App也在用,比如UIKit框架、GUI框架、I/O、網路等等。這些庫都是通過連結器連結到Mach-O檔案中的。

靜態庫

靜態庫是編譯時連結的庫,需要連線進入Mach-O檔案中,如果需要更新就必須重新編譯一次,無法做到動態載入和更新

動態庫

動態庫是執行時連結的庫。

Mach-O是檔案編譯之後的產物,所以動態庫並沒有參與Mach-O檔案的編譯和連結。所以Mach-O檔案中沒有包含動態庫的符號定義,也就是說這些符號會直接顯示未定義,但是他們的名字和對應庫的路徑會被記錄下來。在執行時通過dlopen和dlsym匯入動態庫時,會根據記錄的路徑找到對應的庫,再通過記錄的名字元號找到繫結的地址。

動態庫共享快取(dyld shared cache)

從iOS3.1開始,為了提高效能,絕大部分的系統動態庫檔案都打包存放到了一個快取檔案中(dyld shared cache),快取路徑是/System/Library/Caches/com.apple.dyld/dyld_shared_cache_armX

dyld_shared_cache_armX裡面的X代表ARM處理器指令集的架構

ARM指令集

ARM指令集(CPU指令的集合)有以下幾種

ARM指令集 支援的裝置
armv6 iPhone、iPhone3G
iPod Touch、iPod Touch2
armv7 iPhone3GS、iPhone4、iPhone4S
iPad、iPad2、iPad3(The New iPad)、iPad mini
iPod、Touch3G、iPod Touch4、iPod Touch5
armv7s iPhone5、iPhone5C、iPad4
arm64 iPhone5S、iPhone6、iPhone6 Plus、iPhone6S、iPhone6S Plus
iPhoneSE、iPhone7、iPhone7Plus、iPhone8、iPhone8 Plus、iPhoneX
iPad5、iPad Air、iPad Air2、iPad Pro、iPad Pro2
iPad mini with Retina display、iPad mini3、iPad mini4
iPod Touch6

以上所有的指令集都是向下相容的 為什麼要使用動態庫共享快取呢?最大的好處就是節省記憶體。

從動態庫共享快取抽取動態庫

由於動態庫共享快取太大,如果想獲取其中某個動態庫,例如UIKit,就需要從動態庫共享快取中抽取對應的動態庫

  • 使用dyld原始碼中提供的方式來進行抽取,工具在原始碼中的launch-cache/dsc_extractor.cpp檔案中

    • 首先需要去掉原始碼中的#if 0判斷
    • 然後使用如下命令編譯dsc_extractor.cpp檔案
    clang++ -o dsc_extractor dsc_extractor.cpp
    複製程式碼

    此處是將dsc_extractor.cpp編譯生成可執行檔案dsc_extractor

    • 進入執行檔案dsc_extractor所在目錄。通過以下的命令來抽取動態庫
    ./dsc_extractor 動態庫共享快取檔案的路徑 用於存放抽取結果的目錄
    複製程式碼

    建議抽取armv7s架構的動態庫,arm64抽取時會報以上錯誤,原因是dsc_extractor.bundle不能在Xcode10之後使用

    • 抽取完成之後,使用Hopper Disassmbler開啟想要逆向的動態庫,就可以看到動態庫中的原始碼資訊。