ld-linux.so ELF hooker
TL;DR
Stéphane and
myself are releasing ag
new tool injecting code at runtime, just between the ELF loader and
target binary. It is an alternative to LD_PRELOAD
, just a little bit
more intrusive but 100% reliable :)\
When a binary is execve(), the
kernel extracts from the ELF headers the interpreter to be launched,
usually /lib/ld-linux.so.2. The kernel
creates a new process and prepares the environment (arguments and
auxiliary data). The target ELF entry point is set in auxiliary vector
of type “ENTRY”.
Then the kernel opens the requested
interpreter, maps the memory regions and start its execution at ld’s ELF
entry point. Then the loader analyzes the target ELF file, performs its
loader work and sets EIP to target ELF entry point (extracted from
auxv). At this point, main()’s program is eventually executed.
Our goal was to permit the execution
of code for arbitrary dynamically linked binary without patching each of
them. So our interest moved on the loader, the common point
between most executables. Thus, we decided to patch a normal ld in order
to inject code. My awesome colleague, Stéphane
Duverger (the ramooflax
author!) and myself wrote
ld-shatner. Its task is
to patch ld-linux.so file
accordingly:
\
- After ELF header, we shift “ELF program header” a few pages away
- In this new section, we inject a “loader routine” (hooked.s) and embedded code to be executed at runtime
- After having been saved in our section, ld’s ELF entry point is overwritten to jump directly on our routine. This routine extracts from auxiliary vectors the target ELF entry point and overwrites it with a pointer to our embedded code (func() in the payload).
- Original ld’s entry point is called and ld works as usual
- Eventually, it calls entry point set in auxiliary vector (which was replaced by a pointer to our payload)
- Embedded code runs
- It returns to our routine which finally jumps on original target entry point
Some pictures before/after ld-shatner voodoo:
Screenshot
$ make clean all
$ cp /lib/ld-linux.so.2 /bin/ls .
$ ./ld-shatner ld-linux.so.2 obj.elf
$ sudo cp ld-hook.so /lib/
$ ./interpatch ls
$ ./ls
ld-hook <---------------------- output of obj.elf
[...]
(Ok, we cheat for the moment because we have to patch ls binary but we
will not have to do that eventually)\
So what?
My ultimate goal for ld-shatner is to use this method for starting applications in my sandbox project, seccomp-nurse. For the moment, I rely on LD_PRELOAD feature but this approach is… hackish and I have to work around some bugs because of this special context…