苹果系统的链接器/usr/lib/dyld
提供了一个叫dyld-interposing
的功能(从 Mac OS X 10.4 开始),可以在程序启动时替换掉某个函数的实现。这个功能可以用来实现代码注入(详见:《Mac OS X Internals: A Systems Approach》 - Amit Singh - 第二章 2.6.3.4 dyld interposing)
举个栗子 比如,我们可以在程序运行时,替换掉malloc
函数的实现:
malloc_trace.c 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <stdio.h> #include <stdlib.h> #include <mach-o/dyld-interposing.h> #include <memory.h> // memset #include <malloc/malloc.h> // malloc_printf void *trace_malloc (size_t size) { char *p = malloc (size); memset (p, '#' , size); malloc_printf("malloc(%u) = %p\n" , size, p); return (void *)p; } DYLD_INTERPOSE(trace_malloc, malloc );
test.c 1 2 3 4 5 6 7 8 9 #include <stdio.h> #include <stdlib.h> int main () { char *p = (char *)malloc (10 ); printf ("malloc return %p, %s\n" , p, p); free (p); return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 $ cc -dynamiclib -o libmalloctrace.dylib malloc_trace.c -install_name libmalloctrace.dylib $ cc -o test test.c $ DYLD_INSERT_LIBRARIES=libmalloctrace.dylib ./test test(46555,0x11bdd3600) malloc: malloc(1536) = 0x7febbc808200 test(46555,0x11bdd3600) malloc: malloc(32) = 0x7febbc704130 test(46555,0x11bdd3600) malloc: malloc(32) = 0x7febbc704170 test(46555,0x11bdd3600) malloc: malloc(20) = 0x7febbc705550 test(46555,0x11bdd3600) malloc: malloc(422) = 0x7febbc7055d0 test(46555,0x11bdd3600) malloc: malloc(50) = 0x7febbc7057e0 test(46555,0x11bdd3600) malloc: malloc(16) = 0x7febbc705880 test(46555,0x11bdd3600) malloc: malloc(52) = 0x7febbc705900 test(46555,0x11bdd3600) malloc: malloc(12) = 0x7febbc7059b0 test(46555,0x11bdd3600) malloc: malloc(10) = 0x7febbc705b00 test(46555,0x11bdd3600) malloc: malloc(4096) = 0x7febbc808800 malloc return 0x7febbc705b00, ##########
ps. 不能在替换函数(replacement)中使用会调用被替换函数(replacee)的其它函数。比如printf()
内部可能会使用malloc()
则trace_malloc
中不能调用printf
,不然死给你看~
头文件<mach-o/dyld-interposing.h>
来自 dyld 源码:https://github.com/apple-oss-distributions/dyld/blob/dyld-1042.1/include/mach-o/dyld-interposing.h
其中宏 DYLD_INTERPOSE
的定义:
1 2 3 #define DYLD_INTERPOSE(_replacement,_replacee) \ __attribute__((used)) static struct { const void * replacement; const void * replacee; } _interpose_##_replacee \ __attribute__ ((section ("__DATA,__interpose,interposing" ))) = { (const void *)(unsigned long )&_replacement, (const void *)(unsigned long )&_replacee };
malloc_trace.c
展开后的代码如下:
1 2 3 4 5 6 7 static struct { const void *replacement; const void *replacee; } _interpose_malloc __attribute__ ((section ("__DATA,__interpose,interposing" ))) = { (const void *)&trace_malloc, (const void *)&malloc };
这段代码声明了一个_interpose_malloc
的结构体,replacement
成员指向trace_malloc
函数,replacee
成员指向malloc
函数。
并通过__attribute__
编译配置将这个结构体放到了 Mach-O 产物的 __DATA,__interpose
段中。
用 otool
查看:
1 2 3 4 5 6 7 $ otool -l libmalloctrace.dylib | grep -A 5 __interpose sectname __interpose segname __DATA addr 0x0000000000008000 size 0x0000000000000010 offset 32768 align 2^3 (8)
__DATA,__interpose
段的大小为0x10
,也就是16 字节,正好是代码中_interpose_malloc
结构体的大小。
也可以使用 MachOView 查看:
在程序加载dylib
时,dyld
会解析它的__DATA,__interpose
段,找到所有的struct { uintptr_t replacement; uintptr_t replacee; };
结构体,然后将replacee
成员指向的函数替换成replacement
成员指向的函数。
详见源码:RuntimeState::buildInterposingTables()
使用__DATA,__interpose
段实现函数替换是静态替换 ,在程序启动时(dylib
被加载时)就替换了。
曾经还有一种方式是动态替换 ,使用dyld
的私有函数dyld_dynamic_interpose
,在程序运行时替换函数,详见<mach-o/dyld_priv.h>
。不过经过测试,已经失效了~~
使用环境变量 DYLD_PRINT_INTERPOSING=1
,可以打印出被替换函数的替换信息:
1 2 3 4 5 6 7 8 9 10 11 $ DYLD_PRINT_INTERPOSING=1 DYLD_INSERT_LIBRARIES=libmalloctrace.dylib ./test dyld[92700]: libmalloctrace.dylib has interposed '_malloc' to replacing binds to 0x7FF805E19530 with 0x10151FF40 dyld[92700]: interpose replaced 0x7FF805E19530 with 0x7FF805E19530 in /path/to/libmalloctrace.dylib dyld[92700]: interpose replaced 0x7FF805E19530 with 0x10151FF40 in /path/to/test dyld[92700]: interpose: *0x7ff845cdd028 = 0x10151FF40 (dyld cache patch) to _malloc dyld[92700]: interpose: *0x7ff845cddb38 = 0x10151FF40 (dyld cache patch) to _malloc dyld[92700]: interpose: *0x7ff845ce35e0 = 0x10151FF40 (dyld cache patch) to _malloc dyld[92700]: interpose: *0x7ff845ce47c0 = 0x10151FF40 (dyld cache patch) to _malloc dyld[92700]: interpose: *0x7ff845ce6d78 = 0x10151FF40 (dyld cache patch) to _malloc ...
有什么用? 你可能疑惑通过dyld-interposing
实现代码注入有什么用?
方便调试(比如实现malloc
的内存泄漏检测) api trace(比如实现opengl
api trace) hook dylib api (比如实现 hot-reload) … Prevent dyld-interposing? 阻止dyld
加载 DYLD_INSERT_LIBRARIES
:添加链接参数-Wl,-add_empty_section,__RESTRICT,__restrict
(详见:ProcessConfig::Security::getAMFI 、Header::isRestricted() )
Refs