精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

Swift Hook 新思路 -- 虛函數(shù)表

開發(fā) 前端
本文的技術(shù)方案僅針對(duì)通過虛函數(shù)表調(diào)用的函數(shù)進(jìn)行 Hook,不涉及直接地址調(diào)用和objc_msgSend 的調(diào)用的情況。

[[390109]]

摘要:業(yè)界對(duì)Swift 的 Hook 大多都需要依靠 OC 的消息轉(zhuǎn)發(fā)特性來實(shí)現(xiàn),本文從修改 Swift 的虛函數(shù)表的角度,介紹了一種新的 Hook 思路。并以此為主線,重點(diǎn)介紹 Swift 的詳細(xì)結(jié)構(gòu)以及應(yīng)用。

1. 前言

由于歷史包袱的原因,目前主流的大型APP基本都是以 Objective-C 為主要開發(fā)語言。

但是敏銳的同學(xué)應(yīng)該能發(fā)現(xiàn),從 Swift 的 ABI 穩(wěn)定以后,各個(gè)大廠開始陸續(xù)加大對(duì) Swift 的投入。

雖然在短期內(nèi) Swift 還難以取代 Objective-C,但是其與 Objective-C 并駕齊驅(qū)的趨勢(shì)是越來越明顯,從招聘的角度就即可管中窺豹。

在過去一年的招聘過程中我們總結(jié)發(fā)現(xiàn),有相當(dāng)數(shù)量的候選人只掌握 Swift 開發(fā),對(duì)Objective-C 開發(fā)并不熟悉,而且這部分候選人大多數(shù)比較年輕。

另外,以 RealityKit 等新框架為例,其只支持 Swift 不支持 Objective-C。上述種種現(xiàn)象意味著隨著時(shí)間的推移,如果項(xiàng)目不能很好的支持 Swift 開發(fā),那么招聘成本以及應(yīng)用創(chuàng)新等一系列問題將會(huì)凸顯出來。

因此,58 同城在 2020 年 Q4 的時(shí)候在集團(tuán)內(nèi)發(fā)起了跨部門協(xié)同項(xiàng)目,從各個(gè)層面打造 Objective-C 與 Swift 的混編生態(tài)環(huán)境——項(xiàng)目代號(hào) ”混天“。

一旦混編生態(tài)構(gòu)建完善,那么很多問題將迎刃而解。

2. 原理簡述

本文的技術(shù)方案僅針對(duì)通過虛函數(shù)表調(diào)用的函數(shù)進(jìn)行 Hook,不涉及直接地址調(diào)用和objc_msgSend 的調(diào)用的情況。

另外需要注意的是,Swift Compiler 設(shè)置為 Optimize for speed(Release默認(rèn))則TypeContext 的 VTable 的函數(shù)地址會(huì)清空。

設(shè)置為 Optimize for size 則 Swfit 可能會(huì)轉(zhuǎn)變?yōu)橹苯拥刂氛{(diào)用。

以上兩種配置都會(huì)造成方案失效。因此本文重點(diǎn)在介紹技術(shù)細(xì)節(jié)而非方案推廣。

如果 Swift 通過虛函數(shù)表跳表的方式來實(shí)現(xiàn)方法調(diào)用,那么可以借助修改虛函數(shù)表來實(shí)現(xiàn)方法替換。即將特定虛函數(shù)表的函數(shù)地址修改為要替換的函數(shù)地址。但是由于虛函數(shù)表不包含地址與符號(hào)的映射,我們不能像 Objective-C 那樣根據(jù)函數(shù)的名字獲取到對(duì)應(yīng)的函數(shù)地址,因此修改 Swift 的虛函數(shù)是依靠函數(shù)索引來實(shí)現(xiàn)的。

簡單理解就是將虛函數(shù)表理解為數(shù)組,假設(shè)有一個(gè) FuncTable[],我們修改函數(shù)地址只能通過索引值來實(shí)現(xiàn),就像 FuncTable[index] = replaceIMP 。但是這也涉及到一個(gè)問題,在版本迭代過程中我們不能保證代碼是一層不變的,因此這個(gè)版本的第 index 個(gè)函數(shù)可能是函數(shù) A,下個(gè)版本可能第 index 個(gè)函數(shù)就變成了函數(shù) B。顯然這對(duì)函數(shù)的替換會(huì)產(chǎn)生重大影響。

為此,我們通過 Swift 的 OverrideTable 來解決索引變更的問題。在 Swift 的OverrideTable 中,每個(gè)節(jié)點(diǎn)都記錄了當(dāng)前這個(gè)函數(shù)重寫了哪個(gè)類的哪個(gè)函數(shù),以及重寫后函數(shù)的函數(shù)指針。

因此只要我們能獲取到 OverrideTable 也就意味著能獲取被重寫的函數(shù)指針 IMP0 以及重寫后的函數(shù)指針 IMP1。只要在 FuncTable[] 中找到 IMP0 并替換成 IMP1 即可完成方法替換。

接下來將詳細(xì)介紹Swift的函數(shù)調(diào)用、TypeContext、Metadata、VTable、OverrideTable 等細(xì)節(jié),以及他們彼此之間有何種關(guān)聯(lián)。為了方便閱讀和理解,本文所有代碼及運(yùn)行結(jié)果,都是基于 arm64 架構(gòu)

3. Swift 的函數(shù)調(diào)用

首先我們需要了解 Swift 的函數(shù)如何調(diào)用的。與 Objective-C 不同,Swift 的函數(shù)調(diào)用存在三種方式,分別是:基于 Objective-C 的消息機(jī)制、基于虛函數(shù)表的訪問、以及直接地址調(diào)用。

▐ 3.1 Objective-C 的消息機(jī)制

首先我們需要了解在什么情況下 Swift 的函數(shù)調(diào)用是借助 Objective-C 的消息機(jī)制。如果方法通過 @objc dynamic 修飾,那么在編譯后將通過 objc_msgSend 的來調(diào)用函數(shù)。

假設(shè)有如下代碼

  1. class MyTestClass :NSObject { 
  2.     @objc dynamic func helloWorld() { 
  3.         print("call helloWorld() in MyTestClass"
  4.     } 
  5.  
  6. let myTest = MyTestClass.init() 
  7. myTest.helloWorld() 

編譯后其對(duì)應(yīng)的匯編為

  1. 0x1042b8824 <+120>: bl     0x1042b9578               ; type metadata accessor for SwiftDemo.MyTestClass at <compiler-generated> 
  2. 0x1042b8828 <+124>: mov    x20, x0 
  3. 0x1042b882c <+128>: bl     0x1042b8998               ; SwiftDemo.MyTestClass.__allocating_init() -> SwiftDemo.MyTestClass at ViewController.swift:22 
  4. 0x1042b8830 <+132>: stur   x0, [x29, #-0x30] 
  5. 0x1042b8834 <+136>: adrp   x8, 13 
  6. 0x1042b8838 <+140>: ldr    x9, [x8, #0x320] 
  7. 0x1042b883c <+144>: stur   x0, [x29, #-0x58] 
  8. 0x1042b8840 <+148>: mov    x1, x9 
  9. 0x1042b8844 <+152>: str    x8, [sp, #0x60] 
  10. 0x1042b8848 <+156>: bl     0x1042bce88               ; symbol stub for: objc_msgSend 
  11. 0x1042b884c <+160>: mov    w11, #0x1 
  12. 0x1042b8850 <+164>: mov    x0, x11 
  13. 0x1042b8854 <+168>: ldur   x1, [x29, #-0x48] 
  14. 0x1042b8858 <+172>: bl     0x1042bcd5c               ; symbol stub for

從上面的匯編代碼中我們很容易看出調(diào)用了地址為0x1042bce88的objc_msgSend 函數(shù)。

▐ 3.2 虛函數(shù)表的訪問

虛函數(shù)表的訪問也是動(dòng)態(tài)調(diào)用的一種形式,只不過是通過訪問虛函數(shù)表的方式進(jìn)行調(diào)用。

假設(shè)還是上述代碼,我們將 @objc dynamic 去掉之后,并且不再繼承自 NSObject。

  1. class MyTestClass { 
  2.     func helloWorld() { 
  3.         print("call helloWorld() in MyTestClass"
  4.     } 
  5.  
  6. let myTest = MyTestClass.init() 
  7. myTest.helloWorld() 

匯編代碼變成了下面這樣👇

  1. 0x1026207ec <+120>: bl     0x102621548               ; type metadata accessor for SwiftDemo.MyTestClass at <compiler-generated> 
  2. 0x1026207f0 <+124>: mov    x20, x0 
  3. 0x1026207f4 <+128>: bl     0x102620984               ; SwiftDemo.MyTestClass.__allocating_init() -> SwiftDemo.MyTestClass at ViewController.swift:22 
  4. 0x1026207f8 <+132>: stur   x0, [x29, #-0x30] 
  5. 0x1026207fc <+136>: ldr    x8, [x0] 
  6. 0x102620800 <+140>: adrp   x9, 8 
  7. 0x102620804 <+144>: ldr    x9, [x9, #0x40] 
  8. 0x102620808 <+148>: ldr    x10, [x9] 
  9. 0x10262080c <+152>: and    x8, x8, x10 
  10. 0x102620810 <+156>: ldr    x8, [x8, #0x50] 
  11. 0x102620814 <+160>: mov    x20, x0 
  12. 0x102620818 <+164>: stur   x0, [x29, #-0x58] 
  13. 0x10262081c <+168>: str    x9, [sp, #0x60] 
  14. 0x102620820 <+172>: blr    x8 
  15. 0x102620824 <+176>: mov    w11, #0x1 
  16. 0x102620828 <+180>: mov    x0, x11 

從上面匯編代碼可以看出,經(jīng)過編譯后最終是通過 blr 指令調(diào)用了 x8 寄存器中存儲(chǔ)的函數(shù)。至于 x8 寄存器中的數(shù)據(jù)從哪里來的,留到后面的章節(jié)闡述。

▐ 3.3 直接地址調(diào)用

假設(shè)還是上述代碼,我們?cè)賹?Build Setting 中Swift Compiler - Code Generaation -> Optimization Level 修改為 Optimize for Size[-Osize],匯編代碼變成了下面這樣👇

  1. 0x1048c2114 <+40>:  bl     0x1048c24b8               ; type metadata accessor for SwiftDemo.MyTestClass at <compiler-generated> 
  2. 0x1048c2118 <+44>:  add    x1, sp, #0x10             ; =0x10  
  3. 0x1048c211c <+48>:  bl     0x1048c5174               ; symbol stub for: swift_initStackObject 
  4. 0x1048c2120 <+52>:  bl     0x1048c2388               ; SwiftDemo.MyTestClass.helloWorld() -> () at ViewController.swift:23 
  5. 0x1048c2124 <+56>:  adr    x0, #0xc70c               ; demangling cache variable for type metadata for Swift._ContiguousArrayStorage<Any

這是大家就會(huì)發(fā)現(xiàn)bl 指令后跟著的是一個(gè)常量地址,并且是 SwiftDemo.MyTestClass.helloWorld() 的函數(shù)地址。

4. 思考

既然基于虛函數(shù)表的派發(fā)形式也是一種動(dòng)態(tài)調(diào)用,那么是不是以為著只要我們修改了虛函數(shù)表中的函數(shù)地址,就實(shí)現(xiàn)了函數(shù)的替換?

5. 基于 TypeContext 的方法交換

在往期文章《從 Mach-O 角度談?wù)?Swift 和 OC 的存儲(chǔ)差異》我們可以了解到在Mach-O 文件中,可以通過 __swift5_types 查找到每個(gè) Class 的ClassContextDescriptor,并且可以通過 ClassContextDescriptor 找到當(dāng)前類對(duì)應(yīng)的虛函數(shù)表,并動(dòng)態(tài)調(diào)用表中的函數(shù)。

注意:(在 Swift 中,Class/Struct/Enum 統(tǒng)稱為 Type,為了方便起見,我們?cè)谖闹刑岬降腡ypeContext 和 ClassContextDescriptor 都指的是 ClassContextDescriptor)。

首先我們來回顧下 Swift 的類的結(jié)構(gòu)描述,結(jié)構(gòu)體 ClassContextDescriptor 是 Swift 類在Section64(__TEXT,__const) 中的存儲(chǔ)結(jié)構(gòu)。

  1. struct ClassContextDescriptor{ 
  2.     uint32_t Flag; 
  3.     uint32_t Parent; 
  4.     int32_t  Name
  5.     int32_t  AccessFunction; 
  6.     int32_t  FieldDescriptor; 
  7.     int32_t  SuperclassType; 
  8.     uint32_t MetadataNegativeSizeInWords; 
  9.     uint32_t MetadataPositiveSizeInWords; 
  10.     uint32_t NumImmediateMembers; 
  11.     uint32_t NumFields; 
  12.     uint32_t FieldOffsetVectorOffset; 
  13.     <泛型簽名> //字節(jié)數(shù)與泛型的參數(shù)和約束數(shù)量有關(guān) 
  14.     <MaybeAddResilientSuperclass>//有則添加4字節(jié) 
  15.     <MaybeAddMetadataInitialization>//有則添加4*3字節(jié) 
  16.     VTableList[]//先用4字節(jié)存儲(chǔ)offset/pointerSize,再用4字節(jié)描述數(shù)量,隨后N個(gè)4+4字節(jié)描述函數(shù)類型及函數(shù)地址。 
  17.     OverrideTableList[]//先用4字節(jié)描述數(shù)量,隨后N個(gè)4+4+4字節(jié)描述當(dāng)前被重寫的類、被重寫的函數(shù)描述、當(dāng)前重寫函數(shù)地址。 

從上述結(jié)構(gòu)可以看出,ClassContextDescriptor 的長度是不固定的,不同的類 ClassContextDescriptor 的長度可能不同。那么如何才能知道當(dāng)前這個(gè)類是不是泛型?以及是否有 ResilientSuperclass、MetadataInitialization 特征?其實(shí)在前一篇文章《從Mach-O 角度談?wù)?Swift 和 OC 的存儲(chǔ)差異》中已經(jīng)做了說明,我們可以通過 Flag 的標(biāo)記位來獲取相關(guān)信息。

例如,如果 Flag 的 generic 標(biāo)記位為 1,則說明是泛型。

  1. |  TypeFlag(16bit)  |  version(8bit) | generic(1bit) | unique(1bit) | unknow (1bi) | Kind(5bit) | 
  2. //判斷泛型 
  3. (Flag & 0x80) == 0x80 

那么泛型簽名到底能占多少字節(jié)呢?Swift 的 GenMeta.cpp 文件中對(duì)泛型的存儲(chǔ)做了解釋,整理總結(jié)如下:

  1. 假設(shè)有泛型有paramsCount個(gè)參數(shù),有requeireCount個(gè)約束 
  2.  
  3. /** 
  4.      16B  =  4B + 4B + 2B + 2B + 2B + 2B 
  5.      addMetadataInstantiationCache -> 4B 
  6.      addMetadataInstantiationPattern -> 4B 
  7.      GenericParamCount -> 2B 
  8.      GenericRequirementCount -> 2B 
  9.      GenericKeyArgumentCount -> 2B 
  10.      GenericExtraArgumentCount -> 2B 
  11.  */ 
  12.  short pandding = (unsigned)-paramsCount & 3; 
  13.  泛型簽名字節(jié)數(shù) = (16 + paramsCount + pandding + 3 * 4 * (requeireCount) + 4); 

因此只要明確了 Flag 各個(gè)標(biāo)記位的含義以及泛型的存儲(chǔ)長度規(guī)律,那么就能計(jì)算出虛函數(shù)表 VTable 的位置以及各個(gè)函數(shù)的字節(jié)位置。

了解了泛型的布局以及 VTable 的位置,是不是就意味著能實(shí)現(xiàn)函數(shù)指針的修改了呢?答案當(dāng)然是否定的,因?yàn)?VTable 存儲(chǔ)在 __TEXT 段,__TEXT 是只讀段,我們沒辦法直接進(jìn)行修改。不過最終我們通過 remap 的方式修改代碼段,將 VTable 中的函數(shù)地址進(jìn)行了修改,然而發(fā)現(xiàn)在運(yùn)行時(shí)函數(shù)并沒有被替換為我們修改的函數(shù)。那到底是怎么一回事呢?

6. 基于 Metadata 的方法交換

上述實(shí)驗(yàn)的失敗當(dāng)然是我們的不嚴(yán)謹(jǐn)導(dǎo)致的。在項(xiàng)目一開始我們先研究的是類型存儲(chǔ)描述 TypeContext,主要是類的存儲(chǔ)描述 ClassContextDescriptor。在找到 VTable 后我們想當(dāng)然的認(rèn)為運(yùn)行時(shí) Swift 是通過訪問 ClassContextDescriptor 中的 VTable 進(jìn)行函數(shù)調(diào)用的。但是事實(shí)并不是這樣。

7. VTable 函數(shù)調(diào)用

接下來我們將回答下 Swift的函數(shù)調(diào)用 章節(jié)中提的問題,x8 寄存器的函數(shù)地址是從哪里來的。還是前文中的 Demo,我們?cè)?helloWorld() 函數(shù)調(diào)用前打斷點(diǎn)

  1. let myTest = MyTestClass.init() 
  2. ->  myTest.helloWorld() 

斷點(diǎn)停留在 0x100230ab0 處👇

  1. 0x100230aac <+132>: stur   x0, [x29, #-0x30] 
  2. 0x100230ab0 <+136>: ldr    x8, [x0] 
  3. 0x100230ab4 <+140>: ldr    x8, [x8, #0x50] 
  4. 0x100230ab8 <+144>: mov    x20, x0 
  5. 0x100230abc <+148>: str    x0, [sp, #0x58] 
  6. 0x100230ac0 <+152>: blr    x8 

此時(shí) x0 寄存器中存儲(chǔ)的是 myTest 的地址 x0 = 0x0000000280d08ef0,ldr x8, [x0] 則是將 0x280d08ef0 處存儲(chǔ)的數(shù)據(jù)放入 x8(注意,這里是只將 *myTest 存入 x8,而不是將 0x280d08ef0 存入 x8)。單步執(zhí)行后,通過 re read 查看各個(gè)寄存器的數(shù)據(jù)后會(huì)發(fā)現(xiàn) x8 存儲(chǔ)的是 type metadata 的地址,而不是 TypeContext 的地址。

  1. x0 = 0x0000000280d08ef0 
  2. x1 = 0x0000000280d00234 
  3. x2 = 0x0000000000000000 
  4. x3 = 0x00000000000008fd 
  5. x4 = 0x0000000000000010 
  6. x5 = 0x000000016fbd188f 
  7. x6 = 0x00000002801645d0 
  8. x7 = 0x0000000000000000 
  9. x8 = 0x000000010023e708  type metadata for SwiftDemo.MyTestClass 
  10. x9 = 0x0000000000000003 
  11. x10= 0x0000000280d08ef0 
  12. x11= 0x0000000079c00000 

經(jīng)過上步單步執(zhí)行后,當(dāng)前程序要做的是 ldr x8, [x8, #0x50],即將 type metadata + 0x50 處的數(shù)據(jù)存儲(chǔ)到 x8。這一步就是跳表,也就是說經(jīng)過這一步后,x8 寄存器中存儲(chǔ)的就是 helloWorld() 的地址。

  1.     0x100230aac <+132>: stur   x0, [x29, #-0x30] 
  2.     0x100230ab0 <+136>: ldr    x8, [x0] 
  3. ->  0x100230ab4 <+140>: ldr    x8, [x8, #0x50] 
  4.     0x100230ab8 <+144>: mov    x20, x0 
  5.     0x100230abc <+148>: str    x0, [sp, #0x58] 
  6.     0x100230ac0 <+152>: blr    x8 

那是否真的是這樣呢?ldr x8, [x8, #0x50] 執(zhí)行后,我們?cè)俅尾榭?x8,看看寄存器中是否為函數(shù)地址👇

  1. x0 = 0x0000000280d08ef0 
  2. x1 = 0x0000000280d00234 
  3. x2 = 0x0000000000000000 
  4. x3 = 0x00000000000008fd 
  5. x4 = 0x0000000000000010 
  6. x5 = 0x000000016fbd188f 
  7. x6 = 0x00000002801645d0 
  8. x7 = 0x0000000000000000 
  9. x8 = 0x0000000100231090  SwiftDemo`SwiftDemo.MyTestClass.helloWorld() -> () at ViewController.swift:23 
  10. x9 = 0x0000000000000003 

結(jié)果表明 x8 存儲(chǔ)的確實(shí)是 helloWorld() 的函數(shù)地址。上述實(shí)驗(yàn)表明經(jīng)過跳轉(zhuǎn)0x50 位置后,程序找到了 helloWorld() 函數(shù)地址。類的 Metadata 位于__DATA 段,是可讀寫的。其結(jié)構(gòu)如下:

  1. struct SwiftClass { 
  2.     NSInteger kind; 
  3.     id superclass; 
  4.     NSInteger reserveword1; 
  5.     NSInteger reserveword2; 
  6.     NSUInteger rodataPointer; 
  7.     UInt32 classFlags; 
  8.     UInt32 instanceAddressPoint; 
  9.     UInt32 instanceSize; 
  10.     UInt16 instanceAlignmentMask; 
  11.     UInt16 runtimeReservedField; 
  12.     UInt32 classObjectSize; 
  13.     UInt32 classObjectAddressPoint; 
  14.     NSInteger nominalTypeDescriptor; 
  15.     NSInteger ivarDestroyer; 
  16.     //func[0] 
  17.     //func[1] 
  18.     //func[2] 
  19.     //func[3] 
  20.     //func[4] 
  21.     //func[5] 
  22.     //func[6] 
  23.     .... 
  24. }; 

上面的代碼在經(jīng)過0x50 字節(jié)的偏移后正好位于 func[0] 的位置。因此要想動(dòng)態(tài)修改函數(shù)需要修改Metadata中的數(shù)據(jù)。

經(jīng)過試驗(yàn)后發(fā)現(xiàn)修改后函數(shù)確實(shí)是在運(yùn)行后發(fā)生了改變。但是這并沒有結(jié)束,因 為虛函數(shù)表與消息發(fā)送有所不同,虛函數(shù)表中并沒有任何函數(shù)名和函數(shù)地址的映射,我們只能通過偏移來修改函數(shù)地址。

比如,我想修改第1個(gè)函數(shù),那么我要找到 Meatadata,并修改 0x50 處的 8 字節(jié)數(shù)據(jù)。同理,想要修改第 2 個(gè)函數(shù),那么我要修改 0x58 處的 8 字節(jié)數(shù)據(jù)。這就帶來一個(gè)問題,一旦函數(shù)數(shù)量或者順序發(fā)生了變更,那么都需要重新進(jìn)行修正偏移索引。

舉例說明下,假設(shè)當(dāng)前 1.0 版本的代碼為

  1. class MyTestClass { 
  2.     func helloWorld() { 
  3.         print("call helloWorld() in MyTestClass"
  4.     } 

此時(shí)我們對(duì) 0x50 處的函數(shù)指針進(jìn)行了修改。當(dāng) 2.0 版本變更為如下代碼時(shí),此時(shí)我們的偏移應(yīng)該修改為 0x58,否則我們的函數(shù)替換就發(fā)生了錯(cuò)誤。

  1. class MyTestClass { 
  2.     func sayhi() { 
  3.         print("call sayhi() in MyTestClass"
  4.     } 
  5.  
  6.     func helloWorld() { 
  7.         print("call helloWorld() in MyTestClass"
  8.     } 

為了解決虛函數(shù)變更的問題,我們需要了解下 TypeContext 與 Metadata 的關(guān)系。

8. TypeContext 與 Metadata 的關(guān)系

Metadata 結(jié)構(gòu)中的 nominalTypeDescriptor 指向了 TypeContext,也就是說當(dāng)我們獲取到 Metadata 地址后,偏移 0x40 字節(jié)就能獲取到當(dāng)前這個(gè)類對(duì)應(yīng)的 TypeContext地址。那么如何通過 TypeContext 找到 Metadata 呢?

我們還是看剛才的那個(gè) Demo,此時(shí)我們將斷點(diǎn)打到 init() 函數(shù)上,我們想了解下 MyTestClass 的 Metadata 到底是哪里來的。

  1. ->  let myTest = MyTestClass.init() 
  2. myTest.helloWorld() 

此時(shí)展開為匯編我們會(huì)發(fā)現(xiàn),程序準(zhǔn)備調(diào)用一個(gè)函數(shù)。

  1. ->  0x1040f0aa0 <+120>: bl     0x1040f16a8               ; type metadata accessor for SwiftDemo.MyTestClass at <compiler-generated> 
  2.     0x1040f0aa4 <+124>: mov    x20, x0 
  3.     0x1040f0aa8 <+128>: bl     0x1040f0c18               ; SwiftDemo.MyTestClass.__al 

在執(zhí)行 bl 0x1040f16a8 指令之前,x0 寄存器為 0。

  1. x0 = 0x0000000000000000 

此時(shí)通過 si 單步調(diào)試就會(huì)發(fā)現(xiàn)跳轉(zhuǎn)到了函數(shù) 0x1040f16a8 處,其函數(shù)指令較少,如下所示👇

  1. SwiftDemo`type metadata accessor for MyTestClass: 
  2. ->  0x1040f16a8 <+0>:  stp    x29, x30, [sp, #-0x10]! 
  3.     0x1040f16ac <+4>:  adrp   x8, 13 
  4.     0x1040f16b0 <+8>:  add    x8, x8, #0x6f8            ; =0x6f8  
  5.     0x1040f16b4 <+12>: add    x8, x8, #0x10             ; =0x10  
  6.     0x1040f16b8 <+16>: mov    x0, x8 
  7.     0x1040f16bc <+20>: bl     0x1040f4e68               ; symbol stub for: objc_opt_self 
  8.     0x1040f16c0 <+24>: mov    x8, #0x0 
  9.     0x1040f16c4 <+28>: mov    x1, x8 
  10.     0x1040f16c8 <+32>: ldp    x29, x30, [sp], #0x10 
  11.     0x1040f16cc <+36>: ret 

在執(zhí)行 0x1040f16a8 函數(shù)執(zhí)行完后,x0 寄存器就存儲(chǔ)了 MyTestClass 的 Metadata 地址。

  1. x0 = 0x00000001047e6708  type metadata for SwiftDemo.MyTestClass 

那么這個(gè)被標(biāo)記為 type metadata accessor for SwiftDemo.MyTestClass at 的函數(shù)到底是什么?

在上文介紹的 struct ClassContextDescriptor 貌似有個(gè)成員是 AccessFunction,那這個(gè) ClassContextDescriptor 中的 AccessFunction 是不是 Metadata 的訪問函數(shù)呢?這個(gè)其實(shí)很容易驗(yàn)證。

我們?cè)俅芜\(yùn)行 Demo,此時(shí)metadata accessor 為 0x1047d96a8,繼續(xù)執(zhí)行后Metadata地址為 0x1047e6708。

  1. x0 = 0x00000001047e6708  type metadata for SwiftDemo.MyTestClass 

查看 0x1047e6708,繼續(xù)偏移 0x40 字節(jié)后可以得到 Metadata 結(jié)構(gòu)中的 nominalTypeDescriptor 地址 0x1047e6708 + 0x40 = 0x1047e6748。

查看 0x1047e6748 存儲(chǔ)的數(shù)據(jù)為 0x1047df4a0。

  1. (lldb) x 0x1047e6748 
  2. 0x1047e6748: a0 f4 7d 04 01 00 00 00 00 00 00 00 00 00 00 00  ..}............. 
  3. 0x1047e6758: 90 90 7d 04 01 00 00 00 18 8c 7d 04 01 00 00 00  ..}.......}..... 

ClassContextDescriptor 中的 AccessFunction 在第 12 字節(jié)處,因此對(duì) 0x1047df4a0 + 12 可知 AccessFunction 的位置為 0x1047df4ac。繼續(xù)查看 0x1047df4ac 存儲(chǔ)的數(shù)據(jù)為

  1. (lldb) x 0x1047df4ac 
  2. 0x1047df4ac: fc a1 ff ff 70 04 00 00 00 00 00 00 02 00 00 00  ....p........... 
  3. 0x1047df4bc: 0c 00 00 00 02 00 00 00 00 00 00 00 0a 00 00 00  ................ 

由于在 ClassContextDescriptor 中,AccessFunction 為相對(duì)地址,因此我們做一次地址計(jì)算 0x1047df4ac + 0xffffa1fc - 0x10000000 = 0x1047d96a8,與 metadata accessor 0x1047d96a8 相同,這就說明 TypeContext 是通過 AccessFunction 來獲取對(duì)應(yīng)的Metadata的地址的。

當(dāng)然,實(shí)際上也會(huì)有例外,有時(shí)編譯器會(huì)直接使用緩存的 cache Metadata 的地址,而不再通過 AccessFunction 來獲取類的 Metadata。

9. 基于 TypeContext 和 Metadata 的方法交換

在了解了 TypeContext 和 Metadata 的關(guān)系后,我們就能做一些設(shè)想了。在 Metadata中雖然存儲(chǔ)了函數(shù)的地址,但是我們并不知道函數(shù)的類型。這里的函數(shù)類型指的是函數(shù)是普通函數(shù)、初始化函數(shù)、getter、setter 等。

在 TypeContext 的 VTable 中,method 存儲(chǔ)一共是 8 字節(jié),第一個(gè)4字節(jié)存儲(chǔ)的函數(shù)的 Flag,第二個(gè)4字節(jié)存儲(chǔ)的函數(shù)的相對(duì)地址。

  1. struct SwiftMethod { 
  2.     uint32_t Flag; 
  3.     uint32_t Offset; 
  4. }; 

通過 Flag 我們很容易知道是否是動(dòng)態(tài),是否是實(shí)例方法,以及函數(shù)類型 Kind。

  1. |  ExtraDiscriminator(16bit)  |... | Dynamic(1bit) | instanceMethod(1bit) | Kind(4bit) | 

Kind 枚舉如下👇

  1. typedef NS_ENUM(NSInteger, SwiftMethodKind) { 
  2.     SwiftMethodKindMethod             = 0,     // method 
  3.     SwiftMethodKindInit               = 1,     //init 
  4.     SwiftMethodKindGetter             = 2,     // get 
  5.     SwiftMethodKindSetter             = 3,     // set 
  6.     SwiftMethodKindModify             = 4,     // modify 
  7.     SwiftMethodKindRead               = 5,     // read 
  8. }; 

從 Swift 的源碼中可以很明顯的看到,類重寫的函數(shù)是單獨(dú)存儲(chǔ)的,也就是有單獨(dú)的OverrideTable。

并且 OverrideTable 是存儲(chǔ)在 VTable 之后。與 VTable 中的 method 結(jié)構(gòu)不同,OverrideTable 中的函數(shù)需要 3 個(gè) 4 字節(jié)描述:

  1. struct SwiftOverrideMethod { 
  2.     uint32_t OverrideClass;//記錄是重寫哪個(gè)類的函數(shù),指向TypeContext 
  3.     uint32_t OverrideMethod;//記錄重寫哪個(gè)函數(shù),指向SwiftMethod 
  4.     uint32_t Method;//函數(shù)相對(duì)地址 
  5. }; 

也就是說 SwiftOverrideMethod 中能夠包含兩個(gè)函數(shù)的綁定關(guān)系,這種關(guān)系與函數(shù)的編譯順序和數(shù)量無關(guān)。

如果 Method 記錄用于 Hook 的函數(shù)地址,OverrideMethod 作為被Hook的函數(shù),那是不是就意味著無論如何改變虛函數(shù)表的順序及數(shù)量,只要 Swift 還是通過跳表的方式進(jìn)行函數(shù)調(diào)用,那么我們就無需關(guān)注函數(shù)變化了。

為了驗(yàn)證可行性,我們寫 Demo 測(cè)試一下:

  1. class MyTestClass { 
  2.     func helloWorld() { 
  3.         print("call helloWorld() in MyTestClass"
  4.     } 
  5. }//作為被Hook類及函數(shù) 
  6.  
  7. <---------------------------------------------------> 
  8.  
  9. class HookTestClass: MyTestClass  { 
  10.     override func helloWorld() { 
  11.         print("\n********** call helloWorld() in HookTestClass **********"
  12.         super.helloWorld() 
  13.         print("********** call helloWorld() in HookTestClass end **********\n"
  14.     } 
  15. }//通過繼承和重寫的方式進(jìn)行Hook 
  16.  
  17. <---------------------------------------------------> 
  18.    
  19. let myTest = MyTestClass.init() 
  20.  myTest.helloWorld() 
  21.  
  22.  //do hook 
  23.  print("\n------ replace MyTestClass.helloWorld() with   HookTestClass.helloWorld() -------\n"
  24.  
  25.  WBOCTest.replace(HookTestClass.self); 
  26.  
  27.  //hook 生效 
  28.  myTest.helloWorld() 

運(yùn)行后,可以看出 helloWorld() 已經(jīng)被替換成功👇

  1. 2021-03-09 17:25:36.321318+0800 SwiftDemo[59714:5168073] _mh_execute_header = 4368482304 
  2. call helloWorld() in MyTestClass 
  3.  
  4. ------ replace MyTestClass.helloWorld() with HookTestClass.helloWorld() ------- 
  5.  
  6.  
  7. ********** call helloWorld() in HookTestClass ********** 
  8. call helloWorld() in MyTestClass 
  9. ********** call helloWorld() in HookTestClass end ********** 

10. 總結(jié)

本文通過介紹 Swift 的虛函數(shù)表 Hook 思路,介紹了 Swift Mach-O 的存儲(chǔ)結(jié)構(gòu)以及運(yùn)行時(shí)的一些調(diào)試技巧。Swift 的 Hook 方案一直是從 Objective-C 轉(zhuǎn)向 Swift 開發(fā)的同學(xué)比較感興趣的事情。我們想通過本文向大家介紹關(guān)于 Swift 更深層的一些內(nèi)容,至于方案本身也許并不是最重要的,重要的是我們希望是否能夠從中 Swift 的二進(jìn)制中找到更多的應(yīng)用場(chǎng)景。比如,Swift 的調(diào)用并不會(huì)存儲(chǔ)到 classref 中,那如何通過靜態(tài)掃描知道哪些 Swift 的類或 Struct 被調(diào)用了?其實(shí)解決方案也是隱含在本文中。

 

責(zé)任編輯:姜華 來源: Swift 社區(qū)
相關(guān)推薦

2022-07-18 15:32:37

C++虛函數(shù)表

2010-01-18 17:38:54

C++虛函數(shù)表

2011-09-01 11:12:02

Restaurant 美食應(yīng)用餐飲應(yīng)用

2017-01-23 11:18:16

戴爾

2009-12-03 10:32:21

2025-02-04 10:00:30

Spring支付系統(tǒng)

2015-05-07 14:24:36

everRun

2022-05-23 09:18:55

RocketMQ存儲(chǔ)中間件

2016-05-31 10:11:51

2025-05-12 02:45:00

2022-05-26 08:53:47

Go函數(shù)代碼

2013-10-12 13:40:09

2010-12-03 10:49:11

Virtuozzo

2009-01-11 10:27:00

小型辦公室網(wǎng)絡(luò)組建

2013-08-08 10:06:07

CA TechnoloCA Expo

2013-01-16 10:07:30

加密解密破解Android軟件

2017-12-14 09:03:24

租賃數(shù)據(jù)中心設(shè)備

2009-12-30 14:19:50

城域網(wǎng)接入技術(shù)

2023-12-07 13:14:54

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

亚洲精品一区二区三区av| 久久精品亚洲热| 国产精品欧美激情在线观看| 国产小视频在线| 黑人另类精品××××性爽| 国产激情偷乱视频一区二区三区| 美女久久久久久久久久久| 怡红院一区二区| 亚洲成人va| 一区二区三区欧美亚洲| 日本亚洲欧美三级| 午夜成人亚洲理伦片在线观看| 日韩精品免费视频一区二区三区| 黄色成人av网| 中文字幕中文字幕99| 欧美一级特黄aaaaaa| 蜜桃视频一区二区三区 | 亚洲xxxxx性| 在线观看日韩中文字幕| 68国产成人综合久久精品| 中文字幕一区二区三区中文字幕 | 国产99一区视频免费| 日本sm极度另类视频| 91精品一区二区三区蜜桃| 亚洲综合图色| 精品国产伦一区二区三区观看体验| 国产成人精品无码播放| 欧美wwww| 亚洲色图欧洲色图| 日本不卡二区| 国产精品国产高清国产| 国产一区二区三区在线观看免费 | 国产一级淫片a视频免费观看| 亚洲欧美一级二级三级| 中文字幕亚洲欧美在线| 久久综合色视频| 婷婷成人激情| 国产欧美日韩中文久久| 蜜桃91精品入口| 国产 欧美 自拍| 国产精品一区久久久久| 国产欧洲精品视频| 粉色视频免费看| 国产伦久视频在线观看| 亚洲综合一区在线| xxxxxx在线观看| 女女色综合影院| 中文幕一区二区三区久久蜜桃| 精品毛片久久久久久| 可以免费看毛片的网站| 粉嫩在线一区二区三区视频| 产国精品偷在线| www.色视频| 国产专区综合网| 91久久久国产精品| 91亚洲国产成人久久精品麻豆| 奇米精品一区二区三区在线观看| 国产成人一区二区| 免费黄色小视频在线观看| 先锋亚洲精品| 国产成人久久久| 成年人晚上看的视频| 久久精品天堂| 国产精品看片资源| 亚洲专区第一页| 久久草av在线| av在线不卡观看| 日本精品999| 26uuu色噜噜精品一区| 麻豆亚洲一区| av黄色在线观看| 国产一区二区视频在线播放| 91系列在线观看| 性欧美8khd高清极品| 成人18视频日本| 欧美国产视频在线观看| av在线资源站| 亚洲精品乱码久久久久久| www.亚洲成人网| 碰碰在线视频| 欧美日免费三级在线| 精品亚洲视频在线| 色戒汤唯在线| 欧美在线你懂得| 在线免费黄色小视频| jizzjizzjizz欧美| 欧美日韩不卡一区| 97超碰免费在线观看| 欧美丝袜美腿| 一区二区三区www| 欧美一区二区三区爽爽爽| 狠狠综合久久| 国产精品久久久av久久久| 国产手机av在线| 北条麻妃一区二区三区| 欧美一区免费视频| 污污网站在线看| 欧美性生活大片免费观看网址| 激情视频免费网站| eeuss国产一区二区三区四区| 亚洲天堂色网站| 久久久久久久九九九九| 日韩一区精品视频| av噜噜色噜噜久久| 成人在线免费看| 亚洲第一搞黄网站| 777一区二区| 欧美一级二级三级视频| 精品国偷自产在线视频99| 久久久久久久黄色片| 另类成人小视频在线| 国产伦精品一区二区三区视频免费| 在线观看国产原创自拍视频| 同产精品九九九| 999久久久精品视频| 竹菊久久久久久久| 欧美激情精品久久久久久变态| 羞羞色院91蜜桃| 99久久精品情趣| 久久观看最新视频| 国产a亚洲精品| 亚洲美女动态图120秒| 久久99久久98精品免观看软件| 免费黄网站欧美| 欧美区高清在线| 电影在线观看一区| 日韩视频一区二区三区| 国产福利在线导航| 日韩精品久久理论片| 久久99精品久久久水蜜桃| 七七成人影院| 欧美一区二区免费| 999精品在线视频| 秋霞成人午夜伦在线观看| 激情欧美一区二区三区中文字幕| 欧美bbbxxxxx| 精品少妇一区二区三区免费观看| 亚洲一级生活片| 精品夜夜嗨av一区二区三区| 亚洲高清在线观看一区| 影视一区二区三区| 亚洲日韩第一页| 无码人妻丰满熟妇奶水区码| 91免费观看国产| 成人免费aaa| 日韩高清影视在线观看| 韩国日本不卡在线| 天天操天天操天天| 午夜电影网亚洲视频| 国产乱国产乱老熟300部视频| 欧美高清日韩| 99三级在线| 色图在线观看| 亚洲成人激情在线观看| 国产无套粉嫩白浆内谢| av亚洲精华国产精华精华| 成年人网站免费视频| 日韩成人一级| 国产不卡av在线| avtt在线播放| 91精品国产综合久久福利软件| 国产日韩欧美在线观看视频| 国产a视频精品免费观看| 分分操这里只有精品| 欧美日韩看看2015永久免费 | 99久久精品免费| 欧美v在线观看| 欧美日韩精品在线一区| 国产区亚洲区欧美区| 久久日韩视频| 精品久久久久久久久久久久久久久久久 | www.99r| 99久久.com| 99久久久久国产精品免费| 手机电影在线观看| 日韩精品视频在线观看网址| 波多野结衣mp4| 亚洲欧洲日产国码二区| 国产裸体视频网站| 亚洲自啪免费| 一本一本a久久| ccyy激情综合| 国产精品久久久久不卡| 成人高清免费在线| 日韩av网站电影| 中文 欧美 日韩| 一区二区三区小说| 一级黄色片大全| 紧缚奴在线一区二区三区| 日本男女交配视频| 欧美综合在线视频观看| 97视频中文字幕| 成人日韩精品| 欧美黑人国产人伦爽爽爽| 欧美老女人性开放| 6080yy午夜一二三区久久| 青青草av在线播放| 国产精品久久久久久久久果冻传媒| 曰本三级日本三级日本三级| 老司机免费视频久久| 青青视频免费在线观看| 亚洲三级性片| 成人三级在线| 成人亚洲综合| 欧美亚洲另类制服自拍| 国产片在线播放| 欧美性xxxxxxx| 少妇被躁爽到高潮无码文| 久久亚洲一区二区三区明星换脸| 波多野结衣网页| 男男成人高潮片免费网站| 欧美在线一区视频| 欧美在线三级| 亚洲精品一区国产精品| 日韩黄色网络| 国产高清一区二区三区| 日韩一区二区三免费高清在线观看| 97超视频免费观看| 久久不射影院| 欧美人与性动交| 亚洲国产www| 欧美日韩一区中文字幕| 7799精品视频天天看| 亚洲午夜久久久久久久久久久| 999精品视频在线观看播放| 国产三级一区二区三区| 人妻丰满熟妇aⅴ无码| 国产69精品一区二区亚洲孕妇| www.夜夜爽| 日韩精品一级二级| 欧美日韩国产精品激情在线播放| 韩国在线视频一区| 国产午夜精品视频一区二区三区| 色喇叭免费久久综合网| 午夜一区二区三区| 国产尤物久久久| 欧美亚洲国产免费| 亚洲毛片免费看| 美乳视频一区二区| 日韩大胆成人| 青娱乐国产91| 精品国产日韩欧美| 亚洲精品久久久久久一区二区| 伊人久久综合影院| 欧美一级二级三级| 国产aⅴ精品一区二区三区久久| 久久久久久一区| 天堂网av成人| 日产精品久久久一区二区| 国产精品探花在线观看| 日本一区二区不卡高清更新| 欧美人与物videos另类xxxxx| 日本在线一区| 日韩免费特黄一二三区| 亚洲在线不卡| 综合一区在线| 免费网站在线观看视频| 国产精品尤物| 北条麻妃视频在线| 蜜桃视频第一区免费观看| 中文字幕日韩综合| 国产精品白丝av| 精品国产乱码久久久久夜深人妻| 波多野结衣中文字幕一区二区三区 | 韩国三级中文字幕hd久久精品| 手机免费看av网站| 国产成人在线免费观看| 日本性生活一级片| 久久这里只有精品6| 免费看91的网站| |精品福利一区二区三区| 国产一级特黄a高潮片| 精品欧美一区二区三区| 国产免费a视频| 欧美一级一区二区| 无码精品人妻一区二区三区影院 | 国产福利久久精品| 亚洲大片精品免费| 亚洲最新在线| 狠狠综合久久| 国产九九在线视频| 国产精品一卡二卡| 日韩精品卡通动漫网站| 国产精品久久久久影院老司| 九九视频免费观看| 色婷婷亚洲综合| 国产精品熟女久久久久久 | av影片免费在线观看| 欧美xxxx做受欧美.88| 精精国产xxx在线视频app| 国产精品久久久久7777婷婷| 亚洲乱码一区| 涩涩涩999| 亚洲大胆在线| 中文字幕22页| 91啪亚洲精品| 一区二区在线观看免费视频| 91小视频在线| 免费成人深夜蜜桃视频| 亚洲欧美日韩中文字幕一区二区三区 | 一区二区三区视频网| 成人国产精品免费观看动漫| 成人一级片免费看| 五月天激情小说综合| 国产农村妇女毛片精品久久| 亚洲欧美在线看| 免费在线观看的电影网站| 国产精品日韩av| 国产精品免费大片| 国产日韩欧美精品在线观看| 久久国产精品第一页| 国产成人精品无码免费看夜聊软件| 亚洲一区二区三区视频在线播放| 91av久久久| 国产一区二区三区在线| 888av在线视频| 亚洲综合在线小说| 久久高清免费| 国产精品入口免费软件| www国产精品av| 亚洲一区欧美在线| 精品国产乱码久久久久久影片| 欧美激情二区| 国产精品一区二区久久久| 亚欧日韩另类中文欧美| 精品少妇人欧美激情在线观看| 国产主播一区二区| 无码人妻精品中文字幕 | 国内精品久久久久久中文字幕| 亚洲精品tv| 一区二区三区av| 日韩高清一区在线| xxxxx在线观看| 色天天综合色天天久久| 丝袜视频国产在线播放| 97免费中文视频在线观看| 136导航精品福利| 日韩人妻一区二区三区蜜桃视频| 国产美女一区二区| 欧美成人精品欧美一级私黄| 91精品免费在线观看| 视频一区二区三区不卡| 国产精品视频地址| 91欧美大片| 国产美女视频免费看| 中文字幕一区二区三区四区不卡 | 精品乱人伦小说| 九色91在线| 国产一级特黄a大片99| 亚洲人人精品| 中文字幕5566| 在线精品国精品国产尤物884a| 在线观看av黄网站永久| 国产在线拍揄自揄视频不卡99| 99视频精品全部免费在线视频| 午夜xxxxx| 亚洲愉拍自拍另类高清精品| 日本人妻熟妇久久久久久| 亚洲18私人小影院| 亚洲系列另类av| 奇米影音第四色| 亚洲精品成人少妇| 人妻一区二区三区四区| 日韩美女视频免费看| 第一会所sis001亚洲| 一女二男3p波多野结衣| 亚洲欧美一区二区三区孕妇| 亚洲黄色小说网| 欧美一区二区.| 欧美成人直播| 蜜桃色一区二区三区| 天天操天天色综合| yw视频在线观看| 91|九色|视频| 国产精品主播| 日本午夜在线观看| 亚洲国产成人在线视频| 日韩精品极品| 中文字幕一区二区三区四区五区| 国产白丝网站精品污在线入口| 久久99国产综合精品免费| 俺去了亚洲欧美日韩| 久久电影在线| 岛国毛片在线播放| 精品久久久中文| 久做在线视频免费观看| 精品久久久久久亚洲| 久久超碰97中文字幕| 欧美精品亚洲精品日韩精品| 色悠悠久久久久| 韩国女主播一区二区三区| 国产精品一区二区小说| 午夜伊人狠狠久久| 免费在线观看黄| 久久亚洲午夜电影| 国产精品12区| 中文字幕第99页| 5252色成人免费视频| 久久久久国产| www.狠狠爱|