六 Hock原理分析

nx_001.jpeg

上篇文章MachO文件解析已經(jīng)詳細(xì)介紹了MachO,并且由MachO引出了dyld,再由dyld講述了App的啟動(dòng)流程,而在App的啟動(dòng)流程中又說(shuō)到了一些關(guān)鍵的名稱如:LC_LOAD_DYLINKER、LC_LOAD_DYLIB以及objc的回調(diào)函數(shù)_dyld_objc_notify_register等等。

這篇文章我們來(lái)了解一下,符號(hào)表、fishhook相關(guān)的內(nèi)容。

用到的工具:fishhook

接下來(lái)本文會(huì)從以下幾點(diǎn)進(jìn)行闡述:

  • HOOK概述
  • fishHook的簡(jiǎn)單使用
  • fishHook原理探究
  • fishHook源碼分析

1.HOOK概述

HOOK,中文譯為“掛鉤”或“鉤子”。在iOS逆向中是指改變程序運(yùn)行流程的一種技術(shù)。通過(guò)hook可以讓別人的程序執(zhí)行自己所寫(xiě)的代碼。在逆向中經(jīng)常使用這種技術(shù)。所以在學(xué)習(xí)過(guò)程中,我們重點(diǎn)要了解其原理,這樣能夠?qū)阂獯a進(jìn)行有效的防護(hù)。

1.1 Hook 流程圖

六 Hock流程圖.png

如上圖,這就是我們HOOK代碼大概流程。

1.2 HOOK的方式

1.****Method Swizzld
利用OC的Runtime特性,動(dòng)態(tài)改變SEL(方法編號(hào))和IMP(方法實(shí)現(xiàn))的對(duì)應(yīng)關(guān)系,達(dá)到OC方法調(diào)用流程改變的目的。主要用于OC方法。

2.fishhook
它是Facebook提供的一個(gè)動(dòng)態(tài)修改鏈接mach-O文件的工具。利用MachO文件加載原理,通過(guò)修改懶加載和非懶加載兩個(gè)表的指針達(dá)到C函數(shù)HOOK的目的。

3.Cydia Substrate
Cydia Substrate 原名為 Mobile Substrate ,它的主要作用是針對(duì)OC方法、C函數(shù)以及函數(shù)地址進(jìn)行HOOK操作。當(dāng)然它并不是僅僅針對(duì)iOS而設(shè)計(jì)的,安卓一樣可以用。
官方地址:http://www.cydiasubstrate.com/

1.3 Cydia Substrate簡(jiǎn)介

Cydia Substrate的原名為MobileHooker,顧名思義用于HOOK。它定義一系列的宏和函數(shù),底層調(diào)用objcruntimefishhook來(lái)替換系統(tǒng)或者目標(biāo)應(yīng)用的函數(shù).

其中有兩個(gè)函數(shù):
MSHookMessageEx:主要作用于Objective-C方法。

void MSHookMessageEx(Class class, SEL selector, IMP replacement, IMP result) 

MSHookFunction:主要作用于C和C++函數(shù)。

void MSHookFunction(voidfunction,void* replacement,void** p_original)  

Logos語(yǔ)法的%hook 就是對(duì)此函數(shù)做了一層封裝。

1.4 Cydia Substrate特點(diǎn)

1.MobileLoader:MobileLoader用于加載第三方dylib在運(yùn)行的應(yīng)用程序中。啟動(dòng)時(shí)MobileLoader會(huì)根據(jù)規(guī)則把指定目錄的第三方的動(dòng)態(tài)庫(kù)加載進(jìn)去,第三方的動(dòng)態(tài)庫(kù)也就是我們寫(xiě)的破解程序.

2.safe mode: 破解程序本質(zhì)是dylib,寄生在別人進(jìn)程里。 系統(tǒng)進(jìn)程一旦出錯(cuò),可能導(dǎo)致整個(gè)進(jìn)程崩潰,崩潰后就會(huì)造成iOS癱瘓。所以CydiaSubstrate引入了安全模式,在安全模 式下所有基于CydiaSubstratede 的三方dylib都會(huì)被禁用,便于查錯(cuò)與修復(fù)。

2.fishHook的簡(jiǎn)單使用

fishHook代碼地址:fishhook

struct rebinding {
  const char *name;//需要HOOK的函數(shù)名稱,C字符串
  void *replacement;//新函數(shù)的地址
  void **replaced;//原始函數(shù)地址的指針!
};

關(guān)鍵函數(shù):

FISHHOOK_VISIBILITY
int rebind_symbols(struct rebinding rebindings[], 
                   size_t rebindings_nel);
參數(shù)一:存放rebinding結(jié)構(gòu)體的數(shù)組(可以同時(shí)交換多個(gè)函數(shù))
參數(shù)二:rebindings數(shù)組的長(zhǎng)度     

FISHHOOK_VISIBILITY
int rebind_symbols_image(void *header,
                         intptr_t slide,
                         struct rebinding rebindings[],
                         size_t rebindings_nel);

hook系統(tǒng)的NSLog函數(shù)fishhook簡(jiǎn)單使用代碼
運(yùn)行代碼,點(diǎn)擊屏幕,會(huì)發(fā)現(xiàn)我們的代碼執(zhí)行了,

hook C 函數(shù)fishhook 系統(tǒng)C函數(shù)
運(yùn)行代碼,我們會(huì)發(fā)現(xiàn),我們的hook方法失效了,這是為什么么呢?下面我們來(lái)一起探究下吧。。。。

3.fishHook原理探究

在上篇文章已經(jīng)提到了在dyld啟動(dòng)app的第二個(gè)步驟就是加載共享緩存庫(kù),共享緩存庫(kù)包括Foundation框架,NSLog是被包含在Foundation框架的。有了這個(gè)貢獻(xiàn)緩存庫(kù),就可以解決多個(gè)app共同調(diào)用一個(gè)庫(kù)等問(wèn)題了。

六 Hock流程圖.png

3.1 fishHook 加載流程

首先,我們想一下OC為什么可以hook呢?C為什么不能hook?

OC為什么可以hook? 因?yàn)镺C底層使用的Runtime技術(shù),通過(guò)運(yùn)行時(shí)去找到方法的實(shí)現(xiàn),所以O(shè)C可以hook。

C為什么不能hook?
C語(yǔ)言函數(shù)通常是靜態(tài)的,編譯之后,從匯編代碼變成了內(nèi)存地址。它都是通過(guò)內(nèi)存地址去找的,在編譯的時(shí)候就綁定了的,所以hook不了。

那么問(wèn)題來(lái)了,為什么問(wèn)題來(lái)了,為什么fishhook可以hook到C方法呢?
原因是:iOS系統(tǒng)實(shí)現(xiàn)了一個(gè)動(dòng)態(tài)緩存庫(kù)技術(shù),一些公共的系統(tǒng)庫(kù)放進(jìn)內(nèi)存中的某個(gè)地方,當(dāng)某個(gè)iOS項(xiàng)目啟動(dòng)后,machO文件會(huì)在Data段創(chuàng)建一個(gè)指針,dyld動(dòng)態(tài)將machO中Data段中這個(gè)指針指向外部函數(shù),這里的指針指向內(nèi)部函數(shù)的調(diào)用,指向外部函數(shù)的地址,而這個(gè)指針也就是我們通常說(shuō)的符號(hào);這也是為什么fishhook中函數(shù)名為rebind_symbols(重新綁定符號(hào)),實(shí)際上是修改這個(gè)指針指向外部函數(shù)的地址,這也就是為什么修改不了內(nèi)部函數(shù)和自定義函數(shù),只能修改machO外部函數(shù)(在符號(hào)表中能找到的函數(shù))。由于蘋(píng)果實(shí)現(xiàn)了ASLR技術(shù)(不了解ASLR,看這篇逆向?qū)W習(xí)筆記8——ASLR),所以這些動(dòng)態(tài)緩存庫(kù)函數(shù)在APP項(xiàng)目的內(nèi)存地址不確定,每次啟動(dòng)APP的時(shí)候都會(huì)有相應(yīng)的變化,這是C語(yǔ)言的動(dòng)態(tài)表現(xiàn)。

通過(guò)MacOView分析,我們知道了iOS應(yīng)用啟動(dòng)時(shí)的啟動(dòng)流程:

  1. 啟動(dòng)APP會(huì)執(zhí)行dyld,加載程序
  2. 進(jìn)入dyld:main函數(shù)
  3. 配置一些環(huán)境
  4. 加載共享緩存庫(kù)
  5. 實(shí)例化主程序
  6. 加載動(dòng)態(tài)庫(kù)
  7. 鏈接主程序

通過(guò)以上分析,我們可以利用dyld加載的時(shí)候?qū)函數(shù)進(jìn)行修改。

簡(jiǎn)單總結(jié)下就是:

  • 在MachO文件中有個(gè)PIC(貢獻(xiàn)緩存庫(kù)),它在Data段創(chuàng)建了一個(gè)指針,這個(gè)指針指向外部函數(shù)。
  • 當(dāng)我們調(diào)用C方法、函數(shù)的時(shí)候,這個(gè)指針就會(huì)起加載(綁定)這個(gè)外部的C函數(shù)。
  • 而我們的fishhook正是利用了這一步驟進(jìn)行hook的。

注意:
fishhook 修改的是內(nèi)存的地址

下面,我們使用MachOView來(lái)看一下吧
這里我們代碼寫(xiě)的少,所以就直接用 2fishhook hook系統(tǒng)C函數(shù)這個(gè)案例來(lái)講解了。

首先,我們來(lái)看看NSLog的地址在什么時(shí)候被加載的,也就是NSLog到底在哪里。

1.使用MachOView查看,并使用驗(yàn)證

// 在viewDidLoad中添加以下兩句代碼,斷點(diǎn)斷到第一句代碼
- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"測(cè)試1");
    NSLog(@"測(cè)試1");
}

1.在第一個(gè)NSLog使用斷點(diǎn)斷住,在Project中,得到當(dāng)前APP的 MachO文件。使用MachOView進(jìn)行分析。我們可以找到如下內(nèi)容。

六 加載流程1.png

懶加載表 非懶加載表

2.獲得當(dāng)前NSLog的偏移量。

3.在LLDB中使用 image list列出當(dāng)前加載的鏡像。第一個(gè)地址為當(dāng)前MachO文件在內(nèi)存中的首地址

4.通過(guò)首地址加上當(dāng)前的偏移量,我們可以獲得如下NSLog在內(nèi)存中的位置。


六 NSLog共享緩存1.png

5.由于iOS是小端模式,我們的地址從右往左讀,如上圖第二個(gè)lldb的內(nèi)容,最后我們會(huì)看到libdyld.dylib`dyld_stub_binder:。上圖中有體現(xiàn)。

6.我們的斷點(diǎn)在往下走一步,會(huì)發(fā)現(xiàn)我們獲取MachO文件的首地址內(nèi)存發(fā)生了變化。上圖中有體現(xiàn)。

7.這時(shí)我們查看一下當(dāng)前的地址,看看有什么?
六 NSLog共享緩存2.png

,如圖,我們的NSLog被打印出來(lái)了。

到這里,說(shuō)明了我們的NSLog在我們的系統(tǒng)共享緩存區(qū)里,第一次沒(méi)使用的時(shí)候需要綁定,第二次直接去找地址,不用綁定。

2.使用匯編的方式查看
1.同上面的第1,2,3步,通過(guò)Debug -> Debug WorkFlow -> Always Show Disassembly 調(diào)試,如下圖:

六 匯編分析NSLog.png

我們同樣可以得到 libdyld.dylib`dyld_stub_binder:函數(shù)

4.fishHook源碼分析

4.1、fishhook的總體思路

Facebook的開(kāi)源庫(kù)fishhook就可以完美的實(shí)現(xiàn)這個(gè)任務(wù)。
先上一張官網(wǎng)原理圖:

fishhook流程.png

總體來(lái)說(shuō),步驟是這樣的:

  1. 先找到四張表Lazy Symbol Pointer Table、Indirect Symbol Table、Symbol Table、String Table。
  2. MachO有個(gè)規(guī)律:Lazy Symbol Pointer Table中第index行代表的函數(shù)和Indirect Symbol Table中第index行代表的函數(shù)是一樣的。
  3. Indirect Symbol Table中value值表示Symbol Table的index。
  4. 找到Symbol Table的中對(duì)應(yīng)index的對(duì)象,其data代表String Table的偏移值。
  5. 用String Table的基值,也就是第一行的pFile值,加上Symbol Table的中取到的偏移值,就能得到Indirect Symbol Table中value(這個(gè)value代表函數(shù)的偏移值)代表的函數(shù)名了。

4.2、源碼分析

fishhook的源碼總共只有250行左右,所以結(jié)合MachO慢慢看,其實(shí)一點(diǎn)也不費(fèi)勁,接下來(lái),我們簡(jiǎn)單的分析一下吧。

1.fishhook為維護(hù)一個(gè)鏈表,用來(lái)儲(chǔ)存需要hook的所有函數(shù)。

// 給需要rebinding的方法結(jié)構(gòu)體開(kāi)辟出對(duì)應(yīng)的空間
// 生成對(duì)應(yīng)的鏈表結(jié)構(gòu)(rebindings_entry),并將新的entry插入頭部
static int prepend_rebindings(struct rebindings_entry **rebindings_head,
                              struct rebinding rebindings[],
                              size_t nel)

2.根據(jù)linkedit的基值,找到對(duì)應(yīng)的三張表:symbol_table、string_table和indirect_symtab。

// 找到linkedit的頭地址
// linkedit_base其實(shí)就是MachO的頭地址?。。】梢酝ㄟ^(guò)查看linkedit_base值和image list命令查看驗(yàn)證?。。。ㄎ哪└接序?yàn)證圖)
/**********************************************************
 Linkedit虛擬地址 = PAGEZERO(64位下1G) + FileOffset
 MachO地址 = PAGEZERO + ASLR
 上面兩個(gè)公式是已知的 得到下面這個(gè)公式
 MachO文件地址 = Linkedit虛擬地址 - FileOffset + ASLR(slide)
**********************************************************/
uintptr_t linkedit_base = (uintptr_t)slide + linkedit_segment->vmaddr - linkedit_segment->fileoff;
// 獲取symbol_table的真實(shí)地址
nlist_t *symtab = (nlist_t *)(linkedit_base + symtab_cmd->symoff);
// 獲取string_table的真實(shí)地址
char *strtab = (char *)(linkedit_base + symtab_cmd->stroff);
// Get indirect symbol table (array of uint32_t indices into symbol table)
// 獲取indirect_symtab的真實(shí)地址
uint32_t *indirect_symtab = (uint32_t *)(linkedit_base + dysymtab_cmd->indirectsymoff);

3.最核心的一個(gè)步驟,查找并且替換目標(biāo)函數(shù)。

// 在四張表(section,symtab,strtab,indirect_symtab)中循環(huán)查找
// 直到找到對(duì)應(yīng)的rebindings->name,將原先的函數(shù)復(fù)制給新的地址,將新的函數(shù)地址賦值給原先的函數(shù)
static void perform_rebinding_with_section(struct rebindings_entry *rebindings,
                                           section_t *section,
                                           intptr_t slide,
                                           nlist_t *symtab,
                                           char *strtab,
                                           uint32_t *indirect_symtab)

在了解了fishhook的簡(jiǎn)單使用之后,我們就可以做一些基本的防護(hù)的內(nèi)容了,參見(jiàn)代碼

關(guān)于幾個(gè)名詞的解釋。

幾個(gè)名詞(pFile 、offset 、File Offset)的解釋

  1. 首先,這三個(gè)都是表示相對(duì)于MachO的內(nèi)存偏移,只不過(guò)其含義被細(xì)分了。
  2. pFile 和 offset含義相近,不過(guò)offset更詳細(xì),能夠?qū)?yīng)上具體某一個(gè)符號(hào)(DATA? TEXT?)。比如文件里面有許多類,類里面有許多的屬性,pFile就代表各個(gè)類的偏移值,offset代表各個(gè)屬性的偏移值。
  3. File Offset 這個(gè)存在于Segment的字段中。用于從Segment快速找到其代表的「表」真正的偏移值。

參考文章:
作者:一縷清風(fēng)揚(yáng)萬(wàn)里
原文地址:http://m.itdecent.cn/p/95896fb96a03

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容