iOS Hook C++ 嘗試

前言

最近自己心血來潮,想研究下是否可以完美攔截到 WKWebView 的所有網(wǎng)絡(luò)請求,所以就去看下了 WebKit 的源碼,發(fā)現(xiàn)源碼基本都是用 c++ 去實現(xiàn)的,突然就想去研究下能否 hook 私有庫里面c++ 中的函數(shù)。于是就開始了一段學(xué)習(xí)之旅。

搜索

一切研究起于搜索,如果有人已經(jīng)研究出來了,那就不用花費很多時間了,從 Google 到 stackOverflow,再到 gitHub,搜索了 hookc++ 相關(guān)的關(guān)鍵詞,基本沒有找到什么資料,沒人能清晰的告訴我,在 iOS 中究竟能不能 hook c++ 方法。

探索

方案尋找

在搜索沒有找到有用資料時,我是有點懵逼的,因為不知如何下手(之前對 Mach-O 的文件格式基本沒深入了解)。之前知道 fishhook 可以 hook c 函數(shù),因此就想能不能也用 fishhook 來 hook 私有庫里面 c++ 函數(shù),當(dāng)時的嘗試是失敗了。后來在一個研究逆向的同事的幫助下,了解到了可以使用 hookzz 這個庫去 hook c/c++ 函數(shù)。具體 hookzz 的原理還沒有去了解,使用方法如下所示:

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 個參數(shù),第一個是被 hook 函數(shù)的函數(shù)地址,第二個參數(shù)是用來替代原函數(shù)的函數(shù)地址,第三參數(shù)是函數(shù)指針的指針,用于存儲原函數(shù)的函數(shù)指針。
由于第二個和第三個參數(shù)都只自己創(chuàng)建的,所以現(xiàn)在的問題是,如何找到 hook 函數(shù)的函數(shù)地址。只要可以找到函數(shù)地址,就能夠用 hookzz 進(jìn)行 hook。

被 hook 函數(shù)地址尋找

那么,如何尋找一個函數(shù)的函數(shù)指針呢?這里就需要了解下 iOS 的 dyld 的文件格式 -- Mach-O。在 iOS 系統(tǒng)中,所有的 dyld 都 Mach-O 格式(具體什么是 Mach-O,可以上網(wǎng)搜索下,網(wǎng)上有很多大神發(fā)了很多解析文章),在 Mach-O 中,有一個符號表(Symbol Table)是專門存儲代碼的中所有符號和符號對應(yīng)地址。而函數(shù)名稱也是符號一種,所以也可以在符號表中直接找到。我們直接用 MachOView 工具,可以查看 dyld 文件。

  1. 獲取 WebKit 的 dyld 文件,為了方便,我們直接拿 mac 系統(tǒng)中的 WebKit 庫,在文件目錄 /System/Library/Frameworks 中可以找到,如下圖:
WX20190909-110612.png
  1. 直接用 MachOView 工具打開 WebKit framework 中的 WebKit 文件,直接將左邊的滾動欄拉到最下面,就可以看到 Symbol Table,如下圖所示:
符號表演示.png

上圖右邊的第一紅框標(biāo)出的,就是 c++ 函數(shù)的符號,會發(fā)現(xiàn)和我們平時接觸到的 c++ 函數(shù)的定義不太一樣,這是因為相比于 c 函數(shù), c++ 的實體定義較為復(fù)雜,所以區(qū)分不同的實體,編譯器會對 c++ 實體進(jìn)行 mangle 操作,從而保證了程序?qū)嶓w名稱的唯一性。我們可以通過 c++filt 工具進(jìn)行 demangle 操作 (GCC and MSVC C++ Demangler
這個網(wǎng)站突然打不開了,該網(wǎng)站也支持 demangle c++ 函數(shù))如下圖所示

c__filt工具使用.png

可以看到,將符號 __ZNK7WebCore30MediaDevicesEnumerationRequest23userMediaDocumentOriginEv 進(jìn)行 demangle 操作后,能到獲取到 WebCore::MediaDevicesEnumerationRequest::userMediaDocumentOrigin() const 函數(shù)名稱。

代碼實現(xiàn)

上面我們已經(jīng)分析了如何獲取到函數(shù)函數(shù)地址,接下來就是如何用代碼獲取到符號表,這里需要對 Mach-O 文件格式有一定的了解

  1. 獲取到 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;
}
  1. 遍歷鏡像中的 segment ,找到符號表對應(yīng)的 segment,同時也一起獲取到 _TEXT 和 _LINKEDIT 的 segment
// 遍歷鏡像里面的所有 segment
void _enumerate_segment(const mach_header *header, std::function<bool(struct load_command *)> func) {
    // 這里我們只考慮64位應(yīng)用。第一個command從header的下一位開始
    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,  和 符號表的 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;
    });
    
    //.........
    
}

  1. 計算符號表和字符表的位置

    // 獲取到 _LINKEDIT segment 的首地址
    uintptr_t linkedit_addr = (uintptr_t)seg_linkedit->vmaddr -(uintptr_t)seg_text->vmaddr - (uintptr_t)seg_linkedit->fileoff;
    // 獲取到符號表的首地址
    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);

  1. 遍歷符號表
// 遍歷打印出所有的符號
    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));
    }
    
  1. demangle c++ 符號
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 是會返回 status = 4,也就是格式不符合,經(jīng)過試驗后,發(fā)現(xiàn)在 iOS 系統(tǒng)上,只要將字符中開頭的 __Z 修改為 _Z 后,便可以 demangle 成功,具體原因我也不清楚。

當(dāng)我以為自己已經(jīng)快要成功時,現(xiàn)實潑我一桶冷水。由于之前測試都是在模擬器,所以在可以打印出 WebKit 鏡像中所有函數(shù)的符號和其對應(yīng)的地址,如下圖所示:

符號表模擬器運行結(jié)構(gòu).png

但是當(dāng)我在真機上運行的時候,一臉懵逼,獲取到的符號大部分是 <redacted>,只有部分地址解析出來了,而解析出來部分的符號對應(yīng)的地址是 0x0。如下圖所示:

真機獲取符號表.png

經(jīng)過分析后,發(fā)現(xiàn)在真機中,編譯器應(yīng)該做了下面的優(yōu)化處理(純屬個人猜測)

  1. 對于 dyld 中的內(nèi)部函數(shù)對應(yīng)的符號,都可以地址化(去符號化),因為符號是給人閱讀的,對于機器來說一個二進(jìn)制地址就夠了。而且也可以有效的減少內(nèi)存中 dyld 的體積。
  2. 對于 dyld 中暴露出來的函數(shù),可以在符號表中獲取到符號和在 dyld 中的偏移值,因為這些函數(shù)需要給外部調(diào)用,所以不能地址化。
  3. 對于 dyld 中引用的第三方庫中的函數(shù),不會被地址化,但是由于是外部符號,所以需要進(jìn)行重定向才能獲取到真正的地址。

總結(jié)

經(jīng)過自己的研究后,發(fā)現(xiàn)在真機中,可能真的沒有什么方法可以 hook c++ 中的私有方法。如果只是調(diào)試使用,我們可以直接在 mac 上用 MachOView 或 Hooper 來獲取到私有函數(shù)的在對應(yīng) dyld 中的偏移值,然后直接在代碼中用偏移中進(jìn)行 hook 操作。但是想在應(yīng)用中直接通過函數(shù)名稱去 hook dyld 中內(nèi)部私有方法應(yīng)該是沒有辦法的(至少我現(xiàn)在想不出來)。

如果想 hook 私有庫中的共有方法,應(yīng)該是可以實現(xiàn)的??梢灾苯有薷?fishhook 的源碼,在外部符號匹配時,對從 dyld 符號表取到的符號進(jìn)行 demangle 操作,然后再進(jìn)行比較,因為 c 和 c++ 的唯一區(qū)別,就是存儲在符號表中的符號有沒有經(jīng)過一層 demangle 操作。所以只要去除這個區(qū)別,可以把 c++ 的 hook 和 c 等同起來。

ps: 相同的代碼,在 iOS 真機上獲取到的內(nèi)部函數(shù)都是 <redacted>,但是在 Mac 或 iOS 模擬器上可以解析出來。在這個過程中,為了探索是否是 iOS 中內(nèi)置的 dyld 和 Mac 中的不一致,我也從一臺越獄手機中拉取了 iOS 中的共享緩存 dyld_shared_cache_arm64,從共享緩存中抽出 WebKit 庫后,發(fā)現(xiàn)和 Mac 上的并沒有什么區(qū)別。

2019 年 10 月 14 日修改

經(jīng)過研究后發(fā)現(xiàn),hookzz 是無法用于 inline hook 的,所以在非越獄機器上,暫時沒有方法 hook C++ 函數(shù)
使用 HookZz 替換 mach_msg 方法程序崩潰

嘗試使用 fishhook 來 hook 系統(tǒng)的 mach_msg,從而接管整個進(jìn)程的實驗也失敗了。
原因是:由于 fishhook 雖然只能 hook 到部分 mach_msg,對于 WebKit 中被調(diào)用的 mach_msg,無法 hook ,具體原因可以查看下 iOSer 上的討論鏈接 Fishhook 是否無法 hook 到所有的 mach_msg

參考資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容