65.9K
CodeProject 正在变化。 阅读更多。
Home

Mach-O 中的导入函数重定向

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.78/5 (6投票s)

2011年4月26日

CPOL

4分钟阅读

viewsIcon

33861

downloadIcon

229

本文涵盖了在 Mac OS X 程序中拦截和重定向对第三方动态链接库的调用。

目录

引言

上一篇文章中,我们研究了 Mach-O 中函数的动态链接机制。现在让我们开始实践。

我们有一个 Mac OS X 上的程序,它使用了许多第三方动态链接库,而这些库反过来又使用了彼此的函数。

任务如下:我们需要拦截一个库中的某个函数调用到另一个库,并在处理程序中调用原始函数。

测试示例

让我们举一个虚构的例子。假设我们有一个用 C 语言编写的名为“test”的程序(test.c 文件)和一个内容固定、预先编译好的共享库(libtest.c 文件)。该库实现了一个 libtest() 函数。在它们的实现中,程序和库都使用了 C 语言标准库中的 puts() 函数(它随 Mac OS 一起提供,包含在 libSystem.B.dylib 中)。让我们看一下描述情况的示意图

redirection-in-mach-o/2im1.jpg

任务是这样的

  1. 我们需要将 libtest.dylib 库对 puts() 函数的调用替换为主程序(test.c 文件)中实现的 hooked_puts() 函数的调用。后者可以进一步使用原始的 puts() 函数。

    redirection-in-mach-o/2im2.jpg

  2. 我们需要取消所做的更改,即使 libtest() 的后续调用导致调用原始的 puts()

    redirection-in-mach-o/2im3.jpg

不允许更改代码或重新编译库,只能更改主程序。调用重定向本身应该仅针对特定库进行,并且是动态进行的,无需重新启动程序。

重定向算法

让我们用文字描述所有操作,因为即使有大量的注释,代码也可能不够清晰。

  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)的偏移字段查找导入表。
  10. 获得其中目标元素的索引后,重写地址(对于 __la_symbol_ptr)到所需值(或者仅将 CALL/JMP 指令更改为带有操作数 — 所需函数地址的 JMP 指令(对于 __jump_table))。

我将指出,您应该仅在从文件中加载这些表后,才能处理符号表、字符串表和间接符号表。此外,您应该读取描述导入表的段的内容,并在内存中执行重定向。这是因为符号表和字符串表可能不存在,或者无法显示目标 Mach-O 的实际状态。这是因为动态加载器已经在我们之前工作过,并且成功保存了关于符号的所有必要数据,而无需分配这些表本身。

重定向实现

现在是时候将我们的想法转化为代码了。为了优化所需 Mach-O 元素的搜索,我们将所有操作分为三个阶段。

1. void  *mach_hook_init(char const *library_filename, void const *library_address);

基于 Mach-O 文件及其在内存中的表示,此函数返回一个不明确的描述符。此描述符包含导入表、符号表、字符串表以及动态符号表中间接符号映射的偏移量,以及该模块的一些有用索引。描述符如下:

struct mach_hook_handle
{
    void const *library_address;  		//base address of a library in memory
    char const *string_table;  		//buffer to read string_table table 
					//from file
    struct nlist const *symbol_table; 	//buffer to read symbol table from file
    uint32_t const *indirect_table; 	//buffer to read the indirect symbol 
					//table in dynamic symbol table from file
    uint32_t undefined_symbols_count;  	//number of undefined symbols in the 
					//symbol table
    uint32_t undefined_symbols_index;  	//position of undefined symbols in the 
					//symbol table
    uint32_t indirect_symbols_count;  	//number of indirect symbols in the 
					//indirect symbol table of DYSYMTAB
    uint32_t indirect_symbols_index;  	//index of the first imported symbol 
					//in the indirect symbol table of 
					//DYSYMTAB
    uint32_t import_table_offset;  	//the offset of (__DATA, __la_symbol_ptr)
					// or (__IMPORT, __jump_table)
    uint32_t jump_table_present;  		//special flag to show if we work 
					//with (__IMPORT, __jump_table)
};
2. mach_substitution mach_hook(void const *handle, 
char const *function_name, mach_substitution substitution);

此函数使用现有的库描述符、目标符号名称和拦截器的地址,根据上述算法执行重定向。

3. void  mach_hook_free(void *handle);

这样,就完成了对 mach_hook_init() 返回的任何描述符的清理。

考虑到这些原型,我们需要重写测试程序。

#include <stdio.h>
#include <dlfcn.h>

#include "mach_hook.h"

#define LIBTEST_PATH "libtest.dylib"

void libtest();  //from libtest.dylib

int hooked_puts(char const *s)
{
    puts(s);  	//calls the original puts() from libSystem.B.dylib, 
		//because our main executable module called "test" remains intact

    return puts("HOOKED!");
}

int main()
{
    void *handle = 0;  //handle to store hook-related info
    mach_substitution original;  //original data for restoration
    Dl_info info;

    if (!dladdr((void const *)libtest, &info))  //gets an address of a 
				//library which contains libtest() function
    {
        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();  //calls puts() from libSystem.B.dylib

    puts("-----------------------------");

    original = mach_hook(handle, "puts", (mach_substitution)hooked_puts);

    if (!original)
    {
        fprintf(stderr, "Redirection failed!\n");

        goto end;
    }

    libtest();  //calls hooked_puts()

    puts("-----------------------------");

    original = mach_hook(handle, "puts", original);	//restores the original 
						//relocation

    if (!original)
    {
        fprintf(stderr, "Restoration failed!\n");

        goto end;
    }

    libtest();  //again calls puts() from libSystem.B.dylib

end:

    mach_hook_free(handle);
    handle = 0;  	//no effect here, but just a good advice to 
		//prevent double freeing

    return 0;
}

测试开始

我们可以按以下方式进行测试:

user@mac$ arch -i386 ./test
libtest: calls the original puts()
-----------------------------
libtest: calls the original puts()
HOOKED!
-----------------------------
libtest: calls the original puts()


user@mac$ arch -x86_64 ./test
libtest: calls the original puts()
-----------------------------
libtest: calls the original puts()
HOOKED!
-----------------------------
libtest: calls the original puts()

程序的输出表明,最初制定的任务已完全执行。

测试示例的完整实现,包括重定向算法和项目文件,均已附在本文档中。

历史

  • 2011 年 4 月 26 日:初始帖子
© . All rights reserved.