四 代碼注入

nx_001.jpeg

上一篇文章中,在上一篇文章中,我們講了iOS應用重簽名技術,同時我們也把微信的越獄ipa包安裝到了我們的手機上了,那么,這一章,我們就來討論討論代碼注入相關的話題吧。

接下來本文會從以下幾點進行闡述:

  • Framework注入
  • yololib手動注入
  • Dylib注入
  • MethodSwizzle
  • 破壞微信注冊

準備工作: 本篇文章需要用到以下工具
越獄微信7.0.2 提取碼:hyya
MacchOView
yololib
class-dump

1.Framework注入

一般修改原始的程序,是利用代碼注入的方式,注入代碼就會選擇利用FrameWork或者Dylib等三方庫的方式注入。

什么是Framework這里就不多加敘述,我參考這個網(wǎng)站,非常詳細,看不懂你直接@我。點這里:Framework最強講解

接下來直接演示如何創(chuàng)建一個Framework,并且介紹跟咱們Hook微信有關的基礎原理。

1.1 新建項目,完成重簽名過程

根據(jù)上一篇文章,我們學會了腳本自動代碼重簽名,這也是本次要用到的內容,不理解的小伙伴可以移步看看iOS代碼重簽名相關。

首先,我們要創(chuàng)建一個項目,名稱叫做FrameworkInject,并且使用腳本對項目重簽名。(這里我們還是簽上一篇文章中介紹的微信項目)。

由于上一篇文章我們簡單的介紹了下項目的目錄結構,所以 這里我們使用Xcode快捷鍵 command+b 編譯一下,可以看到項目的目錄下出現(xiàn)了Temp文件夾,依次打開 Temp->Payload->WeChat右鍵顯示包內容,找到 WeChat 的可執(zhí)行文件,使用 MachOView 將WeChat打開,下一步我們簡單分析下 MachO這個可執(zhí)行文件。

1.2 MachO文件簡單分析,注意庫加載邏輯

1.將 WeChat 文件使用 MachOView 打開會得到如下的界面:

四 MachO文件預覽.png

2.此時我們只需要關注 Load Commands中的內容就可以了,通過觀察我們會發(fā)現(xiàn),Load Commands下面有許多 LC_LOAD_DYLIB(**)的東西,這里就是這個MachO文件執(zhí)行時需要加載的庫的地址了(這里對MachO文件不做過的的分析,后面會出文章重新分析),可以看到有系統(tǒng)庫也有WeChat自己定義的庫,如圖:TXLiteXX這個庫就是騰訊自己的庫。
四 MachO文件預覽Load Commands.png

注意:微信用的第三方庫有的是在app包里面,有的是在iOS系統(tǒng)里,當程序加載的時候回去 LC_LOAD_DYLIB 這里面查找?guī)斓穆窂?,只有在這里的庫才會被加載,否則不加載。

1.3 注入自己的三方庫。

1.在當前項目中新建framework,打開項目,選擇Targets,選擇下面的添加按鈕。
四 Framework注入1.png

2.選擇iOS下面的Framework。
四 Framework注入2.png

3.添加自己的Framework。
四 Framework注入3.png

注意:
在我們用XCode新建CZHook的時候,其實XCode幫我們做了一部操作:創(chuàng)建CZHook時候,同時將CZHook鏈接到我們的項目中(這是后期的XCode新增功能,早年的XCode這一步是需要我們自己做的)
如圖:

四 Framework注入4.png

4.common+b Build一下,會發(fā)現(xiàn)在已經(jīng)Build出來的文件中的Frameworks下已經(jīng)有CZHook了,已經(jīng)已經(jīng)表明CZHook被Copy我們的ipa文件了。如圖:
四 Framework注入5.png

但是CZHook在ipa文件中,并不代表著CZHook就可以被我們的可執(zhí)行文件所執(zhí)行,因為CZHook并沒有沒導報入我們的可執(zhí)行文件,只有在這個可行執(zhí)行文件的某一個地方做好標記,告知可執(zhí)行文件,在適當?shù)臅r候需要加載外部的CZHook,才能夠正常運行。
而這個地方所說的可執(zhí)行文件就是MachO文件,我們可以利用工具MachOView來查看MachO中到底有什么內容。結果肯定是找不到的,所以,下一步我們來將CZHook文件標記到我們的 MachO文件中。

5.這里我們就需要用到終端命令行工具 yololib

將下載下來的yololib.zip解壓后得到的yololib放在??目錄/usr?/local?/bin?下,這樣我們在終端中就可以使用yololib命令了

四 Framework注入6.png

以下命令就是將FYHook注入WeChat的命令

// yololib 「MachO路徑」 「CZHook相對MachO的路徑」
yololib WeChat Frameworks/CZHook.framework/CZHook
1.4 使用yololib手動注入。

這里有個細節(jié)需要注意下,我們使用腳本重新簽名,每次重新簽名的文件都在目錄Temp下,所以我們修改Temp下的文件是沒有用的。

這里我們要修改APP目錄下的ipa才可以。

1.解壓APP目錄下的*.ipa包,依次進入Payload->WeChat顯示包內容。

2.在當前文件夾下執(zhí)行命令 yololib WeChat Frameworks/CZHook.framework/CZHook。在MachOView中查看,出現(xiàn)下圖則說明成功。
四 Framework注入7.png

3.推出到Payload文件目錄下執(zhí)行 zip -ry WeChat.ipa Payload,完成之后將 ipa替換到 APP文件目錄下。

4.在 CZHook 中添加文件繼承與NSObject,并添加以下代碼

+(void)load{
    NSLog(@"來了 。。。。 ");
}

如圖:
四 Framework注入8.png

5.運行項目,當項目輸出 來了 。。。。這句話的時候說明我們手動注入代碼成功。

參考代碼01FrameworkInject

2.Dylib注入

接下來我們來講一下Dylib注入,Dylib也是常見的注入方法,只是Dylib注入不屬于iOS注入的方法,而是屬于Mac的注入方法,以下是步驟:

1.首先,我們要創(chuàng)建一個項目,名稱叫做DylibInject,并且使用腳本對項目重簽名。

2.創(chuàng)建新的TARGET,這里選擇 macOS 的 Library.
四 Dylib注入1.png
注意:
1. Dylib是給 macOS 使用的庫,所以默認不支持iOS使用,我們需要手動設置兩個地方。
2. 選擇 CZHook -> Build Settings,將Base SDK 改成 iOS。此時我們會發(fā)現(xiàn)這個庫支持運行在手機上了。
3. 修改CZHook的簽名信息,選擇 CZHook -> Build Settings,選擇 ALL 搜索 sign,會發(fā)現(xiàn) Code Signing Identity是使用的Mac develop證書簽名的,我們要把它改成iOS develop證書簽名。如下圖:
四 Dylib注入3.png

四 Dylib注入2.png

3.此時,先選擇CZHook,command+b Build一下,把CZHook庫編譯出來,然后選擇我們的項目工程,再次Build一次,然后查看Products下的*.app,選擇show in finder 查看信息。

4.此時查看 libCZHook.dylib 庫是有的,但是我們要去看安裝包中的Framework中有沒有l(wèi)ibCZHook.dylib這個庫。目前是沒有這個庫的。

5.原因是我們需要自己把libCZHook.dylib這個庫添加到我們的可執(zhí)行文件中,操作方法如下:

  • 在TARGETS中選擇我們的工程項目
  • 選中Build Phases,點擊加號選擇New Copy Files Phase。添加我們的libCZHook.dylib庫


    四 Dylib注入4.png

6.此時 Build一下,此時會發(fā)現(xiàn)libCZHook.dylib出現(xiàn)在我們項目可執(zhí)行文件中了,但是沒有在Frameworks這個文件夾中。

7.然后我們去腳本中注入libCZHook.dylib這個庫。在腳本最后添加

yololib "$TARGET_APP_PATH/$APP_BINARY" "Frameworks/libCZHook.dylib"

8.此時,我們command+r 運行一下,我運行成功,沒有問題。

注意:

  • 假如這里報錯,在 dyld 加載的時候報錯,我們要查看報錯的信息,看看是否加載了,或者沒有加載成功,這里可能會遇到這個庫被加載了,但是沒有加載成功(可能是簽名信息有誤,沒有成功)。
  • 原因是我們使用腳本對Frameworks中的庫多簽名了一次,這里就沒有必要使用Xcode再次幫我們拷貝一份新的庫了,所以這里我們需要自己手動把這個包放到Framework中去,然后根據(jù)上一篇文章的方法把ipa重新打包一下。

參考代碼02DylibInject

3.MethodSwizzle 簡介

在前面的案例中,我們不停的注入代碼,添加自己的代碼,添加自己的邏輯,此時我們會用到Objective-C中的MethodSwizzle,這里我們簡單介紹一下。

利用OC的Runtime特性,動態(tài)改變SEL(方法編號)和IMP(方法實現(xiàn))的對應關系,達到OC方法調用流程改變的目的。主要用于OC方法。

在OC中,SEL 和 IMP 之間的關系,就好像一本書的“目錄”。
SEL 是方法編號,就像“標題”一樣。IMP是方法實現(xiàn)的真實地址,就像“頁碼”一樣。他們是一一對應的關系。
如圖:

四 MethodSwizzle1.png

Runtime提供了交換兩個SEL和IMP對應關系的函數(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對應關系的技術,我們稱之為Method Swizzle(方法欺騙)

四 MethodSwizzle2.png

3.1 Objecive-C 消息案例(簡單解析)

在OC中,函數(shù)調用我們可以理解成消息轉發(fā),就是給某一個對象發(fā)送一個消息,如下圖:
四 MethodSwizzle3.png

在簡單了解一下后,我們來擼一個真實的案例如下:

1.使用NSUrl發(fā)送請求。這個請求是沒有問題的。
NSURL *url2 = [NSURL URLWithString:@"www.baidu.com"];
NSURLRequest *request = [NSURLRequest requestWithURL:url2];
NSLog(@"%@",request);

2.模擬NSUrl有中文的情況。這時url不轉碼的話會為空,此時報錯。
NSURL *url2 = [NSURL URLWithString:@"www.baidu.com/中文"];
NSURLRequest *request = [NSURLRequest requestWithURL:url2];
NSLog(@"%@",request);

3.此時我們怎么處理呢? 因為使用 URLWithString:的地方比較多,找起來很不好處理,這時我們可以用消息轉發(fā)的方式,我們自己給他添加轉碼的邏輯。hook 怎個項目中URLWithString的方法。

4.使用分類處理。

分類代碼如圖:
四 MethodSwizzle4.png
// 代碼如下
+(void)load{
    // 獲取方法
    Method urlWithStr = class_getClassMethod(self, @selector(URLWithString:));
    
    Method czUrlWithStr = class_getClassMethod(self, @selector(CZUrlWithString:));
    
    // 交換方法
    method_exchangeImplementations(urlWithStr, czUrlWithStr);
}

+(instancetype)CZUrlWithString:(NSString *)str{
    // 調用系統(tǒng)原來的方法
    NSURL *url = [NSURL CZUrlWithString:str];
    // 判斷url是否為空,轉碼
    if (url == nil) {
        str = [str stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
    }
    url = [NSURL CZUrlWithString:str];
    
    return url;
}

參考代碼03MethodSwizzleDemo

4.ViewDebug、LLDB、class-dump分析微信登錄頁面

4.1.class-dump介紹和簡單使用

class-dump,是可以把Objective-C運行時的聲明的信息導出來的工具。其實就是可以導出.h文件。用class-dump可以把未經(jīng)加密的app的頭文件導出來。

將class-dump拷貝到Mac的目錄/usr?/local?/bin?下,這樣我們在終端中就可以使用class-dump命令了。
四 class_dump1.png

1.在左面創(chuàng)建空的文件夾 WeChatHeadrs。并進入到當前的文件夾。

2.進入到 WeChat的可執(zhí)行文件夾中,運行命令將WeChat所有的頭文件導出來。

// class-dump -H 「app的MachO文件」 -o 「輸入的目錄」
class-dump -H WeChat -o /Users/XX/desktop/WeChatHeaders
四 class_dump2.png

4.2.通過ViewDebug、LLDB來找到微信的登錄按鈕,并Hook其方法。

4.2.1 嘗試Hook微信的注冊方法。

任務

1.先將微信項目跑起來,通過ViewDebug找到微信的注冊按鈕。
2.向微信的注冊按鈕事件中添加一句話。

操作如下:

1.運行我們的注入項目,參照本篇文章第一部分。
四 ViewDebug調試1.png

2.選中注冊按鈕,這時會發(fā)現(xiàn)注冊按鈕的Clase Name為FixTitleColorButton這個是微信自定義的按鈕。

3.按鈕的Target保存了按鈕添加在哪個視圖,通過底下的po命令輸出,我們會發(fā)現(xiàn)按鈕被添加到了控制器WCAccountLoginControlLogic上,分析Action地址發(fā)現(xiàn)按鈕執(zhí)行的函數(shù)叫做onFirstViewRegester。

4.這時就要用到我們通過class-dump獲取到的頭文件了,搜索 @interface WCAccountLoginControlLogic文件。找到 onFirstViewRegester。如圖:

四 ViewDebug調試2.png

5.接下來,我們就來Hook onFirstViewRegester這個方法了。

Hook代碼:
+(void)load{
    NSLog(@"來了 。。。。 ");
    //錯誤寫法,注意 "WCAccountLoginControlLogic" 前面不能加@
//    Method oldMethod = class_getInstanceMethod(objc_getClass(@"WCAccountLoginControlLogic"), @selector(onFirstViewRegester));

    Method oldMethod = class_getInstanceMethod(objc_getClass("WCAccountLoginControlLogic"), @selector(onFirstViewRegester));
    
    Method newMethod = class_getInstanceMethod(self, @selector(CZHookOnFirstViewRegester));

    method_exchangeImplementations(oldMethod, newMethod);
}

-(void)CZHookOnFirstViewRegester{
    NSLog(@"檢測到異常,不能注冊!!!!!");
}

參考代碼04FrameworkInject(動態(tài)調試Hook微信注冊按鈕)

4.2.2 嘗試Hook微信的登錄賬號、密碼。

操作步驟類似于上面的4.2.1,結果如下圖:
四 ViewDebug調試3.png

接下來我們來分析下登錄注冊文本框事件。

由以上內容的出,我們現(xiàn)在所在的類中,具體的文本框代碼不清楚,這時就需要我們去分析頭文件了。

首先,我們來找一下WCAccountMainLoginViewController這個類的頭文件,看看有什么收獲吧。

1.如圖,我們找到了類似賬號、密碼登錄的控件,但是此時并不敢確定,還是要用ViewDug去調試一下,發(fā)現(xiàn)文本框的類型并不是WCAccountTextFieldItem類型

四 ViewDebug調試4.png

2.我們接著去找WCAccountTextFieldItem的頭問津啊查看情況,會發(fā)現(xiàn)WCAccountTextFieldItem里面的信息更少了,但是我們發(fā)現(xiàn)這個類繼承于WCBaseTextFieldItem,我們去看看吧。

四 ViewDebug調試5.png

3.在 WCBaseTextFieldItem中我們發(fā)現(xiàn)了WCUITextField類型的變量m_textField,此時和我們要找的控件類型一致了,然而,還是讓我們去驗證一下吧 。。

四 ViewDebug調試6.png

4.我們通過KVC的方式從WCAccountMainLoginViewController獲取WCAccountTextFieldItem。

po [(WCAccountMainLoginViewController *)0x13606fa00 valueForKey:@"_textFieldUserNameItem"]

5.從WCAccountTextFieldItem中獲取WCUITextField

po [(WCAccountTextFieldItem *)0x281c18d20 valueForKey:@"m_textField"]

最終結果如下:
四 ViewDebug調試7.png

6.最終,我們的效果如下:
四 ViewDebug調試8.png
部分代碼:
#import "CZHookInject.h"
#import <objc/runtime.h>

@implementation CZHookInject

+(void)load{
    Method oldMyNext = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext));
    
    Method newMyNext = class_getInstanceMethod(self, @selector(hook_myNext));
    
    method_exchangeImplementations(oldMyNext, newMyNext);
}

-(void)hook_myNext{
    NSString *userNmaeStr = [[[self valueForKey:@"_textFieldUserNameItem"] valueForKey:@"m_textField"] performSelector:@selector(text)];
    
    NSLog(@"輸入的用戶名是:%@",userNmaeStr);
}

7.當獲取完用戶的密碼的時候,我們需要代碼走之前的邏輯,這時我們需要調用原來的方法,但是怎么調用呢?是調用hook_myNext方法嗎?會發(fā)生什么呢?

參考代碼05FrameworkInject(動態(tài)調試Hook微信賬號密碼)

4.2.3 解決崩潰

崩潰原因介紹:當我們調用hook_myNext的方式時,WCAccountMainLoginViewController的方法列表中沒有hook_myNext方法的實現(xiàn),所以崩潰了,這里不做多解釋。

解決崩潰的方法:

  1. 通過動態(tài)添加方法解決
  2. 通過方法替換完成交換
  3. 通過getIMP和setIMP方式解決

添加方法的方式解決
代碼如下:

/* 添加方法
 +(void)load{
     Method oldMyNext = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext));
     
     // 添加新方法
     BOOL didAddMethod = class_addMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(hook_myNext), hook_myNext, @"v@:");

     // 獲取新添加的方法
     Method newMethod = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(hook_myNext));
     
     method_exchangeImplementations(oldMyNext, newMethod);
 }

 void hook_myNext(id self,SEL _cmd) {

     NSString *userNmaeStr = [[[self valueForKey:@"_textFieldUserNameItem"] valueForKey:@"m_textField"] performSelector:@selector(text)];

     NSLog(@"輸入的用戶名是:%@",userNmaeStr);

     [self performSelector:hook_myNext];
 }

替換方法

1.定義一個函數(shù)指針,保存之前的函數(shù)
 2.獲取之前的函數(shù)
 3.替換之前的函數(shù)
 
 缺點:
 如果你的交換沒有onNext方法,使用 replaceMethod 會默認幫你添加一個空方法
 
代碼:
IMP (*old_onNext)(id self,SEL _cmd);

+(void)load{
    // 1,拿到原始的imp
    old_onNext = method_getImplementation(class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext)));
    
    // 替換imp
    class_replaceMethod(objc_getClass(@"WCAccountMainLoginViewController"), @selector(onNext), my_next, @"v@:");
}

void my_next(id self,SEL _cmd){
    NSString *userNameStr = [[[self valueForKey:@"_textFieldUserNameItem"] valueForKey:@"m_textField"] performSelector:@selector(text)];
    NSLog(@"輸入的用戶名是:%@",userNameStr);
    
    // 調用之前的函數(shù)
    old_onNext(self,_cmd);
}

通過getIMP和setIMP方式解決

setIMP 和 getIMP 方法
/*
 1.拿到舊的方法的IMP
 2.保存舊的IMP
 3.設置IMP
 */
 
IMP (*old_onNext)(id self,SEL _cmd);

+(void)load{
    // 1.拿到原始的method,以下兩句代碼效果一樣,下面的不會報警告
//    Method onNext = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext));
    Method onNext = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), sel_registerName("onNext"));
    
    // 2.保存舊的IMP
    old_onNext = method_getImplementation(onNext);
    
    // 3.設置IMP
    method_setImplementation(onNext, (IMP)my_next);
}

void my_next(id self,SEL _cmd){
    NSString *userNameStr = [[[self valueForKey:@"_textFieldUserNameItem"] valueForKey:@"m_textField"] performSelector:@selector(text)];
    NSLog(@"輸入的用戶名是:%@",userNameStr);
    
    // 調用之前的函數(shù)
    old_onNext(self,_cmd);
}

參考代碼06FrameworkInject(解決hook的崩潰)

警告!
非越獄狀態(tài),玩逆向微信不要真的登錄,有被警告甚至封號風險

參考文章:
作者:一縷清風揚萬里
原文地址:http://www.itdecent.cn/p/31232eef35c5

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容