.so injection under linux: how to locate address of dlopen()?

自闭症网瘾萝莉.ら 提交于 2019-12-03 20:26:35

First, regarding the address of main: It seems one would have to use the Section Headers to find out the address of main. Doing it just using the dynamic section seems not possible. Running readelf -D -s and readelf -D --dyn-sym does not give the address of main either.

Now, regarding finding the address of dlopen. It turns out I was reading the wrong number of symbolic table entries from the hash tables. There are two types of hash tables (I have encountered so far): DT_HASH tables, and DT_GNU_HASH tables. The former have the amount of entries at hash_table_addr + 4 (source), the latter do not specify the amount of hash tables explicitely. One needs to obtain this amount by iterating through the hash table's bucket table. Other than that, my approach was good, and now I am able to find the address of dlopen, malloc, etc.

To obtain the number of entries (i.e. size of) a Symbol Table from a Hash Table, one can use (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++;
}

There are so many bad assumptions and mis-understandings in your question, I don't know where to begin.

using CreateRemoteThread()

There is no Linux equivalent for this Windows feature (arguably mis-feature).

Obtaining the ELF-header (under 64bits stored at 0x400000 in my experience.)

This is the default location for non-PIE executable. It is by no means guaranteed.

I want to obtain the address of the dlopen() function in the remote process

You are assuming that there is dlopen in remote process, but that would only be true if the remote process binary was linked against libdl. That's a small subset of all Linux binaries.

somehow readelf has managed to find main, while I can't.

You don't understand the difference between dynamic and static ELF symbol tables. Running nm a.out and nm -D a.out and comparing results will be illustrative. You'll find main in one, but not the other.

Note: static symbol table is not required at runtime, and could be stripped.

You should probably not use a hardcoded base address for the main executable (because it need not be 0x400000) and iterate over the link_map structure found in the second GOT entry (because anything the dynamic linker wants to find herre might be there instead).

A better solution would be:

  1. use /proc/$pid/maps to find the mapped ELF files;
  2. find the dynamic section table from this;
  3. find dlopen from there;
  4. if you do not have dlopen you might want to find _rtld_global_ro in ld.so instead, this global variables should have a _dl_open function pointer that you may want to call.

This is what libdl does (using _dl_open from by ld.so):

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);
}

with:

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

and:

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[]);
  [...]
};
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!