iOS逆向(5)-不知MachO怎敢說本身懂DYLD

在上篇文章代碼注入,竊取微信密碼中我們已經簡單的提到了MachO,在用Framework作代碼注入的時候,必須先向MachO的Load Commons中插入該Framework的的相對路徑,讓咱們的iPhone在執行MachO的時候可以識別並加載Framework!linux

窺一斑而知全豹,從這些許內容其實已經能夠了解到MachO在咱們APP中的地位是多麼的重要。一樣,在我們逆向的實踐中,MachO也是一道繞不過去門檻!git

老規矩,片頭先上福利:點擊下載demo
這篇文章會用到的工具備:github

廢話很少說,本篇文章將會從如下幾點細說到底什麼是MachO!bootstrap

  • 什麼是MachO
  • MachO的文件結構
  • 從DYLD源碼的角度看APP啓動流程 (重點!!!)

1、什麼是MachO

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

一、常見的MachO文件

a、目標文件:.o
b、庫文件:.a .dylib Framework
c、可執行文件:dyld .dsymwindows

二、如何查看文件格式

咱們能夠經過file指令查看文件的具體格式 緩存

file指令.png

目前已知的架構分爲armv7,armv7s,arm64,i386,x86_64等等,MachO中其實也是這些架構的集合。能夠隨意創建一個空工程:Dome1(空工程就不給Demo了)bash

查看Build出的Dome1.ipa中的MachO 微信

x86架構MachO.png

將最低版本設置爲iOS 12,用release打包出的Dome1.ipa中的MachO 架構

arm64架構MachO.png

將最低版本設置爲iOS 8,用release打包出的Dome1.ipa中的MachO

多架構MachO.png

從上面三張圖就能夠肯定MachO能夠是多架構的二進制文件,稱之爲「通用二進制文件」

通用二進制文件是蘋果公司提出的一種程序代碼。能同時適用多種架構的二進制文件 a. 同一個程序包中同時爲多種架構提供最理想的性能。 b. 由於須要儲存多種代碼,通用二進制應用程序一般比單一平臺二進制的程序要大。 c. 可是因爲兩種架構有共通的非執行資源,因此並不會達到單一版本的兩倍之多。 d. 並且因爲執行中只調用一部分代碼,運行起來也不須要額外的內存。

注:其實除了更改最低版本號能夠改變MachO的架構,在XCode的中也能夠主動設置

主動修改架構.png

三、拆分、重組MachO

// 使用lipo -info 能夠查看MachO文件包含的架構
$ lipo -info MachO文件
// 使用lipo –thin 拆分某種架構
$ lipo MachO文件 –thin 架構 –output 輸出文件路徑
// 使用lipo -create  合併多種架構
$ lipo -create MachO1  MachO2  -output 輸出文件路徑
複製代碼

拆分,重組MachO.png

2、MachO的文件結構

先上一張官網圖:

MachO的文件結構.png
MachO分爲三部分結構:Header、Load Commons、Data

一、Header

Header 包含該二進制文件的通常信息 字節順序、架構類型、加載指令的數量等。 使得能夠快速確認一些信息,好比當前文件用於32位仍是64位,對應的處理器是什麼、文件類型是什麼

本文從兩個視角分析Header,分別是「用MachOView可視化後直觀的查看」和「系統源碼解析」

  • 用MachOView可視化後直觀的查看 上篇文章已經講過使用MacOView能夠直接查看一個MachO文件,以下圖
    MachO-Header.png
  • 系統源碼解析 在MachO的源碼文件中一樣有對應的字段。以下圖:
    MachO-Header源碼.png

二、Load Commons

Load commands是一張包含不少內容的表。 內容包括區域的位置、符號表、動態符號表等。

MachO-LoadCommons.png
上圖Load Commons中的大部分字段在下表中能夠找到相關的含義。

名稱 含義
LC_SEGMENT_64 將文件中(32位或64位)的段映射到進程地址空間中
LC_DYLD_INFO_ONLY 動態連接相關信息
LC_SYMTAB 符號地址
LC_DYSYMTAB 動態符號表地址
LC_LOAD_DYLINKER 使用誰加載,咱們使用dyld
LC_UUID 文件的UUID
LC_VERSION_MIN_MACOSX 支持最低的操做系統版本
LC_SOURCE_VERSION 源代碼版本
LC_MAIN 設置程序主線程的入口地址和棧大小
LC_LOAD_DYLIB 依賴庫的路徑,包含三方庫
LC_FUNCTION_STARTS 函數起始地址表
LC_CODE_SIGNATURE 代碼簽名

其中LC_LOAD_DYLINKERLC_LOAD_DYLIB

  • LC_LOAD_DYLINKER 該字段標明咱們的MachO是被誰加載進去的。
    能夠理解爲LC_LOAD_DYLINKER指向的地址是微信APP加載小程序的引擎,而咱們的MachO是小程序。在上圖中能夠看到咱們的Demo1的LC_LOAD_DYLINKER指向的地址就是dylddyld確實是用來加載咱們app的,在下面一節將會對dyld的源碼進行分析,講述dyld是如何對MachO進行加載的。

  • LC_LOAD_DYLIB 該字段標記了全部動態庫的地址,只有在LC_LOAD_DYLIB中有標記,咱們MachO外部的動態庫(如:Framework)才能被dyld正確的引用,不然dyld不會主動加載,這也是上篇文章,代碼注入的關鍵所在!

三、Data

Data 一般是對象文件中最大的部分,包含Segement的具體數據,如靜態C字符串,帶參數/不帶參數的OC方法,帶參數/不帶參數的C函數。

在Demo1中編寫一下代碼

  • 靜態C字符串
  • 靜態OC字符串
  • 帶參數的OC方法
  • 不帶參數的OC方法
  • 帶參數的C函數
  • 不帶參數的C函數
    如圖:
    代碼.png

查看MachO中對應的Data段:cstring,methname,以下兩圖:

MachO-字符串.png
MachO-方法名.png

能夠看到,全局靜態C字符(myCString),方法裏面的字符串(myCFuncAString:%d,myCFuncString,%s,myOCFuncAString:%s,myOCFuncString:%s)都被保存在data段的cstring裏了,哪怕是%d,%s等等這樣的參數類型字符串也被保存在內。但全部一樣的字符串只會被保存一次。
一樣全部的OC方法都被保存在methname裏了。

這裏有個問題: 在這兩個表中並無看到全局的靜態OC字符串(myOCString)和C函數(myCFuncA(int a),myCFunc())這裏爲何沒有?他們應該會被以是形式保存在哪裏?

上面用cstringmethname距離了data段的做用,一樣的全部類名,協議名等也是以一樣形式存儲在這。

上面已經對MachO有了一個大概的瞭解,接下來本文就對dyld這麼一個重要的東西進行一個初探。

3、從DYLD源碼的角度看APP啓動流程

一、在main函數中斷點查看

首先思考,在main函數中掛斷點能不能查看到APP啓動對應的堆棧?
這部分其實靠想,靠猜想很難有答案,咱們直接用XCode直接嘗試:

main斷點.png

能夠看到在main函數斷點並不能看到啓動的對應堆棧,說明main函數也是被別人調用的,而不是處於app啓動的堆棧中。
既然main查不到啓動堆棧,那麼比app更早執行的load方式是否能夠找獲得呢?

二、在load方法中斷點查看

一樣的,直接XCode調試:

load斷點.png

在這能夠發現更多的信息,好比在堆棧底部的彙編(這裏用的是手機調試,因此是arm64架構)能夠很明顯的發現,是調用了用dyld中的dyldbootstrap文件中的start方法。
快馬加鞭,打開dyld源碼,找到對應的dyldbootstrap文件中的start函數。
點擊這裏下載dyld源碼

三、在dyldbootstrap中查看start函數

//
//  This is code to bootstrap dyld.  This work in normally done for a program by dyld and crt.
//  In dyld we have to do this manually.
//
uintptr_t start(const struct macho_header* appsMachHeader, int argc, const char* argv[], 
				intptr_t slide, const struct macho_header* dyldsMachHeader,
				uintptr_t* startGlue)
{
	// if kernel had to slide dyld, we need to fix up load sensitive locations
	// we have to do this before using any global variables
    // 滑塊,ASLR技術,地址偏移,是MachO文件在內存中的地址重定向
    slide = slideOfMainExecutable(dyldsMachHeader);
    bool shouldRebase = slide != 0;
#if __has_feature(ptrauth_calls)
    shouldRebase = true;
#endif
    if ( shouldRebase ) {
        // 重定向
        rebaseDyld(dyldsMachHeader, slide);
    }

	// allow dyld to use mach messaging
    // 消息初始化
	mach_init();

	// kernel sets up env pointer to be just past end of agv array
	const char** envp = &argv[argc+1];
	
	// kernel sets up apple pointer to be just past end of envp array
	const char** apple = envp;
	while(*apple != NULL) { ++apple; }
	++apple;

	// set up random value for stack canary
    // 棧溢出保護
	__guard_setup(apple);

#if DYLD_INITIALIZER_SUPPORT
	// run all C++ initializers inside dyld
	runDyldInitializers(dyldsMachHeader, slide, argc, argv, envp, apple);
#endif

	// now that we are done bootstrapping dyld, call dyld's main // 正在的啓動函數,在dyld中的_main函數中 uintptr_t appsSlide = slideOfMainExecutable(appsMachHeader); return dyld::_main(appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue); } 複製代碼

從start函數的源碼可得知道:dlyd會內存中找到一塊地址給MachO使用,也就是ASLR,內存偏移。
最後start函數執行了一個main函數(這個能夠不是咱們app中的main函數,而是dyld的)並返回。一樣的,咱們不能只蹭一蹭,要進去幹!

四、在dlyd中查看main函數

這個函數厲害了,以下圖,足足快500行了!

dyld的main函數.png

咱們抓住其中的關鍵代碼,足步分析在main函數以前dyld到底幫咱們作了哪一些事情。

一、配置環境變量

從main函數的初始,到函數getHostInfo()以前都是在配置一些環境變量,已經一些線程相關的,涉及內容太過底層,這就不一一分析了(實際上是能力不及😆)

配置環境變量.png
在這一步中有不少 if判斷,其實裏面都是對應的環境變量,這些都是能夠在XCode進行相關的配置,進行對應的操做(如Log相關信息)。

二、加載共享緩存庫

在iOS系統中,每一個程序依賴的動態庫都須要經過dyld(位於/usr/lib/dyld)一個一個加載到內存,然而若是在每一個程序運行的時候都重複的去加載一次,勢必形成運行緩慢,爲了優化啓動速度和提升程序性能,共享緩存機制就應運而生。全部默認的動態連接庫被合併成一個大的緩存文件,放到/System/Library/Caches/com.apple.dyld/目錄下,按不一樣的架構保存分別保存着。其中包括UIKit,Foundation等基礎庫。

加載共享緩存庫_1.png
加載共享緩存庫_2.png
在源碼中能夠看到在咱們iOS系統中,共享緩存庫被明確必定會被加載。
由於這種機制的存在,使得iOS在的對這些基礎庫的加載的時候時間和內存都獲得節約!
可是有時由於共享緩存庫的機制的存在使得iOS在共享緩存庫裏面的C函數,也就是系統C函數變的不是那麼靜態,有了些許OC運行時的特性!
這部份內容將會在下一篇文章着重講解!從不同的角度看Runtime!

三、實例化主程序

加載主程序其實就是對MachO文件中LoadCommons段的一些列加載! 咱們繼續對代碼的跟進,以下6張圖:

加載主程序_1.png

加載主程序_2.png

補充:實例化完以後調用addImage(image),將實例化出來的鏡像加入全部的鏡像列表sAllImages,主程序永遠是sAllImages的第一個對象!

加載主程序_3.png

加載主程序_LoadCommons_1.png

加載主程序_LoadCommons_2.png

加載主程序_LoadCommons_3.png
從源代碼能夠看出,加載主程序這一步其實很簡單,就是將MachO文件中的部分信息一步一步的放入內存。
其中從最後一張圖能夠了解到:

  • 最大的segment數量爲256個!
  • 最大的動態庫(包括系統的個自定義的)個數爲4096個!
四、加載動態連接庫

加載動態連接庫,如XCode的ViewDebug、MainThreadChecker,咱們以後代碼注入的庫也是經過這種形式添加的!

插入動態連接庫.png

五、連接主程序

連接主程序.png

link函數裏面其實就是對以前的imges(不是圖片,這是鏡像)進行一些內核操做,這部分Apple沒有開源出來,只能看到些許源碼,有興許的同窗能夠自行查閱:

Link.png

六、加載Load和特定的C++的構造函數方法

不管是從以前斷點load方法仍是咱們如今一步步對源碼的根據,都能瞭解到,dyldinitializeMainExecutable就是就加載load的入口:

initializeMainExecutable_1.png

initializeMainExecutable_2.png

而且最後都能接到一個結論:
dyldnotifySingle函數通過一系列的跳轉,最終會跳轉到objc源碼中的call_load_methods函數!!

那麼這中間的的過程究竟是怎麼樣的呢?看下方的gif:

函數查找過程.gif

最後找到函數_dyld_objc_notify_register,就在全局都找不到一個調用的地方了,其實這個函數自己就不是給dyld調用的,而是提供給外部調用的。怎麼找到是誰調用了_dyld_objc_notify_register呢?
繼續打開以前的Demo1,在工程中加上_dyld_objc_notify_register的符號斷點看看。

符號斷點.png

運行工程,斷住以後再次查看函數調用棧:

符號斷點後的調用堆棧.png
這就能夠很清晰的看到,原來是 objc_init調用了我們的 _dyld_objc_notify_register函數。

一樣打開objc的源碼(點擊下載objc源碼 ) 快速定位_dyld_objc_notify_register的調用位置。如圖:

_objc_init.png

load_images.png

這樣dyld是如何加載我們的load方法就被找到了。 期間若是有細心的同窗可能看到了在notifySingle後面緊跟着doInitialization這樣一個函數,這是一個系統特定的C++構造函數的調用方法。

doInitialization_1.png

doModInitFunctions.png

ImageLoaderMachO_2.png

這種C++構造函數有特定的寫法,以下:

__attribute__((constructor)) void CPFunc(){
    printf("C++Func1");
}
複製代碼

有興趣的同窗能夠嘗試實現一次,在MachO文件中找到對應的方法! 固然,這在Demo1也是有的。

七、尋找APP的main函數並調用

當上面的load和C++方法加載完成以後就會回到dyld的main方法裏面,尋找APP的main函數並調用。

尋找APP的main函數並調用.png

最終dyld的main函數中的主要流程就已經走完了,固然這7個步驟是一條主線,期間還會有不少其餘的步驟,過程很是繁瑣,這就不一一舉例了。你們能夠經過閱讀dyld的源碼一覽無餘。

4、總結

本文講述了MachO的概述,文件結構,在從其中Load Commons中的LC_LOAD_DYLINKER引出dyld,接下根據dyld源碼分析了APP的啓動流程。分別是:
一、配置環境變量
二、加載共享緩存庫
三、實例化主程序
四、加載動態連接庫
五、連接主程序
六、加載Load和特定的C++的構造函數方法
七、尋找APP的main函數並調用
另外dyld中LC_LOAD_DYLIB的(加載動態連接庫)存在,爲咱們逆向注入代碼提供了無限可能。
MachO中其實還有一些符號表,爲系統提供查詢對應的方法名稱提供了路徑,這些在下一張文章中將會更加詳細的講到。

5、參考

一、Dynamic Linking of Imported Functions in Mach-O 二、《iOS應用逆向工程》沙梓社,吳航 著 ,機械工業出版社

相關文章
相關標籤/搜索