动态修改 C 语言函数的实现 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
iOS 开发实用技术导航
NSHipster 中文版
http://nshipster.cn/
cocos2d 开源 2D 游戏引擎
http://www.cocos2d-iphone.org/
CocoaPods
http://cocoapods.org/
Google Analytics for Mobile 统计解决方案
http://code.google.com/mobile/analytics/
WWDC
https://developer.apple.com/wwdc/
Design Guides and Resources
https://developer.apple.com/design/
Transcripts of WWDC sessions
http://asciiwwdc.com
Cocoa with Love
http://cocoawithlove.com/
Cocoa Dev Central
http://cocoadevcentral.com/
NSHipster
http://nshipster.com/
Style Guides
Google Objective-C Style Guide
NYTimes Objective-C Style Guide
Useful Tools and Services
Charles Web Debugging Proxy
Smore
Draven
V2EX    iDev

动态修改 C 语言函数的实现

  •  1
     
  •   Draven
    draveness 2016-08-03 22:07:53 +08:00 6229 次点击
    这是一个创建于 3357 天前的主题,其中的信息可能已经有所发展或是发生改变。

    动态修改 C 语言函数的实现

    Objective-C 作为基于 Runtime 的语言,它有非常强大的动态特性,可以在运行期间自省、进行方法调剂、为类增加属性、修改消息转发链路,在代码运行期间通过 Runtime 几乎可以修改 Objecitve-C 层的一切类、方法以及属性。

    真正绝对意义上的动态语言或者静态语言事不存在的。

    C 语言往往会给我们留下不可修改的这一印象;在之前的几年时间里,笔者确实也是这么认为的,然而最近接触到的 fishhook 使我对 C 语言的不可修改有了更加深刻的理解。

    在文章中涉及到一个比较重要的概念,就是镜像( image );在 Mach-O 文件系统中,所有的可执行文件、 dylib 以及 Bundle 都是镜像。

    fishhook 简介

    到这里,我们该简单介绍一下今天分享的 fishhook ; fishhook 是一个由 facebook 开源的第三方框架,其主要作用就是动态修改 C 语言函数实现

    这个框架的代码其实非常的简单,只包含两个文件:fishhook.c 以及 fishhook.h;两个文件所有的代码加起来也不超过 300 行。

    不过它的实现原理是非常有意思并且精妙的,我们可以从 fishhook 提供的接口中入手。

    从接口开始

    fishhook 提供非常简单的两个接口以及一个结构体:

    struct rebinding { const char *name; void *replacement; void **replaced; }; int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel); int rebind_symbols_image(void *header, intptr_t slide, struct rebinding rebindings[], size_t rebindings_nel); 

    其中 rebind_symbols 接收一个 rebindings 数组,也就是重新绑定信息,还有就是 rebindings_nel,也就是 rebindings 的个数。

    使用 fishhook 修改 C 函数

    使用 fishhook 修改 C 函数很容易,我们使用它提供的几个范例来介绍它的使用方法。

    这里要修改的是底层的 open 函数的实现,首先在工程中引入 fishhook.h 头文件,然后声明一个与原函数签名相同的函数指针:

    #import "fishhook.h" static int (*origianl_open)(const char *, int, ...); 

    然后重新实现 new_open 函数:

    int new_open(const char *path, int oflag, ...) { va_list ap = {0}; mode_t mode = 0; if ((oflag & O_CREAT) != 0) { // mode only applies to O_CREAT va_start(ap, oflag); mode = va_arg(ap, int); va_end(ap); printf("Calling real open('%s', %d, %d)\n", path, oflag, mode); return orig_open(path, oflag, mode); } else { printf("Calling real open('%s', %d)\n", path, oflag); return orig_open(path, oflag, mode); } } 

    这里调用的 original_open 其实相当于执行原 open;最后,在 main 函数中使用 rebind_symbols 对符号进行重绑定:

    // 初始化一个 rebinding 结构体 struct rebinding open_rebinding = { "open", new_open, (void *)&original_open }; // 将结构体包装成数组,并传入数组的大小,对原符号 open 进行重绑定 rebind_symbols((struct rebinding[1]){open_rebinding}, 1); // 调用 open 函数 __unused int fd = open(argv[0], O_RDONLY); 

    在对符号进行重绑定之后,所有调用 open 函数的地方实际上都会执行 new_open 的实现,也就完成了对 open 的修改。

    fishhook-result

    程序运行之后打印了 Calling real open('/Users/apple/Library/Developer/Xcode/DerivedData/Demo-cdnoozusghmqtubdnbzedzdwaagp/Build/Products/Debug/Demo', 0) 说明我们的对 open 函数的修改达到了预期的效果。

    整个 main.m 文件中的代码在文章的最后面 main.m

    fishhook 的原理以及实现

    在介绍 fishhook 具体实现原理之前,有几个非常重要的知识需要我们了解,那就是 dyld、动态链接以及 Mach-O 文件系统。

    dyld 与动态链接

    dyld 是 the dynamic link editor 的缩写~~(笔者并不知道为什么要这么缩写)~~。至于它的作用,简单一点说,就是负责将各种各样程序需要的镜像加载到程序运行的内存空间中,这个过程发生的时间非常早 --- 在 objc 运行时初始化之前

    在 dyld 加载镜像时,会执行注册过的回调函数;当然,我们也可以使用下面的方法注册自定义的回调函数,同时也会为所有已经加载的镜像执行回调

    extern void _dyld_register_func_for_add_image( void (*func)(const struct mach_header* mh, intptr_t vmaddr_slide) ); 

    对于每一个已经存在的镜像,当它被动态链接时,都会执行回调 void (*func)(const struct mach_header* mh, intptr_t vmaddr_slide),传入文件的 mach_header 以及一个虚拟内存地址 intptr_t

    需要注意的是, dyld 只负责将一些需要动态链接的库加载进来,比如说 C 语言标准库,或者 Foundation 这些 ObjC 提供的库;而我们自己在程序中写的代码是不会通过 dyld 进行加载的。

    以一个最简单的 Hello World 程序为例:

    #include <stdio.h> int main(int argc, const char * argv[]) { printf("Hello, World!\n"); return 0; } 

    代码中只引用了一个 stdio 库中的函数 printf;我们如果 Build 这段代码,生成可执行文件之后,使用下面的命令 nm

    $ nm -nm HelloWorld 

    nm 命令可以查看可执行文件中的符号(对 nm 不熟悉的读者可以在终端中使用 man nm 查看手册):

     (undefined) external _printf (from libSystem) (undefined) external dyld_stub_binder (from libSystem) 0000000100000000 (__TEXT,__text) [referenced dynamically] external __mh_execute_header 0000000100000f50 (__TEXT,__text) external _main 
     

    在可执行文件中的符号列表中,_printf 这个符号是未定义( undefined )的,换句话说,编译器还不知道这个符号对应什么东西。

    但是,如果在文件中加入一个 C 函数 hello_world

    #include <stdio.h> void hello_world() { printf("Hello, World!\n"); } int main(int argc, const char * argv[]) { printf("Hello, World!\n"); return 0; } 

    在构建之后,同样使用 nm 查看其中的符号:

     (undefined) external _printf (from libSystem) (undefined) external dyld_stub_binder (from libSystem) 0000000100000000 (__TEXT,__text) [referenced dynamically] external __mh_execute_header 0000000100000f30 (__TEXT,__text) external _hello_world 0000000100000f50 (__TEXT,__text) external _main 

    我们的符号 _hello_world 并不是未定义的( undefined ),它包含一个内存地址以及 __TEXT 段。也就是说手写的一些函数,在编译之后,其地址并不是未定义的,这一点对于之后分析 fishhook 有所帮助。

    使用 nm 打印出的另一个符号 dyld_stub_binder 对应另一个同名函数。dyld_stub_binder 会在目标符号(例如 printf)被调用时,将其链接到指定的动态链接库 libSystem,再执行 printf 的实现(printf 符号位于 __DATA 端中的 lazy 符号表中):

    fishhook-symbo

    每一个镜像中的 __DATA 端都包含两个与动态链接有关的表,其中一个是 __nl_symbol_ptr,另一个是 __la_symbol_ptr

    • __nl_symbol_ptr 中的 non-lazy 符号是在动态链接库绑定的时候进行加载的
    • __la_symbol_ptr 中的符号会在该符号被第一次调用时,通过 dyld 中的 dyld_stub_binder 过程来进行加载
    0000000100001010 dq 0x0000000100000f9c ; XREF=0x1000002f8, imp___stubs__printf 

    地址 0x0000000100000f9c 就是 printf 函数打印字符串实现的位置:

    fishbook-printf

    在上述代码调用 printf 时,由于符号是没有被加载的,就会通过 dyld_stub_binder 动态绑定符号。

    Mach-O

    由于文章中会涉及一些关于 Mach-O 文件格式的知识,所以在这里会简单介绍一下 Mach-O 文件格式的结构。

    每一个 Mach-O 文件都会被分为不同的 Segments ,比如 __TEXT, __DATA, __LINKEDIT

    fishhook-mach-o

    这也就是 Mach-O 中的 segment_command( 32 位与 64 位不同):

    struct segment_command_64 { /* for 64-bit architectures */ uint32_t cmd; /* LC_SEGMENT_64 */ uint32_t cmdsize; /* includes sizeof section_64 structs */ char segname[16]; /* segment name */ uint64_t vmaddr; /* memory address of this segment */ uint64_t vmsize; /* memory size of this segment */ uint64_t fileoff; /* file offset of this segment */ uint64_t filesize; /* amount to map from the file */ vm_prot_t maxprot; /* maximum VM protection */ vm_prot_t initprot; /* initial VM protection */ uint32_t nsects; /* number of sections in segment */ uint32_t flags; /* flags */ }; 

    而每一个 segment_command 中又包含了不同的 section

    struct section_64 { /* for 64-bit architectures */ char sectname[16]; /* name of this section */ char segname[16]; /* segment this section goes in */ uint64_t addr; /* memory address of this section */ uint64_t size; /* size in bytes of this section */ uint32_t offset; /* file offset of this section */ uint32_t align; /* section alignment (power of 2) */ uint32_t reloff; /* file offset of relocation entries */ uint32_t nreloc; /* number of relocation entries */ uint32_t flags; /* flags (section type and attributes)*/ uint32_t reserved1; /* reserved (for offset or index) */ uint32_t reserved2; /* reserved (for count or sizeof) */ uint32_t reserved3; /* reserved */ }; 

    你只需要对这几个概念有一个简单的了解,知道它们有怎样的包含关系,当文章中挑出这个名字时,对它不是一无所知就足够了,这里并不会涉及太多相关的知识。

    fishhook 的原理

    到目前为止,我们对 dyld 以及 Mach-O 有了一个初步的了解,而 fishhook 使用了前面章节提到的 _dyld_register_func_for_add_image 注册了一个回调,在每次加载镜像到程序中执行回调,动态修改 C 函数实现。

    在具体分析其源代码之前,先为各位读者详细地介绍它的实现原理:

    dyld 通过更新 Mach-O 二进制文件 __DATA 段中的一些指针来绑定 lazy 和 non-lazy 的符号;而 fishhook 先确定某一个符号在 __DATA 段中的位置,然后保存原符号对应的函数指针,并使用新的函数指针覆盖原有符号的函数指针,实现重绑定。

    整个过程可以用这么一张图来表示:

    fishhook-before-afte

    原理看起来还是很简单的,其中最复杂的部分就是从二进制文件中寻找某个符号的位置,在 fishhook 的 README 中,有这样一张图:

    fishhook-imp

    这张图初看很复杂,不过它演示的是寻找符号的过程,我们根据这张图来分析一下这个过程:

    1. __DATA 段中的 lazy 符号指针表中查找某个符号,获得这个符号的偏移量 1061,然后在每一个 section_64 中查找 reserved1,通过这两个值找到 Indirect Symbol Table 中符号对应的条目
    2. 在 Indirect Symbol Table 找到符号表指针以及对应的索引 16343 之后,就需要访问符号表
    3. 然后通过符号表中的偏移量,获取字符串表中的符号 _close

    fishhook 的实现

    上面梳理了寻找符号的过程,现在,我们终于要开始分析 fishhook 的源代码,看它是如何一步一步替换原有函数实现的。

    对实现的分析会 rebind_symbols 函数为入口,首先看一下函数的调用栈:

    int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel); └── extern void _dyld_register_func_for_add_image(void (*func)(const struct mach_header* mh, intptr_t vmaddr_slide)); static void _rebind_symbols_for_image(const struct mach_header *header, intptr_t slide) └── static void rebind_symbols_for_image(struct rebindings_entry *rebindings, const struct mach_header *header, intptr_t slide) └── static void perform_rebinding_with_section(struct rebindings_entry *rebindings, section_t *section, intptr_t slide, nlist_t *symtab, char *strtab, uint32_t *indirect_symtab) 

    其实函数调用栈非常简单,因为整个库中也没有几个函数,rebind_symbols 作为接口,其主要作用就是注册一个函数并在镜像加载时回调:

    int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel) { int retval = prepend_rebindings(&_rebindings_head, rebindings, rebindings_nel); if (retval < 0) return retval; if (!_rebindings_head->next) { _dyld_register_func_for_add_image(_rebind_symbols_for_image); } else { uint32_t c = _dyld_image_count(); for (uint32_t i = 0; i < c; i++) { _rebind_symbols_for_image(_dyld_get_image_header(i), _dyld_get_image_vmaddr_slide(i)); } } return retval; } 

    rebind_symbols 最开始执行时,会先调用一个 prepend_rebindings 的函数,将整个 rebindings 数组添加到 _rebindings_head 这个私有数据结构的头部:

    static int prepend_rebindings(struct rebindings_entry **rebindings_head, struct rebinding rebindings[], size_t nel) { struct rebindings_entry *new_entry = malloc(sizeof(struct rebindings_entry)); if (!new_entry) { return -1; } new_entry->rebindings = malloc(sizeof(struct rebinding) * nel); if (!new_entry->rebindings) { free(new_entry); return -1; } memcpy(new_entry->rebindings, rebindings, sizeof(struct rebinding) * nel); new_entry->rebindings_nel = nel; new_entry->next = *rebindings_head; *rebindings_head = new_entry; return 0; } 

    也就是说每次调用的 rebind_symbols 方法传入的 rebindings 数组以及数组的长度都会以 rebindings_entry 的形式添加到 _rebindings_head 这个私有链表的首部:

    struct rebindings_entry { struct rebinding *rebindings; size_t rebindings_nel; struct rebindings_entry *next; }; static struct rebindings_entry *_rebindings_head; 

    这样可以通过判断 _rebindings_head->next 的值来判断是否为第一次调用,然后使用 _dyld_register_func_for_add_image_rebind_symbols_for_image 注册为回调或者为所有存在的镜像单独调用 _rebind_symbols_for_image

    static void _rebind_symbols_for_image(const struct mach_header *header, intptr_t slide) { rebind_symbols_for_image(_rebindings_head, header, slide); } 

    _rebind_symbols_for_image 只是对另一个名字非常相似的函数 rebind_symbols_for_image 的封装,从这个函数开始,就到了重绑定符号的过程;不过由于这个方法的实现比较长,具体分析会分成三个部分并省略一些不影响理解的代码:

    static void rebind_symbols_for_image(struct rebindings_entry *rebindings, const struct mach_header *header, intptr_t slide) { segment_command_t *cur_seg_cmd; segment_command_t *linkedit_segment = NULL; struct symtab_command* symtab_cmd = NULL; struct dysymtab_command* dysymtab_cmd = NULL; uintptr_t cur = (uintptr_t)header + sizeof(mach_header_t); for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) { cur_seg_cmd = (segment_command_t *)cur; if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) { if (strcmp(cur_seg_cmd->segname, SEG_LINKEDIT) == 0) { linkedit_segment = cur_seg_cmd; } } else if (cur_seg_cmd->cmd == LC_SYMTAB) { symtab_cmd = (struct symtab_command*)cur_seg_cmd; } else if (cur_seg_cmd->cmd == LC_DYSYMTAB) { dysymtab_cmd = (struct dysymtab_command*)cur_seg_cmd; } } ... } 

    这部分的代码主要功能是从镜像中查找 linkedit_segment symtab_commanddysymtab_command;在开始查找之前,要先跳过 mach_header_t 长度的位置,然后将当前指针强转成 segment_command_t,通过对比 cmd 的值,来找到需要的 segment_command_t

    在查找了几个关键的 segment 之后,我们可以根据几个 segment 获取对应表的内存地址:

    static void rebind_symbols_for_image(struct rebindings_entry *rebindings, const struct mach_header *header, intptr_t slide) { ... uintptr_t linkedit_base = (uintptr_t)slide + linkedit_segment->vmaddr - linkedit_segment->fileoff; nlist_t *symtab = (nlist_t *)(linkedit_base + symtab_cmd->symoff); char *strtab = (char *)(linkedit_base + symtab_cmd->stroff); uint32_t *indirect_symtab = (uint32_t *)(linkedit_base + dysymtab_cmd->indirectsymoff); ... } 

    linkedit_segment 结构体中获得其虚拟地址以及文件偏移量,然后通过一下公式来计算当前 __LINKEDIT 段的位置:

    slide + vmaffr - fileoff 

    类似地,在 symtab_command 中获取符号表偏移量和字符串表偏移量,从 dysymtab_command 中获取间接符号表( indirect symbol table )偏移量,就能够获得_符号表_、_字符串表_以及_间接符号表_的引用了。

    • 间接符号表中的元素都是 uint32_t *,指针的值是对应条目 n_list 在符号表中的位置

    • 符号表中的元素都是 nlist_t 结构体,其中包含了当前符号在字符串表中的下标

      struct nlist_64 { union { uint32_t n_strx; /* index into the string table */ } n_un; uint8_t n_type; /* type flag, see below */ uint8_t n_sect; /* section number or NO_SECT */ uint16_t n_desc; /* see <mach-o/stab.h> */ uint64_t n_value; /* value of this symbol (or stab offset) */ }; 
    • 字符串表中的元素是 char 字符

    该函数的最后一部分就开启了遍历模式,查找整个镜像中的 SECTION_TYPES_LAZY_SYMBOL_POINTERS 或者 S_NON_LAZY_SYMBOL_POINTERS 的 section ,然后调用下一个函数 perform_rebinding_with_section 来对 section 中的符号进行处理:

    static void perform_rebinding_with_section(struct rebindings_entry *rebindings, section_t *section, intptr_t slide, nlist_t *symtab, char *strtab, uint32_t *indirect_symtab) { uint32_t *indirect_symbol_indices = indirect_symtab + section->reserved1; void **indirect_symbol_bindings = (void **)((uintptr_t)slide + section->addr); for (uint i = 0; i < section->size / sizeof(void *); i++) { uint32_t symtab_index = indirect_symbol_indices[i]; uint32_t strtab_offset = symtab[symtab_index].n_un.n_strx; char *symbol_name = strtab + strtab_offset; struct rebindings_entry *cur = rebindings; while (cur) { for (uint j = 0; j < cur->rebindings_nel; j++) { if (strcmp(&symbol_name[1], cur->rebindings[j].name) == 0) { if (cur->rebindings[j].replaced != NULL && indirect_symbol_bindings[i] != cur->rebindings[j].replacement) { *(cur->rebindings[j].replaced) = indirect_symbol_bindings[i]; } indirect_symbol_bindings[i] = cur->rebindings[j].replacement; goto symbol_loop; } } cur = cur->next; } symbol_loop:; } } 

    该函数的实现的核心内容就是将符号表中的 symbol_namerebinding 中的名字 name 进行比较,如果出现了匹配,就会将原函数的实现传入 origian_open 函数指针的地址,并使用新的函数实现 new_open 代替原实现:

    if (cur->rebindings[j].replaced != NULL && indirect_symbol_bindings[i] != cur->rebindings[j].replacement) { *(cur->rebindings[j].replaced) = indirect_symbol_bindings[i]; // 将原函数的实现传入 original_open 函数指针的地址 } indirect_symbol_bindings[i] = cur->rebindings[j].replacement; // 使用新的函数实现 new_open 替换原实现 

    如果你理解了上面的实现代码,该函数的其它代码就很好理解了:

    1. 通过 indirect_symtab + section->reserved1 获取 indirect_symbol_indices *,也就是符号表的数组
    2. 通过 (void **)((uintptr_t)slide + section->addr) 获取函数指针列表 indirect_symbol_bindings
    3. 遍历符号表数组 indirect_symbol_indices * 中的所有符号表中,获取其中的符号表索引 symtab_index
    4. 通过符号表索引 symtab_index 获取符号表中某一个 n_list 结构体,得到字符串表中的索引 symtab[symtab_index].n_un.n_strx
    5. 最后在字符串表中获得符号的名字 char *symbol_name

    到这里比较前的准备工作就完成了,剩下的代码会遍历整个 rebindings_entry 数组,在其中查找匹配的符号,完成函数实现的替换:

    while (cur) { for (uint j = 0; j < cur->rebindings_nel; j++) { if (strcmp(&symbol_name[1], cur->rebindings[j].name) == 0) { if (cur->rebindings[j].replaced != NULL && indirect_symbol_bindings[i] != cur->rebindings[j].replacement) { *(cur->rebindings[j].replaced) = indirect_symbol_bindings[i]; } indirect_symbol_bindings[i] = cur->rebindings[j].replacement; goto symbol_loop; } } cur = cur->next; } 

    在之后对某一函数的调用(例如 open),当查找其函数实现时,都会查找到 new_open 的函数指针;在 new_open 调用 origianl_open 时,同样也会执行原有的函数实现,因为我们通过 *(cur->rebindings[j].replaced) = indirect_symbol_bindings[i] 将原函数实现绑定到了新的函数指针上。

    实验

    fishhook 在 dyld 加载镜像时,插入了一个回调函数,交换了原有函数的实现;但是 fishhook 能否修改非动态链接库,比如开发人员自己手写的函数呢?我们可以做一个非常简单的小实验,下面是我们的 main.m 文件:

    #import <Foundation/Foundation.h> #import "fishhook.h" void hello() { printf("hello\n"); } static void (*original_hello)(); void new_hello() { printf("New_hello\n"); original_hello(); } int main(int argc, const char * argv[]) { @autoreleasepool { struct rebinding open_rebinding = { "hello", new_hello, (void *)&original_hello }; rebind_symbols((struct rebinding[1]){open_rebinding}, 1); hello(); } return 0; } 

    这里的函数实现非常的简单,相信也不需要笔者过多解释了,我们直接运行这份代码:

    fishhook-hello

    代码中只打印了 hello,说明 fishhook 对这种手写的函数是没有作用的,如果在下面这里打一个断点:

    fishhook-hello-breakpoint

    代码并不会进这里,因为 hello 这个函数并不在任何的镜像中存在,这也符合在最开始我们研究 dyld 时得出的结论:

    dyld 只负责将一些需要动态链接的库加载进来,比如说 C 语言标准库,或者 Foundation 这些 ObjC 提供的库;而我们自己在程序中写的代码是不会通过 dyld 进行加载的,也就无法修改其实现。

    小结

    fishhook 的实现非常的巧妙,但是它的使用也有一定的局限性,在接触到 fishhook 之前,从没有想到过可以通过一种方式修改 C 函数的实现,在笔者的印象中, C 语言作为静态语言,在运行时就很难改变其中的任何东西了,但是 fishhook 确实给我上了非常生动的一课 --- 原来 C 语言还可以这么玩。

    Reference

    其它

    main.m

    #import <Foundation/Foundation.h> #import "fishhook.h" static int (*original_open)(const char *, int, ...); int new_open(const char *path, int oflag, ...) { va_list ap = {0}; mode_t mode = 0; if ((oflag & O_CREAT) != 0) { // mode only applies to O_CREAT va_start(ap, oflag); mode = va_arg(ap, int); va_end(ap); printf("Calling real open('%s', %d, %d)\n", path, oflag, mode); return original_open(path, oflag, mode); } else { printf("Calling real open('%s', %d)\n", path, oflag); return original_open(path, oflag, mode); } } int main(int argc, const char * argv[]) { @autoreleasepool { struct rebinding open_rebinding = { "open", new_open, (void *)&original_open }; rebind_symbols((struct rebinding[1]){open_rebinding}, 1); __unused int fd = open(argv[0], O_RDONLY); } return 0; } 

    关注仓库,及时获得更新:iOS-Source-Code-Analyze

    Follow: Draveness Github

    原文链接: https://draveness.me/fishhook

    29 条回复    2016-08-08 13:16:49 +08:00
    airqj
        1
    airqj  
       2016-08-03 22:41:56 +08:00
    先 mark
    有时间看看
    yangff
        2
    yangff  
       2016-08-03 23:21:53 +08:00
    LD_PRELOAD
    GentleSadness
        3
    GentleSadness  
       2016-08-03 23:36:34 +08:00 via Android
    所以是 c 还是 oc ?
    mthli
        4
    mthli  
       2016-08-04 00:26:14 +08:00 via Android
    可以可以
    franklinyu
        5
    franklinyu  
       2016-08-04 00:26:16 +08:00
    所以是 c 还是 oc ?
    jesse_luo
        6
    jesse_luo  
       2016-08-04 01:16:50 +08:00
    @GentleSadness C ,不过只能 hook dylib 里的符号……
    jeffersonpig
        7
    jeffersonpig  
       2016-08-04 08:33:52 +08:00
    只能是 mac 系统上吗?
    Draven
        8
    Draven  
    OP
       2016-08-04 08:50:16 +08:00
    @jeffersonpig 这里的实现是通过 dyld 来进行的,所以可以在 iOS 或者 macOS
    ytjfmv
        9
    ytjfmv  
       2016-08-04 08:54:22 +08:00
    居然这么一大堆东西,我直接动态加载切换 .so 行不行
    xylophone21
        10
    xylophone21  
       2016-08-04 09:43:12 +08:00
    A library that enables dynamically rebinding symbols in Mach-O binaries running on iOS.

    这么重大的限制,不放在最前面,有那么一点标题党的嫌疑了。
    ben3ai
        11
    ben3ai  
       2016-08-04 09:46:14 +08:00 via iPhone
    mark 先
    21grams
        12
    21grams  
       2016-08-04 09:54:24 +08:00
    实际上是 mach-o 的特性,跟 C 关系不大,你要是能修改 PE 或 ELF 才算本事。
    nyanyh
        13
    nyanyh  
       2016-08-04 11:29:23 +08:00
    r#12 @21grams ELF 不了解,但是 PE 上的 hook 这些年已经玩烂啦,进了 kernel 更是花样百出
    21grams
        14
    21grams  
       2016-08-04 11:35:13 +08:00
    @nyanyh "在接触到 fishhook 之前,从没有想到过可以通过一种方式修改 C 函数的实现,在笔者的印象中, C 语言作为静态语言,在运行时就很难改变其中的任何东西了",这是你的原话。 然后你又说已经被玩烂了,烂了,了。。。。。
    nyanyh
        15
    nyanyh  
       2016-08-04 12:17:50 +08:00
    r#14 @21grams 这个是 LZ 原话不是我的原话……
    zhicheng
        16
    zhicheng  
       2016-08-04 12:39:01 +08:00
    提供两个 Google 关键词 LD_LIBRARY_PATH 和 DYLD_LIBRARY_PATH 。
    xieyudi1990
        17
    xieyudi1990  
       2016-08-04 12:40:53 +08:00 via Android
    前几天才写个个 wrapper ,再用 LD_PRELOAD 把 spotify 送到声卡的 pcm dump 出来。

    @21grams 什么鬼,这不是彻底暴露水平?
    sgissb1
        18
    sgissb1  
       2016-08-04 12:50:42 +08:00
    @21grams 修改 pe 或 elf 太麻烦了,还要除了要了解这两种的结构,还要了解不同平台和编译器的差异。
    尤其是堆栈平衡就够玩了。

    唉。。。。。感觉有些有点炒冷饭,基础好了才是真的好
    21grams
        19
    21grams  
       2016-08-04 13:00:48 +08:00 via Android
    @nyanyh sorry,没仔细看,以为你是楼主了
    franklinyu
        20
    franklinyu  
       2016-08-04 13:02:39 +08:00
    主自己原文接: http://draveness.me/fishhook/
    kingddc314
        21
    kingddc314  
       2016-08-04 13:10:47 +08:00 via Android
    不错不错,玩出了
    xdeng
        22
    xdeng  
       2016-08-04 14:35:50 +08:00
    这跟 C 语言有关吗?
    fengjianxinghun
        23
    fengjianxinghun  
       2016-08-04 14:51:34 +08:00 via iPhone
    好吧,这明明是 mach-o 特性……
    feuvan
        24
    feuvan  
       2016-08-04 15:05:59 +08:00
    stormpeach
        25
    stormpeach  
       2016-08-04 16:04:49 +08:00
    mark
    freeznet
        26
    freeznet  
       2016-08-04 16:22:57 +08:00
    安利一个 super cool 的 repo https://github.com/thorkill/eresi
    wadahana
        27
    wadahana  
       2016-08-04 17:06:23 +08:00
    fishhook 和 elfhook 是符号表层面的 hook , detours 是 inline hook
    fishhook 在 ios9 上 hook 系统库是不行的...
    wizardforcel
        28
    wizardforcel  
       2016-08-04 19:22:52 +08:00
    hook 是你不能控制 caller 时候用的, caller 能由你控制还用玩 hook ??
    jayzjj000
        29
    jayzjj000  
       2016-08-08 13:16:49 +08:00
    马克
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     3148 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 31ms UTC 12:20 PVG 20:20 LAX 05:20 JFK 08:20
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86