前言
上篇文章10-應用重簽名搀罢,我們利用CodeSign終端指令
和Shell腳本
2種方式肪凛,成功實現(xiàn)了對微信app
的重簽名太示,已經(jīng)能夠查看微信的登錄注冊頁面
的UI層級
镀钓,接下來病毡,我們想做些自己的事情,例如注入
自己的代碼中狂,修改
微信注冊
或登錄
的流程凫碌。
一、代碼注入
一般修改原始的程序胃榕,是利用代碼注入
的方式盛险,注入代碼就會選擇利用Framework
或者Dylib
等第三方庫的方式注入。
1.1 FrameWork注入
我們知道勋又,重簽名
app后自己的殼工程
的代碼就被替換
掉了(替換的是MachO二級制執(zhí)行文件)苦掘,那么原有的工程代碼并不會執(zhí)行。iOS系統(tǒng)是通過dyld
(iOS系統(tǒng)提供的動態(tài)鏈接器
)加載MachO可執(zhí)行文件赐写,而加載的過程鸟蜡,首先就是讀取MachO的Load Commands
(其中包括_PAGEZERO膜赃、_TEXT挺邀、_DATA、_LINKEDIT
等)??
App的執(zhí)行過程跳座,除了執(zhí)行自己的工程代碼外端铛,還依賴一些系統(tǒng)基礎(chǔ)庫(例如UIKit,F(xiàn)oudation
等)和第三方的庫(.framwork
或.a
庫等)疲眷,而這些庫最終也是一個MachO
可執(zhí)行文件,分析MachO文件會發(fā)現(xiàn)Frameworks
中的庫在Load Commands
中以LC_LOAD_DYLIB
存在禾蚕,路徑是Frameworks下對應庫??
只要
Load Command
中有對應庫的LC_LOAD_DYLIB
,dyld
就會去對應路徑
下加載庫
狂丝。
知道了這點换淆,那么代碼注入的過程就很明確了??
- 確認目標App的
MachO
執(zhí)行文件的Load Commands
中有對應的LC_LOAD_DYLIB
- 將自己的代碼寫入動態(tài)庫中
1.1.1 注入步驟
- 給自己的殼工程添加一個
target
??LGHook Framework
動態(tài)庫
??注意:這里的殼工程,用的是上篇文章10-應用重簽名中的
shell腳本重簽名
的模式几颜。
-
LGHook
添加LGInject
類倍试,并且重寫+load
方法
如果LGHook
被加載進內(nèi)存,則會打印log
蛋哭。
-
run
县习,查看Produces
中.app
動態(tài)庫LGHook
已經(jīng)在目標App
的Framewroks
中了,接著我們查看MachO
??
- 查看MachO文件
Load Commands
中并沒有LGHook
的LC_LOAD_DYLIB
,所以run
起來躁愿,控制臺中并沒有打印日志叛本。
1.1.2 yololib手動注入
這個時候就需要使用yololib
工具修改MachO
文件,將LGHook
真正加入到目標App的Load Commands
中??
- 拷貝
yololib
和目標App可執(zhí)行文件
到同一目錄
??(可單獨新建
一個文件夾)
然后執(zhí)行命令??
./yololib 目標可執(zhí)行文件 要添加的Framework路徑名稱
例如??
./yololib WeChat Frameworks/LGHook.framework/LGHook
再查看可執(zhí)行文件Load Commands
??
此時就有LGHook
了彤钟。
- 拿取
原始.ipa包
来候,解壓,在Payload
文件夾中找到.app
逸雹,右鍵顯示包內(nèi)容
吠勘,替換成上一步
生成的二進制MachO文件
,再回到Payload
所在目錄峡眶,輸入以下指令生成.ipa
包
zip -ry xx.ipa Payload/
??注意:這一步一定要用
原始.ipa包
剧防,替換原始包里的二進制MachO文件。
- 原始包
微信8.0.2.ipa
辫樱,將后綴改為.zip
峭拘,解壓??
- 右鍵
顯示包內(nèi)容
??
- 替換上一步的
WeChat
Mach-O
??
- 返回到
Payload
所在目錄,輸入指令生成.ipa包
??
- 將上一步生成的.ipa包狮暑,放入APP文件夾中??
cmd+shift+k
先清空XCode項目的緩存鸡挠,再來run
??
解決??
修改
LGHook.framework
所支持的最低版本。
再次run
??
大功告成搬男!??????????????????
小結(jié)
以上步驟大致為??
- 新建殼工程
WeChat
椒功,配置重簽名腳本
- 通過Xcode新建
LGHook.framwork
(注意改支持的版本
),真機運行阴汇,將庫安裝進入APP包 - 通過
yololib
冰悠,對WeChat
的MachO
注入LGHook.framwork
庫路徑。命令 ??$./yololib MachO文件路徑 庫路徑
- 使用原始微信.ipa包褐奴,將第3步生成的
MachO
按脚,替換原始包里面的MachO
,再次打包生成.ipa
包 - 殼工程的
APP文件夾
中敦冬,替換第4步生成的.ipa包
辅搬,直接運行即可。
所有的Framwork
加載都是由DYLD
加載進入內(nèi)存被執(zhí)行的脖旱。注入成功的庫路徑會寫入到MachO
文件的LC_LOAD_DYLIB
中堪遂。
1.2 Dylib注入
除了Framwork
注入外,還能用Dylib
注入萌庆。原理和Framwork
相同溶褪,過程和Framwork
差不多。
- 創(chuàng)建dylib庫踊兜。這里選擇了
macOS
竿滨,為了是庫為動態(tài)庫
??
修改Base SDK
為iOS
??
Code Signing Identity
修改為iOS Developer
??
build Phases
中添加Copy Files
佳恬,增加libLGDylibHook.dylib
??
- LGDylibHook.m添加Hook代碼??
+ (void)load {
NSLog(@"LGDylibHook Success ????????????????");
}
此時run,仍然只是Frameworks
中有l(wèi)ibLGDylibHook.dylib
庫于游,MachO
中Load Commands
仍然沒有LC_LOAD_DYLIB
毁葱。
1.2.1 yololib自動注入
- 直接在
appResign.sh
腳本中添加yololib
的修改MachO
腳本的指令??
#yololib修改MachO文件
#./yololib "$TARGET_APP_PATH/$APP_BINARY" "Frameworks/LGHook.framework/LGHook"
./yololib "$TARGET_APP_PATH/$APP_BINARY" "Frameworks/libLGDylibHook.dylib"
將yololib
庫放在工程目錄中??
- 觀察
libLGDylibHook.dylib
是否在Frameworks
中??
MachO
中Load Commands
??
- 運行
??注意:需要
先編譯
庫libLGDylibHook.dylib
,再編譯殼工程
小結(jié)
注入步驟
- 通過Xcode新建
dylib
庫(注意:dylib屬于MacOS
贰剥,所以需要修改屬性
)-
Base SDK
為iOS
-
Code Signing Identity
修改為iOS Developer
-
- 添加
Target依賴
倾剿,讓Xcode將自定義Dylib
文件打包進入APP
包 - 利用yololib進行注入
二、示例演示
接下來蚌成,我們實戰(zhàn)一下前痘,針對微信的注冊登錄頁面
,代碼HOOK注冊和登錄
的流程担忧。
2.1 注冊分析
首先是注冊
芹缔,我們查看注冊按鈕的UI層級
??
上圖,打印地址可知瓶盛,發(fā)現(xiàn)注冊按鈕的Target
是WCAccountLoginControlLogic
最欠,action
是onFirstViewRegister
。
然后我們直接hook
這個這個方法就可以攔截了注冊事件??
#import "LGDylibHook.h"
#import <objc/runtime.h>
@implementation LGDylibHook
+ (void)load {
NSLog(@"LGDylib Hook success");
//攔截微信注冊事件
Method oldMethod = class_getInstanceMethod(objc_getClass("WCAccountLoginControlLogic"), @selector(onFirstViewRegister));
Method newMethod = class_getInstanceMethod(self, @selector(hook_onFirstViewRegister));
method_exchangeImplementations(oldMethod, newMethod);
}
- (void)hook_onFirstViewRegister {
NSLog(@"WeChat click login");
}
@end
運行看看??
成功攔截惩猫!接下來芝硬,我們想在攔截到事件后,先執(zhí)行自己的代碼轧房,然后恢復原有的流程拌阴,怎么做到呢?相信大家都能想到 ?? Method Swizzle奶镶。
Method Swizzle
在OC中迟赃,SEL
和 IMP
之間的關(guān)系,就好像一本書的目錄
实辑。SEL
是方法編號捺氢,就像標題
一樣藻丢。IMP
是方法實現(xiàn)的真實地址剪撬,就像頁碼
一樣。他們是一一對應
的關(guān)系悠反。
Runtime
提供了交換
兩個SEL
和IMP
對應關(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
對應關(guān)系的技術(shù)稱之為Method Swizzle(方法欺騙)
。
2.2 登錄調(diào)試分析
接下來我們調(diào)試下【登錄】功能??
上圖可知斋否,view debug
分析可以得到Target
是WCAccountMainLoginViewController
梨水,action
是onNext
。同理茵臭,【登錄】事件可以攔截拿到疫诽,那么pwd
怎么獲取呢?
view debug
可以看到pwd
控件是WCUITextField
并且能看到對應的text
(即密碼
)。假如我們不借助view debug
奇徒,怎么去獲取密碼呢雏亚?
2.2.1動態(tài)分析
第一種方式就是動態(tài)分析
?? 可以通過響應鏈
一步步分析控件層級
和響應關(guān)系
??
結(jié)合presponder
和pviews
分析。不過這種方式一般比較麻煩
摩钙。
2.2.2靜態(tài)分析
第二種就是靜態(tài)分析
了 ?? 使用class-dump
工具導出頭文件
(??swift文件不行
)罢低。
./class-dump -H WeChat -o ./Headers
執(zhí)行完成后,所有頭文件都在Headers文件夾
中??
文件過多22335個胖笛,
不推薦Xcode
打開网持。
- 搜索
WCAccountMainLoginViewController
,找到onNext
??
onNext
方法既沒有參數(shù)也沒有返回值长踊,再看看有沒其它的和密碼相關(guān)的??
我們發(fā)現(xiàn)了_textFieldUserPwdItem
不就是密碼的輸入框嗎功舀。
- 搜索
WCAccountTextFieldItem
沒有找到WCUITextField相關(guān)的內(nèi)容,接著找父類WCBaseTextFieldItem
找到了WCUITextField
類型的m_textField
成員變量身弊。這個不就是輸入賬號密碼
的控件日杈。
- 調(diào)試驗證
接下來我們來驗證下,是否是輸入密碼的控件佑刷??? KVC
控制臺打印查看??
相關(guān)指令??
(lldb) po [(WCAccountMainLoginViewController *)0x117813000 valueForKey:@"_textFieldUserPwdItem"]
<WCAccountTextFieldItem: 0x2830f9ec0>
(lldb) po [(WCAccountTextFieldItem *)0x2830f9ec0 valueForKey:@"m_textField"]
<WCUITextField: 0x1120a4400; baseClass = UITextField; frame = (20 0; 345 44); text = 'qwer1234'; opaque = NO; autoresize = W+H; tintColor = UIExtendedSRGBColorSpace 0.027451 0.756863 0.376471 1; gestureRecognizers = <NSArray: 0x2818beca0>; layer = <CALayer: 0x281441dc0>>
(lldb) po [(WCUITextField *)0x1120a4400 text]
qwer1234
果然找到了對應的類
和想要的密碼
莉擒。????????????????
2.3 登錄代碼注入
??注意:別用
自己的常用賬號
去嘗試,有可能被封號
瘫絮。
最后涨冀,也是重點,注入自己的代碼??
+ (void)load {
NSLog(@"LGDylib Hook success");
//攔截微信登錄事件
Method oldMethod = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext));
Method newMethod = class_getInstanceMethod(self, @selector(hook_onNext));
method_exchangeImplementations(oldMethod, newMethod);
}
- (void)hook_onNext {
NSLog(@"WeChat click login");
//獲取密碼
UITextField *textField = (UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"];
NSString *pwd = [textField text];
NSLog(@"password: %@",pwd);
}
接下來麦萤,在這個過程中我們應該調(diào)用回原來的方法鹿鳖,讓登錄
進行下去??
- (void)hook_onNext {
NSLog(@"WeChat click login");
//獲取密碼
UITextField *textField = (UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"];
NSString *pwd = [textField text];
NSLog(@"password: %@",pwd);
[self hook_onNext];
}
運行??
報錯:找不到方法!WCAccountMainLoginViewController
中不存在hook_onNext
方法壮莹。因為??
一般情況下我們進行
方法交換
在分類
中進行翅帜,現(xiàn)在不是在分類中
.
那有沒有解決方案呢?當然有命满,且有3種??
class_addMethod方式
利用AddMethod
方式涝滴,讓原始方法可以被調(diào)用,不至于因為找不到SEL而崩潰
??
//1.class_addMethod 方式
+ (void)load {
//攔截微信登錄事件
Class cls = objc_getClass("WCAccountMainLoginViewController");
Method onNext = class_getInstanceMethod(cls, @selector(onNext));
//給WC添加新方法
class_addMethod(cls, @selector(new_onNext), new_onNext, "v@:");
//交換
method_exchangeImplementations(onNext, class_getInstanceMethod(cls, @selector(new_onNext)));
}
//相當于imp
void new_onNext(id self, SEL _cmd) {
//獲取密碼
UITextField *textField = (UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"];
NSString *pwd = [textField text];
NSLog(@"password: %@",pwd);
[self performSelector:@selector(new_onNext)];
}
class_replaceMethod方式
利用class_replaceMethod
胶台,直接給原始的方法替換IMP
??
//2.class_replaceMethod 方式
+ (void)load {
//攔截微信登錄事件
Class cls = objc_getClass("WCAccountMainLoginViewController");
//替換
origin_onNext = class_replaceMethod(cls, @selector(onNext), new_onNext, "v@:");
}
//原始imp
IMP (*origin_onNext)(id self, SEL _cmd);
//相當于imp
void new_onNext(id self, SEL _cmd) {
//獲取密碼
UITextField *textField = (UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"];
NSString *pwd = [textField text];
NSLog(@"password: %@",pwd);
origin_onNext(self,_cmd);
}
method_setImplementation方式
利用method_setImplementation
歼疮,直接重新賦值
原始的IMP
??
//3.method_setImplementation 方式
+ (void)load {
//攔截微信登錄事件
Class cls = objc_getClass("WCAccountMainLoginViewController");
//獲取method
Method onNext = class_getInstanceMethod(cls,@selector(onNext));
//get
origin_onNext = method_getImplementation(onNext);
//set
method_setImplementation(onNext, new_onNext);
}
//原始imp
IMP (*origin_onNext)(id self, SEL _cmd);
//相當于imp
void new_onNext(id self, SEL _cmd) {
//獲取密碼
UITextField *textField = (UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"];
NSString *pwd = [textField text];
NSLog(@"password: %@",pwd);
origin_onNext(self,_cmd);
}
以上3種方式都可以成功回到原來的登錄流程。大家可以自行驗證诈唬。
Demo(包含微信.ipa原始包)
??注意:微信ipa包體過大韩脏,無法上傳gitHub,如果有需要铸磅,請留言發(fā)郵箱或私信我赡矢。
總結(jié)
- 代碼注入:Framework(推薦)/ dylib
- 創(chuàng)建
Framework/dylib
-
yololib
修改Macho Load Commands
./yololib 要修改的可執(zhí)行文件 要添加的Framework/dylib路徑&名稱
- 創(chuàng)建
- 調(diào)試分析
-
動態(tài)
調(diào)試:view debug
調(diào)試控件層級
結(jié)合presponder
和pviews
-
靜態(tài)
分析:通過class-dump
導出頭文件
分析代碼邏輯
-
- 代碼Hook
-
+ load
方法中hook對應類的對應方法 -
hook
方法中調(diào)用回原來的方法class_addMethod
class_replaceMethod
method_setImplementation
-
相關(guān)鏈接
yololib
class-dump官網(wǎng)
class-dump github
class-dump 兼容Swift版本