上一篇咱們已經講到使用shell腳原本重籤並調試別人的APP,那麼咱們又是重籤又是附加調試別人的APP是爲了啥呢?是吃的太飽了嗎,固然不是...咱們接下來的任務就是代碼注入git
在開始代碼注入以前,咱們先了解一下一個iOS的APP在運行的時候,究竟會執行哪些代碼,以及咱們從哪裏入手注入代碼程序員
其中系統庫,在非越獄手機上咱們改不了;MachO咱們能夠修改,可是比較麻煩,須要會寫彙編;而Framework是咱們最容易入手的,咱們本身寫一個就好了...github
那麼如今的問題就是咱們寫的Framework別人的APP怎麼會執行呢?shell
MachO文件裏面有一個Load Commands的部分,DYLD(the dynamic link editor是蘋果的動態連接器,後面的文章也會介紹)會讀取這個Load Commands裏面的內容,並加載到內存中,若是咱們能把咱們本身的Framework插入到別人APP的MachO的Load Commands裏,那麼咱們的代碼就天然的被執行了api
那麼如今的問題就變成了如何把咱們寫的Framework插入到別人App的MachO文件裏?微信
yololib這是一個終端命令行工具,就是用來把咱們寫的Framework插入到MachO文件裏,代碼很少就200來行,感興趣的能夠看看源碼,用法是yololib 參數1 參數2
其中參數1是MachO文件,參數2是咱們的Framework相對於MachO文件的路徑;爲了方便咱們使用這個工具,建議把它放到系統的usr/local/bin目錄下,記得給它提高可執行權限(在終端chmod +x yololib
),這樣咱們任意打開一個終端均可以使用這個命令了,也方便咱們使用腳原本操做;好了,如今完事具有,只欠咱們動手操做了...還記得上一篇文章講到的內容嗎,使用shell腳本重籤APP,咱們能夠接着來,也能夠新建一個項目來執行後面的代碼注入,但前提依然是完成了shell腳本重籤APP的步驟;我這裏新建一個項目來演示後面的代碼注入markdown
在完成了上一篇shell腳本重籤的步驟以後,咱們先來新建一個Framework,把咱們的Framework準備好 Framework的名字就叫FrankyHook(這個名字無所謂,可是你要記住,由於在使用yololib的時候要用到),在裏面新建一個類CodeInject(這個叫什麼也無所謂,繼承NSObject就夠了,主要是須要load方法),並在load方法裏面打印點內容 函數
記得紅框部分的Framework名字,是你本身建立的...(要是照着個人抄的的當我沒說 這裏面有一個小細節,就是你的腳本運行必定要在嵌入Frameworks以前,否則每次腳本運行都把你的Framework給刪掉了...(上面剛用yololib把你寫的Framework路徑加到MachO裏面,這裏腳本放在最後的話立刻就把你的Framework給幹掉了,會報image not found運行不起來的) 實不相瞞,作了四年多的iOS開發,也是到今天才知道這個地方竟然能夠拖動...工具
怎麼樣,是否是發現咱們已經能夠在WeChat裏面執行咱們的代碼了?接下來咱們來實現兩個小小的需求oop
接下來的內容須要對Objective-C的rumtime有必定的瞭解,瞭解的同窗這部分能夠直接跳過,看下一部分
Method Swizzle方法交換
runtime提供了一些api來讓咱們實現方法交換,如今假設有這麼一段代碼: 使用NSURL初始化URL的時候,若是URL中含有中文,那麼初始化就會失敗,返回nil;這個問題作過iOS開發的同窗應該都遇到過,解決的辦法就是對URL進行一次百分號編碼,如今假設一種情景,你剛入職一家新公司,發現了這個問題,而後在工程裏面搜索了一下,發現個人天啦,處處都是這種URL中夾着中文,而沒有進行編碼的狀況,那麼這個時候,你是選擇一個一個的去修改呢,仍是會想其餘更好的辦法?
使用方法交換就是更好的辦法,新建一個NSURL的分類,在分類的load方法中,實現咱們的方法交換 一個OC方法,咱們能夠分爲兩個部分,一個是方法名SEL,一個是方法的實現IMP,正常狀況下,一個方法的名字對應着它的實現,而有時候咱們經過runtime來交換方法的實現,就如上面的load
方法裏面的代碼,默認狀況下方法one
和方法two
的實現都是指向他們本身的IMP的,經過method_exchangeImplementations()
函數交換以後,方法one
的實現就指向了方法two
的實現,而方法two
的實現就指向了方法one
的實現,當代碼調用URLWithString:
的時候,就會來到咱們的HK_URLWithString:
方法,當代碼調用HK_URLWithString:
的時候,就會執行URLWithString:
方法;這樣原來工程裏的全部URLWithString:
方法,都會執行到咱們的代碼邏輯,首先調用一次原始的初始化URL的方法,看可否成功生成url,若是爲nil,就表示咱們可能須要對字符串str進行一下編碼,咱們再用編碼事後的str初始化url,這樣就不用浪費時間精力去一個一個的去修改工程裏的代碼了
使用Debug View Hierarchy查看微信的登陸註冊頁面的視圖層次結構,找到註冊的按鈕,查看按鈕的target和action 咱們知道了註冊按鈕點擊的時候,會調用WCAccountLoginControlLogic
的onFirstViewRegister
方法,並且咱們也已經能夠在咱們的Framework中執行咱們的注入代碼了,那麼接下來咱們如何實現攔截微信的註冊按鈕點擊呢?固然可使用Method Swizzle來實現
在開始寫代碼實現方法交換以前,還有一個小小的問題,雖然咱們根據經驗能夠知道這個onFirstViewRegister
應該是個對象方法,可能沒有返回值,也沒有參數,但這些都只是根據咱們經驗的猜想...我們程序員仍是應該靠譜一點,那麼怎麼驗證咱們的猜想呢?
Class-dump和yololib同樣,也是一個終端命令行工具,一樣也能夠放到usr/local/bin目錄下能夠全局使用,它的做用是能夠把MachO文件裏的頭文件信息所有導出來,咱們能夠cd到工程編譯生成的APP包裏面,使用如下命令
class-dump -H WeChat -o ./headers/
將它的頭文件都導到一個headers的文件夾內,這個過程須要一點時間,耐心等待一下 看了下這個文件挺大的,導完以後能夠把它剪切到工程根目錄下,這樣給咱們手機也能省點空間,也確實徹底不必放在APP包裏面 能夠看到這裏面有15074個項目,微信的頭文件還真很多呢...這麼大的文件夾若是咱們用Xcode打開搜索的話,必定會十分的痛苦,這裏推薦使用sublime...更輕量一點,找起頭文件來也更快,搜索一番發現如圖所示 這樣就確保了這個onFirstViewRegister
方法是無返回值無參數的,能夠開始編寫代碼實現攔截了
如今咱們能夠來到咱們Framework的CodeInject.m文件寫點代碼
代碼就這麼點,如今command + R運行起來以後,再次點擊微信的註冊按鈕試試看?
上面的需求僅僅只是破壞了功能,原來的功能都無法使用了;接下來這個需求讓用戶在神不知鬼不覺的狀況下,帳號密碼就被竊取了;那麼咱們在何時,能拿到用戶的帳號和密碼呢,固然是點擊登陸按鈕的時候,因此你懂的,使用viewDebug查看登陸按鈕 登陸按鈕點擊的target:WCAccountMainLoginViewController
,action:onNext
;
查看剛剛class-dump出來的頭文件發現這個onNext方法是沒有參數的,那麼咱們須要的帳號密碼在哪裏呢?哈哈,作過開發的同窗是否是發現這兩個名字是否是莫名的熟悉
WCAccountTextFieldItem *_textFieldUserNameItem;
WCAccountTextFieldItem *_textFieldUserPwdItem;
複製代碼
變量名是挺熟悉的,可是這個WCAccountTextFieldItem
咱們不知道是個什麼,怎麼辦?頭文件都在手上了,還問怎麼辦?接着搜啊...
WCAccountTextFieldItem
裏面貌似沒啥東西,那就看它父類WCBaseTextFieldItem
在WCBaseTextFieldItem
類裏面發現了一個WXUITextField
的東西,跟咱們熟悉的UITextField
很像了,咱們猜想可能就是這個m_textField
存放着咱們想要的東西,先不着急寫代碼
如今咱們能夠再次使用viewDebug工具調試查看一下咱們的帳號和密碼在哪裏,而且使用lldb來動態調試驗證咱們的猜想 首先控制器的地址,能夠由登陸按鈕的target獲取到;獲取到控制器以後,再使用KVC大法獲取成員變量_textFieldUserPwdItem
的地址(密碼都能獲取到了,帳號也是同樣的操做)並打印它;獲取到_textFieldUserPwdItem
的地址以後,再次使用KVC大法獲取它的成員變量m_textField
的地址並打印這個對象,好傢伙,明文密碼不就在這兒了嗎!!!
接下來咱們經過代碼來實現需求: 這代碼看起來跟攔截微信註冊的代碼差不太多,那麼咱們command + R運行看看結果 WTF?帳號密碼確實是都獲取到了,可是APP缺崩潰了?爲何會發生崩潰,崩潰信息是咱們常常可以遇到的經典報錯unrecognized selector sent to instance 0x10b15b400
,說是控制器WCAccountMainLoginViewController
沒法識別FK_onNext
這個方法;
咱們仔細思考一下,方法交換爲何通常推薦寫在想要交換方法的類所在的分類裏面?由於在想要交換方法的類的分類當中,咱們會新增一個方法,用來實現咱們的邏輯,也正好是由於在分類中,因此當前類也添加了咱們新增的方法,這樣交換下來就不會出現找不到方法的錯誤;而上面的代碼,咱們的本意也是但願在WCAccountMainLoginViewController
裏面新增一個方法處理咱們的邏輯,並交換onNext
方法,但如今的問題是咱們在CodeInject
這個類的load
方法中,那麼有什麼辦法能夠解決這個問題呢?
rumtime的api提供了運行時動態添加方法的能力
BOOL class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types)
參數1: 給哪一個類添加方法
參數2: 方法的名字
參數3: 方法的實現
參數4: 方法的參數和返回值描述,用一些特定的符號表示,也能夠不寫
參數5: BOOL值,表示是否添加成功
那麼借用這個api咱們能想到什麼解決辦法呢,給WCAccountMainLoginViewController
控制器添加咱們的FK_onNext
方法,再讓FK_onNext
和onNext
方法交換,最終的代碼以下 再次command + R運行發現,既能成功獲取到用戶輸入的帳號密碼,又能成功的去調用微信的登陸邏輯了
如今除了添加新方法的方式,咱們還有其餘的辦法嗎?固然有(強大的runtime)
rumtime的api還提供了運行時替換方法的能力
IMP _Nullable class_replaceMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types)
參數1: 替換哪一個類
參數2: 方法的名字
參數3: 方法的實現
參數4: 方法的參數和返回值描述,用一些特定的符號表示,也能夠不寫
返回值: IMP,原始的方法實現
若是使用這個方式,那麼咱們應該是將原始的onNext
方法替換成咱們的FK_onNext
方法,那麼咱們如何去調用微信原始的onNext
方法呢,在替換以前將原始的onNext
方法的實現IMP記錄下來,而後在咱們的FK_onNext
方法中使用這個記錄的IMP來實現對原始onNext
的調用,具體代碼以下圖: 對比方法交換的代碼實現,咱們發現方法替換的方式須要多一個變量originalIMP
,用來記錄原始的onNext
方法的實現,並且還會報個警告,雖然少了一點點代碼,但看起來也不是那麼好理解...固然,這裏使用方法替換也只是爲了學習一下runtime提供的api,感覺一下rumtime的強大,具體使用哪一個方式就看我的的喜愛罷了
那麼,還有更騷的操做嗎?固然有...
IMP _Nonnull method_getImplementation(Method _Nonnull m)
獲取方法m的實現IMP
IMP _Nonnull method_setImplementation(Method _Nonnull m, IMP _Nonnull imp)
設置方法m的實現IMP
這個原理其實跟替換差不太多,首先獲取原始onNext
的實現並記錄,而後設置新的IMP(咱們寫的FK_onNext
)給WCAccountMainLoginViewController
的onNext
方法,具體代碼以下:
好了,到此咱們爲了實現竊取用戶的帳號密碼已經使用了三種runtime提供的方式...到這裏就真的沒了
下一篇文章開始介紹咱們最近一直提到的MachO文件,到底什麼是MachO文件,它包含了什麼東西,幹什麼用的...