iOS 逆向 - Hook / fishHook 原理與符號表

前言 :

本篇文章較與依賴前一篇 Mach-O文件 的先導知識 , 建議先閱讀後再探究 .git

  • 因爲逆向過程當中代碼注入每每會使用 hook 這種方式 , 並且在安全防禦與監測方面常用 .github

  • 另外只知道 runtime 交換 imp 的方式對於中高級開發人員 ( 想偷懶又想裝* ) 顯然是不太夠的 .數組

那麼咱們今天就來好好探討一下 HookfishHook 原理 .緩存

Hook 概述

HOOK,中文譯爲 「掛鉤「「鉤子」 。在 iOS 逆向中是指改變程序運行流程的一種技術。經過 hook 可讓別人的程序執行本身所寫的代碼。在逆向中常用這種技術。因此在學習過程當中,咱們重點要了解其原理,這樣可以對惡意代碼進行有效的防禦。安全

大名鼎鼎的 Hook 已經不知道被多少人玩出了花 ❀ , 其用途之多咱們就不說了 . bash

iOS 中幾種常見的 Hook

1 . Method Swizzle

利用 OCRuntime 特性,動態改變 SEL(方法編號)IMP(方法實現)的對應關係,達到 OC 方法調用流程改變的目的。主要用於 OC 方法。ide

經常使用的有函數

  • method_exchangeImplementations 交換函數 imp
  • class_replaceMethod 替換方法
  • method_getImplementationmethod_setImplementation 直接 get / set imp

關於這些 Runtime 方法的基本使用以及原理 這一點在 重籤應用調試與代碼修改 這篇文章最後有詳細的解釋和 demo , 感興趣的能夠去閱讀一下 .工具

2 . fishhook

它是 Facebook 提供的一個動態修改連接 Mach-O 文件的工具。利用 MachO 文件加載原理,經過修改懶加載和非懶加載兩個表的指針達到 C 函數 Hook 的目的。post

3. Cydia Substrate

Cydia Substrate 原名爲 Mobile Substrate ,它的主要做用是針對 OC 方法、C 函數以及函數地址進行 Hook 操做。固然它並非僅僅針對 iOS 而設計的,安卓同樣能夠用。官方地址:www.cydiasubstrate.com/

它使用的是 logos 語法 , 關於這個工具的使用 , 後續文章我會詳細講述 .

fishhook 基本使用

下載

git - 地址 : fishhook - git

有須要的能夠下載這個有中文註釋的版本 link 提取碼:f4f8 .

demo

#import "ViewController.h"
#import "fishhook.h"

@interface ViewController ()
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //rebinding結構體
    struct rebinding nslog;
    //須要HOOK的函數名稱,C字符串
    nslog.name = "NSLog";
    //新函數的地址
    nslog.replacement = myNslog;
    //原始函數地址的指針!
    nslog.replaced = (void *)&sys_nslog;
    //rebinding結構體數組
    struct rebinding rebs[1] = {nslog};
    /** * 參數1 : 存放rebinding結構體的數組 * 參數2 : 數組的長度 */
    rebind_symbols(rebs, 1);
}
//---------------------------------更改NSLog-----------
//函數指針
static void(*sys_nslog)(NSString * format,...);
//定義一個新的函數
void myNslog(NSString * format,...){
    format = [format stringByAppendingString:@"勾上了!\n"];
    //調用原始的
    sys_nslog(format);
}

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    NSLog(@"點擊了屏幕!!");
}
@end
複製代碼

點擊屏幕 , 打印結果 :

001--fishHookDemo[15776:645816] 點擊了屏幕!!勾上了!
複製代碼

關鍵函數

rebind_symbols , 源碼以下 :

int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel) {
    //prepend_rebindings的函數會將整個 rebindings 數組添加到 _rebindings_head 這個鏈表的頭部
    //Fishhook採用鏈表的方式來存儲每一次調用rebind_symbols傳入的參數,每次調用,就會在鏈表的頭部插入一個節點,鏈表的頭部是:_rebindings_head
    int retval = prepend_rebindings(&_rebindings_head, rebindings, rebindings_nel);
    //根據上面的prepend_rebinding來作判斷,若是小於0的話,直接返回一個錯誤碼回去
    if (retval < 0) {
    return retval;
  }
    //根據_rebindings_head->next是否爲空判斷是否是第一次調用。
  if (!_rebindings_head->next) {
      //第一次調用的話,調用_dyld_register_func_for_add_image註冊監聽方法.
      //已經被dyld加載的image會馬上進入回調。
      //以後的image會在dyld裝載的時候觸發回調。
    _dyld_register_func_for_add_image(_rebind_symbols_for_image);
  } else {
      //遍歷已經加載的image,進行hook
    uint32_t c = _dyld_image_count();
    for (uint32_t i = 0; i < c; i++) {
      _rebind_symbols_for_image(_dyld_get_image_header(i), _dyld_get_image_vmaddr_slide(i));
    }
  }
  return retval;
}
複製代碼

fishhook 的基礎使用很是簡單.

  • 就是須要咱們定義一個指針從而 fishhook 能夠幫咱們保存原系統函數的實現地址 , 另外將須要替換的函數名稱自定義函數地址寫成結構體調用 rebind_symbols 就能夠了 ,
  • 另外能夠一次往數組中寫入多個結構體進行多個函數的 hook.

fishhook 分析

基礎 OC 函數的 hook 原理咱們不在多贅述了 , 其實簡單來講就是替換掉方法實現的 imp , 這個基於 OC 語言的動態運行時機制是很好理解的 .

可是 C 呢 ?

咱們知道 C 函數是靜態的,也就是說在編譯的時候,編譯器就知道了它的實現地址,這也是爲何 C 函數只寫函數聲明調用時會報錯。那麼爲何 fishhook 還可以改變 C 函數的調用呢?難道函數也有動態的特性存在?咱們一塊兒來探究它的原理

注意 :

fishhook 是能夠 hook 系統的函數 , 並不是全部的 C 函數 , 也就是說 fishhook 也只能對帶有符號表的系統函數進行重綁定 , 而對本身實現的 C 函數一樣是沒有辦法的.

咱們大能夠本身寫一個 C 函數實驗一下 .

#import "ViewController.h"
#import "fishhook.h"

@interface ViewController ()
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // hook 自定義C函數
    struct rebinding Cfunction;
    Cfunction.name = "func";
    Cfunction.replacement = newfunc;
    Cfunction.replaced = (void *)&funcOri;
    struct rebinding resbs[1] = {Cfunction};
    rebind_symbols(resbs, 1);
}

// 要hook 的c函數
void func(const char * str){
    NSLog(@"%s",str);
}

//原始的函數指針記錄
static void(*funcOri)(const char *);

void newfunc(const char * str){
    NSLog(@"勾住了!");
    funcOri(str);
}
@end
複製代碼

運行 , 打印結果

2019-11-12 14:54:19.001680+0800 fishhookDemo[35238:1563336] 點擊了屏幕
2019-11-12 14:54:19.706819+0800 fishhookDemo[35238:1563336] 點擊了屏幕
2019-11-12 14:54:19.861428+0800 fishhookDemo[35238:1563336] 點擊了屏幕
複製代碼

沒法 hook 自定義 C 函數 , 接下來咱們經過 fishhook 原理來解析一下緣由 .

fishhook 原理

首先 :

C OC
靜態 動態
編譯時肯定函數地址 運行時肯定函數地址

而系統的 C 函數有動態的部分 , 就是咱們常常提到的符號表 , 用到的技術叫作 Position Independent Code ( 位置代碼獨立 ) , fishhook 也就是在此處作了文章 .

fishhook 原文講述 :

原理概述

因爲 iOS 系統中 UIKit / Foundation 庫每一個應用都會經過 dyld 加載到內存中 , 所以 , 爲了節約空間 , 蘋果將這些系統庫放在了一個地方 : 動態庫共享緩存區 (dyld shared cache) . ( Mac OS 同樣有 ) .

所以 , 相似 NSLog 的函數實現地址 , 並不會也不可能會在咱們本身的工程的 Mach-O 中 , 那麼咱們的工程想要調用 NSLog 方法 , 如何能找到其真實的實現地址呢 ?

其流程以下 :

  • 在工程編譯時 , 所產生的 Mach-O 可執行文件中會預留出一段空間 , 這個空間其實就是符號表 , 存放在 _DATA 數據段中 ( 由於 _DATA 段在運行時是可讀可寫的 )

  • 編譯時 : 工程中全部引用了共享緩存區中的系統庫方法 , 其指向的地址設置成符號地址 , ( 例如工程中有一個 NSLog , 那麼編譯時就會在 Mach-O 中建立一個 NSLog 的符號 , 工程中的 NSLog 就指向這個符號 )

  • 運行時 : dyld將應用加載到內存中時 , 根據 load commands 中列出的須要加載哪些庫文件 , 去作綁定的操做 ( 以 NSLog 爲例 , dyld 就會去找到 FoundationNSLog 的真實地址寫到 _DATA 段的符號表中 NSLog 的符號上面 )

這個過程被稱爲 PIC 技術 . ( Position Independent Code : 位置代碼獨立 )

那麼瞭解了系統函數的整個加載過程 , 咱們再來看 fishhook 的函數名稱 :

rebind_symbols :: 重綁定符號 也就簡單明瞭了.

其原理就是 :

將編譯後系統庫函數所指向的符號 , 在運行時重綁定到用戶指定的函數地址 , 而後將原系統函數的真實地址賦值到用戶指定的指針上.

那麼再回頭看自定義的C函數爲何 hook 不了 ?

那答案就很簡單了 :

  • 自定義 C 函數實際地址就在本身的 Mach-O 內 , 也並無符號和綁定的過程 .
  • 編譯時就已經肯定了 , 並無辦法操做 .

Mach-O 中查看符號表

利用 MachOView 直接查看.

有同窗說了 , 說是這麼說 , 怎麼驗證呢 ?

既然講到了這兒 , 那咱們就順道提一點符號表的知識 , 畢竟一些 bug 收集工具也常常用到符號表還原 , 順便咱們來實際操做一下 , 一邊驗證理論 , 一邊加深記憶 .

符號表與實際操做驗證理論

MachOView 中咱們看到 , 符號表分爲兩種

  • Lazy Symbol Pointers
  • Non-Lazy Symbol Pointers

就是字面意思 , 懶加載和非懶加載 .

所以咱們在使用 fishhook 的時候 , 最好是調用一下原函數 , 以防止可能會出現沒使用並無綁定的問題 .

那麼接下來 , 咱們來玩一下 ?

廢話很少說 , 來到咱們剛剛寫的 hookNSLogdemo , 在 viewdidload 中先加一句 NSLog(@"123");

開始玩

1 準備好代碼和斷點

2 MachOView 查看

cmd + r 運行運行工程來到斷點 , 找到 Mach-O , 使用 MachOView 查看.

3 計算符號地址

  • 首先咱們看到 這個符號基於 Mach-O 文件的首地址偏移量是 3028 , ( 每一個人的都不同 , 你用你本身的 ) .

    那麼 Mach-O 的地址在哪呢 ?

    來到工程 LLDB 輸入指令 : image list

    第一個就是咱們工程的 Mach-O 實際內存地址

  • 打開計算器 cmd + 3 , 選擇十六進制 , cmd + vMach-O 實際內存地址粘貼進去 , 加上 MachOView 的符號偏移地址 3028.

    cmd + c 拷貝計算結果.

4 lldb 查看內存與彙編代碼

x + 0x1042C8028 ( 你的計算結果 )

( memory read 也能夠 , x 就是 memory read 的簡寫 )

5 查看前八個字節的內容

注意 : iOS 小端模式 , 從右往左讀 .

那麼我上圖中對應的實際地址就是 0x01042c69c0 .

查看彙編 : dis -s 0x01042c69c0 ( 你本身的地址 )

那麼咱們看到裏面並無什麼內容 , 也就是說在此時這個斷點這裏 , 符號並無被綁定內容 .

過掉斷點 , 來到第二處斷點 .

( 有對於彙編不熟悉的同窗不用着急 , 對比一下第二個斷點的結果來看 . 另外後續筆者會考慮繼續更彙編部份內容 )

從新查看符號

x + 0x1042C8028 ( 你的計算結果 )

能夠看到明顯內容已經改變了 .

再次查看彙編 : dis -s 0x01042c69c0 ( 你本身的地址 )

大功告成 , 再回想一下咱們以前的原理探究部分. 徹底驗證 !

彆着急 , 這只是驗證了 iOSPIC 部分 , 那麼咱們 fishhook 呢 ?

  • touchesBegan 加個斷點 ( 不必定非要在 touchesBegan 加 , 我這裏只是 fishhookrebind_symbols 後面就沒有代碼能夠過斷點了. )
  • 過掉當前斷點 ( rebind_symbols ) , 點擊屏幕 來到下一個斷點.
  • 再次查看內存和彙編

結果以下 :

fishhook 重綁定後符號指向了咱們自定義的函數地址 . 徹底驗證以前假設 .

最後

fishhook 在逆向中很是重要 , 不少工具也內置了 fishhook , 所以但願你們能認真理解並掌握原理 .

感謝關注 , 咱們下期見 .

相關文章
相關標籤/搜索