9

通过 dyld-interposing 实现C/C++代码注入

 9 months ago
source link: https://yrom.net/blog/2023/10/19/dyld-interposing/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

通过 dyld-interposing 实现C/C++代码注入

2023-10-19 Debug

苹果系统的链接器/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
#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);
// fills with '#'
memset(p, '#', size);
malloc_printf("malloc(%u) = %p\n", size, p);
return (void *)p;
}

DYLD_INTERPOSE(trace_malloc, malloc);
// test.c
#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;
}
$ 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 的定义:

#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 展开后的代码如下:

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 查看:

$ 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 查看:

screenshot-machoview-data-interpose.png

在程序加载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,可以打印出被替换函数的替换信息:

$ 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::getAMFIHeader::isRestricted()


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK