[Tags | | | glibc, got, hijacking, inject, link_map, linux, plt, poisoning, rtld, runtime, so | ] |
[ | 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)
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
...
- 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. :-)
References
- Joe Damato, Injectso64, https://github.com/ice799/injectso64 , May 2007
- Ryan O'Neill, Modern Day ELF Runtime infection via GOT poisoning, May 2009
|