我們知道,程序在啟動的時候會把引用到的庫都放在
Load commands
段當中,所以勘高,通過給這個段增加記錄,就可以注入我們自己寫的庫坟桅。
簡單來說就是通過修改可執(zhí)行文件的Load Commands
华望,增加一個LC_LOAD_DYLIB
,寫入庫路徑仅乓。這樣程序執(zhí)行的時候就會來編譯這個LC_LOAD_DYLIB
找到要注入的庫赖舟。
前期準備:
通過應用重簽完成脫殼應用在手機上的運行,已確保重簽成功夸楣。
一宾抓、framework注入
流程簡介:
通過Xcode
創(chuàng)建新的framework
庫,在庫中新建類裕偿,通過在類中的load
方法里完成代碼編寫洞慎。編譯后將framework
文件放置重簽應用app包中的Frameworks
文件夾中痛单。通過yololib
寫入路徑嘿棘。然后打包app包文件。用腳本重簽并運行
詳細流程:
1旭绒、創(chuàng)建framework
,完成簡單代碼編寫鸟妙。
新建
DyInjectCode
類,編寫簡單代碼挥吵,以驗證注入代碼是否執(zhí)行:
#import "DyInjectCode.h"
@implementation DyInjectCode
+ (void)load{
NSLog(@"");
}
@end
2重父、編譯項目,找到framework
文件忽匈。
這時通過查看工程目錄下的app包內(nèi)
Frameworks
文件夾房午,可見里面存在剛才新建的framework
文件。這里仿佛是已經(jīng)達到了我們的目的丹允,將
DyFramework
放置到了指定的位置郭厌。但是運行時通過查看可執(zhí)行文件袋倔,并沒有發(fā)現(xiàn)這個玩意兒。況且因為腳本重簽的原因折柠,我們項目下的app包都會被解壓的脫殼包給替換宾娜,所以究其根本,我們應該將新建的
DyFramework
放置到脫殼app包文件中的Frameworks
文件夾中扇售。
3前塔、通過yololib寫入庫路徑。
yololib的安裝:網(wǎng)上下載yololib
可執(zhí)行文件承冰,將其放置到/usr/local/bin
文件夾中华弓。(這里固定地址是為了方便后續(xù)腳本編寫)
將DyFramework
放置脫殼應用包中的Frameworks
文件夾中,可得DyFramework
的可執(zhí)行文件的路徑(相對于包文件中)是:
Frameworks/DyFramework.framework/DyFramework
將庫可執(zhí)行文件的路徑寫入可執(zhí)行文件中:
yololib WeChat Frameworks/DyFramework.framework/DyFramework
接著通過查看項目的可執(zhí)行文件可發(fā)現(xiàn)我們自己創(chuàng)建的庫已經(jīng)存在于
load commands
里面了巷懈。4该抒、重新打包文件,運行重簽項目顶燕。便可以看到效果了凑保。
//打包命令
zip -ry WeChat.ipa Payload
腳本自動化執(zhí)行:
以上創(chuàng)建framework
的后續(xù)步驟,可以通過腳本執(zhí)行涌攻,在重簽腳本后面加上:
yololib "$TARGET_APP_PATH/$APP_BINARY" "Frameworks/DyFramework.framework/DyFramework"
一欧引、Dylib注入
是一堆 .o 的集合 ,會被編譯器 編譯成為編譯單元(.o), 每個.o里面有確定的符號和不確定的符號, 所有不確定的符號都在鏈接過程中互相填補恳谎。
dylib
在程序編譯的時候, 并沒有被編譯進二進制目標代碼中, 只有當程序里執(zhí)行相應的函數(shù)才調(diào)用該函數(shù)庫里對應的函數(shù)芝此。
所以具有以下特點:
1.文件體積更小。2.升級會更方便因痛。3.減少編譯時間婚苹。
流程簡介:
創(chuàng)建dylib
,修改BuildSetting
中的部分參數(shù)鸵膏〔采拷貝其文件至脫殼包目錄中的Frameworks
,打包運行即可谭企。
詳細流程:
1廓译、創(chuàng)建Dylib
庫
因為Dylib
是Mac OS
的庫,所以編譯會默認選擇使用Mac的债查。為了讓手機能夠也能夠編譯非区,因此會有如下操作。
-
將
Build Settings
下的Base SDK
更改為iOS
盹廷。
-
修改簽名征绸,為了讓其能夠在iOS平臺上使用。
-
更改
target
為選擇的動態(tài)庫,修改最低支持版本號管怠,編譯得到庫文件剥汤。
2、拷貝文件至Frameworks
中排惨。
這里可以通過添加依賴的方式吭敢,選擇所生成的庫文件,編譯就會把相應的庫文件拷貝至目標文件夾中暮芭。
這時通過查看包文件中的Frameworks
便可以發(fā)現(xiàn)它鹿驼。
這個步驟也可以通過手動拷貝完成。
3辕宏、通過yololib
寫入路徑畜晰。
這里可以通過編寫腳本的方式,在重簽腳本中添加如下代碼:
//同理framework的寫入路徑也可以用腳本完成瑞筐。
yololib "$TARGET_APP_PATH/$APP_BINARY" "Frameworks/libDyounggg.dylib"
也可以手動寫入:
yololib WeChat Frameworks/libDyounggg.dylib
4凄鼻、打包。運行項目聚假,效果如下
三块蚌、通過Swizzle
破壞微信注冊
完成以上任何一種代碼注入的方式之后,我們便可以分析微信運行過程膘格,注入我們的代碼峭范,修改其中功能了。
前期準備:
- 完成之前的任何一種代碼注入方式瘪贱。
-
class-dump
工具纱控,以及其導出微信項目的所有頭文件。 -
sublime text
工具菜秦,完成頭文件的查看甜害。
流程簡介:
運行項目,通過Xcode
的view debug
球昨,選擇注冊按鈕尔店,通過查看或分析其所屬的類以及綁定的方法,完成對方法的替換褪尝。
詳細流程:
1闹获、運行項目期犬,通過view debug
分析河哑。
進入界面,選擇注冊按鈕龟虎,會有如下顯示:
分析可知:
- 通過上紅框處我們可看到注冊按鈕所屬的類以及其對應的地址璃谨。
- 通過下方紅框我們可見按鈕方法所屬的響應者、方法以及它們的地址。在某些
Xcode
版本會有直接響應者和方法名的顯示佳吞。這里沒有拱雏,我們便可以通過命令查看其具體信息:
(lldb) po 0x281563e00
{
className = WCAccountLoginControlLogic;
memoryAddress = 0x28280bed0;
}
(lldb) po 0x281563bc0
{
className = "__NSCFString";
memoryAddress = 0x280ba81e0;
}
(lldb) po 0x280ba81e0
onFirstViewRegester
通過打印地址信息,我們便可以知道按鈕綁定方法的target
是WCAccountLoginControlLogic
底扳,綁定的方法名為:onFirstViewRegester
铸抑。
驗證
將class-dump導出的所有頭文件導入到sublime text
中。搜索WCAccountLoginControlLogic
類衷模,可見:
確定了我們要hook的方法鹊汛,接下來便是對以前的方法做交換了。
2阱冶、通過swizzle
交換注冊方法刁憋。
//class_getInstanceMethod:得到類的實例方法。
//objc_getClass 參數(shù)是類名的字符串木蹬,返回的就是這個類的類對象
//object_getClass 參數(shù)是id類型至耻,它返回的是這個id的isa指針所指向的Class,如果傳參是Class镊叁,則返回該Class的metaClass尘颓。
+ (void)load{
Method systemMethod = class_getInstanceMethod(objc_getClass("WCAccountLoginControlLogic"), NSSelectorFromString(@"onFirstViewRegester"));
Method newMethod = class_getInstanceMethod(self, @selector(newAction));
method_exchangeImplementations(systemMethod, newMethod);
}
//將注冊方法替換為下面的方法.
- (void)newAction{
NSLog(@"");
}
四、竊取登錄時的密碼
我們需要在用戶點擊登錄時晦譬,獲取的用戶密碼泥耀,然后繼續(xù)微信的登錄流暢。
根據(jù)平時的正向開發(fā)經(jīng)驗蛔添,登錄流程大致為輸入賬號痰催、密碼,點擊登錄按鈕迎瞧,獲取密碼輸入框中的密碼夸溶,調(diào)用登錄方法。
所以凶硅,我們這里只需要獲取密碼輸入框的所屬類缝裁,然后再登錄按鈕綁定的Action
里面完成hook
。
1足绅、分析并確定相關類及相關方法捷绑。
同破壞用戶注冊流程相似,先運行重簽代碼氢妈,進入登錄界面粹污,調(diào)出view debug
界面。
選中登錄按鈕首量,得到其相關信息壮吩。
打印
Target
和Action
:
(lldb) po 0x2805b0980
{
className = WCAccountMainLoginViewController;
memoryAddress = 0x10b8f6800;
}
(lldb) po 0x2805b0b40
{
className = NSTaggedPointerString;
memoryAddress = 0xafff44ac59128b09;
}
(lldb) po 0xafff44ac59128b09
onNext
可以確定的是微信登錄按鈕綁定的是WCAccountMainLoginViewController
中的onNext
方法进苍。我們可以繼續(xù)通過打印方法的調(diào)用來驗證我們的推斷是否正確。
通過
SubLime
查看這個類鸭叙,我們也可以找到onNext
方法觉啊。同時我們也可以確定這個方法是沒有參數(shù)的。推斷得出沈贝,密碼是通過控制器來拿的杠人,所以我們應當分析控制器的屬性。接著看頭文件
接著查看
WCAccountTextFieldItem
的相關信息:發(fā)現(xiàn)頭文件沒有我們想要的東西宋下,繼續(xù)查看父類信息:
發(fā)現(xiàn)
WCUITextField
,和下圖我們通過view debug
確定的密碼輸入框同屬一個類搜吧。在微信密碼框輸入密碼,接著我們同樣輸入相關命令驗證自己的推斷杨凑。
(lldb) po 0x2805b0980
<WCAccountMainLoginViewController: 0x2805b0980>
(lldb) po [(WCAccountMainLoginViewController *)0x2805b0980 valueForKey:@"_textFieldUserPwdItem"]
<WCAccountTextFieldItem: 0x282251a40>
(lldb) po [(WCAccountTextFieldItem *)0x282251a40 valueForKey:@"m_textField"]
<WCUITextField: 0x10d975200; baseClass = UITextField; frame = (20 0; 345 44); text = 'fasdafds'; opaque = NO; tintColor = UIExtendedSRGBColorSpace 0.00784314 0.733333 0 1; gestureRecognizers = <NSArray: 0x2809de250>; layer = <CALayer: 0x2806b12a0>>
(lldb) po [[(WCAccountTextFieldItem *)0x282251a40 valueForKey:@"m_textField"] valueForKey:@"text"]
fasdafds
以上滤奈,便可驗證我們推斷正確。接下來便可以編寫hook
代碼撩满。
2蜒程、HOOK代碼編寫的幾種方式。
通過 exchange
交換兩個方法的IMP
指針伺帘。但是這樣破壞之后調(diào)用原有方法時會崩潰昭躺。因為兩個類的方法編號SEL
并沒有改變,所以原有類中并不存在myAction
這個方法伪嫁。
+ (void)load{
Method loginOnNextMethod = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), NSSelectorFromString(@"onNext"));
Method myLoginMethod = class_getInstanceMethod(self, NSSelectorFromString(@"myAction"));
method_exchangeImplementations(loginOnNextMethod, myLoginMethod);
}
- (void)myAction{
NSString * pwd = [[[self valueForKey:@"_textFieldUserPwdItem"]valueForKey :@"m_textField"] performSelector:NSSelectorFromString(@"text")];
NSLog(@"pwd == %@",pwd);
//破壞之后繼續(xù)原來的登錄操作领炫。
// [self myAction];//這里要崩潰,因為WCAccountMainLoginViewController類里面找不到這個方法张咳。
}
針對以上崩潰問題帝洪。我們有以下三種方式解決;
- 通過給原有類添加方法脚猾。
//MARK => 1-通過給原有類添加方法葱峡。
+ (void)load{
Class loginClass = objc_getClass("WCAccountMainLoginViewController");
Method loginOnNextMethod = class_getInstanceMethod(loginClass, NSSelectorFromString(@"onNext"));
//給原有的登錄類添加一個方法編號,將imp指針指向當前類的函數(shù)龙助。
BOOL isSuc = class_addMethod(loginClass,NSSelectorFromString(@"Dy_OnNexy"),myAction,"v@:");
if (isSuc) {
Method new_method = class_getInstanceMethod(loginClass, NSSelectorFromString(@"Dy_OnNexy"));
method_exchangeImplementations(loginOnNextMethod, new_method);
}
}
void myAction(id self,SEL _cmd){
NSString * pwd = [[[self valueForKey:@"_textFieldUserPwdItem"]valueForKey :@"m_textField"] performSelector:NSSelectorFromString(@"text")];
NSLog(@"pwd == %@",pwd);
//破壞之后繼續(xù)原來的登錄操作砰奕。
if ([self respondsToSelector:NSSelectorFromString(@"Dy_OnNexy")]) {
[self performSelector:NSSelectorFromString(@"Dy_OnNexy")];
}
}
- 第一種方式稍顯麻煩。我們可以通過
replace
直接替換原有方法的IMP
指針提鸟。但是這樣的缺點是如果原有方法找不到军援,replace
的內(nèi)部會自動創(chuàng)建一個方法。
+ (void)load{
Class loginClass = objc_getClass("WCAccountMainLoginViewController");
//拿到原有的IMP称勋,并暫時保存胸哥。
old_onNext = method_getImplementation(class_getInstanceMethod(loginClass, NSSelectorFromString(@"onNext")));
//交換原有方法的IMP
class_replaceMethod(loginClass, NSSelectorFromString(@"onNext"), myAction, "v@:");
}
IMP (*old_onNext)(id self,SEL _cmd);
void myAction(id self,SEL _cmd){
NSString * pwd = [[[self valueForKey:@"_textFieldUserPwdItem"]valueForKey :@"m_textField"] performSelector:NSSelectorFromString(@"text")];
NSLog(@"pwd == %@",pwd);
old_onNext(self,_cmd);
}
- 通過
getImp
拿到原有方法的Imp
,并保存到自己定義的Imp
指針中。再通過setImp
給原有方法設置新的Imp
指針铣缠。
+ (void)load{
Class loginClass = objc_getClass("WCAccountMainLoginViewController");
//1烘嘱、拿到原有的method
Method oldMethod = class_getInstanceMethod(loginClass, NSSelectorFromString(@"onNext"));
//2、拿到原有method對應的IMP
old_onNext = method_getImplementation(oldMethod);
//3蝗蛙、給原有的method設置新的IMP
method_setImplementation(oldMethod, myAction);
}
IMP (*old_onNext)(id self,SEL _cmd);
void myAction(id self,SEL _cmd){
NSString * pwd = [[[self valueForKey:@"_textFieldUserPwdItem"]valueForKey :@"m_textField"] performSelector:NSSelectorFromString(@"text")];
NSLog(@"pwd == %@",pwd);
old_onNext(self,_cmd);
}