最近,關於 @Steipete 在Radar發佈的帖子,筆者看到不少人在問「你是怎麼理解那個僞代碼的」。筆者想寫博客已經有一段時間了,如今正好就此發表第一篇博文。筆者在一個叫 Hopper 的工具上花了不少時間(這是筆者的必備工具之一),雖然它很神奇,可是剛接觸的時候可能會讓人感受不知所措。本篇博文的目的是幫助那些迴避或不熟悉逆向工程的人填補知識空白。html
你是否曾經疑惑,別人是怎麼獲取下圖所示的私有 API僞代碼的?這實際上很簡單,並且是找出 UIkit中那些煩人錯誤的好方法。使用 Hopper 這樣的工具後,只須要點幾下鼠標,就能獲得僞代碼。更酷的還在後頭。有了 Obj-C runtime 和 lldb,即便不能提升僞代碼的語法正確性,也必定能提升僞代碼的可讀性!讓咱們深刻探討一下吧!編程
Decompilation of a method in UIKit.
在 UIKit 中反編譯一個方法。緩存
摘自 Hopper 主頁的定義:「Hopper 是一種適用於 OS X 和 Linux 的逆向工程工具,能夠用於反彙編、反編譯和調試 32位/64位英特爾處理器的 Mac、Linux、Windows 和 iOS 可執行程序!」用更簡潔的話來講,這表明咱們能夠用一個編譯二進制(你的 iOS app,UIKit 二進制等等)生成你以前看到的僞代碼!sass
反彙編和反編譯有什麼區別?很簡單,反彙編(經過反彙編程序來實現)是指將 opcode (二進制原始字節)轉化成對應的彙編指令(也叫 mnemonics)的過程。下圖展現了一個被反彙編的文件。反編譯(經過反編譯程序來實現)是指將該彙編指令轉化成僞代碼的過程。下圖分別爲同一個文件的反彙編結果與反編譯結果。性能優化
在本文發表時,Hopper的售價只有90美圓,這簡直就是白送。對那些瞭解這個工具的威力的人來講,這個工具徹底能夠賣到幾百美圓。所以,若是你以爲這個價格貴,再好好考慮一下吧!他們也提供功能受限的免費試用版本,不過用來了解本文內容應該夠用了。網絡
在下載並安裝 Hopper 以後,打開並按順序點擊 「File -> Read Executable to Disassemble...」app
點擊 Read Exectuble to Disassemble 來開始反彙編。框架
在這裏,你須要點擊用於試驗的二進制文件並點擊「Open」。在本文中,咱們用的是如下路徑的 UIKit 二進制文件:工具
</Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator8.1.sdk/System/Library/Frameworks/UIKit.framework性能
你可能須要更新一些目錄名稱,不過以上路徑已經提示了大體的方向。這是模擬器使用的 x86 二進制文件,也是本文要關注的文件。ARM 二進制文件儲存在設備上,而且在運行時加載。點擊打開以後,你將會看到下圖所示畫面。UIKit 是個很肥的二進制文件,也就是說它包含了多個二進制文件。在示範的 UIKit 中,包含 x86 32位和64位。咱們將要反彙編32位的文件。確保選中該文件並點擊「Next」。
注意 x86(32位) 和 x86(64位)兩種文件的存在。
在接下來的頁面中點擊「OK」,你就會來到 Hopper 的主界面。Hopper 會開始分析 Mach-O 二進制文件,在這個過程當中,你會看到界面右下角的「Working...」狀態提示。由於 UIKit 是個大文件,可能須要一段時間才能完成分析。若是是小文件,分析很快就能完成。如今,若是你以爲這個界面太費解,不要擔憂,咱們只須要關注整個界面中的三個按鈕。
首先是左側操做面板中的「Labels」和「Strings」。「Labels」會向你展現二進制文件中包含的全部類和方法(你還會看到其餘文件,不過本教程中只須要關注方法簽名)。
下拉查看全部的方法!
如今點擊左側操做面板中的「String」,你會看到 app 中全部的字符串。這就是爲何你絕對不能對 app 中的重要字符串進行硬編碼。
檢查你本身的 app,確保沒有對任何你不想讓別人看到的東西硬編碼!
回到「Labels」界面,注意搜索框。在這裏你能夠搜索任何類和方法的名稱。輸入「UIPopoverPresentationController dimmingView」,查看搜索結果。點擊「-[UIPopoverPresentationController dimmingViewWasTapped:]」簽名,注意主界面就會跳轉到「-[UIPopoverPresentationController dimmingViewWasTapped:]」的反彙編界面。
搜索方法名稱和類。
若是你不瞭解彙編指令,這個界面就沒有太大幫助,請查看界面右上角,注意那個帶有代碼的按鈕。這個按鈕能夠開啓反編譯過程,併產生僞代碼。點下去吧!
此處輸入圖片的描述
沒那麼難用,對吧??
在調試第三方 SDK 或者查看本身的 app 時,這個工具效果很是好。
通常筆者在查找程序錯誤時會採用如下步驟:
在代碼中找出自認爲致使問題的那個文件,而後打開 Hopper
搜索那個類和方法名稱,而後進行反編譯
經過反編譯,我能夠很容易就能收集方法簽名,並在 lldb 中設置它們的斷點(參見 lldb 部分)。
以上就是所有操做,徹底沒有一點刪減。若是你不想再看,能夠到此爲止,不過筆者在工做流程中添加了 lldb 操做來對僞代碼進行更進一步的清理!
首先,下載 @Steipete 最近發佈在 Radar 中的示範代碼。打開項目以後,筆者通常喜歡把調試構架設成「$(ARCHS_STANDARD_32_BIT) 」,由於這樣會讓編譯過程更加友好(前提是已經從給定的二進制文件中反編譯了32位文件)。
好了,通常來講,Hopper 產生的僞代碼就已經能知足你的需求了,不過有時候它會比較難懂,須要進行一些清理工做。這就到了 Obj-C runtime 和 lldb 大顯身手的時候了。
首先,在模擬器中打開示範代碼,經過調試暫停程序執行。暫停後,代碼會轉存到 lldb中,在這裏,你能夠給選定的任何方法簽名設置斷點。輸入「b -[UIPopoverPresentationController dimmingViewWasTapped:]」,給「-[UIPopoverPresentationController dimmingViewWasTapped:]」設置斷點,而後按回車鍵。調試控制檯界面與下圖相似:
b 是設置斷點的簡稱。
如今繼續程序執行,一旦啓動「-[UIPopoverPresentationController dimmingViewWasTapped:]」,就會啓動你所設置的斷點。遵循示例項目中的操做指令(雙擊黃色區域)。
若是進展順利,你就會看到斷點啓動,而後看到方法簽名與對應的彙編指令。爲了好玩,你能夠比較一下 Xcode 的反編匯結果和 Hopper 產生的結果。它們應該基本一致。若是存在不一樣,多是由於它們使用的彙編語法不一樣,一個是 Intel,一個是 AT&T。若是你遇到其餘狀況,請看文末的「其餘」部分。
就是感受挺累的,這段時間,總之,想請假,以爲坐在這裏,也沒很大的意義。
到了這一步,你可能會由於不瞭解彙編而有些擔憂,可是筆者要告訴你,你真的不須要懂。只要有一點兒直覺,加上反覆嘗試,你就能夠完成任務了。換句話說,咱們如今所要作的就是讓反編譯過程稍微簡單一些(替換寄存器等等)。這樣產生的僞代碼會好懂一些,不過若是你遇到僞代碼難懂的狀況,能夠採用一樣的理念對僞代碼進行清理。
好了,如今該左右對照反彙編和反編譯了。筆者經常使用的作法是尋找反編譯的關鍵點,好比說調用 if 語句或者方法的時候。這些關鍵點對應的編譯指令很容易猜到,只須要逐行瀏覽編譯。若是你的關鍵點是一個 if 語句,就去找測試或 cmp(compare)指令。
在本文中,筆者選的是反編譯中的第一個 if 語句,並在 Xcode 的反編譯結果中搜尋測試或 cmp(compare)指令。以下圖所示,筆者找到了一個測試指令。
可能須要嘗試幾回,要有耐心!
如今在那個內存地址(你的地址可能不一樣)用「b 0x148b95c」設置一個斷點。
繼續程序執行,期待你的斷點被啓動。
下一步就會見證 lldb 和 Obj-C runtime 的神奇之處。咱們要清理反編譯結果中大部分艱澀難懂的部分。若是你不熟悉反編譯過程當中的 eax、edi、和 esiare,它們就是 x86 CPU 寄存器,咱們能夠把它們轉存到 lldb 中。若是你看到 r0、r一、r16等等,那些是 ARM 框架。若是這些你全都看不懂,別擔憂,只要把它們和僞代碼匹配就行了。
在 lldb 提示框中輸入「register read」,按回車鍵。
CPU 寄存器。根據你所用的不一樣框架,顯示不一樣名字。
顯示出來的是 CPU 寄存器及其內容值。如今你能夠用 lldb 中顯示的值替代反編譯結果中的寄存器。
不要盲目地更換反編譯的 esi、edi 和其餘寄存器,由於在執行不一樣代碼時,它們可能表明不一樣的值。這就回到了明智選擇關鍵點的重要性。筆者的操做步驟以下:
在一個關鍵點設置斷點,繼續程序執行
轉儲寄存器
用 lldb 生成的內容值替換反編譯結果中關鍵點以上的緩存器
舉個例子,咱們的關鍵點是 dimmingViewWasTapped 方法中的第一個 if 語句,一旦斷點被啓動,轉儲寄存器,替換僞代碼中 if 語句以上的緩存器。若是你跟蹤僞代碼,發現關鍵點以後的緩存器未重置,那就更新這些內容值。
若是你進行這個操做,就會發現 edi 包含委託選擇器,可是 esi 寄存器包含一個 hex 值。這就更加費解了,不過幸虧咱們能夠利用 Obj-C runtime來搞清楚 esi 究竟是什麼。
複製 esi 的內存地址,輸入「po [0x78657dd0 class]」,而後按回車鍵。
Very nice!
太棒了!
啦啦啦,如今咱們知道 esi 是什麼了,而且能夠利用這個值來提高反編譯效果。
筆者發現反編譯說「esi = self」,大家可能已經推斷出esi = UIPopoverPresentationController,可是這個推斷不必定老是成立。並且,若是還不明顯,你能夠嘗試「po [0x78657dd0 anyMethodThatThisClassImplements]」。若是你對某個對象的內部很感興趣,這個操做效果超好。
到了這一步,再說下去就會變成選個新要點的重複說教了,因此筆者打算見好就收了!若有任何問題或反饋,請在推特上聯繫筆者 @bartcone。
有 Hopper 的替代工具嗎?
有的,就是 IDA Pro(HexRays 是他們的反編譯器),不過除非你想一擲千金,否則 Hopper 就是你最好的選擇。他們還提供免費版本,不過只有反彙編器。
個人反編譯和反彙編結果的緩存器顯示的是 r,不是 e
你反編譯或者反彙編了 x86 64位文件。
OneAPM Mobile Insight 以真實用戶體驗爲度量標準進行 Crash 分析,監控網絡請求及網絡錯誤,提高用戶留存。訪問 OneAPM 官方網站感覺更多應用性能優化體驗,想閱讀更多技術文章,請訪問 OneAPM 官方技術博客。
本文轉自 OneAPM 官方博客