Thursday, September 15, 2011

Highway to libc

Rel started the interesting topic on WASM forum about how to obtain the address of libc from the code injected to the process (nothing known about the process, even its base address). He wished something similar to Windows PEB/TEB and indeed there is a way to obtain neccessary information from Thread Control Block -> Dynamic Thread Vector -> Thread Local Storage (in the code: tcb, dtv and tls respectively). After some thinking I found the following solution (it will fail, if the libc doesn't support threads, or the main app is using threads, in the latter case the index in the DTV would be different), so these should be improved.
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <assert.h>
#include <elf.h>

/* Type for the dtv.  */
typedef union dtv
{
  size_t counter;
  struct
  {
    void *val;
    bool is_static;
  } pointer;
} dtv_t;


typedef struct
{
  void *tcb;            /* Pointer to the TCB.  Not necessarily the
                           thread descriptor used by libpthread.  */
  dtv_t *dtv;
  void *self;           /* Pointer to the thread descriptor.  */
  int multiple_threads;
  uintptr_t sysinfo;
  uintptr_t stack_guard;
  uintptr_t pointer_guard;
} tcbhead_t;

int main(int argc, char **argv)
{
        void *get_base(uint32_t addr) {
                addr &= ~4095;
                while (*(uint32_t*)addr != 0x464c457fUL)
                        addr -= 4096;
                        return (void*)addr;
        }
        tcbhead_t *tcb;
        asm ("mov %%gs:0x0,%%eax":"=a"(tcb));
        dtv_t *dtv = tcb->dtv;
        if (dtv->counter > 0) {
                uint32_t *tls = dtv[1].pointer.val;
                Elf32_Ehdr *ehdr = get_base(tls[0]);
                Elf32_Phdr *phdr = (Elf32_Phdr*)((char*)ehdr + ehdr->e_phoff);
                int i;
                Elf32_Dyn *dyn = NULL;
                int delta;
                uint32_t low;
                for (i = 0; i < ehdr->e_phnum; i++)
                        if (phdr[i].p_type == PT_DYNAMIC)
                                dyn = (Elf32_Dyn*)phdr[i].p_vaddr;
                        else
                        if (phdr[i].p_type == PT_LOAD && phdr[i].p_offset == 0)
                                delta = phdr[i].p_vaddr;
                delta = (uint32_t)ehdr - delta;
                dyn = (Elf32_Dyn*)((char*)dyn + delta);
                assert(dyn != NULL);
                char *strtab = NULL;
                int soname = 0;
                while (dyn->d_tag != DT_NULL) {
                        if (dyn->d_tag == DT_STRTAB)
                                strtab = (char*)dyn->d_un.d_ptr;
                        else if (dyn->d_tag == DT_SONAME) 
                                soname = dyn->d_un.d_val;
                        dyn++;
                }
                assert(strtab != NULL && soname != 0);
                printf("%s\n", soname + strtab);
        }
}
Here is the test output:
$ uname -r
2.6.18-238.19.1.el5
$ cat /etc/redhat-release 
CentOS release 5.6 (Final)
$ ./a.out 
libc.so.6
$ uname -r
2.6.24-rc7
$ cat /etc/*rel*
Gentoo Base System release 1.12.11.1
$ ./a.out 
libc.so.6

No comments:

Post a Comment