HOOK 譯為“鈎子”或掛鈎。在 iOS 逆向中指改變程序運行流程的一種技術。
iOS 中 hook 技術的幾種方式
Method Swizzle
利用 OC 的 Runtime 特性,動態改變 SEL(方法編號)和 IMP(方法實現)的對應關系,達到 OC 方法調用流程改變的目的。主要用於 OC 方法。
fishhook
它是 Facebook 提供的一個動態修改鏈接 mach-O 文件的工具。利用 MachO 文件加載原理,通過修改懶加載和非懶加載兩個表的指針達到 C 函數 Hook 的目的。
Cydia Substrate
Cydia Substrate 原名為 Mobile Substrate,它的主要作用是針對 OC 方法、C 函數以及函數地址進行 Hook 操作。當然它並不是僅僅針對 iOS 而設計的,安卓一樣可以用。官方地址
一、Method Swizzle
在 OC 中,SEL 和 IMP 之間的關系,就好像一本書的“目錄”。
SEL 是方法編號,就像“標題”一樣,IMP 是方法實現的真實地址,就像“頁碼”一樣。他們是一一對應的關系。
Runtime 提供了交換兩個 SEL 和 IMP 對應關系的函數。
OBJC_EXPORT void
method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2)
OBJC_AVAILABLE(10.5, 2.0, 9.0 1.0, 2.0);
通過這個函數交換兩個 SEL 和 IMP 對應關系的技術,稱之為 Method Swizzle(方法欺騙)。
使用場景:
- 埋點
- 防止程序崩潰
方法交換后要注意死遞歸
現象。死遞歸最終導致棧溢出,因為每次遞歸方法,會將方法壓棧。
二、Cydia Substrate
Mobile Hooker
顧名思義用於 Hook。它定義一系列的宏和函數,底層調用 objc 的 runtime 和 fishhook 來替換系統或者目標應用的函數。
MSHookMessageEx
主要作用於 Objective-C 方法。
void MSHookMessageEx(Class class, SEL selector, IMP replacement, IMP result)
MSHookFunction
主要作用於 C 和 C++ 函數。
void MSHookFunction(void function, void * replacement, void** p_original)
Logos 語法 %hook 就是對此函數做了一層封裝
MobileLoader
MobileLoader 用於加載第三方 dylib 在運行的應用程序中。啟動時 MobileLoader 會根據規則把指定目錄的第三方的動態庫加載進去,第三方的動態庫也就是我們寫的破解程序。
safe mode
破解程序本質是 dylib,寄生在別的進程里。系統進程一旦出錯,可能導致整個進程崩潰,崩潰后就會造成 iOS 癱瘓。所以 CydiaSubstrate 引入了安全模式,在安全模式下所有基於 CydiaSubstrate 的三方 dylib 都會被禁用,便於查錯和修復。
三、fishHook
3.1 使用過程
- (void)viewDidLoad
{
[super viewDidLoad];
// 定義 rebinding 結構體
struct rebinding nslog;
nslog.name = "NSLog";
nslog.replacement = myNSLog;
nslog.replaced = (void **)&sys_nslog;
// 定義數組
struct rebinding rebs[1] = { nslog };
/** 用來重新綁定符號
參數 1:存放 rebinding 結構體的數組
參數 2:數組的長度
*/
rebind_symbols(rebs, 1);
}
// 函數指針,用來保存原始的函數地址
static void(* sys_nslog)(NSString * format, ...);
/// 定義一個新函數。
void myNSLog(NSString *format, ...)
{
format = [format stringByAppendingString:@"被 hook 了!"];
// 保留原始的調用
sys_nslog(format);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
NSLog(@"NSLog ");
}
NSLog 被 hook 了!
3.2 fishhook 原理
PIC
Position Independent Code 位置代碼獨立。
為了使多個進程能夠共享內存中的同一份代碼拷貝,已達到節約內存資源的目的。
ASLR
Address Space Layout Randomization 地址空間布局隨機化技術。
它將進程的某些內存空間地址進行隨機化來增大入侵者預測目的地址的難度,從而降低進程被成功入侵的風險。因而每次啟動程序時,程序在內存中的地址都不一樣。
DYLD
Dynamic Loader 動態加載器。
在 Darwin/OS X 被叫做 dyld,它用來加載所有的 frameworks, dynamic libraries, and bundles (plug-ins)。
由於蘋果的動態庫遵循 PIC,存在於共享緩存庫,同時采用了 ASLR 技術。每次重啟手機后,動態庫的地址都會隨機偏移,需要通過基地址加偏移地址來獲取代碼的真實地址。
MachO 文件
MachO 文件是蘋果系統的可執行文件。通過 DYLD 動態加載。
我們的應用程序為了能夠獲取動態庫的真實地址,由 DYLD 動態加載 MachO 中的符號表,將符號表中的指針指向動態庫地址,從而達到調用系統庫的目的。
fishhook 正是通過 dyld 來修改符號表中指向動態庫的指針來達到 hook 的目的。
MachO 文件中,__DATA 段中與動態符號綁定相關的有兩個 section:__nl_symbol_ptr
和__la_symbol_ptr
。
- __nl_symbol_ptr 是一個非懶加載數據的指針數組。
- __la_symbol_ptr 在第一次調用前,dyld 會去通過 dyld_stub_binder 填充指針數組。在第一次調用之后才會真正鏈接到動態庫的位置。
為了在 MachO 中找到想要 hook 的函數的名字,需要在幾個間接的層間進行跳轉。在這兩個 section 的 section header 提供了一個 offset
用來指向對應的 section。
而 __la_symbol_ptr 指針數組的下標和 indirect_symbol_table 中的下標是一一對應的。
indirect_symbol_table 中的每個元素的 data 中保存着一個十六進制的數,這個數就是真正的符號表中的數組下標。比如 NSLog 這個函數的 Data 為 0x185,對應十進制 389,在symbol_table 中的第 389 位即可找到。
而 symbol_table 對應的元素中的 data 同樣保存着一個十六進制的數,這個數是字符串表中的偏移地址。比如 NSLog 在 string_table 中的偏移為 0x000000C1,找到 string_table 的首地址,為 0x00009934,則該函數的字符串常量位於 0x9934 + 0xC1 = 0x99F5 的位置。
3.3 驗證方法交換過程
注意:因為 NSLog 是懶加載的,所以在第一次使用之前,是沒有確定位置的。
所以上面代碼改成:
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(@"First Use");
// 使用結構體的簡便創建方式
struct rebinding rebs[1] = { { "NSLog", myNSLog, (void **)&sys_nslog } };
rebind_symbols(rebs, 1);
}
添加斷點在
rebind_symbols(rebs, 1);
lldb 命令輸出加載的模塊的列表。第一個加載可執行文件,第二個加載 dyld。
(lldb) image list
[ 0] 069E0DBD-6F91-39FC-BEDD-5463147B622A 0x000000010461d000 /Users/dubinbin/Library/Developer/Xcode/DerivedData/fishhookDmeo-bxkdwcgogbunzidzmmsmdpjedffq/Build/Products/Debug-iphonesimulator/fishhookDmeo.app/fishhookDmeo
[ 1] CE635DB2-D47E-3C05-A0A3-6BD982E7E750 0x000000010bc55000 /usr/lib/dyld
[ 2] C3514384-926E-3813-BF0C-69FFC704E283 0x000000010462e000 /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/usr/lib/dyld_sim
[ 3] E5391C7B-0161-33AF-A5A7-1E18DBF9041F 0x0000000104917000 /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/Foundation.framework/Foundation
[ 4] 177A61B3-9E02-3A09-9A98-C1C3C9AB7958 0x0000000104f3d000 /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/usr/lib/libobjc.A.dylib
...
由上可知 MachO 的地址是 0x000000010461d000,MachO 文件中 NSLog 的偏移為 0x50A0。讀取指針:
(lldb) x 0x000000010461d000+0x50A0
0x1046220a0: 76 d2 9b 04 01 00 00 00 70 8c 9a 04 01 00 00 00 v.......p.......
0x1046220b0: 91 b7 80 08 01 00 00 00 72 05 62 04 01 00 00 00 ........r.b.....
查看匯編(注意:iOS 是小端序,地址從右往左讀取,即為 0x00000001049bd276):
(lldb) dis -s 0x01049bd276
Foundation`NSLog:
0x1049bd276 <+0>: pushq %rbp
0x1049bd277 <+1>: movq %rsp, %rbp
0x1049bd27a <+4>: subq $0xd0, %rsp
0x1049bd281 <+11>: testb %al, %al
0x1049bd283 <+13>: je 0x1049bd2ab ; <+53>
0x1049bd285 <+15>: movaps %xmm0, -0xa0(%rbp)
0x1049bd28c <+22>: movaps %xmm1, -0x90(%rbp)
(lldb) p sys_nslog
(void (*)(NSString *, ...)) $1 = 0x0000000000000000
如果沒有先執行一條 NSLog,這里輸出是:
(lldb) dis -s 0x01049bd276
0x10d5c4460: pushq $0x1e
0x10d5c4465: jmp 0x10d5c4450
0x10d5c446a: pushq $0x2c
0x10d5c446f: jmp 0x10d5c4450
0x10d5c4474: pushq $0x152 ; imm = 0x152
0x10d5c4479: jmp 0x10d5c4450
這個位置還不是 NSlog。
替換方法實現后,重新讀取 MachO 相同偏移位置的指針內容:
(lldb) x 0x000000010461d000+0x50A0
0x1046220a0: 50 de 61 04 01 00 00 00 70 8c 9a 04 01 00 00 00 P.a.....p.......
0x1046220b0: 91 b7 80 08 01 00 00 00 2b 51 42 07 01 00 00 00 ........+QB.....
(lldb) dis -s 0x010461de50
fishhookDmeo`myNSLog:
0x10461de50 <+0>: pushq %rbp
0x10461de51 <+1>: movq %rsp, %rbp
0x10461de54 <+4>: subq $0x30, %rsp
0x10461de58 <+8>: movq $0x0, -0x8(%rbp)
0x10461de60 <+16>: leaq -0x8(%rbp), %rax
0x10461de64 <+20>: movq %rdi, -0x10(%rbp)
0x10461de68 <+24>: movq %rax, %rdi
0x10461de6b <+27>: movq -0x10(%rbp), %rsi
(lldb) p sys_nslog
(void (*)(NSString *, ...)) $2 = 0x000000011c72780a
可以看到 sys_nslog 保存了交換前 NSLog 的地址
3.4 總結
fishhook 正是通過查找到需要 hook 的函數指針,然后替換成自己寫的函數指針,從而實現 hook 系統函數的目的。
fishhook 不能 hook 自定義的函數,是因為自定義的函數不在 __la_symbol_ptr 中。