iOS Hook C++ 尝试

作者: Zsj_Sky | 来源:发表于2019-11-09 09:19 被阅读0次

    前言

    最近自己心血来潮,想研究下是否可以完美拦截到 WKWebView 的所有网络请求,所以就去看下了 WebKit 的源码,发现源码基本都是用 c++ 去实现的,突然就想去研究下能否 hook 私有库里面c++ 中的函数。于是就开始了一段学习之旅。

    搜索

    一切研究起于搜索,如果有人已经研究出来了,那就不用花费很多时间了,从 Google 到 stackOverflow,再到 gitHub,搜索了 hookc++ 相关的关键词,基本没有找到什么资料,没人能清晰的告诉我,在 iOS 中究竟能不能 hook c++ 方法。

    探索

    方案寻找

    在搜索没有找到有用资料时,我是有点懵逼的,因为不知如何下手(之前对 Mach-O 的文件格式基本没深入了解)。之前知道 fishhook 可以 hook c 函数,因此就想能不能也用 fishhook 来 hook 私有库里面 c++ 函数,当时的尝试是失败了。后来在一个研究逆向的同事的帮助下,了解到了可以使用 hookzz 这个库去 hook c/c++ 函数。具体 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 个参数,第一个是被 hook 函数的函数地址,第二个参数是用来替代原函数的函数地址,第三参数是函数指针的指针,用于存储原函数的函数指针。
    由于第二个和第三个参数都只自己创建的,所以现在的问题是,如何找到 hook 函数的函数地址。只要可以找到函数地址,就能够用 hookzz 进行 hook。

    被 hook 函数地址寻找

    那么,如何寻找一个函数的函数指针呢?这里就需要了解下 iOS 的 dyld 的文件格式 -- Mach-O。在 iOS 系统中,所有的 dyld 都 Mach-O 格式(具体什么是 Mach-O,可以上网搜索下,网上有很多大神发了很多解析文章),在 Mach-O 中,有一个符号表(Symbol Table)是专门存储代码的中所有符号和符号对应地址。而函数名称也是符号一种,所以也可以在符号表中直接找到。我们直接用 MachOView 工具,可以查看 dyld 文件。

    1. 获取 WebKit 的 dyld 文件,为了方便,我们直接拿 mac 系统中的 WebKit 库,在文件目录 /System/Library/Frameworks 中可以找到,如下图:
    WX20190909-110612.png
    1. 直接用 MachOView 工具打开 WebKit framework 中的 WebKit 文件,直接将左边的滚动栏拉到最下面,就可以看到 Symbol Table,如下图所示:
    符号表演示.png

    上图右边的第一红框标出的,就是 c++ 函数的符号,会发现和我们平时接触到的 c++ 函数的定义不太一样,这是因为相比于 c 函数, c++ 的实体定义较为复杂,所以区分不同的实体,编译器会对 c++ 实体进行 mangle 操作,从而保证了程序实体名称的唯一性。我们可以通过 c++filt 工具进行 demangle 操作 (GCC and MSVC C++ Demangler
    这个网站突然打不开了,该网站也支持 demangle c++ 函数)如下图所示

    c__filt工具使用.png

    可以看到,将符号 __ZNK7WebCore30MediaDevicesEnumerationRequest23userMediaDocumentOriginEv 进行 demangle 操作后,能到获取到 WebCore::MediaDevicesEnumerationRequest::userMediaDocumentOrigin() const 函数名称。

    代码实现

    上面我们已经分析了如何获取到函数函数地址,接下来就是如何用代码获取到符号表,这里需要对 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 ,找到符号表对应的 segment,同时也一起获取到 _TEXT 和 _LINKEDIT 的 segment
    // 遍历镜像里面的所有 segment
    void _enumerate_segment(const mach_header *header, std::function<bool(struct load_command *)> func) {
        // 这里我们只考虑64位应用。第一个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 需要区分下 iOS 系统和 MacOS 系统,在 iOS 系统中,直接 demangle 是会返回 status = 4,也就是格式不符合,经过试验后,发现在 iOS 系统上,只要将字符中开头的 __Z 修改为 _Z 后,便可以 demangle 成功,具体原因我也不清楚。

    当我以为自己已经快要成功时,现实泼我一桶冷水。由于之前测试都是在模拟器,所以在可以打印出 WebKit 镜像中所有函数的符号和其对应的地址,如下图所示:

    符号表模拟器运行结构.png

    但是当我在真机上运行的时候,一脸懵逼,获取到的符号大部分是 <redacted>,只有部分地址解析出来了,而解析出来部分的符号对应的地址是 0x0。如下图所示:

    真机获取符号表.png

    经过分析后,发现在真机中,编译器应该做了下面的优化处理(纯属个人猜测)

    1. 对于 dyld 中的内部函数对应的符号,都可以地址化(去符号化),因为符号是给人阅读的,对于机器来说一个二进制地址就够了。而且也可以有效的减少内存中 dyld 的体积。
    2. 对于 dyld 中暴露出来的函数,可以在符号表中获取到符号和在 dyld 中的偏移值,因为这些函数需要给外部调用,所以不能地址化。
    3. 对于 dyld 中引用的第三方库中的函数,不会被地址化,但是由于是外部符号,所以需要进行重定向才能获取到真正的地址。

    总结

    经过自己的研究后,发现在真机中,可能真的没有什么方法可以 hook c++ 中的私有方法。如果只是调试使用,我们可以直接在 mac 上用 MachOView 或 Hooper 来获取到私有函数的在对应 dyld 中的偏移值,然后直接在代码中用偏移中进行 hook 操作。但是想在应用中直接通过函数名称去 hook dyld 中内部私有方法应该是没有办法的(至少我现在想不出来)。

    如果想 hook 私有库中的共有方法,应该是可以实现的。可以直接修改 fishhook 的源码,在外部符号匹配时,对从 dyld 符号表取到的符号进行 demangle 操作,然后再进行比较,因为 c 和 c++ 的唯一区别,就是存储在符号表中的符号有没有经过一层 demangle 操作。所以只要去除这个区别,可以把 c++ 的 hook 和 c 等同起来。

    ps: 相同的代码,在 iOS 真机上获取到的内部函数都是 <redacted>,但是在 Mac 或 iOS 模拟器上可以解析出来。在这个过程中,为了探索是否是 iOS 中内置的 dyld 和 Mac 中的不一致,我也从一台越狱手机中拉取了 iOS 中的共享缓存 dyld_shared_cache_arm64,从共享缓存中抽出 WebKit 库后,发现和 Mac 上的并没有什么区别。

    2019 年 10 月 14 日修改

    经过研究后发现,hookzz 是无法用于 inline hook 的,所以在非越狱机器上,暂时没有方法 hook C++ 函数
    使用 HookZz 替换 mach_msg 方法程序崩溃

    尝试使用 fishhook 来 hook 系统的 mach_msg,从而接管整个进程的实验也失败了。
    原因是:由于 fishhook 虽然只能 hook 到部分 mach_msg,对于 WebKit 中被调用的 mach_msg,无法 hook ,具体原因可以查看下 iOSer 上的讨论链接 Fishhook 是否无法 hook 到所有的 mach_msg

    参考资料

    相关文章

      网友评论

        本文标题:iOS Hook C++ 尝试

        本文链接:https://www.haomeiwen.com/subject/shaiyctx.html