一般修改原始程序感论,會利用代碼注入的方式焚鲜,注入代碼就會選擇利用FrameWork
或dylib
等三方庫的方式注入垦页。
注入原理
當(dāng)運行重簽名的App
時雀费,想讓它觸發(fā)當(dāng)前項目中的代碼,例如:寫在ViewController
的中代碼痊焊,這是不可能的盏袄。因為項目中的App
在安裝時會被重簽名App
替換掉,它們根本不是一個MachO
文件薄啥。
代碼注入原理:
使用
MachOView
分析可執(zhí)行文件從
wx8.0.2.ipa
中導(dǎo)出MachOView
,可能會出現(xiàn)沒有權(quán)限
的錯誤提示
解決方法垄惧,使用
MachOView
菜單中的File -> Open...
選擇
成功解析
當(dāng)
dyld
加載可執(zhí)行文件時刁愿,先讀取MachO
中的Header
,獲取MachO
類型到逊。然后讀取Load Commands
铣口,通過讀取__PAGEZERO
、__TEXT
觉壶、__DATA
脑题、__LINKEDIT
等段,可以得到MachO
的大小铜靶,代碼段和數(shù)據(jù)段的位置叔遂,告知dyld
應(yīng)該如何將MachO
加載到內(nèi)存中。當(dāng)dyld
讀取代碼段時争剿,通過讀取LC_MAIN
找到程序入口已艰。所以讀取Load Commands
幾乎可以讀取到MachO
的所有信息
dyld
除了加載MachO
,還要加載UIkit
秒梅、Foundation
等系統(tǒng)庫旗芬,以及Frameworks
目錄下的動態(tài)庫
在
Load Commands
中舌胶,列出MachO
所依賴的所有系統(tǒng)庫以及三方庫
所以
dyld
會加載Load Commands
中包含的所有庫捆蜀。如果將注入的代碼包裝成一個動態(tài)庫,將其插入到Load Commands
中幔嫂,理論上動態(tài)庫可以被加載辆它,注入的代碼也可以被執(zhí)行
Framework注入
Framwork
注入流程:
- 通過
Xcode
新建Framwork
,將庫安裝進入App
包- 通過
yololib
注入Framwork
庫路徑./yololib MachO文件路徑 庫路徑
- 所有的
Framwork
加載都是由dyld
加載進入內(nèi)存被執(zhí)行的- 注入成功的庫路徑會寫入到
MachO
文件的LC_LOAD_DYLIB
字段中
案例1:
使用
Framework
注入代碼打開自動重簽名
Framework
動態(tài)庫
命名
HOOK
動態(tài)庫中創(chuàng)建
Class
锰茉,命名Inject
打開
Inject.m
文件,寫入以下代碼:#import "Inject.h" @implementation Inject +(void)load{ NSLog(@"\n\n\n\n\n??????????\n\n\n\n\n"); } @end
編譯項目切心,在
App
包中的Frameworks
目錄飒筑,可以找到HOOK.framework
動態(tài)庫
此時
HOOK
動態(tài)庫中的代碼還不能被執(zhí)行片吊,因為MachO
的Load Commands
中,還沒有插入HOOK
動態(tài)庫的LC_LOAD_DYLIB
字段
使用
yololib
給MachO
注入動態(tài)庫將
MachO
文件协屡,拷貝到yololib
文件的同級目錄
注入動態(tài)庫
./yololib WeChat Frameworks/HOOK.framework/HOOK ------------------------- 2021-04-22 17:14:52.339 yololib[22774:33058817] dylib path @executable_path/Frameworks/HOOK.framework/HOOK 2021-04-22 17:14:52.340 yololib[22774:33058817] dylib path @executable_path/Frameworks/HOOK.framework/HOOK Reading binary: WeChat 2021-04-22 17:14:52.341 yololib[22774:33058817] Thin 64bit binary! 2021-04-22 17:14:52.341 yololib[22774:33058817] dylib size wow 72 2021-04-22 17:14:52.341 yololib[22774:33058817] mach.ncmds 124 2021-04-22 17:14:52.341 yololib[22774:33058817] mach.ncmds 125 2021-04-22 17:14:52.341 yololib[22774:33058817] Patching mach_header.. 2021-04-22 17:14:52.399 yololib[22774:33058817] Attaching dylib.. 2021-04-22 17:14:52.399 yololib[22774:33058817] size 71 2021-04-22 17:14:52.399 yololib[22774:33058817] complete!
查看
MachO
的Load Commands
俏脊,成功將HOOK
動態(tài)庫插入到最后,路徑是注入時設(shè)置的參數(shù)2
解壓縮
wx8.0.2.ipa
肤晓,將MachO
文件替換爷贫,然后重新打包ipa
將重新打包的
wx8.0.2.ipa
瞻离,拷貝到項目中APP
目錄
真機運行項目窝革,
App
安裝成功,正常運行缀踪,同時打印注入代碼
dylib注入
dylib
注入流程:
- 通過
Xcode
新建dylib
庫(注意:dylib
屬于macOS
盈匾,所以需要修改屬性)- 添加
Target
依賴腾务,讓Xcode
將自定義dylib
文件打包進入App
包- 利用
yololib
進行注入
案例1:
使用
dylib
注入代碼打開自動重簽名
dylib
動態(tài)庫
命名
HOOK
在
Build Settings
中威酒,將Base SDK
設(shè)置項修改為iOS
將
Code Signing Identity
設(shè)置項修改為iOS Developer
切換到
App
的Target
窑睁,選擇Build Phases
,點擊+
葵孤,選擇New Copy Files Phase
在
Copy Files
的Destination
中担钮,選擇Frameworks
點擊
+
,選擇libHOOK.dylib
打開
HOOK.m
文件尤仍,寫入以下代碼#import "HOOK.h" @implementation HOOK +(void)load{ NSLog(@"\n\n\n\n\n???? dylib ????\n\n\n\n\n"); } @end
使用
腳本注入
動態(tài)庫:將yololib
文件箫津,拷貝到項目根目錄
打開
rsign.sh
文件,加入以下代碼:./yololib "$TARGET_APP_PATH/$APP_BINARY" "Frameworks/libHOOK.dylib"
真機運行項目宰啦,
App
安裝成功苏遥,正常運行,同時打印注入代碼
Method Swizzle
利用OC
的Runtime
特性赡模,動態(tài)改變SEL
(方法編號)和IMP
(方法實現(xiàn))的對應(yīng)關(guān)系田炭,達到OC
方法調(diào)用流程改變的目的。主要用于OC
方法漓柑。
在
OC
中教硫,SEL
和IMP
之間的關(guān)系,就好像一本書的“目錄
”
SEL
是方法編號辆布,就像“標(biāo)題
”一樣IMP
是方法實現(xiàn)的真實地址瞬矩,就像“頁碼
”一樣- 它們是一一對應(yīng)的關(guān)系
Runtime
提供了交換兩個SEL
和IMP
對應(yīng)關(guān)系的函數(shù)OBJC_EXPORT void method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
通過這個函數(shù)交換兩個
SEL
和IMP
對應(yīng)關(guān)系的技術(shù),我們稱之為Method Swizzle
(方法欺騙)
多種
HOOK
方式
class_addMethod
方式:讓原始方法可以被調(diào)用锋玲,不至于因為找不到SEL
而崩潰class_replaceMethod
方式:直接給原始的方法替換IMP
method_setImplementation
方式:直接重新賦值新的IMP
竊取登錄密碼
案例1:
點擊登錄按鈕景用,輸出用戶輸入的密碼
延用
Framwork
代碼注入的案例打開
rsign.sh
文件,加入以下代碼:./yololib "$TARGET_APP_PATH/$APP_BINARY" "Frameworks/HOOK.framework/HOOK"
真機運行項目惭蹂,進入登錄頁伞插,使用
Debug View Hierarchy
動態(tài)調(diào)試找到登錄按鈕的
Target
和Action
Target
:WCAccountMainLoginViewController
Action
:onNext
想要打印密碼框的內(nèi)容割粮,必須先找到密碼框的位置,然后通過控件的屬性拿到內(nèi)容
【方式一】:動態(tài)調(diào)試
找到密碼框的位置媚污,找到存儲內(nèi)容的
text
屬性
- 可以通過
響應(yīng)鏈條
穆刻,根據(jù)左側(cè)樹狀圖結(jié)構(gòu),對ViewController.view.subviews
進行遞歸遍歷杠步,找到符合條件的WCUITextField
為止
使用動態(tài)調(diào)試氢伟,雖然也能找到密碼框的位置,但尋找的過程太過繁瑣幽歼,不推薦使用
【方式二】:靜態(tài)分析
目前通過動態(tài)分析朵锣,可以確定登錄按鈕和密碼框都在
WCAccountMainLoginViewController
中使用
class-dump
工具,通過MachO
導(dǎo)出OC
中所有類和方法列表以及成員變量將
MachO
文件甸私,拷貝到class-dump
文件的同級目錄
導(dǎo)出
OC
中所有類和方法列表./class-dump -H WeChat -o ./headers/ ------------------------- 2021-04-25 13:51:24.070 class-dump[31659:33427860] Warning: Parsing instance variable type failed, ready_ 2021-04-25 13:51:26.308 class-dump[31659:33427860] Warning: Parsing instance variable type failed, underlying 2021-04-25 13:51:26.308 class-dump[31659:33427860] Warning: Parsing instance variable type failed, enable ... 2021-04-25 13:52:09.288 class-dump[31659:33427860] Warning: Parsing method types failed, getKeyExtensionList: 2021-04-25 13:52:09.292 class-dump[31659:33427860] Warning: Parsing method types failed, getKeyExtensionList: 2021-04-25 13:52:09.292 class-dump[31659:33427860] Warning: Parsing method types failed, getExtensionListForSelector:
打開
headers
目錄诚些,列出所有導(dǎo)出的文件列表,找到WCAccountMainLoginViewController
文件
打開
WCAccountMainLoginViewController
文件皇型,沒有找到WCUITextField
控件诬烹,但發(fā)現(xiàn)一個WCAccountTextFieldItem
類型控件,名稱為_textFieldUserPwdItem
打開
WCAccountTextFieldItem
文件弃鸦,還是沒有找到WCUITextField
控件绞吁,但它繼承自WCBaseTextFieldItem
類,我們要找的控件很有可能在父類中
打開
WCBaseTextFieldItem
文件唬格,找到WCUITextField
控件
使用代碼之前家破,可以先結(jié)合
動態(tài)調(diào)試
,驗證能否順利獲取到用戶密碼
通過
動態(tài)調(diào)試
购岗,成功獲取密碼框里的內(nèi)容
使用代碼汰聋,獲取用戶密碼
打開
Inject
文件,寫入以下代碼:#import "Inject.h" #import <objc/runtime.h> #import <UIKit/UIKit.h> @implementation Inject +(void)load{ Method wx_onNext = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext)); Method my_onNext = class_getInstanceMethod(self, @selector(hook_onNext)); method_exchangeImplementations(wx_onNext, my_onNext); } -(void)hook_onNext{ UITextField *txtPwd = (UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"]; NSLog(@"密碼:%@", txtPwd.text); } @end
真機運行項目喊积,點擊登錄按鈕
密碼:123456
成功輸出用戶密碼
案例2:
竊取密碼后烹困,希望可以調(diào)用原始的代碼邏輯
日常開發(fā)中,方法交互一般寫在當(dāng)前類的分類中乾吻。由于交互后
hook_onNext
指向了onNext
的IMP
髓梅,所以代碼中直接調(diào)用hook_onNext
即可打開
Inject
文件,修改hook_onNext
方法:-(void)hook_onNext{ UITextField *txtPwd = (UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"]; NSLog(@"密碼:%@", txtPwd.text); [self hook_onNext]; }
但是溶弟,
HOOK
動態(tài)庫中的Inject
文件女淑,和WCAccountMainLoginViewController
類沒有任何關(guān)聯(lián)瞭郑。雖然hook_onNext
指向onNext
的IMP
辜御,但是調(diào)用objc_msgSend
函數(shù)時,給VC
發(fā)送hook_onNext
屈张,程序會因為找不到SEL
而崩潰
下面介紹三種方式擒权,都可以解決上述問題
【方式一】:
class_addMethod
既然
VC
中沒有hook_onNext
方法袱巨,可以使用class_addMethod
函數(shù)給VC
添加一個hook_onNext
,調(diào)用時不會因為找不到SEL
而崩潰打開
Inject
文件碳抄,寫入以下代碼:#import "Inject.h" #import <objc/runtime.h> #import <UIKit/UIKit.h> @implementation Inject +(void)load{ Method wx_onNext = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext)); class_addMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(hook_onNext), hook_onNext, @"v@:"); Method my_onNext = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(hook_onNext)); method_exchangeImplementations(wx_onNext, my_onNext); } void hook_onNext(id self, SEL _cmd){ UITextField *txtPwd = (UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"]; NSLog(@"密碼:%@", txtPwd.text); [self performSelector:@selector(hook_onNext)]; } @end
- 之前的
hook_onNext
方法愉老,改為函數(shù)實現(xiàn),增加OC
方法的兩個隱式參數(shù)- 使用
class_addMethod
函數(shù)剖效,給VC
增加hook_onNext
方法嫉入,函數(shù)名即是IMP
- 將
VC
下的hook_onNext
和onNext
進行方法交互- 在
hook_onNext
函數(shù)中,如果直接調(diào)用函數(shù)璧尸,調(diào)用的不是替換后的IMP
咒林,而是hook_onNext
的IMP
,這樣會形成遞歸爷光。所以想調(diào)用原始方法垫竞,要使用objc_msgSend
或performSelector
方式調(diào)用真機運行項目,點擊登錄按鈕蛀序,竊取密碼后欢瞪,調(diào)用原始的代碼邏輯
密碼:123456
【方式二】:
class_replaceMethod
將
VC
中onNext
方法的IMP
替換打開
Inject
文件,寫入以下代碼:#import "Inject.h" #import <objc/runtime.h> #import <UIKit/UIKit.h> @implementation Inject IMP (*oldImp)(id self, SEL _cmd); +(void)load{ oldImp = class_replaceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext), hook_onNext, @"v@:"); } void hook_onNext(id self, SEL _cmd){ UITextField *txtPwd = (UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"]; NSLog(@"密碼:%@", txtPwd.text); oldImp(self, _cmd); } @end
- 聲明
IMP
類型的oldImp
函數(shù)指針- 使用
class_replaceMethod
函數(shù)徐裸,直接將onNext
的IMP
替換為hook_onNext
的IMP
遣鼓,將返回的原始IMP
賦值給oldImp
- 在
hook_onNext
函數(shù)中,想調(diào)用原始方法重贺,直接調(diào)用oldImp
并傳入隱式參數(shù)即可
【方式三】:
method_setImplementation
獲取原始的
IMP
譬正,重新賦值新IMP
打開
Inject
文件,寫入以下代碼:#import "Inject.h" #import <objc/runtime.h> #import <UIKit/UIKit.h> @implementation Inject IMP (*oldImp)(id self, SEL _cmd); +(void)load{ Method wx_onNext = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext)); oldImp = method_getImplementation(wx_onNext); method_setImplementation(wx_onNext, hook_onNext); } void hook_onNext(id self, SEL _cmd){ UITextField *txtPwd = (UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"]; NSLog(@"密碼:%@", txtPwd.text); oldImp(self, _cmd); } @end
- 使用
method_getImplementation
函數(shù)檬姥,獲取onNext
的IMP
曾我,賦值給oldImp
- 使用
method_setImplementation
函數(shù),直接賦值hook_onNext
的IMP
總結(jié)
注入原理:
Xcode
將注入的動態(tài)庫打包進App
包中MachO
的Load Commands
中健民,包含注入動態(tài)庫的LC_LOAD_DYLIB
字段- 由
DYLD
加載注入的動態(tài)庫注入方式:
Framework
注入dylib
注入
Framwork
注入流程:
- 通過
Xcode
新建Framwork
抒巢,將庫安裝進入App
包- 通過
yololib
注入Framwork
庫路徑
dylib
注入流程:
- 通過
Xcode
新建dylib
庫(注意:dylib
屬于macOS
,所以需要修改屬性)- 添加
Target
依賴秉犹,讓Xcode
將自定義dylib
文件打包進入App
包- 利用
yololib
進行注入案例蛉谜,竊取登錄密碼:
- 分析思路
- 動態(tài)調(diào)試,界面入手
- 靜態(tài)分析崇堵,使用
class-dump
型诚,通過MachO
導(dǎo)出OC
的類和方法列表
Method Swizzle
- 使用
method_exchangeImplementations
交互SEL
和IMP
對應(yīng)關(guān)系,此方式存在隱患鸳劳,如果不在同一個類狰贯,無法調(diào)用原始方法,因為找不到SEL
而崩潰- 使用
class_addMethod
添加方法,不至于因為找不到SEL
而崩潰涵紊。過程比較復(fù)雜傍妒,不推薦使用- 使用
class_replaceMethod
替換SEL
的IMP
- 使用
method_getImplementation
和method_setImplementation
配合,直接重新賦值新的IMP
摸柄。邏輯清晰颤练,大部分HOOK
框架使用此方式,推薦使用