本篇文章較與依賴前一篇 Mach-O文件 的先導知識 , 建議先閱讀後再探究 .git
因爲逆向過程當中代碼注入每每會使用
hook
這種方式 , 並且在安全防禦與監測方面常用 .github另外只知道
runtime
交換imp
的方式對於中高級開發人員 ( 想偷懶又想裝* ) 顯然是不太夠的 .數組那麼咱們今天就來好好探討一下
Hook
與fishHook
原理 .緩存
HOOK,中文譯爲 「掛鉤「
或 「鉤子」
。在 iOS 逆向中是指改變程序運行流程的一種技術。經過 hook
可讓別人的程序執行本身所寫的代碼。在逆向中常用這種技術。因此在學習過程當中,咱們重點要了解其原理,這樣可以對惡意代碼進行有效的防禦。安全
大名鼎鼎的 Hook 已經不知道被多少人玩出了花 ❀ , 其用途之多咱們就不說了 . bash
利用 OC 的 Runtime
特性,動態改變 SEL
(方法編號)和 IMP
(方法實現)的對應關係,達到 OC 方法調用流程改變的目的。主要用於 OC 方法。ide
經常使用的有函數
method_exchangeImplementations
交換函數 impclass_replaceMethod
替換方法method_getImplementation
與 method_setImplementation
直接 get
/ set
imp關於這些
Runtime
方法的基本使用以及原理 這一點在 重籤應用調試與代碼修改 這篇文章最後有詳細的解釋和demo
, 感興趣的能夠去閱讀一下 .工具
它是 Facebook
提供的一個動態修改連接 Mach-O
文件的工具。利用 MachO
文件加載原理,經過修改懶加載和非懶加載兩個表的指針達到 C 函數 Hook
的目的。post
Cydia Substrate
原名爲 Mobile Substrate
,它的主要做用是針對 OC 方法、C 函數以及函數地址進行 Hook
操做。固然它並非僅僅針對 iOS 而設計的,安卓同樣能夠用。官方地址:www.cydiasubstrate.com/
它使用的是
logos
語法 , 關於這個工具的使用 , 後續文章我會詳細講述 .
git - 地址 : fishhook - git
有須要的能夠下載這個有中文註釋的版本 link 提取碼:f4f8 .
#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
.基礎 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
原理來解析一下緣由 .
首先 :
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
就會去找到 Foundation
中 NSLog
的真實地址寫到 _DATA
段的符號表中 NSLog
的符號上面 )
這個過程被稱爲 PIC
技術 . ( Position Independent Code : 位置代碼獨立 )
那麼瞭解了系統函數的整個加載過程 , 咱們再來看 fishhook
的函數名稱 :
rebind_symbols :: 重綁定符號
也就簡單明瞭了.
其原理就是 :
將編譯後系統庫函數所指向的符號 , 在運行時重綁定到用戶指定的函數地址 , 而後將原系統函數的真實地址賦值到用戶指定的指針上.
那麼再回頭看自定義的C函數爲何 hook
不了 ?
那答案就很簡單了 :
C
函數實際地址就在本身的 Mach-O
內 , 也並無符號和綁定的過程 .利用 MachOView
直接查看.
有同窗說了 , 說是這麼說 , 怎麼驗證呢 ?
既然講到了這兒 , 那咱們就順道提一點符號表的知識 , 畢竟一些 bug 收集工具也常常用到符號表還原 , 順便咱們來實際操做一下 , 一邊驗證理論 , 一邊加深記憶 .
從 MachOView
中咱們看到 , 符號表分爲兩種
Lazy Symbol Pointers
Non-Lazy Symbol Pointers
就是字面意思 , 懶加載和非懶加載 .
所以咱們在使用 fishhook
的時候 , 最好是調用一下原函數 , 以防止可能會出現沒使用並無綁定的問題 .
那麼接下來 , 咱們來玩一下 ?
廢話很少說 , 來到咱們剛剛寫的 hook
了 NSLog
的 demo
, 在 viewdidload
中先加一句 NSLog(@"123")
;
開始玩
cmd + r
運行運行工程來到斷點 , 找到 Mach-O
, 使用 MachOView
查看.
首先咱們看到 這個符號基於 Mach-O
文件的首地址偏移量是 3028
, ( 每一個人的都不同 , 你用你本身的 ) .
那麼 Mach-O
的地址在哪呢 ?
來到工程 LLDB
輸入指令 : image list
第一個就是咱們工程的 Mach-O
實際內存地址
打開計算器 cmd + 3
, 選擇十六進制 , cmd + v
把 Mach-O
實際內存地址粘貼進去 , 加上 MachOView
的符號偏移地址 3028
.
cmd
+ c
拷貝計算結果.
x
+ 0x1042C8028
( 你的計算結果 )
( memory read
也能夠 , x
就是 memory read
的簡寫 )
注意 : iOS
小端模式 , 從右往左讀 .
那麼我上圖中對應的實際地址就是 0x01042c69c0
.
查看彙編 : dis -s 0x01042c69c0 ( 你本身的地址 )
那麼咱們看到裏面並無什麼內容 , 也就是說在此時這個斷點這裏 , 符號並無被綁定內容 .
過掉斷點 , 來到第二處斷點 .
( 有對於彙編不熟悉的同窗不用着急 , 對比一下第二個斷點的結果來看 . 另外後續筆者會考慮繼續更彙編部份內容 )
x
+ 0x1042C8028
( 你的計算結果 )
能夠看到明顯內容已經改變了 .
再次查看彙編 : dis -s 0x01042c69c0 ( 你本身的地址 )
大功告成 , 再回想一下咱們以前的原理探究部分. 徹底驗證 !
彆着急 , 這只是驗證了 iOS
的 PIC
部分 , 那麼咱們 fishhook
呢 ?
touchesBegan
加個斷點 ( 不必定非要在 touchesBegan
加 , 我這裏只是 fishhook
的 rebind_symbols
後面就沒有代碼能夠過斷點了. )rebind_symbols
) , 點擊屏幕 來到下一個斷點.結果以下 :
fishhook
重綁定後符號指向了咱們自定義的函數地址 . 徹底驗證以前假設 .
fishhook
在逆向中很是重要 , 不少工具也內置了 fishhook
, 所以但願你們能認真理解並掌握原理 .
感謝關注 , 咱們下期見 .