.so在linux下注入:如何查找dlopen()的地址?

最近我对Linux感兴趣,正在尝试创build一个能够注入共享对象(即.so文件,“dynamic加载库”,Windows下的“DLL”)的程序。我知道这可以通过设置一个环境variables,但我想要在已经运行的进程上执行。

我已经知道如何在Windows下做到这一点。 有几种方法,但一般来说,您可以通过使用CreateRemoteThread()创build远程线程来调用LoadLibrary()。 当然你需要在远程进程中使用LoadLibrary的地址,但是(以我的经验),每个进程的偏移总是相同的。

我已经做了一些关于如何在Linux下完成的研究。 例如Phrack 59中一篇有趣的文章展示了如何做到这一点。 文章也有一个源代码附加,但由于一些假设是由目标进程,它是32位,我不能得到它的工作。 其他的东西,我碰到: 一个codeproject文章 ,但这只是解释如何从gdb内做到这一点。 (我会张贴更多的链接,但网站限制我2: – /。)

首先,我想获取远程进程中dlopen()函数的地址。 为此,我想通过获取stream程的ELF头,并遍历符号表。 其实,我设法做到这一点,通过:

1)获取ELF头(根据我的经验,存储在0x400000下的64位)

2)在标题为DYNAMIC的程序标题中find全局偏移量表。

3)通过访问全局偏移表中的第二个条目来获取第一个link_map。

4)迭代link_map链的dynamic部分,从而获得string表,符号表和散列表的地址(* Hash_Table + 0x4保存符号表中的条目数量。)

5)遍历符号表

从我的程序输出一些示例:

** looking at lib "" ** Trying to find symbol main in symbol table... numentries: 49 index 1 name: val: 0 ... index 49 name: memcpy val: 0 symbol not found. ** looking at lib "" ** Trying to find symbol main in symbol table... numentries: 11 index 1 name: val: 0 ... index 11 name: __vdso_time val: 0xffffffffff700a80 symbol not found. ** looking at lib "/lib/x86_64-linux-gnu/libc.so.6" ** Trying to find symbol main in symbol table... numentries: 2190 index 1 name: val: 0 ... index 2190 name: wcpcpy val: 0xa3570 symbol not found. 

但是,我无法finddlopen的有效地址! (甚至是主要的地址,对于这个问题!)为了testing的目的,我让程序分析自己,所以我知道主要存在。 我也尝试readelf -s来看看符号表,它显示:

 Symbol table '.symtab' contains 151 entries: Num: Value Size Type Bind Vis Ndx Name ... 149: 0000000000401880 216 FUNC GLOBAL DEFAULT 13 main 

所以,不知何故,readelf设法find主要,而我不能。 我也看了一下libelf库,但是它依赖于从应用程序文件中读取数据,而不是访问进程的内存(即在进程运行时不能使用它)。有没有人有一个线索,我可以如何定位dlopen()在远程进程,甚至是主要的,对于这个问题?

我正在运行的Ubuntu 12.04 64位。

Solutions Collecting From Web of ".so在linux下注入:如何查找dlopen()的地址?"

首先,关于main的地址:似乎必须使用Section Headers来查找main的地址。 这样做只是使用动态部分似乎是不可能的。 运行readelf -D -sreadelf -D --dyn-sym不会给出main的地址。

现在,关于查找dlopen的地址。 事实证明,我正在从哈希表中读取错误数量的符号表项。 有两种类型的散列表(我迄今为止遇到过): DT_HASH表和DT_GNU_HASH表。 前者具有hash_table_addr + 4 ( 源 )项的数量,后者没有明确指定哈希表的数量。 需要通过迭代散列表的桶表来获得这个数量。 除此之外,我的方法很好,现在我可以找到dlopenmalloc等的地址。

为了从哈希表中获得符号表的条目数量(即大小),可以使用(C):

 ssize_t ReadData(int pid, void* buffer, const void* source, ssize_t size) { // Under Ubuntu and other distros with a 'hardened kernel', processes using this function // should be run as root. // See https://wiki.ubuntu.com/SecurityTeam/Roadmap/coreelHardening#ptrace_Protection iovec local_vec; local_vec.iov_base = Buffer; local_vec.iov_len = Size; iovec remote_vec; remote_vec.iov_base = Address; remote_vec.iov_len = Size; return process_vm_readv(pid, &local_vec, 1, &remote_vec, 1, 0); } unsigned long FindNumEntriesHashTable(int pid, void* TablePtr, const void* TableLibAddr) { // Check if TablePtr is smaller than 0. unsigned long pointer = ((long)TablePtr < 0) ? (unsigned long)TablePtr + (unsigned long)TableLibAddr : (unsigned long)TablePtr; unsigned long ret = 0; ReadData(pid, &ret, (void*)(pointer + sizeof(Elf_Word)), sizeof(Elf_Word)); return ret; } unsigned long FindNumEntriesGnuHashTable(int pid, void *TablePtr, const remote_voidptr TableLibAddr) { unsigned long pointer = ((long)TablePtr < 0) ? (unsigned long)TablePtr + (unsigned long)TableLibAddr : (unsigned long)TablePtr; // Read in required info on the gnu_hash table unsigned long nbuckets = 0; unsigned long symndx = 0; unsigned long maskwords = 0; ReadData(pid, &nbuckets, (const remote_voidptr)pointer, sizeof(Elf_Word)); ReadData(pid, &symndx, (const remote_voidptr)(pointer + sizeof(Elf_Word)), sizeof(Elf_Word)); ReadData(pid, &maskwords, (const remote_voidptr)(pointer + 2 * sizeof(Elf_Word)), sizeof(Elf_Word)); // Calculate the offset to the bucket table. The size of the maskwords entries is 4 under 32 bit, 8 under 64 bit. unsigned long masktab_size = (ENV_NUMBITS == 32) ? 4 * maskwords : 8 * maskwords; unsigned long buckettab_offs = 4 * sizeof(Elf_Word) + masktab_size; // Read in the bucket table Elf_Word buckettab[nbuckets]; ReadData(pid, &buckettab, (const remote_voidptr)(pointer + buckettab_offs), nbuckets * sizeof(Elf_Word)); // Loop through the bucket table. If the given index is larger than the already known index, update. unsigned long num_entries = 0; for (size_t i = 0; i < nbuckets; i++) { if (num_entries == 0 || buckettab[i] > num_entries) { num_entries = buckettab[i]; } } if (num_entries == 0) { return 0; } // Add one, since the first entry is always NULL. return num_entries++; } 

在你的问题上有太多的错误的假设和误解,我不知道从哪里开始。

使用CreateRemoteThread()

这个Windows功能没有Linux等价物(可以说是错误的功能)。

获取ELF头(根据我的经验,存储在0x400000下的64位)

这是非PIE可执行文件的默认位置。 这绝不是保证。

我想获得远程进程中dlopen()函数的地址

你假定在远程进程中有dlopen,但是如果远程进程二进制文件与libdl链接,这只会是真的。 这是所有Linux二进制文件的一小部分。

不知何故,readelf已经设法找到主要,而我不能。

你不明白动态和静态ELF符号表之间的区别。 运行nm a.outnm -D a.out并比较结果将是说明性的。 你会发现一个main的,但不是其他。

注意:静态符号表在运行时不是必需的,可以被剥离。

你可能不应该为主可执行文件使用一个硬编码的基地址(因为它不需要是0x400000)并且遍历在第二个GOT入口找到的link_map结构(因为动态连接器想要找到herre的东西可能在那里)。

更好的解决方案是:

  1. 使用/ proc / $ pid / maps找到映射的ELF文件;
  2. 从这里找到动态部分表;
  3. 从那里找到dlopen;
  4. 如果你没有dlopen,你可能想在ld.so中找到_rtld_global_ro,而这个全局变量应该有一个你可能想要调用的_dl_open函数指针。

这是libdl所做的(使用ld.so中的_dl_open):

 dlopen_doit (void *a) { struct dlopen_args *args = (struct dlopen_args *) a; if (args->mode & ~(RTLD_BINDING_MASK | RTLD_NOLOAD | RTLD_DEEPBIND | RTLD_GLOBAL | RTLD_LOCAL | RTLD_NODELETE | __RTLD_SPROF)) GLRO(dl_signal_error) (0, NULL, NULL, _("invalid mode parameter")); args->new = GLRO(dl_open) (args->file ?: "", args->mode | __RTLD_DLOPEN, args->caller, args->file == NULL ? LM_ID_BASE : NS, __dlfcn_argc, __dlfcn_argv, __environ); } 

有:

  # define GLRO(name) _rtld_local_ro._##name 

和:

 struct rtld_global_ro { [...] void *(*_dl_open) (const char *file, int mode, const void *caller_dlopen, Lmid_t nsid, int argc, char *argv[], char *env[]); [...] };