Runtime GOT poisoning from injected shared object |
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) return; 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; else if (dyn->d_tag == DT_STRTAB) str = (char*)dyn->d_un.d_ptr; else if (dyn->d_tag == DT_JMPREL) rel = (Elf64_Rela*)dyn->d_un.d_ptr; else if (dyn->d_tag == DT_PLTRELSZ) rsz = dyn->d_un.d_val; } if (sym == NULL || rel == NULL || rsz < sizeof(Elf64_Rela)) { return; }
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; break; }
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:
main() { for (;;) { printf("I am running %d\n", getpid()); sleep(1); } }
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 ... success! me: {__libc_dlopen_mode:0x33e2726db0, dlopen_offset:126db0} => daemon: {__libc_dlopen_mode:0x33e2726db0, libc:0x33e2600000} rdi=7fff79b284d0 rsp=7fff79b28328 rip=33e26aca20 done! ... 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 0x7fff79bff580 0x600768 base _DYNAMIC 0x600768 I am running 32457 I am running 32457 ...
That's all. Have a nice day. :-)
Комментировать | « Пред. запись — К дневнику — След. запись » | Страницы: [1] [Новые] |