iOS逆向 程式碼注入+Hook

我是好寶寶發表於2019-12-04

寫在前面

本文涉及內容無風險,但某信有檢測BundId機制,建議不要大號登入

本文是建立在應用重簽名的基礎上

iOS逆向 應用重簽名+微信重簽名實戰

iOS逆向 Shell指令碼+指令碼重簽名

工具:yololib+class_dump 密碼:8ujj

一、初次注入

程式碼注入有兩種方案:通過FrameWork和dylib

1.指令碼重簽名

照著iOS逆向 Shell指令碼+指令碼重簽名重簽名

2.FrameWork注入

2.1 新建FrameWork

在Xcode中File->Target新增一個Framework

iOS逆向 程式碼注入+Hook

2.2 FrameWork中新建一個類
2.3 新增一個load方法

iOS逆向 程式碼注入+Hook

僅僅這樣還不夠,DYLD會動態載入專案中的Frameworks,但不會載入當前FrameWork

2.4 執行編譯一下

保證FrameWork放到FrameWorks目錄下

iOS逆向 程式碼注入+Hook

2.5 yololib注入動態庫

建議將yololib複製貼上到/usr/local/bin目錄下,可以隨時隨地呼叫

app.sh的最後一句程式碼啟用(注意修改FrameWork名稱)

yololib "$TARGET_APP_PATH/$APP_BINARY" "Frameworks/XXXX.framework/XXXX"
複製程式碼
2.6 執行

不出意外會列印 ❎❎❎❎❎❎❎❎❎❎

iOS逆向 程式碼注入+Hook

3.dylib注入

其實就是換了個Target

3.1 新建Library

iOS逆向 程式碼注入+Hook

3.2 修改dylib的BaseSDK

iOS逆向 程式碼注入+Hook

3.3 修改dylib的簽名

修改成iPhone Developer

iOS逆向 程式碼注入+Hook

3.4 新增依賴

iOS逆向 程式碼注入+Hook

3.5 執行編譯

只有加進來了才算成功了一半

iOS逆向 程式碼注入+Hook

3.6 修改指令碼
yololib "$TARGET_APP_PATH/$APP_BINARY" "Frameworks/libFXHook.dylib"
複製程式碼
3.7 執行

列印 ❎❎❎❎❎❎❎❎❎❎ (有時候會報錯“image notound”,如果FrameWorks包含了dylib則重新執行就好了)

iOS逆向 程式碼注入+Hook

二、Method Swizzling初用

1.定義

在OC中,SEL和IMP之間的關係,就好像一本書的“目錄”。 SEL是方法編號,就像“標題”一樣。 IMP是方法實現的真實地址,就像“頁碼”一樣。 他們是一一對應的關係。 Runtime提供了交換兩個SEL和IMP對應關係的函式method_exchangeImplementations(<#Method _Nonnull m1#>, <#Method _Nonnull m2#>),通過這個函式交換兩個SEL和IMP對應關係的技術,我們稱之為Method Swizzle(方法欺騙)

iOS逆向 程式碼注入+Hook

我更願意把SEL和IMP的關係理解成書的封面和書,原先一本《三國》和《水滸》,在經過方法交換之後翻開《三國》的封面卻是《水滸》的內容

2.程式碼演示

NSURL *url = [NSURL URLWithString:[@"www.Felix.com/好好學習" stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]];
NSLog(@"%@",url);
複製程式碼

比如上述程式碼看起來很繁瑣,這時候就可以用Method Swizzling來實現

2.1 新建NSURL的分類
2.2 方法交換
#import "NSURL+FXUrl.h"
#import <objc/runtime.h>

@implementation NSURL (FXUrl)

+ (void)load {
    // 獲取原來的方法
    Method URLWithString = class_getClassMethod(self, @selector(URLWithString:));
    // 獲取自定義方法
    Method FXURLWithString = class_getClassMethod(self, @selector(FX_URLWithString:));
    // 交換方法
    method_exchangeImplementations(URLWithString, FXURLWithString);
}

+ (instancetype)FX_URLWithString:(NSString *)string {
    NSURL *url = [NSURL FX_URLWithString:string];
    if (!url) {
        string = [string stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
    }
    return [NSURL FX_URLWithString:string];
}

@end
複製程式碼
2.3 疑問點

①為什麼要用分類去做方法交換呢?

下文中將會提及

②自定義FX_URLWithString中是不是遞迴了?

答:load方法執行順序較早,呼叫FX_URLWithString時已經進行了方法交換,想呼叫FX_URLWithString就應該呼叫URLWithString

各位看官可能覺得太簡單了,接下來就回到重簽名專案開始重頭戲

三、Hook微信——破壞微信註冊

目標:點選“註冊”按鈕使之無效

1.獲取到物件名稱和方法名稱

iOS逆向 程式碼注入+Hook
之前文章中有講到過,選中控制元件就能通過地址列印對應的資訊(有可能直接顯示了物件名稱和方法名稱)

2.利用class-dump匯出MachO的標頭檔案

MachO檔案在編譯出來的ipa包中

iOS逆向 程式碼注入+Hook

3.搜尋標頭檔案檢視方法宣告

這裡用的是sublime工具,先全域性找類(找不到就找父類)再找方法

iOS逆向 程式碼注入+Hook

4.交換方法

在第一節注入FrameWork的程式碼中繼續

#import "InjectCode.h"
#import <objc/runtime.h>

@implementation InjectCode

+ (void)load {
    Method oldMethod = class_getInstanceMethod(objc_getClass("WCAccountLoginControlLogic"), @selector(onFirstViewRegister));
    Method newMethod = class_getInstanceMethod(self, @selector(FX_onFirstViewRegister));
    method_exchangeImplementations(oldMethod, newMethod);
}

- (void)FX_onFirstViewRegister {
    NSLog(@"想註冊嗎?想註冊先打錢!");
}

@end
複製程式碼

四、Hook微信——竊取登入密碼

目標:點選“登入”按鈕獲取到賬號密碼並繼續登入

1.分析

列印地址去標頭檔案列表查詢宣告方法

iOS逆向 程式碼注入+Hook

發現這是一個不帶引數的方法,那麼賬號密碼去那裡獲取呢?

iOS逆向 程式碼注入+Hook

我們可以去Viewcontroller的變數中找找線索

發現了兩個可疑的例項變數_textFieldUserNameItem_textFieldUserPwdItem

檢視WCAccountTextFieldItem沒發現什麼有實際意義的內容,那再找找父類吧

iOS逆向 程式碼注入+Hook

WCUITextField *m_textField這個例項變數看起來有點用

iOS逆向 程式碼注入+Hook

怎麼判斷是否是我們要找的賬號密碼呢?

輸入賬號和密碼再ViewDebug除錯一下

如下圖所示,我們找到了寫Hook程式碼的方向

iOS逆向 程式碼注入+Hook

(lldb) po 0x133800600
<WCAccountMainLoginViewController: 0x133800600>

(lldb) po [(WCAccountMainLoginViewController *)0x133800600 valueForKey:@"_textFieldUserNameItem"]
<WCAccountTextFieldItem: 0x28231f180>

(lldb) po [(WCAccountTextFieldItem *)0x28231f180 valueForKey:@"m_textField"]
<WCUITextField: 0x13090d600; baseClass = UITextField; frame = (20 0; 345 44); text = 'Felix'; opaque = NO; autoresize = W+H; tintColor = UIExtendedSRGBColorSpace 0.00784314 0.733333 0 1; gestureRecognizers = <NSArray: 0x280891650>; layer = <CALayer: 0x280612560>>
複製程式碼

2.開始Hook

#import "InjectCode.h"
#import <objc/runtime.h>
#import "UIKit/UIKit.h"

//@interface WCAccountTextFieldItem: NSObject
//
//@end

@implementation InjectCode

+ (void)load {
    Method oldMethod = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext));
    Method newMethod = class_getInstanceMethod(self, @selector(FX_onNext));
    method_exchangeImplementations(oldMethod, newMethod);
}

- (void)FX_onNext {
//    /// 宣告WCAccountTextFieldItem類,為了不報錯
//    WCAccountTextFieldItem *account = [self valueForKey:@"_textFieldUserNameItem"];
//    /// 匯入UIKit框架
//    UITextField *accountTF = [account valueForKey:@"m_textField"];
    
    UITextField *accountTF = [[self valueForKey:@"_textFieldUserNameItem"] valueForKey:@"m_textField"];
    UITextField *passwordTF = [[self valueForKey:@"_textFieldUserNameItem"] valueForKey:@"m_textField"];
    NSLog(@"賬號:“%@”\n密碼:“%@”", accountTF.text, passwordTF.text);
    
    [self FX_onNext];
}

@end
複製程式碼

正當我們滿心歡喜等待神奇的一刻時,熟悉的味道來了

iOS逆向 程式碼注入+Hook
崩潰原因:WCAccountMainLoginViewController找不到FX_onNext的方法編號,即原工程中WCAccountMainLoginViewController沒有FX_onNext宣告

OC方法呼叫有兩個隱藏引數:self(方法呼叫者)、cmd(方法編號),FrameWork中把onNext的imp替換成了FX_onNext,頁面呼叫登入方法來到我們自定義的方法實現;然後給VC傳送FX_onNext訊息,必然是unrecognized selector sent to instance

此時此刻用分類Hook的好處就體現的淋漓盡致,直接給分類加個方法就完事了

3.解決崩潰完成Hook

3.1 class_addMethod方法

利用class_addMethod方法讓原始方法可以被呼叫(麻煩不推薦)

#import "InjectCode.h"
#import <objc/runtime.h>
#import "UIKit/UIKit.h"

@implementation InjectCode

+ (void)load {
    /**
     * 1、給哪個類新增方法
     * 2、方法編號
     * 3、方法實現(地址)
     * 4、v代表Void @代表id型別 :代表@selecter型別(可以在幫助文件檢視這個方法)
     */
    BOOL didAddMethod = class_addMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(FX_onNext), FX_onNext, "v@:");
    
    if (didAddMethod) {
        NSLog(@"新增方法成功");
        Method oldMethod = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext));
        Method newMethod = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(FX_onNext));
        method_exchangeImplementations(oldMethod, newMethod);
    }
}

//方法實現IMP
void FX_onNext(id self, SEL _cmd) {
    UITextField *accountTF = [[self valueForKey:@"_textFieldUserNameItem"] valueForKey:@"m_textField"];
    UITextField *passwordTF = [[self valueForKey:@"_textFieldUserNameItem"] valueForKey:@"m_textField"];
    NSLog(@"賬號:“%@” 密碼:“%@”", accountTF.text, passwordTF.text);
    
    //使用原來邏輯
    [self performSelector:@selector(FX_onNext)];
}

@end
複製程式碼
3.2 class_replaceMethod方法

儲存原始方法,利用replaceMethod方法將原始方法的IMP覆蓋

#import "InjectCode.h"
#import <objc/runtime.h>
#import "UIKit/UIKit.h"

@implementation InjectCode

+ (void)load {
    onNext = method_getImplementation(class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext)));
    class_replaceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext), FX_onNext, "v@:");
}

IMP (*onNext)(id self,SEL _cmd);

void FX_onNext(id self, SEL _cmd) {
    UITextField *accountTF = [[self valueForKey:@"_textFieldUserNameItem"] valueForKey:@"m_textField"];
    UITextField *passwordTF = [[self valueForKey:@"_textFieldUserNameItem"] valueForKey:@"m_textField"];
    NSLog(@"賬號:“%@” 密碼:“%@”", accountTF.text, passwordTF.text);
    
    //使用原來邏輯
    onNext(self,_cmd);
}
複製程式碼
3.3 method_setImplementation方法

儲存原始方法,利用setImplementation方法將原始方法的IMP重寫

#import "InjectCode.h"
#import <objc/runtime.h>
#import "UIKit/UIKit.h"

@implementation InjectCode

+ (void)load {
    onNext = method_getImplementation(class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext)));
    method_setImplementation(class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext)), FX_onNext);
}

IMP (*onNext)(id self,SEL _cmd);

//方法實現IMP
void FX_onNext(id self, SEL _cmd) {
    UITextField *accountTF = [[self valueForKey:@"_textFieldUserNameItem"] valueForKey:@"m_textField"];
    UITextField *passwordTF = [[self valueForKey:@"_textFieldUserNameItem"] valueForKey:@"m_textField"];
    NSLog(@"賬號:“%@” 密碼:“%@”", accountTF.text, passwordTF.text);
    
    //使用原來邏輯
    onNext(self,_cmd);
}

@end
複製程式碼

五、Runtime-API

// 1.objc_xxx 系列函式
// 函式名稱     函式作用
objc_getClass     獲取Class物件
objc_getMetaClass     獲取MetaClass物件
objc_allocateClassPair     分配空間,建立類(僅在 建立之後,註冊之前 能夠新增成員變數)
objc_registerClassPair     註冊一個類(註冊後方可使用該類建立物件)
objc_disposeClassPair     登出某個類
objc_allocateProtocol     開闢空間建立協議
objc_registerProtocol     註冊一個協議
objc_constructInstance     構造一個例項物件(ARC下無效)
objc_destructInstance     析構一個例項物件(ARC下無效)
objc_setAssociatedObject     為例項物件關聯物件
objc_getAssociatedObje*ct     獲取例項物件的關聯物件
objc_removeAssociatedObjects     清空例項物件的所有關聯物件

objc_系列函式關注於巨集觀使用,如類與協議的空間分配,註冊,登出等操作

// 2.class_xxx 系列函式
函式名稱     函式作用
class_addIvar     為類新增例項變數
class_addProperty     為類新增屬性
class_addMethod     為類新增方法
class_addProtocol     為類遵循協議
class_replaceMethod     替換類某方法的實現
class_getName     獲取類名
class_isMetaClass     判斷是否為元類
objc_getProtocol     獲取某個協議
objc_copyProtocolList     拷貝在執行時中註冊過的協議列表
class_getSuperclass     獲取某類的父類
class_setSuperclass     設定某類的父類
class_getProperty     獲取某類的屬性
class_getInstanceVariable     獲取例項變數
class_getClassVariable     獲取類變數
class_getInstanceMethod     獲取例項方法
class_getClassMethod     獲取類方法
class_getMethodImplementation     獲取方法的實現
class_getInstanceSize     獲取類的例項的大小
class_respondsToSelector     判斷類是否實現某方法
class_conformsToProtocol     判斷類是否遵循某協議
class_createInstance     建立類的例項
class_copyIvarList     拷貝類的例項變數列表
class_copyMethodList     拷貝類的方法列表
class_copyProtocolList     拷貝類遵循的協議列表
class_copyPropertyList     拷貝類的屬性列表

class_系列函式關注於類的內部,如例項變數,屬性,方法,協議等相關問題

// 3.object_xxx 系列函式
函式名稱     函式作用
object_copy     物件copy(ARC無效)
object_dispose     物件釋放(ARC無效)
object_getClassName     獲取物件的類名
object_getClass     獲取物件的Class
object_setClass     設定物件的Class
object_getIvar     獲取物件中例項變數的值
object_setIvar     設定物件中例項變數的值
object_getInstanceVariable     獲取物件中例項變數的值 (ARC中無效,使用object_getIvar)
object_setInstanceVariable     設定物件中例項變數的值 (ARC中無效,使用object_setIvar)

objcet_系列函式關注於物件的角度,如例項變數

// 4.method_xxx 系列函式
函式名稱     函式作用
method_getName     獲取方法名
method_getImplementation     獲取方法的實現
method_getTypeEncoding     獲取方法的型別編碼
method_getNumberOfArguments     獲取方法的引數個數
method_copyReturnType     拷貝方法的返回型別
method_getReturnType     獲取方法的返回型別
method_copyArgumentType     拷貝方法的引數型別
method_getArgumentType     獲取方法的引數型別
method_getDescription     獲取方法的描述
method_setImplementation     設定方法的實現
method_exchangeImplementations     替換方法的實現

method_系列函式關注於方法內部,如果方法的引數及返回值型別和方法的實現

// 5.property_xxx 系列函式
函式名稱     函式作用
property_getName     獲取屬性名
property_getAttributes     獲取屬性的特性列表
property_copyAttributeList     拷貝屬性的特性列表
property_copyAttributeValue     拷貝屬性中某特性的值

property_系類函式關注與屬性*內部,如屬性的特性等

// 6.protocol_xxx 系列函式
函式名稱     函式作用
protocol_conformsToProtocol     判斷一個協議是否遵循另一個協議
protocol_isEqual     判斷兩個協議是否一致
protocol_getName     獲取協議名稱
protocol_copyPropertyList     拷貝協議的屬性列表
protocol_copyProtocolList     拷貝某協議所遵循的協議列表
protocol_copyMethodDescriptionList     拷貝協議的方法列表
protocol_addProtocol     為一個協議遵循另一協議
protocol_addProperty     為協議新增屬性
protocol_getProperty     獲取協議中的某個屬性
protocol_addMethodDescription     為協議新增方法描述
protocol_getMethodDescription     獲取協議中某方法的描述

// 7.ivar_xxx 系列函式
函式名稱     函式作用
ivar_getName     獲取Ivar名稱
ivar_getTypeEncoding     獲取型別編碼
ivar_getOffset     獲取偏移量

// 8.sel_xxx 系列函式
函式名稱     函式作用
sel_getName     獲取名稱
sel_getUid     註冊方法
sel_registerName     註冊方法
sel_isEqual     判斷方法是否相等

// 9.imp_xxx 系列函式
函式名稱     函式作用
imp_implementationWithBlock     通過程式碼塊建立IMP
imp_getBlock     獲取函式指標中的程式碼塊
imp_removeBlock     移除IMP中的程式碼塊
複製程式碼

寫在結尾

習武是為了強身健體,學習逆向是為了防護

相關文章