逆向block是一個稍微難點的活,因為你并不知道需要傳什么樣的參數(shù),也不知道是什么類型的返回。另外,Swift的block的內(nèi)存分布的實現(xiàn)與OC不一樣會帶你入坑。
接著上回逆向系列0x01-在Swift中使用Social框架,我們將實現(xiàn)SLComposeViewController的completion回調(diào)。
crash?
打開ViewController.swift把下面邏輯加入到sharingButtonTapped(_:)中:
...
vc.completionHandler = {
print("SLComposeViewController completed")
}
present(vc, animated: true)
...
請確保你已經(jīng)在設(shè)備上登陸了Facebook賬號(假設(shè)我們使用的的serviceType是Facebook的)。構(gòu)建并運行,點擊分享按鈕,然后在這個分享vc出現(xiàn)后點擊取消。
嗯,你又會遇到一個crash了。我們來用LLDB分析一下,選擇Frame 1:(lldb) frame select 1

仔細(xì)觀察這里的匯編,紅色線的上一個指令是個call指令,地址為對RDI內(nèi)容的偏移。那現(xiàn)在RDI里面存放的是個什么鬼呢?
跳回到第一個棧幀(你不能在frame 1訪問寄存器RDI):
(lldb) frame select 0然后打印RDI:
(lldb) po $rdi輸出結(jié)果為:
(Function)。有點意思,來看看它是否是個NSObject的子類:
(lldb) po [$rdi class]
_SwiftValue
(lldb) po [$rdi superclass]
NSObject
從我先前的文章常用lldb可以知道,這個類應(yīng)該繼承自一個叫NSBlock的私有類。于是編譯器對Swfit的閉包要轉(zhuǎn)換成的OC類型做了一個錯誤的假設(shè)。
這里的意思是你需要顯示的將Swift閉包轉(zhuǎn)換成OC的block。這是因為Swift編譯器沒有completionHandler的類型信息,所以它沒有幫我們自動轉(zhuǎn)換。
沒錯,在上篇的NSObject+P_SLComposeViewController.h中我們的確聲明了completionHandler的類型為id。
打開ViewController.swift用以下內(nèi)容進(jìn)行替換:
@IBAction func sharingButtonTapped(_ sender: Any) {
guard let vcClass =
NSClassFromString("SLComposeViewController") else { return }
let vc = vcClass.composeViewController(forServiceType:
"com.apple.social.twitter") as! UIViewController
vc.setInitialText("Yay! Doggie Love!")
if let originalImage = imageView.image {
vc.addImage(originalImage)
}
typealias CompletionBlock = @convention(block) () -> Void
vc.completionHandler = {
print("SLComposeViewController completed")
} as CompletionBlock
present(vc, animated: true)
}
我們增加了一個叫CompletionBlock的typealias,它將會把這個Swift閉包轉(zhuǎn)換成何時的OC對象。再次構(gòu)建并運行app,程序再不會crash了!
OC block的參數(shù)
我們先了解以下調(diào)用OC block時的匯編指令。
在Xcode中創(chuàng)建一個GUI斷點在給completionHandler賦值的那一行。構(gòu)建并運行app然后點擊分享按鈕。這個時候斷點就會命中一次,Continue忽略這一次繼續(xù)執(zhí)行。

接著按Cancel取消按鈕,這時候斷點會再一次被觸發(fā)。去到frame2:(lldb) frame select 2
仔細(xì)觀察這里的匯編,會發(fā)現(xiàn)一個有趣的指令。沒錯了,就是下面紅框標(biāo)注的那一行。

在調(diào)用OC的block之前,一個參數(shù)會被保存到RSI寄存器中。這意味著這個OC block有一個參數(shù)。
在當(dāng)執(zhí)行停止時RSI寄存器沒有被重寫的前提下,我們可以打印出RSI的內(nèi)容。又因為我們剛好停在block執(zhí)行的最開始,所以RSI寄存器的內(nèi)容應(yīng)該是完好如初的。
跳回到frame 0并打印RSI的內(nèi)容:
(lldb) frame select 0
(lldb) register read rsi
rsi = 0x0000000000000000
那兒并沒有任何東西。這似乎有點悲劇。但它至少能告訴我們一些東西。在經(jīng)過一番研究折騰蘋果是如何實現(xiàn)他們的API之后,我們可以假設(shè)一個為nil的NSObject被傳進(jìn)去了,又或者它是某種東西。在這個情形里,它可能是個枚舉值enum來標(biāo)記vc是否正常結(jié)束。
只有一種途徑來驗證這個猜想。我們需要一次成功的內(nèi)容推送然后觀察RSI寄存器有什么變化。
那就推送吧(反正圖片是兩只可愛的狗狗,非常河蟹),點擊Post之后斷點應(yīng)該再一次命中。此時再來檢驗一下RSI的值:
(lldb) register read rsi
rsi = 0x0000000000000001
完美!現(xiàn)在它的值是1了。這意味著這個傳進(jìn)來的參數(shù)是個會根據(jù)操作結(jié)果是失敗還是成功而變化的枚舉值(或者布爾值,差不多的)。好了我們要更新一下completionHandlerblock的類型了。
打開NSObject+P_SLComposeViewController.h然后用以下內(nèi)容替換:
typedef enum : NSUInteger {
P_SLComposePostFailed = 0,
P_SLComposePostSuccess = 1
} P_SLComposePost;
@interface NSObject (P_SLComposeViewController)
+ (id)composeViewControllerForServiceType:(NSString *)serviceType;
- (BOOL)setInitialText:(id)text;
- (BOOL)addImage:(id)image;
@property (copy, nonatomic) void (^completionHandler)(P_SLComposePost);
稍微改一下ViewController.swift:
vc.completionHandler = { result in
let resultString = result == P_SLComposePostFailed ? "failed" :
"success"
print("SLComposeViewController completed with result: \(resultString)")
}
注意到我們再也不需要typealias了。這是因為我們已經(jīng)把completionHandler的類型從id改為了了一個block類型,于是編譯器便知道了應(yīng)該把這里的Swift閉包看待成OC的block。
大功告成!我們成功的探索并使用了別人的代碼來發(fā)送一個Facebook的post,這個過程中我們并沒有借助任何頭文件或者其他關(guān)于模塊的信息。
下篇預(yù)告:查找和執(zhí)行代碼只是逆向Framework時的挑戰(zhàn)之一。下一個挑戰(zhàn)便是修改Framework的函數(shù)的參數(shù)或邏輯來滿足我們自己的需求!暴走吧!逆向!