iOS逆向?qū)崙?zhàn)--015:代碼注入

一般修改原始程序感论,會利用代碼注入的方式焚鲜,注入代碼就會選擇利用FrameWorkdylib等三方庫的方式注入垦页。

注入原理

當(dāng)運行重簽名的App時雀费,想讓它觸發(fā)當(dāng)前項目中的代碼,例如:寫在ViewController的中代碼痊焊,這是不可能的盏袄。因為項目中的App在安裝時會被重簽名App替換掉,它們根本不是一個MachO文件薄啥。

代碼注入原理:

使用MachOView分析可執(zhí)行文件

wx8.0.2.ipa中導(dǎo)出WeChat可執(zhí)行文件辕羽,拖入MachOView,可能會出現(xiàn)沒有權(quán)限的錯誤提示

解決方法垄惧,使用MachOView菜單中的File -> Open...

選擇WeChat可執(zhí)行文件

成功解析WeChat中的內(nèi)容

當(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注入代碼

打開自動重簽名WeChat項目履恩,創(chuàng)建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í)行片吊,因為MachOLoad Commands中,還沒有插入HOOK動態(tài)庫的LC_LOAD_DYLIB字段

使用yololibMachO注入動態(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!

查看MachOLoad 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注入代碼

打開自動重簽名WeChat項目,創(chuàng)建dylib動態(tài)庫

命名HOOK

Build Settings中威酒,將Base SDK設(shè)置項修改為iOS

Code Signing Identity設(shè)置項修改為iOS Developer

切換到AppTarget窑睁,選擇Build Phases,點擊+葵孤,選擇New Copy Files Phase

Copy FilesDestination中担钮,選擇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

利用OCRuntime特性赡模,動態(tài)改變SEL(方法編號)和IMP(方法實現(xiàn))的對應(yīng)關(guān)系田炭,達到OC方法調(diào)用流程改變的目的。主要用于OC方法漓柑。

OC中教硫,SELIMP之間的關(guān)系,就好像一本書的“目錄

  • SEL是方法編號辆布,就像“標(biāo)題”一樣
  • IMP是方法實現(xiàn)的真實地址瞬矩,就像“頁碼”一樣
  • 它們是一一對應(yīng)的關(guān)系

Runtime提供了交換兩個SELIMP對應(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ù)交換兩個SELIMP對應(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)試

找到登錄按鈕的TargetAction

  • TargetWCAccountMainLoginViewController
  • ActiononNext

想要打印密碼框的內(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指向了onNextIMP髓梅,所以代碼中直接調(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指向onNextIMP辜御,但是調(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_onNextonNext進行方法交互
  • hook_onNext函數(shù)中,如果直接調(diào)用函數(shù)璧尸,調(diào)用的不是替換后的IMP咒林,而是hook_onNextIMP,這樣會形成遞歸爷光。所以想調(diào)用原始方法垫竞,要使用objc_msgSendperformSelector方式調(diào)用

真機運行項目,點擊登錄按鈕蛀序,竊取密碼后欢瞪,調(diào)用原始的代碼邏輯

密碼:123456

【方式二】:class_replaceMethod

VConNext方法的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ù)徐裸,直接將onNextIMP替換為hook_onNextIMP遣鼓,將返回的原始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ù)檬姥,獲取onNextIMP曾我,賦值給oldImp
  • 使用method_setImplementation函數(shù),直接賦值hook_onNextIMP
總結(jié)

注入原理:

  • Xcode將注入的動態(tài)庫打包進App包中
  • MachOLoad 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交互SELIMP對應(yīng)關(guān)系,此方式存在隱患鸳劳,如果不在同一個類狰贯,無法調(diào)用原始方法,因為找不到SEL而崩潰
  • 使用class_addMethod添加方法,不至于因為找不到SEL而崩潰涵紊。過程比較復(fù)雜傍妒,不推薦使用
  • 使用class_replaceMethod替換SELIMP
  • 使用method_getImplementationmethod_setImplementation配合,直接重新賦值新的IMP摸柄。邏輯清晰颤练,大部分HOOK框架使用此方式,推薦使用
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末驱负,一起剝皮案震驚了整個濱河市嗦玖,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌跃脊,老刑警劉巖踏揣,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異匾乓,居然都是意外死亡捞稿,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進店門拼缝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來娱局,“玉大人,你說我怎么就攤上這事咧七∷テ耄” “怎么了?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵继阻,是天一觀的道長耻涛。 經(jīng)常有香客問我,道長瘟檩,這世上最難降的妖魔是什么抹缕? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮墨辛,結(jié)果婚禮上卓研,老公的妹妹穿的比我還像新娘。我一直安慰自己睹簇,他們只是感情好奏赘,可當(dāng)我...
    茶點故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著太惠,像睡著了一般磨淌。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上凿渊,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天梁只,我揣著相機與錄音缚柳,去河邊找鬼。 笑死敛纲,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的剂癌。 我是一名探鬼主播淤翔,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼佩谷!你這毒婦竟也來了旁壮?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤谐檀,失蹤者是張志新(化名)和其女友劉穎抡谐,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體桐猬,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡麦撵,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了溃肪。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片免胃。...
    茶點故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖惫撰,靈堂內(nèi)的尸體忽然破棺而出羔沙,到底是詐尸還是另有隱情,我是刑警寧澤厨钻,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布扼雏,位于F島的核電站,受9級特大地震影響夯膀,放射性物質(zhì)發(fā)生泄漏诗充。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一诱建、第九天 我趴在偏房一處隱蔽的房頂上張望其障。 院中可真熱鬧,春花似錦涂佃、人聲如沸励翼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽汽抚。三九已至,卻和暖如春伯病,著一層夾襖步出監(jiān)牢的瞬間造烁,已是汗流浹背否过。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留惭蟋,地道東北人苗桂。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像告组,于是被迫代替她去往敵國和親煤伟。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,601評論 2 353

推薦閱讀更多精彩內(nèi)容

  • 本文涉及內(nèi)容無風(fēng)險木缝,但某信有檢測BundId機制便锨,建議不要大號登錄 本文是建立在應(yīng)用重簽名的基礎(chǔ)上 iOS逆向 應(yīng)...
    Lee堅武閱讀 1,975評論 2 2
  • 代碼注入 一般修改原始的程序,是利用代碼注入的方式我碟,注入代碼就會選擇利用FrameWork或者Dylib等三方庫的...
    king_jensen閱讀 1,384評論 2 5
  • 一放案、代碼注入 重簽名app后自己的殼工程的代碼就被替換掉了(替換了整個MachO),并不會執(zhí)行矫俺。iOS系統(tǒng)是通過d...
    HotPotCat閱讀 1,358評論 0 3
  • 利用LLDB對微信進行分析厘托,然后利用分析的結(jié)果考婴,再逐步講解如何Hook微信的登錄過程,截獲微信密碼催烘。 在上一篇文章...
    一縷清風(fēng)揚萬里閱讀 7,628評論 46 45
  • 我是黑夜里大雨紛飛的人啊 1 “又到一年六月沥阱,有人笑有人哭,有人歡樂有人憂愁伊群,有人驚喜有人失落考杉,有的覺得收獲滿滿有...
    陌忘宇閱讀 8,535評論 28 53