前言
最近自己心血來(lái)潮贰锁,想研究下是否可以完美攔截到 WKWebView
的所有網(wǎng)絡(luò)請(qǐng)求各谚,所以就去看下了 WebKit
的源碼玫坛,發(fā)現(xiàn)源碼基本都是用 c++
去實(shí)現(xiàn)的,突然就想去研究下能否 hook 私有庫(kù)里面c++
中的函數(shù)反惕。于是就開(kāi)始了一段學(xué)習(xí)之旅尝艘。
搜索
一切研究起于搜索,如果有人已經(jīng)研究出來(lái)了姿染,那就不用花費(fèi)很多時(shí)間了背亥,從 Google 到 stackOverflow,再到 gitHub悬赏,搜索了 hook
狡汉、 c++
相關(guān)的關(guān)鍵詞,基本沒(méi)有找到什么資料闽颇,沒(méi)人能清晰的告訴我盾戴,在 iOS 中究竟能不能 hook c++ 方法。
探索
方案尋找
在搜索沒(méi)有找到有用資料時(shí)进萄,我是有點(diǎn)懵逼的捻脖,因?yàn)椴恢绾蜗率郑ㄖ皩?duì) Mach-O 的文件格式基本沒(méi)深入了解)锐峭。之前知道 fishhook 可以 hook c 函數(shù)中鼠,因此就想能不能也用 fishhook 來(lái) hook 私有庫(kù)里面 c++ 函數(shù),當(dāng)時(shí)的嘗試是失敗了沿癞。后來(lái)在一個(gè)研究逆向的同事的幫助下援雇,了解到了可以使用 hookzz 這個(gè)庫(kù)去 hook c/c++ 函數(shù)。具體 hookzz 的原理還沒(méi)有去了解椎扬,使用方法如下所示:
extern "C" {
extern int ZzReplace(void *function_address, void *replace_call, void **origin_call);
}
size_t (*origin_fread)(void * ptr, size_t size, size_t nitems, FILE * stream);
size_t (fake_fread)(void * ptr, size_t size, size_t nitems, FILE * stream) {
// Do What you Want.
return origin_fread(ptr, size, nitems, stream);
}
void hook_fread() {
ZzReplace((void *)fread, (void *)fake_fread, (void **)&origin_fread);
}
ZzReplace
的一共需要傳入 3 個(gè)參數(shù)惫搏,第一個(gè)是被 hook 函數(shù)的函數(shù)地址,第二個(gè)參數(shù)是用來(lái)替代原函數(shù)的函數(shù)地址蚕涤,第三參數(shù)是函數(shù)指針的指針筐赔,用于存儲(chǔ)原函數(shù)的函數(shù)指針。
由于第二個(gè)和第三個(gè)參數(shù)都只自己創(chuàng)建的揖铜,所以現(xiàn)在的問(wèn)題是茴丰,如何找到 hook 函數(shù)的函數(shù)地址。只要可以找到函數(shù)地址天吓,就能夠用 hookzz 進(jìn)行 hook贿肩。
被 hook 函數(shù)地址尋找
那么,如何尋找一個(gè)函數(shù)的函數(shù)指針呢龄寞?這里就需要了解下 iOS 的 dyld 的文件格式 -- Mach-O汰规。在 iOS 系統(tǒng)中,所有的 dyld 都 Mach-O 格式(具體什么是 Mach-O物邑,可以上網(wǎng)搜索下溜哮,網(wǎng)上有很多大神發(fā)了很多解析文章)滔金,在 Mach-O 中,有一個(gè)符號(hào)表(Symbol Table)是專(zhuān)門(mén)存儲(chǔ)代碼的中所有符號(hào)和符號(hào)對(duì)應(yīng)地址茬射。而函數(shù)名稱(chēng)也是符號(hào)一種鹦蠕,所以也可以在符號(hào)表中直接找到。我們直接用 MachOView 工具在抛,可以查看 dyld 文件钟病。
- 獲取 WebKit 的 dyld 文件,為了方便刚梭,我們直接拿 mac 系統(tǒng)中的 WebKit 庫(kù)肠阱,在文件目錄
/System/Library/Frameworks
中可以找到,如下圖:
- 直接用 MachOView 工具打開(kāi) WebKit framework 中的 WebKit 文件朴读,直接將左邊的滾動(dòng)欄拉到最下面屹徘,就可以看到 Symbol Table,如下圖所示:
上圖右邊的第一紅框標(biāo)出的衅金,就是 c++ 函數(shù)的符號(hào)噪伊,會(huì)發(fā)現(xiàn)和我們平時(shí)接觸到的 c++
函數(shù)的定義不太一樣,這是因?yàn)橄啾扔?c 函數(shù)氮唯, c++ 的實(shí)體定義較為復(fù)雜鉴吹,所以區(qū)分不同的實(shí)體,編譯器會(huì)對(duì) c++
實(shí)體進(jìn)行 mangle 操作惩琉,從而保證了程序?qū)嶓w名稱(chēng)的唯一性豆励。我們可以通過(guò) c++filt
工具進(jìn)行 demangle 操作 (GCC and MSVC C++ Demangler
這個(gè)網(wǎng)站突然打不開(kāi)了,該網(wǎng)站也支持 demangle c++ 函數(shù))如下圖所示
可以看到瞒渠,將符號(hào) __ZNK7WebCore30MediaDevicesEnumerationRequest23userMediaDocumentOriginEv
進(jìn)行 demangle 操作后良蒸,能到獲取到 WebCore::MediaDevicesEnumerationRequest::userMediaDocumentOrigin() const
函數(shù)名稱(chēng)。
代碼實(shí)現(xiàn)
上面我們已經(jīng)分析了如何獲取到函數(shù)函數(shù)地址伍玖,接下來(lái)就是如何用代碼獲取到符號(hào)表嫩痰,這里需要對(duì) Mach-O 文件格式有一定的了解
- 獲取到 WebKit dyld 的鏡像地址,代碼如下:
- (void*)findDyldImageWithName:(NSString *)targetName {
int count = _dyld_image_count();
for (int i = 0; i < count; i++) {
const char* name = _dyld_get_image_name(i);
if(strstr(name, [targetName cStringUsingEncoding:NSUTF8StringEncoding]) > 0) {
return (void*)_dyld_get_image_header(i);
}
}
return NULL;
}
- 遍歷鏡像中的 segment 窍箍,找到符號(hào)表對(duì)應(yīng)的 segment串纺,同時(shí)也一起獲取到 _TEXT 和 _LINKEDIT 的 segment
// 遍歷鏡像里面的所有 segment
void _enumerate_segment(const mach_header *header, std::function<bool(struct load_command *)> func) {
// 這里我們只考慮64位應(yīng)用。第一個(gè)command從header的下一位開(kāi)始
struct load_command *baseCommand = (struct load_command *)((struct mach_header_64 *)header + 1);
if (baseCommand == nullptr) return;
struct load_command *command = baseCommand;
for (int i = 0; i < header->ncmds; i++) {
if (func(command)) {
return;
}
command = (struct load_command *)((uintptr_t)command + command->cmdsize);
}
}
void _log_dyld_all_symbol(char *dyld_name) {
const struct mach_header *header = NULL;
uint64_t slide;
int count = _dyld_image_count();
// 獲取到 WebKit 鏡像的 header 和 slide 大小
for (int i = 0; i < count; i++) {
const char* name = _dyld_get_image_name(i);
if(strstr(name, dyld_name) > (char *)0) {
header = _dyld_get_image_header(i);
slide = _dyld_get_image_vmaddr_slide(i);
break;
}
}
segment_command_64 *seg_linkedit = NULL;
segment_command_64 *seg_text = NULL;
struct symtab_command *symtab_command = NULL;
// 遍歷 load_command仔燕,獲取到 _LINKEDIT segment造垛,_TEXT segment, 和 符號(hào)表的 load_commond
_enumerate_segment(header, [&](struct load_command *command) {
if (command->cmd == LC_SEGMENT_64) {
struct segment_command_64 *segCmd = (struct segment_command_64 *)command;
if (0 == strcmp((segCmd)->segname, SEG_LINKEDIT))
seg_linkedit = segCmd;
else if (0 == strcmp((segCmd)->segname, SEG_TEXT))
seg_text = segCmd;
} else if (command->cmd == LC_SYMTAB) {
symtab_command = (struct symtab_command *)command;
}
return false;
});
//.........
}
- 計(jì)算符號(hào)表和字符表的位置
// 獲取到 _LINKEDIT segment 的首地址
uintptr_t linkedit_addr = (uintptr_t)seg_linkedit->vmaddr -(uintptr_t)seg_text->vmaddr - (uintptr_t)seg_linkedit->fileoff;
// 獲取到符號(hào)表的首地址
struct nlist_64 *nlist = (struct nlist_64 *)((uintptr_t)header + (uintptr_t)symtab_command->symoff + linkedit_addr);
// 獲取到字符表的首地址
intptr_t string_table = (intptr_t)header + ((uintptr_t)symtab_command->stroff + (uintptr_t)linkedit_addr);
- 遍歷符號(hào)表
// 遍歷打印出所有的符號(hào)
for (int i = 0; i < symtab_command->nsyms ; i++) {
char * symbol_name = (char *)(string_table + nlist->n_un.n_strx);
char * demangle_symbol = _demangle_symbol(symbol_name);
printf("symbol name: %s\n", demangle_symbol);
nlist = (struct nlist_64 *)((uintptr_t)nlist + sizeof(struct nlist_64));
}
- demangle c++ 符號(hào)
char * _demangle_symbol(char* mangle_symbol) {
size_t str_len = strlen(mangle_symbol);
if (str_len < 3) {
return mangle_symbol;
}
if (PLATFORM_IOS) {
if (strstr(mangle_symbol, "__Z") == mangle_symbol) {
char *new_mangle_symbol = mangle_symbol + 1;
int status;
char *demangle_symbol = abi::__cxa_demangle (new_mangle_symbol, nullptr, 0, &status);
return status == 0 ? demangle_symbol : mangle_symbol;
}
} else {
int status;
char *demangle_symbol = abi::__cxa_demangle (mangle_symbol, nullptr, 0, &status);
return status == 0 ? demangle_symbol : mangle_symbol;
}
return mangle_symbol;
}
這里的 demangle 需要區(qū)分下 iOS 系統(tǒng)和 MacOS 系統(tǒng),在 iOS 系統(tǒng)中晰搀,直接 demangle 是會(huì)返回 status = 4五辽,也就是格式不符合,經(jīng)過(guò)試驗(yàn)后外恕,發(fā)現(xiàn)在 iOS 系統(tǒng)上杆逗,只要將字符中開(kāi)頭的 __Z
修改為 _Z
后乡翅,便可以 demangle 成功,具體原因我也不清楚罪郊。
當(dāng)我以為自己已經(jīng)快要成功時(shí)蠕蚜,現(xiàn)實(shí)潑我一桶冷水。由于之前測(cè)試都是在模擬器悔橄,所以在可以打印出 WebKit 鏡像中所有函數(shù)的符號(hào)和其對(duì)應(yīng)的地址靶累,如下圖所示:
但是當(dāng)我在真機(jī)上運(yùn)行的時(shí)候,一臉懵逼癣疟,獲取到的符號(hào)大部分是 <redacted>
挣柬,只有部分地址解析出來(lái)了,而解析出來(lái)部分的符號(hào)對(duì)應(yīng)的地址是 0x0
睛挚。如下圖所示:
經(jīng)過(guò)分析后邪蛔,發(fā)現(xiàn)在真機(jī)中,編譯器應(yīng)該做了下面的優(yōu)化處理(純屬個(gè)人猜測(cè))
- 對(duì)于 dyld 中的內(nèi)部函數(shù)對(duì)應(yīng)的符號(hào)扎狱,都可以地址化(去符號(hào)化)侧到,因?yàn)榉?hào)是給人閱讀的,對(duì)于機(jī)器來(lái)說(shuō)一個(gè)二進(jìn)制地址就夠了淤击。而且也可以有效的減少內(nèi)存中 dyld 的體積匠抗。
- 對(duì)于 dyld 中暴露出來(lái)的函數(shù),可以在符號(hào)表中獲取到符號(hào)和在 dyld 中的偏移值遭贸,因?yàn)檫@些函數(shù)需要給外部調(diào)用戈咳,所以不能地址化心软。
- 對(duì)于 dyld 中引用的第三方庫(kù)中的函數(shù)壕吹,不會(huì)被地址化,但是由于是外部符號(hào)删铃,所以需要進(jìn)行重定向才能獲取到真正的地址耳贬。
總結(jié)
經(jīng)過(guò)自己的研究后,發(fā)現(xiàn)在真機(jī)中猎唁,可能真的沒(méi)有什么方法可以 hook c++ 中的私有方法咒劲。如果只是調(diào)試使用,我們可以直接在 mac 上用 MachOView 或 Hooper 來(lái)獲取到私有函數(shù)的在對(duì)應(yīng) dyld 中的偏移值诫隅,然后直接在代碼中用偏移中進(jìn)行 hook 操作腐魂。但是想在應(yīng)用中直接通過(guò)函數(shù)名稱(chēng)去 hook dyld 中內(nèi)部私有方法應(yīng)該是沒(méi)有辦法的(至少我現(xiàn)在想不出來(lái))。
如果想 hook 私有庫(kù)中的共有方法逐纬,應(yīng)該是可以實(shí)現(xiàn)的蛔屹。可以直接修改 fishhook 的源碼豁生,在外部符號(hào)匹配時(shí)兔毒,對(duì)從 dyld 符號(hào)表取到的符號(hào)進(jìn)行 demangle 操作漫贞,然后再進(jìn)行比較,因?yàn)?c 和 c++ 的唯一區(qū)別育叁,就是存儲(chǔ)在符號(hào)表中的符號(hào)有沒(méi)有經(jīng)過(guò)一層 demangle 操作迅脐。所以只要去除這個(gè)區(qū)別,可以把 c++ 的 hook 和 c 等同起來(lái)豪嗽。
ps: 相同的代碼谴蔑,在 iOS 真機(jī)上獲取到的內(nèi)部函數(shù)都是 <redacted>,但是在 Mac 或 iOS 模擬器上可以解析出來(lái)龟梦。在這個(gè)過(guò)程中树碱,為了探索是否是 iOS 中內(nèi)置的 dyld 和 Mac 中的不一致,我也從一臺(tái)越獄手機(jī)中拉取了 iOS 中的共享緩存 dyld_shared_cache_arm64变秦,從共享緩存中抽出 WebKit 庫(kù)后成榜,發(fā)現(xiàn)和 Mac 上的并沒(méi)有什么區(qū)別。
2019 年 10 月 14 日修改
經(jīng)過(guò)研究后發(fā)現(xiàn)蹦玫,hookzz 是無(wú)法用于 inline hook 的赎婚,所以在非越獄機(jī)器上,暫時(shí)沒(méi)有方法 hook C++ 函數(shù)
使用 HookZz 替換 mach_msg 方法程序崩潰
嘗試使用 fishhook 來(lái) hook 系統(tǒng)的 mach_msg樱溉,從而接管整個(gè)進(jìn)程的實(shí)驗(yàn)也失敗了挣输。
原因是:由于 fishhook 雖然只能 hook 到部分 mach_msg,對(duì)于 WebKit 中被調(diào)用的 mach_msg福贞,無(wú)法 hook 撩嚼,具體原因可以查看下 iOSer 上的討論鏈接 Fishhook 是否無(wú)法 hook 到所有的 mach_msg
參考資料
- Mach-O 可執(zhí)行文件
- 探秘 Mach-O 文件
- iOS逆向基礎(chǔ)Mach-O文件(1)
- 趣探 Mach-O:文件格式分析
- 動(dòng)態(tài)修改 C 語(yǔ)言函數(shù)的實(shí)現(xiàn)
- 巧用符號(hào)表 - 探求 fishhook 原理(一)
- Hook 原理之 fishhook 源碼解析
- HookZz
- dyld詳解
- iOS 逆向----一鍵砸殼工具frida-ios-dump
- monkeyDev
- iOS 逆向----從越獄的手機(jī)中提取App Store下載的APP
- frida-ios-dump
- iOS逆向----SSH連接越獄iPhone
- iproxy-通過(guò)USB使用SSH連接iOS設(shè)備