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

.so linux下的注入:如何定位dlopen()的地址?,linux,shared-libraries,elf,dll-injection,Linux,Shared Libraries,Elf,Dll Injection,最近我对Linux产生了兴趣,我正在尝试创建一个能够注入共享对象的程序(例如,so文件、“动态可加载库”和Windows下的“DLL”)。我知道这可以通过设置环境变量来实现,但我想在已经运行的进程上实现 我已经知道如何在Windows下执行此操作。有几种方法,但一般来说,您可以通过使用CreateRemoteThread()创建远程线程来调用LoadLibrary()。当然,在远程进程中需要LoadLibrary的地址,但是(以我的经验)每个进程的偏移量总是相同的 我做了一些关于如何在Linux

最近我对Linux产生了兴趣,我正在尝试创建一个能够注入共享对象的程序(例如,so文件、“动态可加载库”和Windows下的“DLL”)。我知道这可以通过设置环境变量来实现,但我想在已经运行的进程上实现

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

我做了一些关于如何在Linux下实现这一点的研究。例如,Phrack 59中的一个有趣的短语显示了如何实现这一点。本文还附带了一个源代码,但由于对目标进程进行了一些假设,并且它是32位的,所以我无法让它工作。我遇到的其他事情:,但这一个只解释了如何从gdb内部实现它。(我会发布更多链接,但该网站将我限制在2:-/)

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

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

2) 在标记为动态的程序标题中查找全局偏移表

3) 通过访问全局偏移表中的第二个条目来检索第一个链接映射

4) 迭代链接映射链的动态部分,从而获得字符串表、符号表和哈希表的地址(*哈希表+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找到了main,而我找不到。我还查看了libelf库,但这依赖于从应用程序文件读取,而不是访问进程的内存(即进程运行时不能使用它)。有人知道我如何在远程进程,甚至main中找到dlopen()吗


我正在运行Ubuntu 12.04 64位。

首先,关于main的地址: 似乎必须使用节标题才能找到main的地址。仅仅使用动态部分似乎是不可能的。运行
readelf-D-s
readelf-D--dyn sym
也不会给出main的地址

现在,关于查找
dlopen
的地址。结果是我从哈希表中读取了错误数量的符号表条目。有两种类型的哈希表(我到目前为止遇到过):
DT_hash
表和
DT_GNU_hash
表。前者在
hash\u table\u addr+4
()处有条目数,后者没有明确指定hash表的数量。我们需要通过遍历哈希表的bucket表来获得这个数量。除此之外,我的方法很好,现在我能够找到
dlopen
malloc
等的地址

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

ssize\u t ReadData(int-pid、void*缓冲区、const-void*源、ssize\t大小)
{
//在Ubuntu和其他具有“强化内核”的发行版下,进程使用此函数
//应该以root用户身份运行。
//看https://wiki.ubuntu.com/SecurityTeam/Roadmap/KernelHardening#ptrace_Protection
iovec本地_vec;
local_vec.iov_base=缓冲区;
local_vec.iov_len=大小;
iovec远程通信;
远程_vec.iov_base=地址;
remote_vec.iov_len=大小;
返回进程\u vm\u readv(pid、本地\u vec、1和远程\u vec、1、0);
}
无符号长FindNumEntriesHashTable(int-pid、void*TablePtr、const-void*TableLibAddr)
{
//检查TablePtr是否小于0。
无符号长指针=((长)TablePtr<0)?(无符号长)TablePtr+(无符号长)TableLibAddr:(无符号长)TablePtr;
无符号长ret=0;
读取数据(pid和ret,(void*)(指针+sizeof(Elf_字)),sizeof(Elf_字));
返回ret;
}
无符号长FindNumEntriesGnuHashTable(int-pid,void*TablePtr,const-remote\u voidptr TableLibAddr)
{
无符号长指针=((长)TablePtr<0)?(无符号长)TablePtr+(无符号长)TableLibAddr:(无符号长)TablePtr;
//读入gnu_哈希表上的必需信息
无符号长nbuckets=0;
无符号长symndx=0;
无符号长maskwords=0;
读取数据(pid和nbuckets,(常量远程无效PTR)指针,大小(Elf单词));
读取数据(pid和SYMMDX,(常量远程无效PTR)(指针+大小of(Elf_字)),大小of(Elf_字));
读取数据(pid和maskwords,(常量远程无效)(指针+2*sizeof(Elf_字)),sizeof(Elf_字));
//计算到bucket表的偏移量。maskwords条目的大小在32位下为4,在64位下为8。
无符号长masktab_size=(ENV_NUMBITS==32)?4*maskwords:8*maskwords;
无符号长巴克塔布=4*sizeof(Elf字)+马斯克塔布尺寸;
//读入桶表
Elf_单词buckettab[nbuckets];
读取数据(pid和buckettab,(常量远程无效)(指针+buckettab关闭),nbuckets*sizeof(Elf单词));
//循环遍历bucket表。如果给定索引大于已知索引,则更新。
无符号长num_项=0;
对于(大小i=0;inum|u条目数)
{
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++;
}
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[]);
  [...]
};