iOS逆向之Protocol Witness Table的汇编实现原理
生活随笔
收集整理的這篇文章主要介紹了
iOS逆向之Protocol Witness Table的汇编实现原理
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
一、什么是 Protocol Witness Table?
- 我們都知道 C 函數調用是靜態派發,簡單來說可以理解為是用匯編命令 call $address 來實現,這種方式效率最高,但是靈活性不夠。
- OC 的方法調用完全是基于動態派發,總是調用 objc_msgSend 實現,這種方式非常靈活,允許各種 Hook 黑科技,但是流程最長,效率最低。
- 在 Swift 中,協議方法的調用,使用協議方法表的方式完成,也就是 Protocol Witness Table,下文簡稱 PWT。
- 現有如下代碼:
- 在 foo 函數中,變量 p 并沒有明確的類型,只知道它遵守 Drawable 協議,實現了 draw 方法。但是編譯時并不能知道,調用的是結構體 Line 還是 Point 的 draw 方法。
- 因此,PWT 的實現方式是:每個類都會有一個方法表(通過數組來實現),里面保存了它用于實現協議的函數的地址,只要知道一個類的信息和函數信息,就可以實現函數調用,這個方法表,就是 PWT。
二、PWT 的匯編實現
- 除了從理論上了解 PWT 的概念,還可以從匯編角度來實際感受一下。現有如下代碼:
- Debug 模式下的匯編代碼:
- 首先按照函數調用來分割下,這里實現了結構體的初始化工作:
- 根據結構體的調用慣例,可以知道返回值是通過 rax 和 rdx 兩個寄存器返回的,當然也可以看下這個函數的內部實現來驗證下,可以看出,Debug 模式下對于理解匯編代碼和進行反匯編都是非常友好的,非常耿直的用一個函數調用告訴我們這里實在創建結構體實例;如果是 Release 模式,大概率是直接對 rax 和 rdx 賦值。
- 接下來分別把 metadata 和 Point 類的 PWT 表取出,存到棧上,注意到下一個 call 的函數是 __swift_project_boxed_opaque_existential_1 at ,它的存在是由于我們的這種寫法導致:
- 這里的 p 就是一個 existential 對象,Drawble 協議是一個 existential type,簡單說結論,這個函數調用以后,入參寄存器 rdi 的內容會被賦值給 rax 寄存器來當做返回值。注意這個函數的入參 rdi 寄存器,是由下面幾個關鍵路徑構成的:
- 因此返回值 rax ,其實就是棧基址 rbp 減掉 0x30,這個地址內存貯的值,是結構體的第一個成員變量 x = 1,順便說一下,這個地址向上(高地址方向)偏移 8 字節,存儲的是第二個成員變量 y = 2。
- 下一個關鍵操作是 call rdx,它的取值來源是:
- 這里的 rcx 經過幾次存儲和取出,可以跟蹤到它最初的源頭,就是:
- 從邏輯上看,調用了 PWT 內存地址 + 0x8 位置的函數,具體如下:
- 分析一下:
-
- 首先看下 [rip + 0x189d] 的值是多少,在執行這行命令時,rip 的值是下一行命令的地址,即 0x1073be88b,相加后得到 0x000000010518c128;
-
- 由于 Hopper、MachoView 等工具只能顯示相對便宜,因此要先減去當前程序在內存中的偏移,可以用 image list swift-ui-test 來查看;
-
- 得到結果是 0x4128。
- 因此 0x4128 就是 Point 結構體的 PWT 的位置,可以在 Hopper 中驗證下:
- 這里其實是一個指針數組,第一個指針是 0x100003998,內容如下,暫時沒有深入研究其中存儲內容的含義,但是可以看出名字是:protocol conformance descriptor for swift_ui_test.Point : swift_ui_test.Drawable in swift_ui_test:
- 第二個指針是 0x100002ff0,跳轉過去看下:
- 從 demangle 后的結果也能看出來,這是一個遵守了協議的證明(Protocol Witness),遵守的協議函數是:Drawable.draw() -> Swift.Int,結構體是 Point,協議名是 Drawable,因此 call rdx 實際上就是調用 call 0x100002ff0。
- 再來對比下入參和參數,rax 被作為 r13 傳入,函數內部分別把 r13 和 r13 + 8 的位置讀出來,放入 rdi 和 rsi 寄存器。正如前文所述,r13/rax 這個地址上,存儲的是 x 的值,+0x8 則存儲了 y 的值,因此可以理解為把結構體 p 傳入。
- 最后調用 $s13swift_ui_test5PointV4drawSiyF 這個函數符號,內部邏輯有點啰嗦,猜測是 Debug 環境導致,但本質上就是一個加法運算。
三、結論
- PWT 是為了解決協議方法調用在編譯時無法確定地址,而引入的中間層;
- 每個遵守了協議的類,都會有自己的 PWT。遵守的協議中函數越多,PWT 中存儲的函數地址就越多;
- 準確來說,PWT 是指針數組,但是第一個指針并不是函數指針,而是 protocol conformance descriptor,從第二個開始才是函數指針;
- 對協議方法的調用,首先會調用一個 PWT address + offset 這個函數,這個函數被叫做 protocol witness,它的內部會做一些參數處理,最后再調用真實的函數;
- 對于實際被調用的來說,只看它的內部實現,無法和其它函數做出區分,但是可以觀察它的 caller,如果是一個 protocol witness 就可以說明。
總結
以上是生活随笔為你收集整理的iOS逆向之Protocol Witness Table的汇编实现原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Swift之深入解析如何使用Xcode和
- 下一篇: Swift之深入解析如何进行多重条件排序