在iOSHook系統(tǒng)C函數(shù)(一):使用動(dòng)態(tài)庫一文中,我們探討了如何使用動(dòng)態(tài)庫去HooK系統(tǒng)的C函數(shù),在本文中,我們將探索另外一種方式:fishhook,同樣我們也去勾取getenv函數(shù)
實(shí)現(xiàn)
在fishhook中,主要的hook對(duì)象為:
struct rebinding {
const char *name; // 需要HOOK的函數(shù)名稱,C字符串
void *replacement; //新函數(shù)地址
void **replaced; // 原始函數(shù)地址的指針
};
- 1,
name: 需要HOOK的函數(shù)名稱,C字符串。 - 2,
replacement: 新函數(shù)地址。 - 3,
replaced: 原始函數(shù)地址的指針。
為什么要獲取原始函數(shù)地址指針?
通過保留一個(gè)變量來保存系統(tǒng)的函數(shù),方便調(diào)用系統(tǒng)的函數(shù)。
我們?cè)?code>viewDidLoad里面來進(jìn)行hook:
- (void)viewDidLoad {
[super viewDidLoad];
struct rebinding getEnvb; // 1
getEnvb.name = "getenv"; //2
getEnvb.replacement = my_getenv; //3
getEnvb.replaced = (void *)&sys_getenv; //4
struct rebinding rebs[1] = {getEnvb};
rebind_symbols(rebs, 1); // 5
m
char * c = getenv("HOME");
NSString *str = [NSString stringWithUTF8String:c];
NSLog(@"%@",str);
char * cTwo = getenv("PATH");
NSString *strTwo = [NSString stringWithUTF8String:cTwo];
NSLog(@"%@",strTwo);
}
static char *(* sys_getenv)(const char * str); // 6
char *my_getenv(const char * str){ // 7
if (strcmp(str, "HOME") == 0 ) {
return "YAY";
}else {
return sys_getenv(str);
}
}
6: 根據(jù)
getenv函數(shù)的簽名,創(chuàng)建一個(gè)靜態(tài)的函數(shù)名,用來保存系統(tǒng)的函數(shù)。7: 創(chuàng)建和系統(tǒng)函數(shù)交換自定義函數(shù),
簽名需和系統(tǒng)函數(shù)保持一致。1: 創(chuàng)建
rebinding結(jié)構(gòu)體。2: 需要hook的函數(shù)的名字。
3: 自定義的函數(shù),其方法簽名需于被Hook函數(shù)的簽名保持一致。
4: 函數(shù)聲明,用來存儲(chǔ)系統(tǒng)原函數(shù)。
5: 對(duì)符號(hào)進(jìn)行重新綁定。
我們運(yùn)行一下工程,運(yùn)行結(jié)果如下:
2020-11-22 16:25:29.210773+0800 FishHookDemo[13208:8651857] YAY
2020-11-22 16:25:29.211194+0800 FishHookDemo[13208:8651857] /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/usr/bin:/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/bin:/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/usr/sbin:/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/sbin:/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/usr/local/bin
是的,當(dāng)我們獲取HOME環(huán)境變量時(shí),被我們自定義函數(shù)替換了值,獲取PATH環(huán)境變量時(shí),成功的調(diào)用了系統(tǒng)的函數(shù)方法。
原理分析
fishhook是怎樣把系統(tǒng)的C函數(shù)替換的呢,我們應(yīng)用加載的時(shí)候,使用的是 PIC技術(shù):
- 1,首先會(huì)在內(nèi)存的數(shù)據(jù)段生成
符號(hào)表。 - 2,
dyld將真實(shí)地址賦值到數(shù)據(jù)段里面去,又叫做符號(hào)綁定。
我們根據(jù)其文檔我們可知dyld是通過更新符號(hào)表的內(nèi)存指針來實(shí)現(xiàn)綁定的,符號(hào)表存在于Mach-O二進(jìn)制文件的__DATA段 ,fishhook通過 rebind_symbols函數(shù),對(duì)符號(hào)表進(jìn)行重綁定,來實(shí)現(xiàn)響應(yīng)替換。
下面我們通過lldb指令來探索一下該過程。
我們修改一下代碼
- (void)viewDidLoad {
[super viewDidLoad];
char * c = getenv("HOME");
NSString *str = [NSString stringWithUTF8String:c];
NSLog(@"%@",str);
struct rebinding getEnvb; // 1
getEnvb.name = "getenv"; //2
getEnvb.replacement = my_getenv; //3
getEnvb.replaced = (void *)&sys_getenv; //4
struct rebinding rebs[1] = {getEnvb};
rebind_symbols(rebs, 1); // 5
char * cOne = getenv("HOME");
NSString *strOne = [NSString stringWithUTF8String:cOne];
NSLog(@"%@",strOne);
char * cTwo = getenv("PATH");
NSString *strTwo = [NSString stringWithUTF8String:cTwo];
NSLog(@"%@",strTwo);
}
首先我們將程序 comand + B進(jìn)行編譯一下,得到 FishHookDemo.app文件,然后 顯示包內(nèi)容,得到 Mach-O 文件。

然后我們將
Mach-O文件拖到Mach-O View中:
符號(hào)表存在于 __DATA_CONST, __got和__DATA,__la_symbol_ptr段中,分別為非懶加載符號(hào)表和懶加載符號(hào)表。這兩種符號(hào)表的區(qū)別在于它們綁定的時(shí)機(jī)不一樣,非懶加載符號(hào)表是在程序一啟動(dòng)就綁定了,懶加載符號(hào)表是在用到該符號(hào)時(shí),才會(huì)進(jìn)行綁定。_getenv符號(hào)存在于 Lazy Symbol Pointers,只有在用到時(shí)才會(huì)進(jìn)行綁定。
我們?cè)诘谝淮握{(diào)用的時(shí)候,設(shè)置一個(gè)斷點(diǎn),并運(yùn)行程序,進(jìn)入lldb模式

我們使用 image list命令,來查找當(dāng)前進(jìn)程加載的所有鏡像,并找到FishHookDemo鏡像的首地址。
(lldb) image list
[ 0] 19B79118-4135-3E49-BE15-A7C9B8EC16DF 0x00000001077e8000 /Users/ritamashin/Library/Developer/Xcode/DerivedData/FishHookDemo-ehajjrtywlbbfkfqujggbdpwoaos/Build/Products/Debug-iphonesimulator/FishHookDemo.app/FishHookDemo
......
我們可以看到 0x00000001077e8000為該鏡像的 內(nèi)存地址(首地址) ,我們通過 首地址 + 偏移地址我們可以得到 符號(hào)所得地址值,通過 MachOView我們可以看到_getenv符號(hào)的偏移地址(offset)為00005058,我們來查看一下其內(nèi)存值
(lldb) x 0x00000001077e8000+0x00005058
0x1077ed058: 88 a4 7e 07 01 00 00 00 92 a4 7e 07 01 00 00 00 ..~.......~.....
0x1077ed068: 9c a4 7e 07 01 00 00 00 e8 a3 7e 07 01 00 00 00 ..~.......~.....
-
x:memory read的縮寫,讀取內(nèi)存空間值。
因?yàn)槲覀兪?64位的系統(tǒng),我們從 0x1077ed058往右數(shù)8個(gè)字節(jié),就讀取到我們想要的值,因?yàn)?getenv函數(shù)還未調(diào)用,此時(shí)該值為系統(tǒng)的默認(rèn)值,我們將斷點(diǎn)移至第29行,

然后查看內(nèi)存值
(lldb) x 0x00000001077e8000+0x00005058
0x1077ed058: 67 b1 32 52 ff 7f 00 00 92 a4 7e 07 01 00 00 00 g.2R......~.....
0x1077ed068: 9c a4 7e 07 01 00 00 00 e8 a3 7e 07 01 00 00 00 ..~.......~.....
此時(shí)此刻,因?yàn)檎{(diào)用了getenv函數(shù),對(duì)該符號(hào)進(jìn)行了綁定,內(nèi)存值已經(jīng)發(fā)生變化。我們將該內(nèi)存值使用匯編的形式進(jìn)行查看
(lldb) dis -s 0x00007fff5232b167
libsystem_c.dylib`getenv:
0x7fff5232b167 <+0>: pushq %rbp
0x7fff5232b168 <+1>: movq %rsp, %rbp
0x7fff5232b16b <+4>: pushq %r14
0x7fff5232b16d <+6>: pushq %rbx
0x7fff5232b16e <+7>: subq $0x10, %rsp
0x7fff5232b172 <+11>: movq %rdi, %rbx
0x7fff5232b175 <+14>: leaq 0x37b7b494(%rip), %r14 ; __environ_lock_obj
0x7fff5232b17c <+21>: movq %r14, %rdi
0x7fff5232b17f <+24>: movl $0x10000, %esi ; imm = 0x10000
-
dis -s:使用匯編的形式進(jìn)行查看
接下來,我們?cè)?調(diào)用完rebind_symbols函數(shù)之后,我們來看下其符號(hào)表關(guān)聯(lián)的內(nèi)存情況是否有變化。

(lldb) x 0x00000001077e8000+0x00005058
0x1077ed058: a0 94 7e 07 01 00 00 00 af 7a 43 52 ff 7f 00 00 ..~......zCR....
0x1077ed068: d4 8d 3b 52 ff 7f 00 00 e8 a3 7e 07 01 00 00 00 ..;R......~.....
(lldb) dis -s 0x00000001077e94a0
FishHookDemo`my_getenv:
0x1077e94a0 <+0>: pushq %rbp
0x1077e94a1 <+1>: movq %rsp, %rbp
0x1077e94a4 <+4>: subq $0x10, %rsp
0x1077e94a8 <+8>: movq %rdi, -0x10(%rbp)
0x1077e94ac <+12>: movq -0x10(%rbp), %rdi
0x1077e94b0 <+16>: leaq 0x1d62(%rip), %rsi ; "HOME"
0x1077e94b7 <+23>: callq 0x1077ea3b8 ; symbol stub for: strcmp
0x1077e94bc <+28>: cmpl $0x0, %eax
此時(shí)此刻,_getenv符號(hào)綁定的地址已變?yōu)?FishHookDemo中的my_getenv函數(shù)了,當(dāng)執(zhí)行函數(shù)時(shí),會(huì)執(zhí)行我們自定義的my_getenv函數(shù)。這樣我們證明到這里就結(jié)束了。