iOS Hook 原理(三)- InlinehHook (Dobby)

一、inlinehook概述

inlineHook(內(nèi)聯(lián)鉤子):所謂InlineHook就是直接修改目標函數(shù)的頭部代碼。讓它跳轉到我們自定義的函數(shù)里面執(zhí)行我們的代碼,從而達到Hook的目的。這種Hook技術一般用在靜態(tài)語言的HOOK上面。

Inline Hook 就是在運行的流程中插入跳轉指令來搶奪運行流程的一個方法。大體分為三步:

  • 將原函數(shù)的前 N 個字節(jié)搬運到 Hook 函數(shù)的前 N 個字節(jié);
  • 然后將原函數(shù)的前 N 個字節(jié)填充跳轉到 Hook 函數(shù)的跳轉指令;
  • Hook 函數(shù)末尾幾個字節(jié)填充跳轉回原函數(shù) +N 的跳轉指令;
    image.png
image.png

Dobby(原名:HOOKZz)。是一個全平臺的inlineHook(內(nèi)聯(lián)鉤子)框架,用起來就和fishhook一樣。Dobby 是通過插入 __zDATA 段和__zTEXT 段到 Mach-O 中。

  • __zDATA 用來記錄 Hook 信息(Hook 數(shù)量、每個 Hook 方法的地址)、每個 Hook 方法的信息(函數(shù)地址、跳轉指令地址、寫 Hook 函數(shù)的接口地址)、每個 Hook 的接口(指針)。
  • __zText 用來記錄每個 Hook 函數(shù)的跳轉指令。

Dobby 通過 mmap 把整個 Mach-O 文件映射到用戶的內(nèi)存空間,寫入完成保存本地。所以 Dobby 并不是在原 Mach-O 上進行操作,而是重新生成并替換。
Doddy

二、 編譯Dobby

首先clone工程

#depth用于指定克隆深度,為1即表示只克隆最近一次commit.
git clone https://github.com/jmpews/Dobby.git --depth=1 

由于Dobby是跨平臺的,所以項目并不是一個Xcode工程,要使用cmake將這個工程編譯成為Xcode工程。進入Dobby目錄,創(chuàng)建一個文件夾,然后cmake編譯工程:

cd Dobby && mkdir build_for_ios_arm64 && cd build_for_ios_arm64
cmake .. -G Xcode \
-DCMAKE_TOOLCHAIN_FILE=cmake/ios.toolchain.cmake \
-DPLATFORM=OS64 -DARCHS="arm64" -DCMAKE_SYSTEM_PROCESSOR=arm64 \
-DENABLE_BITCODE=0 -DENABLE_ARC=0 -DENABLE_VISIBILITY=1 -DDEPLOYMENT_TARGET=9.3 \
-DDynamicBinaryInstrument=ON -DNearBranch=ON -DPlugin.SymbolResolver=ON -DPlugin.Darwin.HideLibrary=ON -DPlugin.Darwin.ObjectiveC=ON

編譯完成后,會生成一個Xcode工程(這里在build_for_ios_arm64目錄中 )。接下來編譯Xcode工程生成我們的Framework

新版本的Dobby多了很多功能,這里我們只編譯DpbbyX或者dobby,區(qū)別就是一個是.Framework一個是.dylib

image.png

到這里Dobby庫就已經(jīng)準備好了。

和上一篇文章一樣如果提示需要賬戶直接添加CODE_SIGNING_ALLOWED配置。

三、導入 DobbyX.Framework / libdobby.dylib

新建一個工程DoddyDemo,導入DobbyX.Framework
問題處理

  1. bitcode問題'/Users/zaizai/HookTest/DoddyDemo/DobbyX.framework/DobbyX' does not contain bitcode. You must rebuild it with bitcode enabled (Xcode setting ENABLE_BITCODE), obtain an updated library from the vendor, or disable bitcode for this target. file '/Users/zaizai/HookTest/DoddyDemo/DobbyX.framework/DobbyX' for architecture arm64
    兩個方案:
    1.生成Framework/dylib的時候設置支持bitcode

    image.png

    2.項目關閉bitcode
    image.png

  2. 拷貝Frameworkdyld: Library not loaded: @rpath/DobbyX.framework/DobbyX Referenced from: /private/var/containers/Bundle/Application/BFDFC7CA-7045-4392-881D-1BFEA22E6BFA/DoddyDemo.app/DoddyDemo Reason: image not found

這里就是DobbyXMacho文件中已經(jīng)有了,但是在Frameworks目錄中沒有。Xcode不會幫我們自動Copy,需要添加Copy

image.png

處理完成后運行工程輸出以為內(nèi)容表示成功:

[*] ================================
[*] Dobby
[*] ================================
[*] dobby in debug log mode, disable with cmake flag "-DDOBBY_DEBUG=OFF"

四、 DobbyDemo

dobby最核心的函數(shù)DobbyHook定義如下:

// replace function
int DobbyHook(void *address, void *replace_call, void **origin_call);
  • address:需要HOOK的函數(shù)地址。
  • replace_call:新函數(shù)地址。
  • origin_call:保留原始函數(shù)的指針的地址。

從函數(shù)的參數(shù)就能看出來和fishhook很相似。使用下試試:

#import <DobbyX/dobby.h>

+ (void)load {
    //Hook sum
    DobbyHook(sum, HP_sum, (void *)&sum_p);
}

//要Hook的函數(shù)
int sum(int a, int b){
    return  a + b;
}

//新函數(shù)
int HP_sum(int a,int b) {
    NSLog(@"before hook: %d + %d = %d",a,b,sum_p(a,b));
    return a - b;
}

//原函數(shù)指針
static int(*sum_p)(int a,int b);

調(diào)用:

NSLog(@"after hook: %d + %d = %d",10,20,sum(10, 20));

輸出:

DoddyDemo[9679:6241363] before hook: 10 + 20 = 30
DoddyDemo[9679:6241363] after hook: 10 + 20 = -10

這里也就Hook了自定義C函數(shù),彌補了fishhook不能hook自定義c函數(shù)的問題。

那么外部函數(shù)可以Hook么?HookNSLog試下:

//新函數(shù)
void HP_NSLog(NSString *format, ...) {
    NSLog_p([format stringByAppendingString:@"\nHook Success"]);
}

//原函數(shù)指針
static void(*NSLog_p)(NSString *format, ...);

+ (void)load {
    DobbyHook(NSLog, HP_NSLog, (void *)&NSLog_p);
}

調(diào)用:

NSLog(@"HotpotCat");

輸出:

 HotpotCat
 Hook Success

可以看到也是可以的,由于是直接插入跳轉指令來執(zhí)行自己的代碼肯定都可以。

那么Dobby是怎么做的呢?以sum函數(shù)為例,分別在Hook前后在sum函數(shù)中打斷點看下匯編代碼:

//before hook
DoddyDemo`sum:
    0x104ab9c3c <+0>:  sub    sp, sp, #0x10             ; =0x10 
    0x104ab9c40 <+4>:  str    w0, [sp, #0xc]
    0x104ab9c44 <+8>:  str    w1, [sp, #0x8]
->  0x104ab9c48 <+12>: ldr    w8, [sp, #0xc]
    0x104ab9c4c <+16>: ldr    w9, [sp, #0x8]
    0x104ab9c50 <+20>: add    w0, w8, w9
    0x104ab9c54 <+24>: add    sp, sp, #0x10             ; =0x10 
    0x104ab9c58 <+28>: ret    

//after hook
DoddyDemo`sum:
    0x104ab9c3c <+0>:  adrp   x17, 0
    0x104ab9c40 <+4>:  add    x17, x17, #0xc5c          ; =0xc5c 
    0x104ab9c44 <+8>:  br     x17
->  0x104ab9c48 <+12>: ldr    w8, [sp, #0xc]
    0x104ab9c4c <+16>: ldr    w9, [sp, #0x8]
    0x104ab9c50 <+20>: add    w0, w8, w9
    0x104ab9c54 <+24>: add    sp, sp, #0x10             ; =0x10 
    0x104ab9c58 <+28>: ret  

通過匯編對比可以看到前面3行發(fā)生了變化(??第二個sum調(diào)用沒有拉伸棧空間,這里也就確實修改后匯編的行數(shù)是不變的,棧平衡放到了后面的函數(shù)中),打斷點查看x17的內(nèi)容:

image.png

可以看到在sum的頭部跳轉到了HP_sum中執(zhí)行HP_sum函數(shù)。這也就驗證了inlinehookhook函數(shù)的頭部進行跳轉。在這里br x17后面的代碼就不執(zhí)行了,只有在HP_sum中調(diào)用sum_p的時候才執(zhí)行原來的代碼。

繼續(xù)進入HP_sum

image.png

進入blr x9

image.png

可以看到這里拉伸了棧空間,也就是說只有調(diào)用原來的函數(shù)才開辟空間,在sumbr x17后面做棧回收。這樣也就棧平衡了。

繼續(xù)往下調(diào)用,最后會分別回到HP_sum->sum 也就是執(zhí)行sum后面的一段匯編:

image.png

繼續(xù)進入:

image.png

驗證確實回到了sum中繼續(xù)執(zhí)行。
??修改的__Text段實際上是替換。

五、 地址替換符號

在正常的逆向開發(fā)中,我們一般情況下拿到的都是地址,所以DobbyHook的第一個參數(shù)應該是一個地址。那么怎么將符號替換成地址呢?還是以sum函數(shù)為例。

image.png

在這里我們能算出函數(shù)的偏移值為:0x102dc5c70 - 0x0000000102dc0000 = 0x5C70,查看MachO也確實是sum函數(shù)。
image.png

那么在代碼中直接通過偏移值 + ASLR就能得到函數(shù)執(zhí)行時候的地址了。(一般我們并不知道這個地址是sum,這里只是演示。正常逆向是要分析代碼邏輯找地址然后嘗試的)。
修改sum函數(shù)hook為地址代碼如下:

#import <DobbyX/dobby.h>
#import <mach-o/dyld.h>

//sum 函數(shù)地址 偏移值: PAGEZERO(0x100000000) + offset(0x5C70),這里用 uintptr_t 類型是為了方便計算。這里只是64位,暫不考慮32位。如果最低版本從`iOS11`開始,就不用考慮32位的情況了。
static uintptr_t sum_address_offset = 0x100005C70;

+ (void)load {
    //地址hook
    //獲取ASLR,相當于rebase
    uintptr_t slide = _dyld_get_image_vmaddr_slide(0);
    uintptr_t sum_address = sum_address_offset + slide;
    NSLog(@"sum_address_offset:%p\nslide:%p\nsum_address:%p",(void *)sum_address_offset,(void *)slide,(void *)sum_address);
    DobbyHook((void *)sum_address, HP_sum, (void *)&sum_p);
}

這個時候運行并沒有hook成功:

sum_address_offset:0x100005c70
slide:0x4150000
sum_address:0x104155c70
hook: 10 + 20 = 30

因為我們在主工程添加了代碼,MachO變了,所以對應的獲取的到的0x5C70也發(fā)生了改變:

image.png

這里也就說明了一些app的插件在app更新后就不能用的原因。這里有兩種方式處理偏移值改變的情況。重新獲取偏移值,或者將Hook代碼放入動態(tài)庫中。
重新獲取計算偏移值發(fā)現(xiàn)是0x5D08,直接將sum_address_offset修改:

static uintptr_t sum_address_offset = 0x100005D08;

再次運行工程發(fā)現(xiàn)Hook成功了。

sum_address_offset:0x100005d08
slide:0x2570000
sum_address:0x102575d08
before hook: 10 + 20 = 30
hook: 10 + 20 = -10

??這里有個問題是sum_address_offset的值變了,為什么偏移值沒有變。這里是因為這塊改動很小,數(shù)據(jù)大小是沒有變化的,不會影響到偏移值的變化。當然最好的方案當然是放入動態(tài)庫中。

六、 Dobby注入

正常情況寫分析其它App我們是要注入自己的動態(tài)庫的,寫個AppDemo試下下整個流程。
AppDemo的主頁面有如下代碼:

int sum(int a,int b) {
    return a + b;
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"sum result: %d",sum(10, 20));
}

計算下偏移值為0x5E54。編譯生成AppDemo.app。新建一個Hook工程HookAppDemo。利用HookAppDemo工程對AppDemo.app重簽名和注入。
DobbyX.frameworkAppDemo.app拷貝到HookAppDemo工程的目錄。
目錄結構如下:

image.png

HPHook是自定義注入庫。

這里HPHook依賴DobbyX有兩種方式:

  1. DobbyX加入到主工程:
    image.png

然后在HPHook Target配置:

other linker flags

image.png

這個時候兩個庫都已經(jīng)在Frameworks中了,并且注入成功了。

image.png

  1. DobbyX 加入到HPHook中:
    配置HPHook或者主工程 Copy Files(任意一個都可以):
    image.png
  • 配置HPHook則主工程和Framework MachO分別如下:
    HookAppDemo

    HPHook
  • 配置主工程MachO分別如下:
    HookAppDemo
HPHook

原理一樣,層級不同。

七、 Hook自定義函數(shù)

HPHook中寫Hook代碼

#import "HPInject.h"
#import <DobbyX/dobby.h>
#import <mach-o/dyld.h>

@implementation HPInject

//sum 函數(shù)地址 偏移值: PAGEZERO(0x100000000) + offset(0x5E54),這里用 uintptr_t 類型是為了方便計算。
static uintptr_t sum_address_offset = 0x100005E54;

+ (void)load {
    //獲取ASLR,相當于rebase
    uintptr_t slide = _dyld_get_image_vmaddr_slide(0);
    uintptr_t sum_address = sum_address_offset + slide;
    NSLog(@"sum_address_offset:%p\nslide:%p\nsum_address:%p",(void *)sum_address_offset,(void *)slide,(void *)sum_address);
    DobbyHook((void *)sum_address, HP_sum, (void *)&sum_p);
}

//要Hook的函數(shù)
int sum(int a, int b){
    return  a + b;
}

//新函數(shù)
int HP_sum(int a,int b) {
    NSLog(@"Hook Success");
    return sum_p(a,b);
}

//原函數(shù)指針
static int(*sum_p)(int a,int b);

@end

輸出:

Hook Success
sum result: 30

這個時候就Hook成功了。

八、Hook Swift

還是同樣的邏輯,將AppDemosum改寫為swift實現(xiàn),創(chuàng)建一個Swift AppDemo,代碼如下:

func sum(a: Int, b: Int) -> Int {
    return a + b
}

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    print(sum(a: 10, b: 20))
}

同樣的方式計算偏移值0x6650

HookAppDemo中用SwiftAppDemo.app替換AppDemo.app,修改sum_address_offset值。運行工程:

Hook Success
30

這個時候Swift代碼也Hook成功了。

DobbyTest

inlinehook(Dobby)運行時Hook:對目標函數(shù)的匯編代碼進行修改,修改的是內(nèi)存中的MachO的代碼段(強制替換)。

修改目標函數(shù)后,函數(shù)棧平衡放到調(diào)用原函數(shù)的地方。這么做為了確認是否需要調(diào)用原始的方法,需要的話會找時機拉伸棧后返回執(zhí)行后面的代碼。

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

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