linux下的.so注入:如何定位dlopen()的地址?

时间:2014-02-08 21:00:18

标签: linux shared-libraries elf dll-injection

最近我对Linux感兴趣,并且我正在尝试创建一个能够在Windows下注入共享对象(即.so文件,'动态可加载库',“DLL”)的程序。我知道这可以是通过设置环境变量来完成,但我想在已经运行的进程上进行。

我已经知道如何在Windows下执行此操作。有几种方法,但一般来说,您可以通过使用CreateRemoteThread()创建远程线程来调用LoadLibrary()。当然,您需要远程进程中LoadLibrary的地址,但(根据我的经验),每个进程的偏移量始终相同。

我已经做过一些关于如何在Linux下完成这项工作的研究。例如,Phrack 59中一个有趣的article显示了如何做到这一点。这篇文章还附有一个源代码,但由于对目标进程做了一些假设并且它是32位,我无法让它工作。我碰到的其他一些事情:a codeproject article,但是这个只解释了如何在gdb中做到这一点。 (我会发布更多链接,但网站限制我2: - /。)

首先,我想获取远程进程中dlopen()函数的地址。为此,我发现我必须获取进程的ELF头并遍历符号表。实际上,我设法通过以下方式做到了这一点:

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

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

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

4)迭代link_map链的动态部分,从而获得字符串表,符号表和哈希表的地址(* 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.

但是,我无法找到dlopen的有效地址! (或者甚至是main的地址,就此而言!)为了测试目的,我让程序分析自己,所以我肯定知道main存在。我还尝试了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设法找到了主要的,而我却做不到。我还看了一下libelf库,但它依赖于从应用程序文件中读取,而不是访问进程的内存(即在进程运行时无法使用它。)有没有人知道我如何找到它dlopen()在远程进程中,甚至是主要的,对于那个问题?

我正在运行Ubuntu 12.04 64位。

3 个答案:

答案 0 :(得分:1)

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

现在,关于找到dlopen的地址。事实证明我正在从哈希表中读取错误数量的符号表条目。有两种类型的哈希表(我到目前为止遇到过):DT_HASH表和DT_GNU_HASH表。前者具有hash_table_addr + 4source)的条目数,后者没有明确指定哈希表的数量。人们需要通过迭代哈希表的桶表来获得这个数量。除此之外,我的方法很好,现在我能够找到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/KernelHardening#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++;
}

答案 1 :(得分:1)

你的问题有很多不好的假设和误解,我不知道从哪里开始。

  

使用CreateRemoteThread()

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

  

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

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

  

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

您假设远程进程中 dlopen,但只有远程进程二进制文件与libdl链接时才会出现这种情况。这是所有Linux二进制文件的一小部分。

  

某种程度上,readelf设法找到了主要的,而我却做不到。

您不了解动态和静态ELF符号表之间的区别。运行nm a.outnm -D a.out并比较结果将是说明性的。你会在一个中找到main,而在另一个中找不到。

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

答案 2 :(得分:0)

您可能不应该为主可执行文件使用硬编码的基址(因为它不必是0x400000)并迭代在第二个GOT条目中找到的link_map结构(因为动态链接器想要查找的任何内容可能在那里而不是)。

更好的解决方案是:

  1. 使用/ proc / $ pid / maps查找映射的ELF文件;
  2. 从中找到动态部分表;
  3. 从那里找到dlopen;
  4. 如果您没有dlopen,您可能希望在ld.so中找到_rtld_global_ro,而这个全局变量应该有一个_dl_open函数指针,您可能想要调用它。
  5. 这是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[]);
      [...]
    };