让以一个虚构的例子为例。假设有一个用C语言编写的名为“test”的程序(test.c文件)和一个共享库(libtest.c文件),库中实现了一个libtest()函数。在它们的实现中,程序和库都使用了C语言标准库中的puts()函数(与Mac OS一起提供,包含在libSystem.B.dylib中)。让看一下描述的情况的图解:
任务如下:需要将libtest.dylib库对puts()函数的调用替换为在主程序(test.c文件)中实现的hooked_puts()函数的调用。后者,反过来,可以使用原始的puts()函数。
需要取消执行的更改,即,使libtest()的重复调用导致调用原始的puts()。不允许更改代码或重新编译库,只能更改主程序。调用重定向本身应该只为特定库执行,并且是即时的,而不需要程序重启。
让用文字描述所有操作,因为代码可能尽管有很多注释,但可能仍然不那么清晰:
1. 使用LC_SYMTAB加载命令的数据找到符号表和字符串表。
2. 从LC_DYSYMTAB加载命令中,找出符号表中未定义符号的子集(iundefsym字段)从哪个元素开始。
3. 在符号表中未定义符号的子集中,通过名称找到目标符号。
4. 记住目标符号从符号表开始的索引。
5. 使用LC_DYSYMTAB加载命令的数据找到间接符号表(indirectsymoff字段)。
6. 找出从哪个索引开始映射导入表(__DATA, __la_symbol_ptr节的内容;或__IMPORT, __jump_table — 将有一个)到间接符号表(reserved1字段)。
7. 从这个索引开始,查看间接符号表,并搜索与符号表中目标符号索引相对应的值。
8. 记住目标符号从导入表映射到间接符号表的索引。保存的值是导入表中所需元素的索引。
9. 使用__la_symbol_ptr节(或__jump_table)的数据找到导入表(offset字段)。
10. 有了目标元素的索引,重写地址(对于__la_symbol_ptr)为所需值(或者只是更改CALL/JMP指令为JMP,带有操作数 — 所需函数的地址(对于__jump_table))。
将指出,应该在从文件加载符号表、字符串表和间接符号表之后,以及在内存中读取描述导入表的部分以及执行重定向本身时,只与这些表、字符串和间接符号表一起工作。这是因为符号表和字符串表可能不存在,或者不能在目标Mach-O中显示真实状态。这是因为动态加载器在之前在那里工作,并且它成功地保存了所有必要的符号数据,而没有分配表本身。
是时候将想法转化为代码了。让将所有操作分为三个阶段,以优化搜索所需的Mach-O元素:
#include
#include
#include "mach_hook.h"
#define LIBTEST_PATH "libtest.dylib"
void libtest();
int hooked_puts(char const *s) {
puts(s);
return puts("HOOKED!");
}
int main() {
void *handle = 0;
mach_substitution original;
Dl_info info;
if (!dladdr((void const *)libtest, &info)) {
fprintf(stderr, "Failed to get the base address of a library!\n", LIBTEST_PATH);
goto end;
}
handle = mach_hook_init(LIBTEST_PATH, info.dli_fbase);
if (!handle) {
fprintf(stderr, "Redirection init failed!\n");
goto end;
}
libtest();
puts("-----------------------------");
original = mach_hook(handle, "puts", (mach_substitution)hooked_puts);
if (!original) {
fprintf(stderr, "Redirection failed!\n");
goto end;
}
libtest();
puts("-----------------------------");
original = mach_hook(handle, "puts", original);
if (!original) {
fprintf(stderr, "Restoration failed!\n");
goto end;
}
libtest();
end:
mach_hook_free(handle);
handle = 0;
return 0;
}
可以通过以下方式进行测试:
user@mac$ arch -i386 ./test
libtest: calls the original puts()
-----------------------------
libtest: calls the original puts()
HOOKED!
-----------------------------
libtest: calls the original puts()