1. 程式人生 > >iOS 破殼 反編譯 黑客技術大揭祕

iOS 破殼 反編譯 黑客技術大揭祕

原地址

分享內容簡介:

在黑客的世界裡,沒有堅不可破的防護系統,也沒有無往不勝、所向披靡的入侵利器,有時候看似簡單的問題,破解起來也許花上好幾天、好幾個月,有時候看似很 low 的工具往往能解決大問題;我們以實現微信自動搶紅包為引子,逐步展開 iOS 黑客入侵常用的幾種武器,並簡單的講解一些常用的反入侵策略,以及如何破解反入侵策略,雖然搶紅包的破解程式碼網上有很多,但是我們要講的是這些程式碼是用什麼工具分析出的,為什麼要這樣寫?

內容大體框架:

  1. 讓目標程式破繭而出 -- dumpdecrypted
  2. 執行時分析 -- cycript
  3. 追蹤神器 -- logify
  4. 反彙編工具 -- hopper
  5. 斷點除錯工具 -- lldb+debugserver
  6. 注入工具 -- insert_dylib + install_name_tool

分享人介紹:何兆林 通訊充值與彩票業務部 iOS開發工程師。

負責過的產品: QQ彩票、QQ電影票 iOS客戶端,目前主要負責 QQ彩票 iOS客戶端的開發。

下面是本期分享內容整理

大家晚上好,我是來自 CDG的 jolinhe,目前在做qq彩票專案,負責 iOS側的外掛化和整體架構。

越獄和入侵是我的業餘愛好,正好08和09年的時候,在網龍積累了一些越獄開發經驗,所以今天跟大家分享一下 iOS入侵方面的工具和手段。

為了避免紙上談兵:

  • 第一部分我們就拿微信來開刀,講解一下如何運用這些工具來找到和實現自動搶紅包功能的;
  • 第二部分我們再總結一下常用的入侵原理和反入侵方法。

開始之前,我們需要把環境搞起,硬體方面,需要:

  1. 一臺越獄手機
  2. 一臺裝了開發環境的 Mac電腦

軟體方面內容提綱已經列出來了,需要注意的是它們有一些是手機端的,一些是 PC端的 ,用到的時候我會細講。

群裡不乏老司機,這裡我假設大家對於 ssh、theos、class_dump、hook這些工具和技術都已經有所涉獵了,如果有不清楚的,歡迎私下交流。

一、微信實現自動搶紅包功能

言歸正傳,要實現自動化搶紅包,首先我們需要理清思路:

  • 第一步:需要攔截微信的訊息流,在哪裡攔截?
  • 第二步:在這些資訊流中分辨出哪些訊息是紅包?
  • 第三步:在合適的地方插入自己的“搶”紅包程式碼。

1、讓目標程式破繭而出——dumpdecrypted

因為直接從 AppStore下載下來的二進位制檔案都是加了殼的,所以為了讓它的核心破繭而出,我們需要砸殼操作。

所以第一個工具就是 dumpdecrypted ,這個工具是手機端 的,可以通過 cydia安裝,安裝後文件路徑是:

/usr/lib/dumpdecrypted.dylib

在實際使用時,可以通過 pp助手把這個檔案 copy到目標程式的 documents目錄,然後ssh進手機終端,cd到 documents目錄,執行:

DYLD_INSERT_LIBRARIES=dumpdecrypted.dylib WeChatPath

執行完後會再 documents目錄生成一個砸完殼後的二進位制檔案

2、執行時分析——cycript

砸完殼之後,我們再 dump出頭檔案,但是微信的類太多了,標頭檔案有幾百個,如此多的標頭檔案,讓人眼花繚亂,所以要找到突破口,我們得縮小範圍,我喜歡用的思路是從 ui入手,先找到微信對話介面的 controller,然後再追蹤 controller中對應的訊息處理函式。

這樣第二個工具 cycript 隆重出場了 ,它也是個手機端 的工具,用於檢視 app執行時資料,大夥兒可以通過 cydia安裝,安裝完之後,ssh到越獄手機的終端。

先找到微信的程序idps aux | grep WeChat再執行:cycript -p 微信的pid

完成了注入,進入到 cycript提供的終端。

這時候進入在手機上進入微信的聊天視窗,例如:

然後在 cycript的終端輸入:

UIApp.keyWindow.recursiveDescription().toString()

結果如下:

列印的是當前的ui樹,隨便找一個節點(樹的中間,為什麼要在中間,大家可以思考下 ),copy它的記憶體地址,例如0x14da3f000

執行:[#0x14da3f000 nextResponder]

會輸出當前節點的下一級事件響應鏈,然後對輸出節點再次呼叫 nextResponder ,直到找到它的檢視控制器xxxcontroller

看到了吧,微信的聊天頁對應的類名是 BaseMsgContentViewController

3、追蹤神器 -- logify

目前為止我們已經鎖定了大致的突破口,下面還需繼續追蹤,找到這個類裡面的訊息處理函式,這裡的思路就是通過向群裡傳送一個訊息,然後觀察 controller中哪些方法被呼叫了。

第三個工具 Logify就是幹這個事情的 ,它是 theos的一個元件,和 theos一起安裝在 pc端的,在 pc的終端輸入:

logify.pl /path/to/BaseMsgContentViewController.h >/out/path/to/Tweak.xm

開啟生成的 tweak.xm檔案,可以看到它其實就是 hook了這個類所有的方法,在方法中注入了 nslog,列印方法的入參和返回值,最後把這個檔案用 theos打包並安裝到手機中,再次向群裡發訊息,手機連上 xcode觀察手機控制檯輸出。

輸出內容很多,需要仔細過濾一下,例如我們發的訊息是一個文字“test”,在控制檯搜尋它,你會在它附近找到下面這個函式呼叫:

addMessageNode:layout:addMoreMsg

從函式名字來看,這個應該就是用來處理訊息資料的,從表面來看我們已經找到訊息的攔截點了,但是大家想一下,如果我們hook這個方法,在裡面自動搶紅包,會有什麼致命的缺陷?

老司機們已經看出來了吧,這個方法是 BaseMsgContentViewController類裡面的,而這個類進入聊天頁面才會被建立。

hook這裡會有兩個缺陷:

  • 第一,必須進特定的群的聊天頁面才能生效;
  • 第二,兩個群同時有紅包來,沒法併發的搶。

為了追求更加上流的功能,我們需要再向上追溯訊息的源頭,最好 hook那種微信啟動後就存在的物件,怎麼追溯呢?

我的思路是通過在這個方法中設定斷點,通過呼叫棧,來找到上層的呼叫者。

4、反彙編工具——hopper & 斷點除錯工具——lldb + debugserver

第五個工具 lldb + debugserver 顧名思義,debugserver是手機端的(只要你的手機有連過 xcode進行 debug,這個玩意就自動有了),用於監聽 pc端 lldb的連線,來實現遠端除錯。

這個工具要和第四個 hooper(反彙編工具) 結合起來用。

首先 ssh進手機的終端,輸入:

debugserver *:19999-a WeChat

監聽 lldb的連線

然後開啟pc的終端,啟動 lldb並連線:

lldb
process connect connect://deviceIP:19999

如果連線成功,我們就正式進入 debug狀態了。

那麼問題來了,要精確的設定斷點,必須知道這個函式的記憶體地址,這個記憶體地址怎麼搞出來呢?

有個公式:

記憶體地址=程序記憶體基地址+函式在二進位制中的偏移量

上面我們已經連上了 lldb除錯環境,獲取基地址在 lldb中輸入下面的命令:

image list -o -f

這時會輸出很多行資料,找到檔名為 WeChat的模組地址,這裡第一行就是了:

偏移量需要藉助 hooper,pc端的反彙編利器,用 hooper開啟微信的二進位制檔案,等幾分鐘,反彙編完成後,在搜尋框輸入剛找到的函式名: addMessageNode,定位到相應的彙編程式碼,第一列就是偏移量了:

兩個引數都找到後,在lldb中輸入:

br s -a ‘基地址+偏移量’

然後用 “br l ” 確認一下斷點是否設定成功

進入聊天介面,再次向群傳送一個訊息,會發現 ui卡住了,觀察 lldb控制檯,會提示程序被斷住了,在 lldb中繼續輸入 bt指令,重點觀察模組名是 WeChat的棧,但是由於沒有符號表,我們只能看到棧的記憶體地址:

想要把記憶體地址還原成函式名,需要兩步:

  • 第一要把記憶體地址轉換成二進位制檔案偏移量
  • 第二步再使用 hooper根據偏移量找到函式名

也就是上面公式的逆向過程:

函式在二進位制中的偏移量=記憶體地址-程序記憶體基地址

這裡我們棧地址是0x0000000101ad02f4 ,基地址是0x00000000000e8000 ,做下減法,得到結果0x1019E82F4 ,然後在 hooper中搜索這個地址就能得到方法名:

以此類推,最後得到棧頂的呼叫是這個:

[CMessageMgrMainThreadNotifyToExt:]

CMessageMgr這個類,從名字來看,就是訊息管理器,而且是單例的,我們終於找到了真正需要 hook的類,但是這個方法是用來非同步傳送通知的,不像是訊息的源頭,所以我們用上面說的 logify元件繼續追蹤一下這個類

過程不再重複講,最後的目標終於浮出水面:

(void)AsyncOnAddMsg:(id)message MsgWrap:(CMessageWrap*)msgWrap

第一步訊息的攔截點終於找到了,接下來是第二步:

**第二步:**我們需要知道哪些訊息是紅包,這個就比較簡單,向群裡發一個普通訊息和紅包訊息,通過 logify元件觀察 message引數的內容,發現它裡面有一個 type欄位,如果是紅包,值是49,其他語音和文字各不相同,所以,這裡輕鬆搞定。

第三步:需要實現搶紅包程式碼,這一步稍微複雜一點,先講一下思路,首先進入微信開紅包的介面:

根上面講過的一樣,通過 cycript+logify 我們可以輕鬆拿到開紅包的入口函式,下一步,我們需要自己從AsyncOnAddMsg 的引數中構造搶紅包函式的入參。

找注入點我就不再重複講,直接上結果:

[WCRedEnvelopesReceiveHomeViewOnOpenRedEnvelopes]

這顯然是一個事件處理函式,它裡面肯定會呼叫真正的拆紅包邏輯

所以我們開啟 hooper,找到這個方法然後觀察方法體,發現它在最後呼叫了一個 selector:

WCRedEnvelopesReceiveHomeViewOpenRedEnvelopes

這應該是真正的拆紅包邏輯,但是這個 selector沒有引數,所以我猜想這個方法的作用應該是自己封裝拆紅包所需的資料,並呼叫底層服務來拆紅包。

在hooper中搜索這個方法,觀察一下,果然是這樣的:

函式開始部分的彙編程式碼都是在構造dictionary,只有在最後呼叫了一個可以函式:

這裡說明一下,由於微信這一塊的程式碼全都是 oc的,而 hooper可以直接將 oc反彙編到接近原始碼的水平,所以,還原過程基本不需要掌握多少彙編知識,具體的還原過程可以看下我之前發的文章:http://dev.qq.com/topic/577e0acc896e9ebb6865f321

最後一步,編寫 tweak,替換 AsyncOnAddMsg函式並把自己的成果注入進去就 ok了。

5、注入工具——insert_dylib + install_name_tool

對於越獄機器來說,到這裡已經大功告成了,但是想要在非越獄機器上跑,還需要幾個步驟:

在非越獄機上面,一切都要靠自己,首先手動把你的庫注入到目標二進位制中,這一步使用 insert_dylib 就可以了,它執行在pc端 ,在命令列 cd到微信的二進位制目錄,執行命令:

insert_dylib [@executable_path](/user/name/executable_path)/xxx.dylib WeChat

因為我們的 hook程式碼是基於 cydiaSubstrate的,用l -L xxx.dylib來檢查一下你的 tweak,這個依賴庫在正版機上是沒有的,我們需要把它從越獄機充 cp出來和你的 tweak一起拖進目標 app目錄,並通過install_name_tool 命令修改你的 tweak中對他的引用路徑。

最後,用 codesign命令對微信 bundle裡面所有的 dylib進行簽名:

codesign -f -s "iPhone Developer:xxx" xxx.dylib

打包成ipa:

xcrun -sdk iphoneos PackageApplication-v WeChat.app -o ~/WeChat.ipa

再使用 iResign對 ipa進行簽名,就可以安裝到非越獄的機器上了。

注意一下,如果你不想使用 iResign,在執行 xcrun之前,還需要對微信的二進位制檔案進行簽名:

codesign -f -s "iPhone Developer:xxx” —entitlements Entitlements.plist WeChat.app

經常有人問 Entitlements.plist檔案怎麼寫?照著這個來就行了,把 teamed和 bundle id改成你自己的:

簽名完成後可以通過ldid命令檢視簽入的內容:

ldid -e /path/to/WeChat

到這裡入侵圓滿結束!

二、常用入侵原理和反入侵方法總結

下面我們總結一下用到的幾個工具:

  • dumpdecrypted
  • insert_dylib
  • cycript

第一個工具是砸殼用的 ,程式碼是開源的,而且相當簡單,它並沒有破解 appstore的加密演算法,而是把自己注入到已經通過系統載入器解密的 mach-o檔案,再把解密後的記憶體資料 dump出來,詳細的原理這篇文章寫的很清楚:http://bbs.iosre.com/t/dumpdecrypted/465

那他是怎麼注入的呢?就是利用了 iOS系統中 DYLD_INSERT_LIBRARIES 這個環境變數,如果設定了DYLD_INSERT_LIBRARIES 環境變數,那麼在程式執行時,動態連結器會先載入該環境變數所指定的動態庫;也就是說,這個動態庫的載入優先於任何其它的庫,包括 libc。

由於這個環境變數指定的動態庫載入的時機實在是太早了,所以對於 app來說,除了程式碼混淆外,無良策;

但是我們可以在程式碼中通過判斷環境變數來檢測是不是被注入:

charchar *env = getenv("DYLD_INSERT_LIBRARIES");

如果方法返回非空,我們可以做一些上報之類的。

後面兩個工具都是用來注入的

insert_dylib通過向 mach-o檔案的 loadcommand段插入 LC_LOAD_DYLIB資料來載入第三方庫。

對於 insert_dylib,我們可以通過在 Xcode的Build Settings中找到“Other Linker Flags”在其中加上-Wl,-sectcreate,__RESTRICT,__restrict,/dev/null指令來繞過 dyli