Title : Embedded ELF Debugging
Author : ELFsh crew
==Phrack Inc.==
Volume 0x0b, Issue 0x3f, Phile #0x09 of 0x14
|=------=[ Embedded ELF Debugging : the middle head of Cerberus ]=------=|
|=----------------------------------------------------------------------=|
|=------------=[ The ELF shell crew <elfsh@devhell.org> ]=--------------=|
|=----------------------------------------------------------------------=|
I. Hardened software debugging introduction
a. Previous work & limits
b. Beyond PaX and ptrace()
c. Interface improvements
II. The embedded debugging playground
a. In-process injection
b. Alternate ondisk and memory ELF scripting (feat. linkmap)
c. Real debugging : dumping, backtrace, breakpoints
d. A note on dynamic analyzers generation
III. Better multiarchitecture ELF redirections
a. CFLOW: PaX-safe static functions redirection
b. ALTPLT technique revised
c. ALTGOT technique : the RISC complement
d. EXTPLT technique : unknown function postlinking
e. IA32, SPARC32/64, ALPHA64, MIPS32 compliant algorithms
V. Constrained Debugging
a. ET_REL relocation in memory
b. ET_REL injection for Hardened Gentoo (ET_DYN + pie + ssp)
c. Extending static executables
d. Architecture independant algorithms
VI. Past and present
VII. Greetings
VIII. References
-------[ I. Hardened software debugging introduction
In the past, binary manipulation work has focussed on virii
writing, software cracking, backdoors deployment, or creation of
tiny or obfuscated executables. Besides the tools from the GNU
project such as the GNU binutils that includes the GNU debugger [1]
(which focus more on portability than functionalities), no major
binary manipulation framework does exist. For almost ten years,
the ELF format has been a success and most UNIX Operating Systems
and distributions rely on it.
However, the existing tools do not take advantage of the format
and most of the reverse engineering or debugging softwares are
either very architecture specific, or simply do not care about
binary internals for extracting and redirecting information.
Since our first published work on the ELF shell, we improved so
much the new framework that it is now time to publish a second
deep article focussing on advances in static and runtime
ELF techniques. We will explain in great details the 8 new
binary manipulation functionalities that intersect with the
existing reverse engineering methodology. Those techniques allow
for a new type of approach on debugging and extending closed
source software in hardened environments.
We worked on many architectures (x86, alpha, sparc, mips) and
focussed on constrained environments where binaries are linked
for including security protections (such as hardened gentoo
binaries) in PaX [2] protected machines. It means that our
debugger can stay safe if it is injected inside a (local or)
remote process.
----[ A. Previous work & limits
In the first part of the Cerberus articles serie, we introduced
a new residency technique called ET_REL injection. It consisted
in compiling C code into relocatable (.o) files and injecting
them into existing closed source binary programs. This technique
was proposed for INTEL and SPARC architectures on the ELF32
format.
We improved this technique so that both 32 and 64 bits binaries
are supported so we added alpha64 and sparc64 support. We also
worked on the MIPS r5000 architecture and now provide a nearly
complete environment for it as well. We now also allow for ET_REL
injection into ET_DYN objects (shared libraries) so that our
technique is compatible with fully randomized environments such
as provided by Hardened Gentoo with the PaX protection enabled
on the Linux Operating System. We also worked on other OS such as
BSD based ones, Solaris, and HP-UX and the code was compiled and
tested regulary on those as well.
A major innovation of our binary manipulation based debugging
framework is the absence of ptrace. We do not use kernel residency
like in [8] so that even unprivilegied users can use this and it
is not Operating System dependent.
Existing debuggers use to rely on the ptrace system call so that
the debugger process can attach the debuggee program and enable
various internal processes manipulations such as dumping memory,
putting breakpoints, backtracing, and so on. We propose the same
features without using the system call.
The reasons why we do not use ptrace are multiple and simple.
First of all, a lot of hardened or embedded systems do not
implement it, or just disable it. That's the case for grsecurity
based systems, production systems, or phone systems whoose
Operating System is ELF based but without a ptrace interface.
The second major reason for not using ptrace is the performance
penalties of such a debugging system. We do not suffer from
performance penalties since the debugger resides in the same
process. We provide a full userland technique that does not have
to access the kernel memory, thus it is useful in all stages of
a penetration testing when debugging sensitive software on
hardened environment is needed and no system update is possible.
We allow for plain C code injection inside new binary files (in
the static perspective) and processes (in the runtime mode) using
a unified software. When requested, we only use ELF techniques that
reduce forensics evidences on the disk and only works in memory.
----[ B. Beyond PaX and ptrace
Another key point in our framework are the greatly improved
redirection techniques. We can redirect almost all control flow,
wether or not the function code is placed inside the binary
itself (CFLOW technique) or in a library on which the binary
depends (Our previous work presented new hijacking techniques
such that ALTPLT).
We improved this techniques and passed through many rewrites
and now allow a complete architecture independant implementation.
We completed ALTPLT by a new technique called ALTGOT so that
hijacking a function and calling back the original copy from the
hooking function is possible on Alpha and Mips RISC machines as
well.
We also created a new technique called EXTPLT which allow for
unknown function (for which no dynamic linking information is
available at all in the ELF file) using a new postlinking
algorithm compatible with ET_EXEC and ET_DYN objets.
----[ C. Interface improvements
Our Embedded ELF debugger implementation is a prototype.
Understand that it is really usable but we are still in the
development process. All the code presented here is known to
work. However we are not omniscient and you might encounter a
problem. In that case, drop us an email so that we can figure
out how to create a patch.
The only assumption that we made is the ability to read the
debuggee program. In all case, you can also debug in memory
the unreadable binaries on disk by loading the debugger using
the LD_PRELOAD variable. Nevertheless, e2dbg is enhanced
when binary files are readable. Because the debugger run in the
same address space, you can still read memory [3] [4] and
restore the binary program even though we do not implement it
yet.
The central communication language in the Embedded ELF Debugger
(e2dbg) framework is the ELFsh scripting language. We augmented
it with loop and conditional control flow, transparent support
for lazy typed variables (like perl). The source command (for
executing a script inside the current session) and user-defined
macros (scriptdir command) are also supported.
We also developed a peer2peer stack so called Distributed
Update Management Protocol - DUMP - that allow for linking
multiple debugger instances using the network, but this
capability is not covered by the article. For completeness, we
now support multiusers (parallel or shared) sessions and
environment swapping using the workspace command.
We will go through the use of such interface in the first part
of the paper. In the second part, we give technical details
about the implementation of such features on multiple
architectures. The last part is dedicated to the most recent
and advanced techniques we developed in the last weeks for
constrained debugging in protected binaries. The last algorithms
of the paper are architecture independant and constitute the
core of the relocation engine in ELFsh.
-------[ II. The embedded debugging playground
---[ A. In-process injection
We have different techniques for injecting the debugger
inside the debuggee process. Thus it will share the address
space and the debugger will be able to read its own data
and code for getting (and changing) information in the
debuggee process.
Because the ELF shell is composed of 40000 lines of code,
we did not want to recode everything for allowing process
modification. We used some trick that allow us to select
wether the modifications are done in memory or on disk. The
trick consists in 10 lines of code. Considering the PROFILE
macros not beeing mandatory, here is the exact stuff :
(libelfsh/section.c)
========= BEGIN DUMP 0 =========
void *elfsh_get_raw(elfshsect_t *sect)
{
ELFSH_PROFILE_IN(__FILE__, __FUNCTION__, __LINE__);
/* sect->parent->base is always NULL for ET_EXEC */
if (elfsh_is_debug_mode())
{
sect->pdata = (void *) sect->parent->base + sect->shdr->sh_addr;
ELFSH_PROFILE_ROUT(__FILE__, __FUNCTION__, __LINE__, (sect->pdata));
}
if (sect)
ELFSH_PROFILE_ROUT(__FILE__, __FUNCTION__, __LINE__, (sect->data));
ELFSH_PROFILE_ERR(__FILE__, __FUNCTION__, __LINE__,
"Invalid parameter", NULL);
}
========= END DUMP 0 =========
What is the technique about ? It is quite simple : if the debugger
internal flag is set to static mode (on-disk modification), then we
return the pointer on the ELFsh internal data cache for the section
data we want to access.
However if we are in dynamic mode (process modification), then we
just return the address of that section. The debugger runs in the
same process and thus will think that the returned address is a
readable (or writable) buffer. We can reuse all the ELF shell
API by just taking care of using the elfsh_get_raw() function when
accessing the ->data pointer. The process/ondisk selection is then
transparent for all the debugger/elfsh code.
The idea of injecting code directly inside the process is not
new and we studied it for some years now. Embedded code injection
is also used in the Windows cracking community [12] for bypassing
most of the protections against tracing and debugging, but nowhere
else we have seen an implementation of a full debugger, capable
of such advanced features like ET_REL injection or function
redirection on multiple architectures, both on disk and in memory,
with a single code.
---[ B. Alternate ondisk and memory ELF scripting (feat. linkmap)
We have 2 approaches for inserting the debugger inside the debuggee
program. When using a DT_NEEDED entry and redirecting the main
debuggee function onto the main entry point of the ET_DYN debugger,
we also inject various sections so that we can perform core
techniques such as EXTPLT. That will be described in details in
the next part.
The second approach is about using LD_PRELOAD on the debuggee
program and putting breakpoints (either by 0xCC opcode on x86 or
the equivalent opcode on another architecture, or by function
redirection which is available on many architectures and for many
kind of functions in the framework).
Since binary modification is needed anyway, we are using the
DT_NEEDED technique for adding the library dependance, and all
other sections injections or redirection described in this article,
before starting the real debugging.
The LD_PRELOAD technique is particulary more useful when you
cannot read the binary you want to debug. It is left to the user
the choice of debugger injection technique, depending on the needs
of the moment.
Let's see how to use the embedded debugger and its 'mode' command
that does the memory/disk selection. Then we print the Global
Offset Table (.got). First the memory GOT is displayed, then we
get back in static mode and the ondisk GOT is printed :
========= BEGIN DUMP 1 =========
(e2dbg-0.65) list
.::. Working files .::.
[001] Sun Jul 31 19:23:33 2005 D ID: 9 /lib/libncurses.so.5
[002] Sun Jul 31 19:23:33 2005 D ID: 8 /lib/libdl.so.2
[003] Sun Jul 31 19:23:33 2005 D ID: 7 /lib/libtermcap.so.2
[004] Sun Jul 31 19:23:33 2005 D ID: 6 /lib/libreadline.so.5
[005] Sun Jul 31 19:23:33 2005 D ID: 5 /lib/libelfsh.so
[006] Sun Jul 31 19:23:33 2005 D ID: 4 /lib/ld-linux.so.2
[007] Sun Jul 31 19:23:33 2005 D ID: 3 ./ibc.so.6 # e2dbg.so renamed
[008] Sun Jul 31 19:23:33 2005 D ID: 2 /lib/tls/libc.so.6
[009] Sun Jul 31 19:23:33 2005 *D ID: 1 ./a.out_e2dbg # debuggee
.::. ELFsh modules .::.
[*] No loaded module
(e2dbg-0.65) mode
[*] e2dbg is in DYNAMIC MODE
(e2dbg-0.65) got
[Global Offset Table .::. GOT : .got ]
[Object ./a.out_e2dbg]
0x080498E4: [0] 0x00000000 <?>
[Global Offset Table .::. GOT : .got.plt ]
[Object ./a.out_e2dbg]
0x080498E8: [0] 0x0804981C <_DYNAMIC@a.out_e2dbg>
0x080498EC: [1] 0x00000000 <?>
0x080498F0: [2] 0x00000000 <?>
0x080498F4: [3] 0x0804839E <fflush@a.out_e2dbg>
0x080498F8: [4] 0x080483AE <puts@a.out_e2dbg>
0x080498FC: [5] 0x080483BE <malloc@a.out_e2dbg>
0x08049900: [6] 0x080483CE <strlen@a.out_e2dbg>
0x08049904: [7] 0x080483DE <__libc_start_main@a.out_e2dbg>
0x08049908: [8] 0x080483EE <printf@a.out_e2dbg>
0x0804990C: [9] 0x080483FE <free@a.out_e2dbg>
0x08049910: [10] 0x0804840E <read@a.out_e2dbg>
[Global Offset Table .::. GOT : .elfsh.altgot ]
[Object ./a.out_e2dbg]
0x08049928: [0] 0x0804981C <_DYNAMIC@a.out_e2dbg>
0x0804992C: [1] 0xB7F4A4E8 <_r_debug@ld-linux.so.2 + 24>
0x08049930: [2] 0xB7F3EEC0 <_dl_rtld_di_serinfo@ld-linux.so.2 + 477>
0x08049934: [3] 0x0804839E <fflush@a.out_e2dbg>
0x08049938: [4] 0x080483AE <puts@a.out_e2dbg>
0x0804993C: [5] 0xB7E515F0 <__libc_malloc@libc.so.6>
0x08049940: [6] 0x080483CE <strlen@a.out_e2dbg>
0x08049944: [7] 0xB7E01E50 <__libc_start_main@libc.so.6>
0x08049948: [8] 0x080483EE <printf@a.out_e2dbg>
0x0804994C: [9] 0x080483FE <free@a.out_e2dbg>
0x08049950: [10] 0x0804840E <read@a.out_e2dbg>
0x08049954: [11] 0xB7DAFFF6 <e2dbg_run@ibc.so.6>
(e2dbg-0.65) mode static
[*] e2dbg is now in STATIC mode
(e2dbg-0.65) # Here we switched in ondisk perspective
(e2dbg-0.65) got
[Global Offset Table .::. GOT : .got ]
[Object ./a.out_e2dbg]
0x080498E4: [0] 0x00000000 <?>
[Global Offset Table .::. GOT : .got.plt ]
[Object ./a.out_e2dbg]
0x080498E8: [0] 0x0804981C <_DYNAMIC>
0x080498EC: [1] 0x00000000 <?>
0x080498F0: [2] 0x00000000 <?>
0x080498F4: [3] 0x0804839E <fflush>
0x080498F8: [4] 0x080483AE <puts>
0x080498FC: [5] 0x080483BE <malloc>
0x08049900: [6] 0x080483CE <strlen>
0x08049904: [7] 0x080483DE <__libc_start_main>
0x08049908: [8] 0x080483EE <printf>
0x0804990C: [9] 0x080483FE <free>
0x08049910: [10] 0x0804840E <read>
[Global Offset Table .::. GOT : .elfsh.altgot ]
[Object ./a.out_e2dbg]
0x08049928: [0] 0x0804981C <_DYNAMIC>
0x0804992C: [1] 0x00000000 <?>
0x08049930: [2] 0x00000000 <?>
0x08049934: [3] 0x0804839E <fflush>
0x08049938: [4] 0x080483AE <puts>
0x0804993C: [5] 0x080483BE <malloc>
0x08049940: [6] 0x080483CE <strlen>
0x08049944: [7] 0x080483DE <__libc_start_main>
0x08049948: [8] 0x080483EE <printf>
0x0804994C: [9] 0x080483FE <free>
0x08049950: [10] 0x0804840E <read>
0x08049954: [11] 0x0804614A <e2dbg_run + 6>
========= END DUMP 1 =========
There are many things to notice in this dump. First you can
verify that it actually does what it is supposed to by
looking the first GOT entries which are reserved for the
linkmap and the rtld dl-resolve function. Those entries are
filled at runtime, so the static GOT version contains NULL
pointers for them. However the GOT which stands in memory has
them filled.
Also, the new version of the GNU linker does insert multiple
GOT sections inside ELF binaries. The .got section handles
the pointer for external variables, while .got.plt handles
the external function pointers. In earlier versions of LD,
those 2 sections were merged. We support both conventions.
Finally, you can see in last the .elfsh.altgot section.
That is part of the ALTGOT technique and it will be
explained as a standalone algorithm in the next parts
of this paper. The ALTGOT technique allow for a size
extension of the Global Offset Table. It allows different
things depending on the architecture. On x86, ALTGOT is
only used when EXTPLT is used, so that we can add extra
function to the host file. On MIPS and ALPHA, ALTGOT
allows to redirect an extern (PLT) function without losing
the real function address. We will develop both of these
techniques in the next parts.
---[ C. Real debugging : dumping, backtrace, breakpoints
When performing debugging using a debugger embedded in the
debuggee process, we do not need ptrace so we cannot
modify so easily the process address space. That's why
we have to do small static changes : we add the debugger
as a DT_NEEDED dependancy. The debugger will also overload some
signal handlers (SIGTRAP, SIGINT, SIGSEGV ..) so that it
can takes control on those events.
We can redirect functions as well using either the CFLOW or
ALTPLT technique using on-disk modification, so that we takes
control at the desired moment. Obviously we can also set
breakpoints in runtime but that need to mprotect the code zone
if it was not writable for the moment. We have idea about how
to get rid of mprotect but this was not implemented in that
version (0.65). Indeed, many uses of the mprotect system call
are incompatible with one of the PaX option). Fortunately
we assume for now that we have read access to the debuggee
program, which means that we can copy the file and disable
that option.
This is how the DT_NEEDED dependence is added :
========= BEGIN DUMP 2 =========
elfsh@WTH $ cat inject_e2dbg.esh
#!../../vm/elfsh
load a.out
set 1.dynamic[08].val 0x2
set 1.dynamic[08].tag DT_NEEDED
redir main e2dbg_run
save a.out_e2dbg
========= END DUMP 2 =========
Let's see the modified binary .dynamic section, where the
extra DT_NEEDED entries were added using the DT_DEBUG
technique that we published 2 years ago [0] :
========= BEGIN DUMP 3 =========
elfsh@WTH $ ../../vm/elfsh -f ./a.out -d DT_NEEDED
[*] Object ./a.out has been loaded (O_RDONLY)
[SHT_DYNAMIC]
[Object ./a.out]
[00] Name of needed library => libc.so.6 {DT_NEEDED}
[*] Object ./a.out unloaded
elfsh@WTH $ ../../vm/elfsh -f ./a.out_e2dbg -d DT_NEEDED
[*] Object ./a.out_e2dbg has been loaded (O_RDONLY)
[SHT_DYNAMIC]
[Object ./a.out_e2dbg]
[00] Name of needed library => libc.so.6 {DT_NEEDED}
[08] Name of needed library => ibc.so.6 {DT_NEEDED}
[*] Object ./a.out_e2dbg unloaded
========= END DUMP 3 =========
Let's see how we redirected the main function to the hook_main
function. You can notice the overwritten bytes between the 2 jmp
of the hook_main function. This technique is also available MIPS
architecture, but this dump is from the IA32 implementation :
========= BEGIN DUMP 4 =========
elfsh@WTH $ ../../vm/elfsh -f ./a.out_e2dbg -D main%40
[*] Object ./a.out_e2dbg has been loaded (O_RDONLY)
08045134 [foff: 308] hook_main + 0 jmp <e2dbg_run>
08045139 [foff: 313] hook_main + 5 push %ebp
0804513A [foff: 314] hook_main + 6 mov %esp,%ebp
0804513C [foff: 316] hook_main + 8 push %esi
0804513D [foff: 317] hook_main + 9 push %ebx
0804513E [foff: 318] hook_main + 10 jmp <main + 5>
08045139 [foff: 313] old_main + 0 push %ebp
0804513A [foff: 314] old_main + 1 mov %esp,%ebp
0804513C [foff: 316] old_main + 3 push %esi
0804513D [foff: 317] old_main + 4 push %ebx
0804513E [foff: 318] old_main + 5 jmp <main + 5>
08048530 [foff: 13616] main + 0 jmp <hook_main>
08048535 [foff: 13621] main + 5 sub $2010,%esp
0804853B [foff: 13627] main + 11 mov 8(%ebp),%ebx
0804853E [foff: 13630] main + 14 mov C(%ebp),%esi
08048541 [foff: 13633] main + 17 and $FFFFFFF0,%esp
08048544 [foff: 13636] main + 20 sub $10,%esp
08048547 [foff: 13639] main + 23 mov %ebx,4(%esp,1)
0804854B [foff: 13643] main + 27 mov $<_IO_stdin_used + 43>,(%esp,1)
08048552 [foff: 13650] main + 34 call <printf>
08048557 [foff: 13655] main + 39 mov (%esi),%eax
[*] No binary pattern was specified
[*] Object ./a.out_e2dbg unloaded
========= END DUMP 4 =========
Let's now execute the debuggee program, in which the
debugger was injected.
========= BEGIN DUMP 5 =========
elfsh@WTH $ ./a.out_e2dbg
The Embedded ELF Debugger 0.65 (32 bits built) .::.
.::. This software is under the General Public License V.2
.::. Please visit http://www.gnu.org
[*] Sun Jul 31 17:56:52 2005 - New object ./a.out_e2dbg loaded
[*] Sun Jul 31 17:56:52 2005 - New object /lib/tls/libc.so.6 loaded
[*] Sun Jul 31 17:56:53 2005 - New object ./ibc.so.6 loaded
[*] Sun Jul 31 17:56:53 2005 - New object /lib/ld-linux.so.2 loaded
[*] Sun Jul 31 17:56:53 2005 - New object /lib/libelfsh.so loaded
[*] Sun Jul 31 17:56:53 2005 - New object /lib/libreadline.so.5 loaded
[*] Sun Jul 31 17:56:53 2005 - New object /lib/libtermcap.so.2 loaded
[*] Sun Jul 31 17:56:53 2005 - New object /lib/libdl.so.2 loaded
[*] Sun Jul 31 17:56:53 2005 - New object /lib/libncurses.so.5 loaded
(e2dbg-0.65) b puts
[*] Breakpoint added at <puts@a.out_e2dbg> (0x080483A8)
(e2dbg-0.65) continue
[..: Embedded ELF Debugger returns to the grave :...]
[e2dbg_run] returning to 0x08045139
[host] main argc 1
[host] argv[0] is : ./a.out_e2dbg
First_printf test
The Embedded ELF Debugger 0.65 (32 bits built) .::.
.::. This software is under the General Public License V.2
.::. Please visit http://www.gnu.org
[*] Sun Jul 31 17:57:03 2005 - New object /lib/tls/libc.so.6 loaded
(e2dbg-0.65) bt
.:: Backtrace ::.
[00] 0xB7DC1EC5 <vm_bt@ibc.so.6 + 208>
[01] 0xB7DC207F <cmd_bt@ibc.so.6 + 152>
[02] 0xB7DBC88C <vm_execmd@ibc.so.6 + 174>
[03] 0xB7DAB4DE <vm_loop@ibc.so.6 + 578>
[04] 0xB7DAB943 <vm_run@ibc.so.6 + 271>
[05] 0xB7DA5FF0 <e2dbg_entry@ibc.so.6 + 110>
[06] 0xB7DA68D6 <e2dbg_genericbp_ia32@ibc.so.6 + 183>
[07] 0xFFFFE440 <_r_debug@ld-linux.so.2 + 1208737648> # sigtrap retaddr
[08] 0xB7DF7F3B <__libc_start_main@libc.so.6 + 235>
[09] 0x08048441 <_start@a.out_e2dbg + 33>
(e2dbg-0.65) b
.:: Breakpoints ::.
[00] 0x080483A8 <puts@a.out_e2dbg>
(e2dbg-0.65) delete 0x080483A8
[*] Breakpoint at 080483A8 <puts@a.out_e2dbg> removed
(e2dbg-0.65) b
.:: Breakpoints ::.
[*] No breakpoints
(e2dbg-0.65) b printf
[*] Breakpoint added at <printf@a.out_e2dbg> (0x080483E8)
(e2dbg-0.65) dumpregs
.:: Registers ::.
[EAX] 00000000 (0000000000) <unknown>
[EBX] 08203F48 (0136331080) <.elfsh.relplt@a.out_e2dbg + 1811272>
[ECX] 00000000 (0000000000) <unknown>
[EDX] B7F0C7C0 (3086010304) <__guard@libc.so.6 + 1656>
[ESI] BFE3B7C4 (3219371972) <_r_debug@ld-linux.so.2 + 133149428>
[EDI] BFE3B750 (3219371856) <_r_debug@ld-linux.so.2 + 133149312>
[ESP] BFE3970C (3219363596) <_r_debug@ld-linux.so.2 + 133141052>
[EBP] BFE3B738 (3219371832) <_r_debug@ld-linux.so.2 + 133149288>
[EIP] 080483A9 (0134513577) <puts@a.out_e2dbg>
(e2dbg-0.65) stack 20
.:: Stack ::.
0xBFE37200 0x00000000 <(null)>
0xBFE37204 0xB7DC2091 <vm_dumpstack@ibc.so.6>
0xBFE37208 0xB7DDF5F0 <_GLOBAL_OFFSET_TABLE_@ibc.so.6>
0xBFE3720C 0xBFE3723C <_r_debug@ld-linux.so.2 + 133131628>
0xBFE37210 0xB7DC22E7 <cmd_stack@ibc.so.6 + 298>
0xBFE37214 0x00000014 <_r_debug@ld-linux.so.2 + 1208744772>
0xBFE37218 0xB7DDDD90 <__FUNCTION__.5@ibc.so.6 + 49>
0xBFE3721C 0xBFE37230 <_r_debug@ld-linux.so.2 + 133131616>
0xBFE37220 0xB7DB9DF9 <vm_implicit@ibc.so.6 + 304>
0xBFE37224 0xB7DE1A7C <world@ibc.so.6 + 92>
0xBFE37228 0xB7DA8176 <do_resolve@ibc.so.6>
0xBFE3722C 0x080530B8 <.elfsh.relplt@a.out_e2dbg + 38072>
0xBFE37230 0x00000014 <_r_debug@ld-linux.so.2 + 1208744772>
0xBFE37234 0x08264FF6 <.elfsh.relplt@a.out_e2dbg + 2208758>
0xBFE37238 0xB7DDF5F0 <_GLOBAL_OFFSET_TABLE_@ibc.so.6>
0xBFE3723C 0xBFE3726C <_r_debug@ld-linux.so.2 + 133131676>
0xBFE37240 0xB7DBC88C <vm_execmd@ibc.so.6 + 174>
0xBFE37244 0x0804F208 <.elfsh.relplt@a.out_e2dbg + 22024>
0xBFE37248 0x00000000 <(null)>
0xBFE3724C 0x00000000 <(null)>
(e2dbg-0.65) continue
[..: Embedded ELF Debugger returns to the grave :...]
First_puts
The Embedded ELF Debugger 0.65 (32 bits built) .::.
.::. This software is under the General Public License V.2
.::. Please visit http://www.gnu.org
[*] Sun Jul 31 18:00:47 2005 - /lib/tls/libc.so.6 loaded
[*] Sun Jul 31 18:00:47 2005 - /usr/lib/gconv/ISO8859-1.so loaded
(e2dbg-0.65) dumpregs
.:: Registers ::.
[EAX] 0000000B (0000000011) <_r_debug@ld-linux.so.2 + 1208744763>
[EBX] 08203F48 (0136331080) <.elfsh.relplt@a.out_e2dbg + 1811272>
[ECX] 0000000B (0000000011) <_r_debug@ld-linux.so.2 + 1208744763>
[EDX] B7F0C7C0 (3086010304) <__guard@libc.so.6 + 1656>
[ESI] BFE3B7C4 (3219371972) <_r_debug@ld-linux.so.2 + 133149428>
[EDI] BFE3B750 (3219371856) <_r_debug@ld-linux.so.2 + 133149312>
[ESP] BFE3970C (3219363596) <_r_debug@ld-linux.so.2 + 133141052>
[EBP] BFE3B738 (3219371832) <_r_debug@ld-linux.so.2 + 133149288>
[EIP] 080483E9 (0134513641) <printf@a.out_e2dbg>
(e2dbg-0.65) linkmap
.::. Linkmap entries .::.
[01] addr : 0x00000000 dyn : 0x0804981C -
[02] addr : 0x00000000 dyn : 0xFFFFE590 -
[03] addr : 0xB7DE3000 dyn : 0xB7F0AD3C - /lib/tls/libc.so.6
[04] addr : 0xB7D95000 dyn : 0xB7DDF01C - ./ibc.so.6
[05] addr : 0xB7F29000 dyn : 0xB7F3FF14 - /lib/ld-linux.so.2
[06] addr : 0xB7D62000 dyn : 0xB7D93018 - /lib/libelfsh.so
[07] addr : 0xB7D35000 dyn : 0xB7D5D46C - /lib/libreadline.so.5
[08] addr : 0xB7D31000 dyn : 0xB7D34BB4 - /lib/libtermcap.so.2
[09] addr : 0xB7D2D000 dyn : 0xB7D2FEEC - /lib/libdl.so.2
[10] addr : 0xB7CEB000 dyn : 0xB7D2A1C0 - /lib/libncurses.so.5
[11] addr : 0xB6D84000 dyn : 0xB6D85F28 - /usr/lib/gconv/ISO8859-1.so
(e2dbg-0.65) exit
[*] Unloading object 1 (/usr/lib/gconv/ISO8859-1.so)
[*] Unloading object 2 (/lib/tls/libc.so.6)
[*] Unloading object 3 (/lib/tls/libc.so.6)
[*] Unloading object 4 (/lib/libncurses.so.5)
[*] Unloading object 5 (/lib/libdl.so.2)
[*] Unloading object 6 (/lib/libtermcap.so.2)
[*] Unloading object 7 (/lib/libreadline.so.5)
[*] Unloading object 8 (/home/elfsh/WTH/elfsh/libelfsh/libelfsh.so)
[*] Unloading object 9 (/lib/ld-linux.so.2)
[*] Unloading object 10 (./ibc.so.6)
[*] Unloading object 11 (/lib/tls/libc.so.6)
[*] Unloading object 12 (./a.out_e2dbg) *
.:: Bye -:: The Embedded ELF Debugger 0.65
========= END DUMP 5 =========
As you see, the use of the debugger is quite similar to other
debuggers. The difference is about the implementation technique
which allows for hardened and embedded systems debugging where
ptrace is not present or disabled.
We were told [9] that the sigaction system call enables the
possibility of doing step by step execution without using
ptrace. We did not have time to implement it but we will
provide a step-capable debugger in the very near future. Since
that call is not filtered by grsecurity and seems to be quite
portable on Linux, BSD, Solaris and HP-UX, it is definitely
worth testing it.
---[ D. Dynamic analyzers generation
Obviously, tools like ltrace [7] can be now done in elfsh
scripts for multiple architectures since all the redirection
stuff is available.
We also think that the framework can be used in dynamic
software instrumentation. Since we support multiple
architectures, we let the door open to other development
team to develop such modules or extension inside the ELF
shell framework.
We did not have time to include an example script for now that
can do this, but we will soon. The kind of interresting stuff
that could be done and improved using the framework would
take its inspiration in projects like fenris [6]. That could
be done for multiple architectures as soon as the instruction
format type is integrated in the script engine, using the code
abstraction of libasm (which is now included as sources in
elfsh).
We do not deal with encryption for now, but some promising API
[5] could be implemented as well for multiple architectures
very easily.
-------[ III. Better multiarchitecture ELF redirections
In the first issue of the Cerberus ELF interface [0], we
presented a redirection technique that we called ALTPLT. This
technique is not enough since it allows only for PLT
redirection on existing function of the binary program so
the software extension usable functions set is limited.
Morever, we noticed a bug in the previously released
implementation of the ALTPLT technique : On the SPARC
architecture, when calling the original function, the
redirection was removed and the program continued to work as if
no hook was installed. This bug came from the fact that Solaris
does not use the r_offset field for computing its relocation
but get the file offset by multiplying the PLT entry size by the
pushed relocation offset on the stack at the moment of dynamic
resolution.
We found a solution for this problem. That solution consisted in
adding some architecture specific fixes at the beginning of the
ALTPLT section. However, such a fix is too much architecture
dependant and we started to think about an alternative technique
for implementing ALTPLT. As we had implemented the DT_DEBUG
technique by modifying some entries in the .dynamic sections, we
discovered that many other entries are erasable and allow for
a very strong and architecture independant technique for
redirecting access to various sections. More precisely, when
patching the DT_PLTREL entry, we are able to provide our own
pointer. DT_PLTREL is an architecture dependant entry and the
documentation about it is quite weak, not to say inexistant.
It actually points on the section of the executable beeing
runtime relocated (e.g. GOT on x86 or mips, PLT on sparc and
alpha). By changing this entry we are able to provide our own
PLT or GOT, which leads to possibly extending it.
Let's first have look at the CFLOW technique and then comes
back on the PLT related redirections using the DT_PLTREL
modification.
---[ A. CFLOW: PaX-safe static functions redirection
CFLOW is a simple but efficient technique for function
redirection that are located in the host file and not
having a PLT entry.
Let's see the host file that we use for this test:
========= BEGIN DUMP 6 =========
elfsh@WTH $ cat host.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int legit_func(char *str)
{
printf("legit func (%s) !\n", str);
return (0);
}
int main()
{
char *str;
char buff[BUFSIZ];
read(0, buff, BUFSIZ-1);
str = malloc(10);
if (str == NULL)
goto err;
strcpy(str, "test");
printf("First_printf %s\n", str);
fflush(stdout);
puts("First_puts");
printf("Second_printf %s\n", str);
free(str);
puts("Second_puts");
fflush(stdout);
legit_func("test");
return (0);
err:
printf("Malloc problem\n");
return (-1);
}
========= END DUMP 6 =========
We will here redirect the function legit_func, which is located
inside host.c by the hook_func function located in the
relocatable object.
Let's look at the relocatable file that we are going to inject
in the above binary.
========= BEGIN DUMP 7 =========
elfsh@WTH $ cat rel.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int glvar_testreloc = 42;
int glvar_testreloc_bss;
char glvar_testreloc_bss2;
short glvar_testreloc_bss3;
int hook_func(char *str)
{
printf("HOOK FUNC %s !\n", str);
return (old_legit_func(str));
}
int puts_troj(char *str)
{
int local = 1;
char *str2;
str2 = malloc(10);
*str2 = 'Z';
*(str2 + 1) = 0x00;
glvar_testreloc_bss = 43;
glvar_testreloc_bss2 = 44;
glvar_testreloc_bss3 = 45;
printf("Trojan injected ET_REL takes control now "
"[%s:%s:%u:%u:%hhu:%hu:%u] \n",
str2, str,
glvar_testreloc,
glvar_testreloc_bss,
glvar_testreloc_bss2,
glvar_testreloc_bss3,
local);
free(str2);
putchar('e');
putchar('x');
putchar('t');
putchar('c');
putchar('a');
putchar('l');
putchar('l');
putchar('!');
putchar('\n');
old_puts(str);
write(1, "calling write\n", 14);
fflush(stdout);
return (0);
}
int func2()
{
return (42);
}
========= END DUMP 7 =========
As you can see, the relocatable object use of unknown functions
like write and putchar. Those functions do not have a symbol, plt
entry, got entry, or even relocatable entry in the host file.
We can call it however using the EXTPLT technique that will be
described as a standalone technique in the next part of this paper.
For now we focuss on the CFLOW technique that allow for redirection
of the legit_func on the hook_func. This function does not have a
PLT entry and we cannot use simple PLT infection for this.
We developped a technique that is PaX safe for ondisk redirection of
this kind of function. It consists of putting the good old jmp
instruction at the beginning of the legit_func and redirect the flow
on our own code. ELFsh will take care of executing the overwritten
bytes somewhere else and gives back control to the redirected
function, just after the jmp hook, so that no runtime restoration is
needed and it stays PaX safe on disk.
When these techniques are used in the debugger directly in memory
and not on disk, they all break the mprotect protection of PaX,
which means that this flag must be disabled if you want to redirect
the flow directly into memory. We use use the mprotect syscall on
small code zone for beeing able to changes some specific instructions
for redirection. However, we think that this technique is mostly
interresting for debugging and not for other things, so it is not
our priority to improve this for now.
Let's see the small ELFsh script for this example :
========= BEGIN DUMP 8 =========
elfsh@WTH $ file a.out
a.out: ELF 32-bit LSB executable, Intel 80386, dynamically linked, \
not stripped
elfsh@WTH $ cat relinject.esh
#!../../../vm/elfsh
load a.out
load rel.o
reladd 1 2
redir puts puts_troj
redir legit_func hook_func
save fake_aout
quit
========= END EXAMPLE 8 =========
The output of the ORIGINAL binary is as follow:
========= BEGIN DUMP 9 =========
elfsh@WTH $ ./a.out
First_printf test
First_puts
Second_printf test
Second_puts
LEGIT FUNC
legit func (test) !
========= END DUMP 9 ===========
Now let's inject the stuff:
========= BEGIN DUMP 10 ========
elfsh@WTH $ ./relinject.esh
The ELF shell 0.65 (32 bits built) .::.
.::. This software is under the General Public License V.2
.::. Please visit http://www.gnu.org
~load a.out
[*] Sun Jul 31 15:30:14 2005 - New object a.out loaded
~load rel.o
[*] Sun Jul 31 15:30:14 2005 - New object rel.o loaded
~reladd 1 2
Section Mirrored Successfully !
[*] ET_REL rel.o injected succesfully in ET_EXEC a.out
~redir puts puts_troj
[*] Function puts redirected to addr 0x08047164 <puts_troj>
~redir legit_func hook_func
[*] Function legit_func redirected to addr 0x08047134 <hook_func>
~save fake_aout
[*] Object fake_aout saved successfully
~quit
[*] Unloading object 1 (rel.o)
[*] Unloading object 2 (a.out) *
.:: Bye -:: The ELF shell 0.65
========= END DUMP 10 =========
Let's now execute the modified binary.
========= BEGIN DUMP 11 =========
elfsh@WTH $ ./fake_aout
First_printf test
Trojan injected ET_REL takes control now [Z:First_puts:42:43:44:45:1]
extcall!
First_puts
calling write
Second_printf test
Trojan injected ET_REL takes control now [Z:Second_puts:42:43:44:45:1]
extcall!
Second_puts
calling write
HOOK FUNC test !
Trojan injected ET_REL takes control now [Z:LEGIT FUNC:42:43:44:45:1]
extcall!
calling write
legit func (test) !
elfsh@WTH $
========= END DUMP 11 =========
Fine. Clearly legit_func has been redirected on the hook
function, and hook_func takes care of calling back the
legit_func using the old symbol technique described in
the first issue of the Cerberus articles serie.
Let's see the original legit_func code which is redirected
using the CFLOW technique on the x86 architecture :
========= BEGIN DUMP 12 =========
080484C0 legit_func + 0 push %ebp
080484C1 legit_func + 1 mov %esp,%ebp
080484C3 legit_func + 3 sub $8,%esp
080484C6 legit_func + 6 mov $<_IO_stdin_used + 4>,(%esp,1)
080484CD legit_func + 13 call <.plt + 32>
080484D2 legit_func + 18 mov $<_IO_stdin_used + 15>,(%esp,1)
========= END DUMP 12 =========
Now the modified code:
========= BEGIN DUMP 13 =========
080484C0 legit_func + 0 jmp <hook_legit_func>
080484C5 legit_func + 5 nop
080484C6 legit_func + 6 mov $<_IO_stdin_used + 4>,(%esp,1)
080484CD legit_func + 13 call <puts>
080484D2 legit_func + 18 mov $<_IO_stdin_used + 15>,(%esp,1)
080484D9 legit_func + 25 mov 8(%ebp),%eax
080484DC legit_func + 28 mov %eax,4(%esp,1)
080484E0 legit_func + 32 call <printf>
080484E5 legit_func + 37 leave
080484E6 legit_func + 38 xor %eax,%eax
========= END DUMP 13 =========
We create a new section .elfsh.hooks whoose data is an array
of hook code stubs like this one:
========= BEGIN DUMP 14 =========
08042134 hook_legit_func + 0 jmp <hook_func>
08042139 old_legit_func + 0 push %ebp
0804213A old_legit_func + 1 mov %esp,%ebp
0804213C old_legit_func + 3 sub $8,%esp
0804213F old_legit_func + 6 jmp <legit_func + 6>
========= END DUMP 14 =========
Because we want to be able to recall the original function
(legit_func), we add the erased bytes of it, just after the
first jmp. Then we call back the legit_func at the good offset
(so that we do not recurse inside the hook because the function
was hijacked), as you can see starting at the old_legit_func
symbol of example 14.
This old symbols technique is coherent with the ALTPLT technique
that we published in the first article. We can as well use
the old_funcname() call inside the injected C code for
calling back the good hijacked function, and we do that without
a single byte restoration at runtime. That is why the CFLOW
technique is PaX compatible.
For the MIPS architecture, the CFLOW technique is quite similar,
we can see the result of it as well (DUMP 15 is the original
binary and DUMP 16 the modified one):
======== BEGIN DUMP 15 =========
400400 <func>: lui gp,0xfc1
400404 <func+4>: addiu gp,gp,-21696
400408 <func+8>: addu gp,gp,t9
40040c <func+12>: addiu sp,sp,-40
400410 <func+16>: sw ra,36(sp)
[...]
======== END DUMP 15 =========
The modified func code is now :
======== BEGIN DUMP 16 =========
<func>
400400: addi t9,t9,104 # Register T9 as target function
400404: j 0x400468 <func2> # Direct JMP on hook function
400408: nop # Delay slot
40040c: addiu sp,sp,-40 # The original func code
400410: sw ra,36(sp)
400414: sw s8,32(sp)
400418: move s8,sp
40041c: sw gp,16(sp)
400420: sw a0,40(s8)
======== END DUMP 16 =========
The func2 function can be anything we want, provided that it has
the same number and type of parameters. When the func2 function
wants to call the original function (func), then it jumps on
the old_func symbol that points inside the .elfsh.hooks section
entry for this CFLOW hook. That is how looks like such a hooks
entry on the MIPS architecture :
======== BEGIN DUMP 17 =========
<old_func>
3ff0f4 addi t9,t9,4876
3ff0f8 lui gp,0xfc1
3ff0fc addiu gp,gp,-21696
3ff100 addu gp,gp,t9
3ff104 j 0x400408 <func + 8>
3ff108 nop
3ff10c nop
======== END DUMP 17 ===========
As you can see, the three instructions that got erased for
installing the CFLOW hook at the beginning of func() are
now located in the hook entry for func(), pointed by
the old_func symbol. The T9 register is also reset so that
we can come back to a safe situation before jumping back
on func + 8.
---[ B. ALTPLT technique revised
ALTPLT technique v1 was presented in the Cerberus ELF Interface [0]
paper. As already stated, it was not satisfying because it was
removing the hook on SPARC at the first original function call.
Since on SPARC the first 4 PLT entries are reserved, there is
room for 12 instructions that would fix anything needed (actually
the first PLT entry) at the moment when ALTPLT+0 takes control.
ALTPLTv2 is working indeed in 12 instructions but it needed to
reencode the first ALTPLT section entry with the code from PLT+0
(which is relocated in runtime on SPARC before the main takes
control, which explains why we cannot patch this on the disk
statically).
By this behavior, it breaks PaX, and the implementation is
very architecture dependant since its SPARC assembly. For those
who want to see it, we let the code of this in the ELFsh source
tree in libelfsh/sparc32.c .
For the ALPHA64 architecture, it gives pretty much the same in its
respective instructions set, and this time the implementation is
located in libelfsh/alpha64.c .
As you can see in the code (that we will not reproduce here for
clarity of the article), ALTPLTv2 is a real pain and we needed to
get rid of all this assembly code that was requesting too much
efforts for potential future ports of this technique to other
architectures.
Then we found the .dynamic DT_PLTREL trick and we tried to see what
happened when changing this .dynamic entry inside the host binary.
Changing the DT_PLTREL entry is very attractive since this is
completely architecture independant so it works everywhere.
Let's see how look like the section header table and the .dynamic
section used in the really simple ALTPLTv3 technique. We use the
.elfsh.altplt section as a mirror of the original .plt as explained
in our first paper. The other .elfsh.* sections has been explained
already or will be just after the log.
The output (modified) binary looks like :
=============== BEGIN DUMP 18 ================
[SECTION HEADER TABLE .::. SHT is not stripped]
[Object fake_aout]
[000] 0x00000000 ------- foff:00000000 sz:0000000 link:00
[001] 0x08042134 a-x---- .elfsh.hooks foff:00000308 sz:0000016 link:00
[002] 0x08043134 a-x---- .elfsh.extplt foff:00004404 sz:0000048 link:00
[003] 0x08044134 a-x---- .elfsh.altplt foff:00008500 sz:0004096 link:00
[004] 0x08045134 a--ms-- rel.o.rodata.str1.32 foff:12596 sz:4096 link:00
[005] 0x08046134 a--ms-- rel.o.rodata.str1.1 foff:16692 sz:4096 link:00
[006] 0x08047134 a-x---- rel.o.text foff:00020788 sz:0004096 link:00
[007] 0x08048134 a------ .interp foff:00024884 sz:0000019 link:00
[008] 0x08048148 a------ .note.ABI-tag foff:00024904 sz:0000032 link:00
[009] 0x08048168 a------ .hash foff:00024936 sz:0000064 link:10
[010] 0x080481A8 a------ .dynsym foff:00025000 sz:0000176 link:11
[011] 0x08048258 a------ .dynstr foff:00025176 sz:0000112 link:00
[012] 0x080482C8 a------ .gnu.version foff:00025288 sz:0000022 link:10
[013] 0x080482E0 a------ .gnu.version_r foff:00025312 sz:0000032 link:11
[014] 0x08048300 a------ .rel.dyn foff:00025344 sz:0000016 link:10
[015] 0x08048310 a------ .rel.plt foff:00025360 sz:0000056 link:10
[016] 0x08048348 a-x---- .init foff:00025416 sz:0000023 link:00
[017] 0x08048360 a-x---- .plt foff:00025440 sz:0000128 link:00
[018] 0x08048400 a-x---- .text foff:00025600 sz:0000736 link:00
[019] 0x080486E0 a-x---- .fini foff:00026336 sz:0000027 link:00
[020] 0x080486FC a------ .rodata foff:00026364 sz:0000116 link:00
[021] 0x08048770 a------ .eh_frame foff:00026480 sz:0000004 link:00
[022] 0x08049774 aw----- .ctors foff:00026484 sz:0000008 link:00
[023] 0x0804977C aw----- .dtors foff:00026492 sz:0000008 link:00
[024] 0x08049784 aw----- .jcr foff:00026500 sz:0000004 link:00
[025] 0x08049788 aw----- .dynamic foff:00026504 sz:0000200 link:11
[026] 0x08049850 aw----- .got foff:00026704 sz:0000004 link:00
[027] 0x08049854 aw----- .got.plt foff:00026708 sz:0000040 link:00
[028] 0x0804987C aw----- .data foff:00026748 sz:0000012 link:00
[029] 0x08049888 aw----- .bss foff:00026760 sz:0000008 link:00
[030] 0x08049890 aw----- rel.o.bss foff:00026768 sz:0004096 link:00
[031] 0x0804A890 aw----- rel.o.data foff:00030864 sz:0000004 link:00
[032] 0x0804A894 aw----- .elfsh.altgot foff:00030868 sz:0000048 link:00
[033] 0x0804A8E4 aw----- .elfsh.dynsym foff:00030948 sz:0000208 link:34
[034] 0x0804AA44 aw----- .elfsh.dynstr foff:00031300 sz:0000127 link:33
[035] 0x0804AB24 aw----- .elfsh.reldyn foff:00031524 sz:0000016 link:00
[036] 0x0804AB34 aw----- .elfsh.relplt foff:00031540 sz:0000072 link:00
[037] 0x00000000 ------- .comment foff:00031652 sz:0000665 link:00
[038] 0x00000000 ------- .debug_aranges foff:00032324 sz:0000120 link:00
[039] 0x00000000 ------- .debug_pubnames foff:00032444 sz:0000042 link:00
[040] 0x00000000 ------- .debug_info foff:00032486 sz:0006871 link:00
[041] 0x00000000 ------- .debug_abbrev foff:00039357 sz:0000511 link:00
[042] 0x00000000 ------- .debug_line foff:00039868 sz:0000961 link:00
[043] 0x00000000 ------- .debug_frame foff:00040832 sz:0000072 link:00
[044] 0x00000000 ---ms-- .debug_str foff:00040904 sz:0008067 link:00
[045] 0x00000000 ------- .debug_macinfo foff:00048971 sz:0029295 link:00
[046] 0x00000000 ------- .shstrtab foff:00078266 sz:0000507 link:00
[047] 0x00000000 ------- .symtab foff:00080736 sz:0002368 link:48
[048] 0x00000000 ------- .strtab foff:00083104 sz:0001785 link:47
[SHT_DYNAMIC]
[Object ./testsuite/etrel_inject/etrel_original/fake_aout]
[00] Name of needed library => libc.so.6 {DT_NEEDED}
[01] Address of init function => 0x08048348 {DT_INIT}
[02] Address of fini function => 0x080486E0 {DT_FINI}
[03] Address of symbol hash table => 0x08048168 {DT_HASH}
[04] Address of dynamic string table => 0x0804AA44 {DT_STRTAB}
[05] Address of dynamic symbol table => 0x0804A8E4 {DT_SYMTAB}
[06] Size of string table => 00000127 bytes {DT_STRSZ}
[07] Size of symbol table entry => 00000016 bytes {DT_SYMENT}
[08] Debugging entry (unknown) => 0x00000000 {DT_DEBUG}
[09] Processor defined value => 0x0804A894 {DT_PLTGOT}
[10] Size in bytes for .rel.plt => 000072 bytes {DT_PLTRELSZ}
[11] Type of reloc in PLT => 00000017 {DT_PLTREL}
[12] Address of .rel.plt => 0x0804AB34 {DT_JMPREL}
[13] Address of .rel.got section => 0x0804AB24 {DT_REL}
[14] Total size of .rel section => 00000016 bytes {DT_RELSZ}
[15] Size of a REL entry => 00000008 bytes {DT_RELENT}
[16] SUN needed version table => 0x80482E0 {DT_VERNEED}
[17] SUN needed version number => 001 {DT_VERNEEDNUM}
[18] GNU version VERSYM => 0x080482C8 {DT_VERSYM}
=============== END DUMP 18 ================
As you can see, various sections has been copied and extended,
and their entries in .dynamic changed. That holds for .got
(DT_PLTGOT), .rel.plt (DT_JMPREL), .dynsym (DT_SYMTAB), and
.dynstr (DT_STRTAB). Changing those entries allow for the
new ALTPLT technique without any line of assembly.
Of course the ALTPLT technique version 3 does not need any
non-mandatory information like debug sections. It may sound
obvious but some peoples really asked this question.
---[ C. ALTGOT technique : the RISC complement
On the MIPS architecture, calls to PLT entries are
done differently. Indeed, instead of a direct call instruction on
the entry, an indirect jump is used for using the GOT entry linked
to the desired function. If such entry is filled, then the
function is called directly. By default, the GOT entries contains
the pointer on the PLT entries. During the execution eventually,
the dynamic linker is called for relocating the GOT section (MIPS,
x86) or the PLT section (on SPARC or ALPHA).
Here is the MIPS assembly log that prove this on some dumb
helloworld program using printf :
00400790 <main>:
400790: 3c1c0fc0 lui gp,0xfc0 # Set GP to GOT base
400794: 279c78c0 addiu gp,gp,30912 # address + 0x7ff0
400798: 0399e021 addu gp,gp,t9 # using t9 (= main)
40079c: 27bdffe0 addiu sp,sp,-32
4007a0: afbf001c sw ra,28(sp)
4007a4: afbe0018 sw s8,24(sp)
4007a8: 03a0f021 move s8,sp
4007ac: afbc0010 sw gp,16(sp)
4007b0: 8f828018 lw v0,-32744(gp)
4007b4: 00000000 nop
4007b8: 24440a50 addiu a0,v0,2640
4007bc: 2405002a li a1,42
4007c0: 8f828018 lw v0,-32744(gp)
4007c4: 00000000 nop
4007c8: 24460a74 addiu a2,v0,2676
4007cc: 8f99803c lw t9,-32708(gp) # Load printf GOT entry
4007d0: 00000000 nop
4007d4: 0320f809 jalr t9 # and jump on it
4007d8: 00000000 nop
4007dc: 8fdc0010 lw gp,16(s8)
4007e0: 00001021 move v0,zero
4007e4: 03c0e821 move sp,s8
4007e8: 8fbf001c lw ra,28(sp)
4007ec: 8fbe0018 lw s8,24(sp)
4007f0: 27bd0020 addiu sp,sp,32
4007f4: 03e00008 jr ra # return from the func
4007f8: 00000000 nop
4007fc: 00000000 nop
We note that the global pointer register %gp is always set
on the GOT section base address on MIPS, more or less some
fixed signed offset, in our case 0x7ff0 (0x8000 on ALPHA).
In order to call a function whoose address is unknown, the GOT
entries are filled and then the indirect jump instruction
on MIPS does not use the PLT entry anymore. What do we learn
from this ? Simply that we cannot rely on a classical PLT
hijacking because the PLT entry code wont be called if the GOT
entry is already filled, which means that we will hijack the
function only the first time.
Because of this, we will hijack functions using GOT patching
on MIPS. However it does not resolve the problem of recalling
the original function. In order to allow such recall, we will
just insert the old_ symbols on the real PLT entry, so that
we can still access the dynamic linking mechanism code stub
even if the GOT has been modified.
Let's see the detailed results of the ALTGOT technique on the
ALPHA and MIPS architecture. It was done without a single
line of assembly code which makes it very portable :
========= BEGIN DUMP 19 =========
elfsh@alpha$ cat host.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
char *str;
str = malloc(10);
if (str == NULL)
goto err;
strcpy(str, "test");
printf("First_printf %s\n", str);
fflush(stdout);
puts("First_puts");
printf("Second_printf %u\n", 42);
puts("Second_puts");
fflush(stdout);
return (0);
err:
printf("Malloc problem %u\n", 42);
return (-1);
}
elfsh@alpha$ gcc host.c -o a.out
elfsh@alpha$ file ./a.out
a.out: ELF 64-bit LSB executable, Alpha (unofficial), for NetBSD 2.0G,
dynamically linked, not stripped
========= END DUMP 19 =========
The original binary executes:
========= BEGIN DUMP 20 =========
elfsh@alpha$ ./a.out
First_printf test
First_puts
Second_printf 42
Second_puts
========= END DUMP 20 ==========
Let's look again the relocatable object we are injecting:
========= BEGIN DUMP 21 =========
elfsh@alpha$ cat rel.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int glvar_testreloc = 42;
int glvar_testreloc_bss;
char glvar_testreloc_bss2;
short glvar_testreloc_bss3;
int puts_troj(char *str)
{
int local = 1;
char *str2;
str2 = malloc(10);
*str2 = 'Z';
*(str2 + 1) = 0x00;
glvar_testreloc_bss = 43;
glvar_testreloc_bss2 = 44;
glvar_testreloc_bss3 = 45;
printf("Trojan injected ET_REL takes control now "
"[%s:%s:%u:%u:%hhu:%hu:%u] \n",
str2, str,
glvar_testreloc,
glvar_testreloc_bss,
glvar_testreloc_bss2,
glvar_testreloc_bss3,
local);
old_puts(str);
fflush(stdout);
return (0);
}
int func2()
{
return (42);
}
========= END DUMP 21 =========
As you can see, the relocatable object rel.c uses old_ symbols
which means that it relies on the ALTPLT technique. However
we do not perform EXTPLT technique on ALPHA and MIPS yet so
we are not able to call unknown function from the binary on
those architectures for now. Our rel.c is a copy from the one
in example 7 without the calls to the unknown functions
write and putchar of example 7.
Now we inject the stuff:
========= BEGIN DUMP 22 =========
elfsh@alpha$ ./relinject.esh > relinject.out
elfsh@alpha$ ./fake_aout
First_printf test
Trojan injected ET_REL takes control now [Z:First_puts:42:43:44:45:1]
First_puts
Second_printf 42
Trojan injected ET_REL takes control now [Z:Second_puts:42:43:44:45:1]
Second_puts
========= END DUMP 22 ==========
The section list on ALPHA is then as follow. A particular
look at the injected sections is recommended :
========= BEGIN DUMP 23 =========
elfsh@alpha$ elfsh -f fake_aout -s -p
[*] Object fake_aout has been loaded (O_RDONLY)
[SECTION HEADER TABLE .::. SHT is not stripped]
[Object fake_aout]
[000] 0x000000000 ------- foff:00000 sz:00000
[001] 0x120000190 a------ .interp foff:00400 sz:00023
[002] 0x1200001A8 a------ .note.netbsd.ident foff:00424 sz:00024
[003] 0x1200001C0 a------ .hash foff:00448 sz:00544
[004] 0x1200003E0 a------ .dynsym foff:00992 sz:00552
[005] 0x120000608 a------ .dynstr foff:01544 sz:00251
[006] 0x120000708 a------ .rela.dyn foff:01800 sz:00096
[007] 0x120000768 a------ .rela.plt foff:01896 sz:00168
[008] 0x120000820 a-x---- .init foff:02080 sz:00128
[009] 0x1200008A0 a-x---- .text foff:02208 sz:01312
[010] 0x120000DC0 a-x---- .fini foff:03520 sz:00104
[011] 0x120000E28 a------ .rodata foff:03624 sz:00162
[012] 0x120010ED0 aw----- .data foff:03792 sz:00000
[013] 0x120010ED0 a------ .eh_frame foff:03792 sz:00004
[014] 0x120010ED8 aw----- .dynamic foff:03800 sz:00352
[015] 0x120011038 aw----- .ctors foff:04152 sz:00016
[016] 0x120011048 aw----- .dtors foff:04168 sz:00016
[017] 0x120011058 aw----- .jcr foff:04184 sz:00008
[018] 0x120011060 awx---- .plt foff:04192 sz:00116
[019] 0x1200110D8 aw----- .got foff:04312 sz:00240
[020] 0x1200111C8 aw----- .sdata foff:04552 sz:00024
[021] 0x1200111E0 aw----- .sbss foff:04576 sz:00024
[022] 0x1200111F8 aw----- .bss foff:04600 sz:00056
[023] 0x120011230 a-x---- rel.o.text foff:04656 sz:00320
[024] 0x120011370 aw----- rel.o.sdata foff:04976 sz:00008
[025] 0x120011378 a--ms-- rel.o.rodata.str1.1 foff:04984 sz:00072
[026] 0x1200113C0 a-x---- .alt.plt.prolog foff:05056 sz:00048
[027] 0x1200113F0 a-x---- .alt.plt foff:05104 sz:00120
[028] 0x120011468 a------ .alt.got foff:05224 sz:00072
[029] 0x1200114B0 aw----- rel.o.got foff:05296 sz:00080
[030] 0x000000000 ------- .comment foff:05376 sz:00240
[031] 0x000000000 ------- .debug_aranges foff:05616 sz:00048
[032] 0x000000000 ------- .debug_pubnames foff:05664 sz:00027
[033] 0x000000000 ------- .debug_info foff:05691 sz:02994
[034] 0x000000000 ------- .debug_abbrev foff:08685 sz:00337
[035] 0x000000000 ------- .debug_line foff:09022 sz:00373
[036] 0x000000000 ------- .debug_frame foff:09400 sz:00048
[037] 0x000000000 ---ms-- .debug_str foff:09448 sz:01940
[038] 0x000000000 ------- .debug_macinfo foff:11388 sz:12937
[039] 0x000000000 ------- .ident foff:24325 sz:00054
[040] 0x000000000 ------- .shstrtab foff:24379 sz:00393
[041] 0x000000000 ------- .symtab foff:27527 sz:02400
[042] 0x000000000 ------- .strtab foff:29927 sz:00948
[Program header table .::. PHT]
[Object fake_aout]
[00] 0x120000040 -> 0x120000190 r-x => Program header table
[01] 0x120000190 -> 0x1200001A7 r-- => Program interpreter
[02] 0x120000000 -> 0x120000ECA r-x => Loadable segment
[03] 0x120010ED0 -> 0x120011510 rwx => Loadable segment
[04] 0x120010ED8 -> 0x120011038 rw- => Dynamic linking info
[05] 0x1200001A8 -> 0x1200001C0 r-- => Auxiliary information
[Program header table .::. SHT correlation]
[Object fake_aout]
[*] SHT is not stripped
[00] PT_PHDR
[01] PT_INTERP .interp
[02] PT_LOAD .interp .note.netbsd.ident .hash .dynsym .dynstr
.rela.dyn .rela.plt .init .text .fini .rodata
[03] PT_LOAD .data .eh_frame .dynamic .ctors .dtors .jcr .plt
.got .sdata .sbss .bss rel.o.text rel.o.sdata
rel.o.rodata.str1.1 .alt.plt.prolog .alt.plt
.alt.got rel.o.got
[04] PT_DYNAMIC .dynamic
[05] PT_NOTE .note.netbsd.ident
[*] Object fake_aout unloaded
========= END DUMP 23 =========
Segments are extended the good way. We see this because of
the correlation between SHT and PHT : all bounds are correct.
the end. The .alt.plt.prolog section is there for implementing
the ALTPLTv2 on ALPHA. This could will patch in runtime the
first ALTPLT entry bytes with the first PLT entry bytes on
the first time that ALTPLT first entry is called (when calling
some original function from a hook function for the first time).
When we discovered how to do the ALTPLTv3 (without a line
of assembly), then .alt.plt.prolog just became a padding
section so that GOT and ALTGOT were well aligned on some
size that was necessary for setting up ALTPLT because of
the ALPHA instruction encoding of indirect control flow
jumps.
---[ D. EXTPLT technique : unknown function postlinking
This technique is one of the major one of the new ELFsh
version. It works on ET_EXEC and ET_DYN files, including
when the injection is done directly in memory. EXTPLT
consists in adding a new section (.elfsh.extplt) so that
we can add entries for new functions.
When coupled to .rel.plt, .got, .dynsym, and .dynstr mirroring
extensions, it allows for placing relocation entries that match
the needs of the new ALTPLT/ALTGOT couple. Let's look at the
additional relocation information using the elfsh -r command.
First, let see the original binary relocation table:
========= BEGIN DUMP 24 =========
[*] Object ./a.out has been loaded (O_RDONLY)
[RELOCATION TABLES]
[Object ./a.out]
{Section .rel.dyn}
[000] R_386_GLOB_DAT 0x08049850 sym[010] : __gmon_start__
[001] R_386_COPY 0x08049888 sym[004] : stdout
{Section .rel.plt}
[000] R_386_JMP_SLOT 0x08049860 sym[001] : fflush
[001] R_386_JMP_SLOT 0x08049864 sym[002] : puts
[002] R_386_JMP_SLOT 0x08049868 sym[003] : malloc
[003] R_386_JMP_SLOT 0x0804986C sym[005] : __libc_start_main
[004] R_386_JMP_SLOT 0x08049870 sym[006] : printf
[005] R_386_JMP_SLOT 0x08049874 sym[007] : free
[006] R_386_JMP_SLOT 0x08049878 sym[009] : read
[*] Object ./testsuite/etrel_inject/etrel_original/a.out unloaded
========= END DUMP 24 =========
Let's now see the modified binary relocation tables:
========= BEGIN DUMP 25 =========
[*] Object fake_aout has been loaded (O_RDONLY)
[RELOCATION TABLES]
[Object ./fake_aout]
{Section .rel.dyn}
[000] R_386_GLOB_DAT 0x08049850 sym[010] : __gmon_start__
[001] R_386_COPY 0x08049888 sym[004] : stdout
{Section .rel.plt}
[000] R_386_JMP_SLOT 0x0804A8A0 sym[001] : fflush
[001] R_386_JMP_SLOT 0x0804A8A4 sym[002] : puts
[002] R_386_JMP_SLOT 0x0804A8A8 sym[003] : malloc
[003] R_386_JMP_SLOT 0x0804A8AC sym[005] : __libc_start_main
[004] R_386_JMP_SLOT 0x0804A8B0 sym[006] : printf
[005] R_386_JMP_SLOT 0x0804A8B4 sym[007] : free
[006] R_386_JMP_SLOT 0x0804A8B8 sym[009] : read
{Section .elfsh.reldyn}
[000] R_386_GLOB_DAT 0x08049850 sym[010] : __gmon_start__
[001] R_386_COPY 0x08049888 sym[004] : stdout
{Section .elfsh.relplt}
[000] R_386_JMP_SLOT 0x0804A8A0 sym[001] : fflush
[001] R_386_JMP_SLOT 0x0804A8A4 sym[002] : puts
[002] R_386_JMP_SLOT 0x0804A8A8 sym[003] : malloc
[003] R_386_JMP_SLOT 0x0804A8AC sym[005] : __libc_start_main
[004] R_386_JMP_SLOT 0x0804A8B0 sym[006] : printf
[005] R_386_JMP_SLOT 0x0804A8B4 sym[007] : free
[006] R_386_JMP_SLOT 0x0804A8B8 sym[009] : read
[007] R_386_JMP_SLOT 0x0804A8BC sym[011] : _IO_putc
[008] R_386_JMP_SLOT 0x0804A8C0 sym[012] : write
[*] Object fake_aout unloaded
========= END DUMP 25 =========
As you see, _IO_putc (internal name for putchar) and write
functions has been used in the injected object. We had to
insert them inside the host binary so that the output binary
can work.
The .elfsh.relplt section is copied from the .rel.plt
section but with a doubled size so that we have room
for additional entries. Even if we extend only one of the
relocation table, both tables needs to be copied, because
on ET_DYN files, the rtld will assume that both tables
are adjacent in memory, so we cannot just copy .rel.plt
but also need to keep .rel.dyn (aka .rel.got) near the
.rel.plt copy. That is why you can see with .elfsh.reldyn
and .elfsh.relplt .
When extra symbols are needed, more sections are moved
after the BSS, including .dynsym and .dynstr.
---[ E. IA32, SPARC32/64, ALPHA64, MIPS32 compliant algorithms
Let's now give all algorithms details about the techniques we
introduced by the practice in the previous paragraphs. We
cover here all pseudos algorithms for ELF redirections. More
constrained debugging detailed algorithms are given at the end
of the next part.
Because of ALTPLT and ALTGOT techniques are so complementary,
we implemented them inside only one algorithm that we give
now. There is no conditions on the SPARC architecture since
it is the default architecture case in the listing.
The main ALTPLTv3 / ALTGOT algorithm (libelfsh/altplt.c) can be
found in elfsh_build_plt() and elfsh_relink_plt(), is as
follow.
It could probably be cleaned if all the code go in architecture
dependant handlers but that would duplicate some code, so we
keep it like this :
Multiarchitecture ALTPLT / ALTGOT algorithm
+-------------------------------------------+
0/ IF [ ARCH is MIPS AND PLT is not found AND File is dynamic ]
[
- Get .text section base address
- Find MIPS opcodes fingerprint for embedded PLT
located inside .text
- Fixup SHT to include PLT section header
]
1/ SWITCH on ELF architecture
[
MIPS:
* Insert mapped .elfsh.gotprolog section
* Insert mapped .elfsh.padgot section
ALPHA:
* Insert mapped .elfsh.pltprolog section
DEFAULT:
* Insert mapped .elfsh.altplt section (copy of .plt)
]
2/ IF [ ARCH is (MIPS or ALPHA or IA32) ]
[
* Insert .elfsh.altgot section (copy of .got)
]
3/ FOREACH (ALT)PLT ENTRY:
[
IF [ FIRST PLT entry ]
[
IF [ARCH is MIPS ]
[
* Insert pairs of ld/st instructions in
.elfsh.gotprolog for copying extern variables
addresses fixed in GOT by the RTLD inside
ALTGOT section. See MIPS altplt handler
in libelfsh/mips32.c
]
ELSE IF [ ARCH is IA32 ]
[
* Reencode the first PLT entry using GOT - ALTGOT
address difference (so we relocate into ALTGOT
instead of GOT)
]
]
IF [ ARCH is MIPS ]
* Inject OLD symbol on current PLT entry
ELSE
* Inject OLD symbol on current ALTPLT entry
IF [ ARCH is ALPHA ]
* Shift relocation entry pointing at current location
IF [ ARCH is IA32 ]
* Reencode PLT and ALTPLT current entry
]
4/ SWITCH on ELF architecture
[
MIPS:
IA32:
* Change DT_PLTGOT entry from GOT to ALTGOT address
* Shift GOT related relocation
SPARC:
* Change DT_PLTGOT entry from PLT to ALTPLT address
* Shift PLT related relocations
]
On MIPS, there is no relocation tables inside ET_EXEC binaries.
If we want to shift the relocations that make reference to GOT
inside the MIPS code, we need to fingerprint such code patterns
so that we fix them using the ALTGOT - GOT difference. They are
easily found since the needed patches are always on the same
binary instructions pattern :
3c1c0000 lui gp,0x0
279c0000 addiu gp,gp,0
The zero fields in those instructions should be patched at
linking time when they match HI16 and LO16 MIPS relocations.
However this information is not available in a table for
ET_EXEC files, so we had to find them back in the binary code.
It way easier to do this on RISC architectures since all
instructions are the same length so false positives are very
unlikely to happen. Once we found all those patterns, we fix
them using the ALTGOT-GOT difference in the relocatable fields.
Of course, we wont change ALL references to GOT inside the
code, because that would result in just moving the GOT without
performing any hijack. We just fix those references in the
first 0x100 bytes of .text, and in .init, .fini, that means
only the references at the reserved GOT entries (filled with
dl-resolve virtual address and linkmap address). That way, we
make the original code use the ALTGOT section when accessing
reserved entries (since they have been runtime relocated in
ALTGOT and not GOT) and the original GOT entries when accessing
the function entries (so that we can hijack functions using
GOT modification).
EXTPLT algorithm
+----------------+
The EXTPLT algorithm fits well in the previous algorithm. We
just needed to add 2 steps in the previous listing :
Step 2 BIS : Insert the EXTPLT (copy of PLT) section on
supported architectures.
Step 5 : Mirror (and extend) dynamic linking sections on
supported architectures. Let's give more details
about this algorithm implemented in
libelfsh/extplt.c.
* Mirror .rel.got (.rel.dyn) and .rel.plt sections after BSS,
with a double sized mirror sections. Those 2 sections needs to
stay adjacent in memory so that EXTPLT works on ET_DYN objects
as well.
* Update DT_REL and DT_JMPREL entries in .dynamic
* Mirror .dynsym and .dynstr sections with a double size
* Update DT_SYMTAB and DT_STRTAB entries in .dynamic
Once those operations are done, we have room in all the various
dynamic linking oriented sections and we can add on-demand
dynamic symbols, symbols names, and relocation entry necessary
for adding extra PLT entries in the EXTPLT section.
Then, each time we encounter a unknown symbol in the process of
relocating a ET_REL object inside a ET_EXEC or ET_DYN object,
we can use the REQUESTPLT algorithm, as implemented in
elfsh_request_pltent() function in the libelfsh/extplt.c file :
* Check room in EXTPLT, RELPLT, DYNSYM, DYNSTR, and
ALTGOT sections.
* Initialize ALTGOT entry to EXTPLT allocated new entry.
* Encode EXTPLT entry for using the ALTGOT entry.
* Insert relocation entry inside .elfsh.relplt for ALTGOT
new entry.
* Add relocation entry size to DT_PLTRELSZ entry value in
.dynamic section.
* Insert missing symbol in .elfsh.dynsym, with name inserted in
.elfsh.dynstr section.
* Add symbol name length to DT_STRSZ entry value in .dynamic
section.
This algorithm is called from the main ET_REL injection and
relocation algorithm each time the ET_REL object use an unknown
function whoose symbol is not present in the host file. The
new ET_REL injection algorithm is given at the end of the
constrained debugging part of the article.
CFLOW algorithm
+----------------+
This technique is implemented using an architecture dependant
backend but the global algorithm stays the same for all
architectures :
- Create .elfsh.hooks sections (only 1 time)
- Find number of bytes aligned on instruction size :
* Using libasm on IA32
* Manually on RISC machines
- Insert HOOK entry on demand (see CFLOW dump for format)
- Insert JMP to hook entry in hijacked function prolog
- Align JUMP hook on instruction size with NOP in hijacked prolog
- Insert hook_funcname and old_funcname symbols in hook entry for
beeing able to call back the original function.
The technique is PaX safe since it does not need any runtime
bytes restoration step. We can hook the address of our choice
using the CFLOW technique, however executing the original bytes
in the hook entry instead of their original place will not work
when placing hooks on relative branching instructions. Indeed,
relatives branching will be resolved to a wrong virtual address
if we execute their opcodes at the wrong place (inside
.elfsh.hooks instead of their original place) inside the
process. Remember this when placing CFLOW hooks : it is not
intended to hook relative branch instructions.
-------[ V. Constrained Debugging
In nowadays environment, hardened binaries are usually
of type ET_DYN. We had to support this kind of injection
since it allows for library files modification as much
powerful as the the executable files modification. Moreover
some distribution comes with a default binary set compiled
in ET_DYN, such as hardened gentoo.
Another improvement that we wanted to be done is the ET_REL
relocation in memory. The algorithm for it is the same than
the ondisk injection, but this time the disk is not changed
so it reduces forensics evidences like in [12]. It is believed
that this kind of injection can be used in exploits and direct
process backdooring without touching the hard disk. Evil eh ?
We are aware of another implementation of the ET_REL injection
into memory [10]. Ours supports a wider range of architecture and
couples with the EXTPLT technique directly in memory, which
was not previously implemented to our knowledge.
A last technique that we wanted to develop was about extending
and debugging static executables. We developed this new technique
that we called EXTSTATIC algorithm. It allows for static
injections by taking parts of libc.a when functions or code is
missing. The same ET_REL injection algorithm is used except
that more than one relocatable file taken from libc.a is
injected at a time using a recursive dependency algorithm.
---[ A. ET_REL relocation in memory
Because we want to be able to provide a handler for breakpoints
as they are specified, we allow for direct mapping of an ET_REL
object into memory. We use extra mmap zone for this, always
taking care that it does not break PaX : we do not map any zone
beeing both executable and writable.
In e2dbg, breakpoints can be implemented in 2 ways. Either an
architecture specific opcode (like 0xCC on IA32) is used on the
desired redirected access, or the CFLOW/ALTPLT primitives can be
used in runtime. In the second case, the mprotect system
call must be used to be able to modify code at runtime. However
we may be able to get rid of mprotect soon for runtime injections
as the CFLOW techniques improves for beeing both static and
runtime PaX safe.
Let's look at some simple binary that does just use printf and
and puts to understand more those concepts:
========= BEGIN DUMP 26 =========
elfsh@WTH $ ./a.out
[host] main argc 1
[host] argv[0] is : ./a.out
First_printf test
First_puts
Second_printf test
Second_puts
LEGIT FUNC
legit func (test) !
========= END DUMP 26 =========
We use a small elfsh script as e2dbg so that it creates
another file with the debugger injected inside it, using
regular elfsh techniques. Let's look at it :
========= BEGIN DUMP 27 =========
elfsh@WTH $ cat inject_e2dbg.esh
#!../../vm/elfsh
load a.out
set 1.dynamic[08].val 0x2 # entry for DT_DEBUG
set 1.dynamic[08].tag DT_NEEDED
redir main e2dbg_run
save a.out_e2dbg
========= END DUMP 27 =========
We then execute the modified binary.
========= BEGIN DUMP 28 =========
elfsh@WTH $ ./aout_e2dbg
The Embedded ELF Debugger 0.65 (32 bits built) .::.
.::. This software is under the General Public License V.2
.::. Please visit http://www.gnu.org
[*] Sun Jul 31 16:24:00 2005 - New object ./a.out_e2dbg loaded
[*] Sun Jul 31 16:24:00 2005 - New object /lib/tls/libc.so.6 loaded
[*] Sun Jul 31 16:24:00 2005 - New object ./ibc.so.6 loaded
[*] Sun Jul 31 16:24:00 2005 - New object /lib/ld-linux.so.2 loaded
[*] Sun Jul 31 16:24:00 2005 - New object /lib/libelfsh.so loaded
[*] Sun Jul 31 16:24:00 2005 - New object /lib/libreadline.so.5 loaded
[*] Sun Jul 31 16:24:00 2005 - New object /lib/libtermcap.so.2 loaded
[*] Sun Jul 31 16:24:00 2005 - New object /lib/libdl.so.2 loaded
[*] Sun Jul 31 16:24:00 2005 - New object /lib/libncurses.so.5 loaded
(e2dbg-0.65) quit
[..: Embedded ELF Debugger returns to the grave :...]
[e2dbg_run] returning to 0x08045139
[host] main argc 1
[host] argv[0] is : ./a.out_e2dbg
First_printf test
First_puts
Second_printf test
Second_puts
LEGIT FUNC
legit func (test) !
elfsh@WTH $
========= END DUMP 28 =========
Okay, that was easy. What if we want to do something more
interresting like ET_REL object injection into memory. We
will make use of the profile command so that we can see
the autoprofiling feature of e2dbg. This command is always
useful to learn more about the internals of the debugger,
and for internal debugging problems that may occur while
developping it.
Our cheap function calls pattern matching makes the output
more understandable than a raw print of profiling information
and took only a few hours to implement using the
ELFSH_PROFILE_{OUT,ERR,ROUT} macros in libelfsh-internals.h
and libelfsh/error.c
We will also print the linkmap list. The linkmap first fields
are OS independant. There are a lot of other internal fields
that we do not display here but a lot of information could
be grabbed from there as well.
See the stuff in action :
========= BEGIN DUMP 29 =========
elfsh@WTH $ ./a.out_e2dbg
The Embedded ELF Debugger 0.65 (32 bits built) .::.
.::. This software is under the General Public License V.2
.::. Please visit http://www.gnu.org
[*] Sun Jul 31 16:12:48 2005 - New object ./a.out_e2dbg loaded
[*] Sun Jul 31 16:12:48 2005 - New object /lib/tls/libc.so.6 loaded
[*] Sun Jul 31 16:12:48 2005 - New object ./ibc.so.6 loaded
[*] Sun Jul 31 16:12:48 2005 - New object /lib/ld-linux.so.2 loaded
[*] Sun Jul 31 16:12:48 2005 - New object /lib/libelfsh.so loaded
[*] Sun Jul 31 16:12:48 2005 - New object /lib/libreadline.so.5 loaded
[*] Sun Jul 31 16:12:48 2005 - New object /lib/libtermcap.so.2 loaded
[*] Sun Jul 31 16:12:48 2005 - New object /lib/libdl.so.2 loaded
[*] Sun Jul 31 16:12:48 2005 - New object /lib/libncurses.so.5 loaded
(e2dbg-0.65) linkmap
.::. Linkmap entries .::.
[01] addr : 0x00000000 dyn : 0x080497D4 -
[02] addr : 0x00000000 dyn : 0xFFFFE590 -
[03] addr : 0xB7E73000 dyn : 0xB7F9AD3C - /lib/tls/libc.so.6
[04] addr : 0xB7E26000 dyn : 0xB7E6F01C - ./ibc.so.6
[05] addr : 0xB7FB9000 dyn : 0xB7FCFF14 - /lib/ld-linux.so.2
[06] addr : 0xB7DF3000 dyn : 0xB7E24018 - /lib/libelfsh.so
[07] addr : 0xB7DC6000 dyn : 0xB7DEE46C - /lib/libreadline.so.5
[08] addr : 0xB7DC2000 dyn : 0xB7DC5BB4 - /lib/libtermcap.so.2
[09] addr : 0xB7DBE000 dyn : 0xB7DC0EEC - /lib/libdl.so.2
[10] addr : 0xB7D7C000 dyn : 0xB7DBB1C0 - /lib/libncurses.so.5
(e2dbg-0.65) list
.::. Working files .::.
[001] Sun Jul 31 16:24:00 2005 D ID: 9 /lib/libncurses.so.5
[002] Sun Jul 31 16:24:00 2005 D ID: 8 /lib/libdl.so.2
[003] Sun Jul 31 16:24:00 2005 D ID: 7 /lib/libtermcap.so.2
[004] Sun Jul 31 16:24:00 2005 D ID: 6 /lib/libreadline.so.5
[005] Sun Jul 31 16:24:00 2005 D ID: 5 /lib/libelfsh.so
[006] Sun Jul 31 16:24:00 2005 D ID: 4 /lib/ld-linux.so.2
[007] Sun Jul 31 16:24:00 2005 D ID: 3 ./ibc.so.6
[008] Sun Jul 31 16:24:00 2005 D ID: 2 /lib/tls/libc.so.6
[009] Sun Jul 31 16:24:00 2005 *D ID: 1 ./a.out_e2dbg
.::. ELFsh modules .::.
[*] No loaded module
(e2dbg-0.65) source ./etrelmem.esh
~load myputs.o
[*] Sun Jul 31 16:13:32 2005 - New object myputs.o loaded
[!!] Loaded file is not the linkmap, switching to STATIC mode
~switch 1
[*] Switched on object 1 (./a.out_e2dbg)
~mode dynamic
[*] e2dbg is now in DYNAMIC mode
~reladd 1 10
[*] ET_REL myputs.o injected succesfully in ET_EXEC ./a.out_e2dbg
~profile
.:: Profiling enable
+ <vm_print_actual@loop.c:38>
~redir puts myputs
+ <vm_implicit@implicit.c:91>
+ <cmd_hijack@fcthijack.c:19>
+ <elfsh_get_metasym_by_name@sym_common.c:283>
+ <elfsh_get_dynsymbol_by_name@dynsym.c:255>
+ <elfsh_get_dynsymtab@dynsym.c:87>
+ <elfsh_get_raw@section.c:691>
[P] --[ <elfsh_get_raw@section.c:691>
[P] --- Last 1 function(s) recalled 1 time(s) ---
+ <elfsh_get_dynsymbol_name@dynsym.c:17>
[W] <elfsh_get_dynsymbol_by_name@dynsym.c:274> Symbol not found
[P] --[ <elfsh_get_raw@section.c:691>
[P] --[ <elfsh_get_dynsymbol_name@dynsym.c:17>
[P] --- Last 2 function(s) recalled 12 time(s) ---
+ <elfsh_get_symbol_by_name@symbol.c:236>
+ <elfsh_get_symtab@symbol.c:110>
+ <elfsh_get_symbol_name@symbol.c:20>
[P] --[ <elfsh_get_symbol_name@symbol.c:20>
[P] --- Last 1 function(s) recalled 114 time(s) ---
+ <elfsh_hijack_function_by_name@hijack.c:25>
+ <elfsh_setup_hooks@hooks.c:199>
+ <elfsh_get_pagesize@hooks.c:783>
+ <elfsh_get_archtype@hooks.c:624>
+ <elfsh_get_arch@elf.c:179>
+ <elfsh_copy_plt@altplt.c:525>
+ <elfsh_static_file@elf.c:491>
+ <elfsh_get_segment_by_type@pht.c:215>
+ <elfsh_get_pht@pht.c:364>
+ <elfsh_get_segment_type@pht.c:174>
[P] --[ <elfsh_get_segment_type@pht.c:174>
[P] --- Last 1 function(s) recalled 4 time(s) ---
+ <elfsh_get_arch@elf.c:179>
[P] --[ <elfsh_get_arch@elf.c:179>
[P] --- Last 1 function(s) recalled 1 time(s) ---
+ <elfsh_relink_plt@altplt.c:121>
+ <elfsh_get_archtype@hooks.c:624>
[P] --[ <elfsh_get_arch@elf.c:179>
[P] --[ <elfsh_relink_plt@altplt.c:121>
[P] --[ <elfsh_get_archtype@hooks.c:624>
[P] --- Last 3 function(s) recalled 1 time(s) ---
+ <elfsh_get_elftype@hooks.c:662>
+ <elfsh_get_objtype@elf.c:204>
+ <elfsh_get_ostype@hooks.c:709>
+ <elfsh_get_real_ostype@hooks.c:679>
+ <elfsh_get_interp@interp.c:41>
+ <elfsh_get_raw@section.c:691>
[P] --[ <elfsh_get_raw@section.c:691>
[P] --- Last 1 function(s) recalled 1 time(s) ---
+ <elfsh_get_section_by_name@section.c:168>
+ <elfsh_get_section_name@sht.c:474>
[P] --[ <elfsh_get_section_name@sht.c:474>
[P] --- Last 1 function(s) recalled 1 time(s) ---
+ <elfsh_get_symbol_by_name@symbol.c:236>
+ <elfsh_get_symtab@symbol.c:110>
+ <elfsh_get_symbol_name@symbol.c:20>
[W] <elfsh_get_symbol_by_name@symbol.c:253> Symbol not found
[P] --[ <elfsh_get_symbol_name@symbol.c:20>
[P] --- Last 1 function(s) recalled 114 time(s) ---
+ <elfsh_is_pltentry@plt.c:73>
[W] <elfsh_is_pltentry@plt.c:77> Invalid NULL parameter
+ <elfsh_get_dynsymbol_by_name@dynsym.c:255>
+ <elfsh_get_dynsymtab@dynsym.c:87>
+ <elfsh_get_raw@section.c:691>
[P] --[ <elfsh_get_raw@section.c:691>
[P] --- Last 1 function(s) recalled 1 time(s) ---
+ <elfsh_get_dynsymbol_name@dynsym.c:17>
[P] --[ <elfsh_is_pltentry@plt.c:73>
[P] --[ <elfsh_get_dynsymbol_by_name@dynsym.c:255>
[P] --[ <elfsh_get_dynsymtab@dynsym.c:87>
[P] --[ <elfsh_get_raw@section.c:691>
[P] --[ <elfsh_get_dynsymbol_name@dynsym.c:17>
[P] --- Last 5 function(s) recalled 1 time(s) ---
+ <elfsh_get_plt@plt.c:16>
+ <elfsh_is_plt@plt.c:49>
+ <elfsh_get_section_name@sht.c:474>
+ <elfsh_is_altplt@plt.c:62>
[P] --[ <elfsh_is_plt@plt.c:49>
[P] --[ <elfsh_get_section_name@sht.c:474>
[P] --[ <elfsh_is_altplt@plt.c:62>
[P] --- Last 3 function(s) recalled 3 time(s) ---
+ <elfsh_get_anonymous_section@section.c:334>
+ <elfsh_get_raw@section.c:691>
[P] --[ <elfsh_is_plt@plt.c:49>
[P] --[ <elfsh_get_section_name@sht.c:474>
[P] --[ <elfsh_is_altplt@plt.c:62>
[P] --[ <elfsh_get_anonymous_section@section.c:334>
[P] --[ <elfsh_get_raw@section.c:691>
[P] --- Last 5 function(s) recalled 44 time(s) ---
+ <elfsh_get_arch@elf.c:179>
[P] --[ <elfsh_get_arch@elf.c:179>
[P] --- Last 1 function(s) recalled 1 time(s) ---
+ <elfsh_hijack_plt_ia32@ia32.c:258>
+ <elfsh_get_foffset_from_vaddr@raw.c:85>
+ <elfsh_get_pltentsz@plt.c:94>
[P] --[ <elfsh_get_arch@elf.c:179>
[P] --[ <elfsh_hijack_plt_ia32@ia32.c:258>
[P] --[ <elfsh_get_foffset_from_vaddr@raw.c:85>
[P] --[ <elfsh_get_pltentsz@plt.c:94>
[P] --- Last 4 function(s) recalled 1 time(s) ---
+ <elfsh_munprotect@runtime.c:97>
+ <elfsh_get_parent_section@section.c:380>
+ <elfsh_get_parent_segment@pht.c:304>
+ <elfsh_segment_is_readable@pht.c:14>
+ <elfsh_segment_is_writable@pht.c:21>
+ <elfsh_segment_is_executable@pht.c:28>
+ <elfsh_raw_write@raw.c:22>
+ <elfsh_get_parent_section_by_foffset@section.c:416>
+ <elfsh_get_sht@sht.c:159>
+ <elfsh_get_section_type@sht.c:887>
+ <elfsh_get_anonymous_section@section.c:334>
+ <elfsh_get_raw@section.c:691>
+ <elfsh_raw_write@raw.c:22>
+ <elfsh_get_parent_section_by_foffset@section.c:416>
+ <elfsh_get_sht@sht.c:159>
+ <elfsh_get_section_type@sht.c:887>
+ <elfsh_get_anonymous_section@section.c:334>
+ <elfsh_get_raw@section.c:691>
+ <elfsh_get_pltentsz@plt.c:94>
+ <elfsh_get_arch@elf.c:179>
+ <elfsh_mprotect@runtime.c:135>
[*] Function puts redirected to addr 0xB7FB6000 <myputs>
+ <vm_print_actual@loop.c:38>
~profile
+ <vm_implicit@implicit.c:91>
.:: Profiling disable
[*] ./etrelmem.esh sourcing -OK-
(e2dbg-0.65) continue
[..: Embedded ELF Debugger returns to the grave :...]
[e2dbg_run] returning to 0x08045139
[host] main argc 1
[host] argv[0] is : ./a.out_e2dbg
First_printf test
Hijacked puts !!! arg = First_puts
First_puts
Second_printf test
Hijacked puts !!! arg = Second_puts
Second_puts
Hijacked puts !!! arg = LEGIT FUNC
LEGIT FUNC
legit func (test) !
elfsh@WTH $
========= END DUMP 29 =========
Really cool. We hijacked 2 functions (puts and legit_func) using
the 2 different (ALTPLT and CFLOW) techniques. For this, we
did not have to inject an additional ET_REL file inside the
ET_EXEC host, but we directly injected the hook module inside
memory using mmap.
We could have printed the SHT and PHT as well just after the
ET_REL injection into memory. We keep track of all mapping
when we inject such relocatable objects, so that we can
eventually unmap them in the future or remap them later :
========= BEGIN DUMP 30 =========
(e2dbg-0.65) s
[SECTION HEADER TABLE .::. SHT is not stripped]
[Object ./a.out_e2dbg]
[000] 0x00000000 ------- foff:00000 size:00308
[001] 0x08045134 a-x---- .elfsh.hooks foff:00308 size:00015
[002] 0x08046134 a-x---- .elfsh.extplt foff:04404 size:00032
[003] 0x08047134 a-x---- .elfsh.altplt foff:08500 size:04096
[004] 0x08048134 a------ .interp foff:12596 size:00019
[005] 0x08048148 a------ .note.ABI-tag foff:12616 size:00032
[006] 0x08048168 a------ .hash foff:12648 size:00064
[007] 0x080481A8 a------ .dynsym foff:12712 size:00176
[008] 0x08048258 a------ .dynstr foff:12888 size:00112
[009] 0x080482C8 a------ .gnu.version foff:13000 size:00022
[010] 0x080482E0 a------ .gnu.version_r foff:13024 size:00032
[011] 0x08048300 a------ .rel.dyn foff:13056 size:00016
[012] 0x08048310 a------ .rel.plt foff:13072 size:00056
[013] 0x08048348 a-x---- .init foff:13128 size:00023
[014] 0x08048360 a-x---- .plt foff:13152 size:00128
[015] 0x08048400 a-x---- .text foff:13312 size:00800
[016] 0x08048720 a-x---- .fini foff:14112 size:00027
[017] 0x0804873C a------ .rodata foff:14140 size:00185
[018] 0x080487F8 a------ .eh_frame foff:14328 size:00004
[019] 0x080497FC aw----- .ctors foff:14332 size:00008
[020] 0x08049804 aw----- .dtors foff:14340 size:00008
[021] 0x0804980C aw----- .jcr foff:14348 size:00004
[022] 0x08049810 aw----- .dynamic foff:14352 size:00200
[023] 0x080498D8 aw----- .got foff:14552 size:00004
[024] 0x080498DC aw----- .got.plt foff:14556 size:00040
[025] 0x08049904 aw----- .data foff:14596 size:00012
[026] 0x08049910 aw----- .bss foff:14608 size:00008
[027] 0x08049918 aw----- .elfsh.altgot foff:14616 size:00044
[028] 0x08049968 aw----- .elfsh.dynsym foff:14696 size:00192
[029] 0x08049AC8 aw----- .elfsh.dynstr foff:15048 size:00122
[030] 0x08049BA8 aw----- .elfsh.reldyn foff:15272 size:00016
[031] 0x08049BB8 aw----- .elfsh.relplt foff:15288 size:00064
[032] 0x00000000 ------- .comment foff:15400 size:00665
[033] 0x00000000 ------- .debug_aranges foff:16072 size:00120
[034] 0x00000000 ------- .debug_pubnames foff:16192 size:00042
[035] 0x00000000 ------- .debug_info foff:16234 size:06904
[036] 0x00000000 ------- .debug_abbrev foff:23138 size:00503
[037] 0x00000000 ------- .debug_line foff:23641 size:00967
[038] 0x00000000 ------- .debug_frame foff:24608 size:00076
[039] 0x00000000 ---ms-- .debug_str foff:24684 size:08075
[040] 0x00000000 ------- .debug_macinfo foff:32759 size:29295
[041] 0x00000000 ------- .shstrtab foff:62054 size:00496
[042] 0x00000000 ------- .symtab foff:64473 size:02256
[043] 0x00000000 ------- .strtab foff:66729 size:01665
[044] 0x40019000 aw----- myputs.o.bss foff:68394 size:04096
[045] 0x00000000 ------- .elfsh.rpht foff:72493 size:04096
[046] 0x4001A000 a-x---- myputs.o.text foff:76589 size:04096
[047] 0x4001B000 a--ms-- myputs.o.rodata.str1.1 foff:80685 size:04096
(e2dbg-0.65) p
[Program Header Table .::. PHT]
[Object ./a.out_e2dbg]
[00] 0x08045034 -> 0x08045134 r-x memsz(00256) filesz(00256)
[01] 0x08048134 -> 0x08048147 r-- memsz(00019) filesz(00019)
[02] 0x08045000 -> 0x080487FC r-x memsz(14332) filesz(14332)
[03] 0x080497FC -> 0x08049C30 rw- memsz(01076) filesz(01068)
[04] 0x08049810 -> 0x080498D8 rw- memsz(00200) filesz(00200)
[05] 0x08048148 -> 0x08048168 r-- memsz(00032) filesz(00032)
[06] 0x00000000 -> 0x00000000 rw- memsz(00000) filesz(00000)
[07] 0x00000000 -> 0x00000000 --- memsz(00000) filesz(00000)
[SHT correlation]
[Object ./a.out_e2dbg]
[*] SHT is not stripped
[00] PT_PHDR
[01] PT_INTERP .interp
[02] PT_LOAD .elfsh.hooks .elfsh.extplt .elfsh.altplt .interp
.note.ABI-tag .hash .dynsym .dynstr .gnu.version
.gnu.version_r .rel.dyn .rel.plt .init .plt
.text .fini .rodata .eh_frame
[03] PT_LOAD .ctors .dtors .jcr .dynamic .got .got.plt .data
.bss .elfsh.altgot .elfsh.dynsym .elfsh.dynstr
.elfsh.reldyn .elfsh.relplt
[04] PT_DYNAMIC .dynamic
[05] PT_NOTE .note.ABI-tag
[06] PT_GNU_STACK
[07] PT_PAX_FLAGS
[Runtime Program Header Table .::. RPHT]
[Object ./a.out_e2dbg]
[00] 0x40019000 -> 0x4001A000 rw- memsz(4096) filesz(4096)
[01] 0x4001A000 -> 0x4001B000 r-x memsz(4096) filesz(4096)
[02] 0x4001B000 -> 0x4001C000 r-x memsz(4096) filesz(4096)
[SHT correlation]
[Object ./a.out_e2dbg]
[*] SHT is not stripped
[00] PT_LOAD myputs.o.bss
[01] PT_LOAD myputs.o.text
[02] PT_LOAD myputs.o.rodata.str1.1
(e2dbg-0.65)
========= BEGIN DUMP 30 =========
Our algorithm is not really optimized since it allocates
a new PT_LOAD by section. Here, we created a new table RPHT
(Runtime PHT) which handle the list of all runtime injected
pages. This table has no legal existance in the ELF file,
but that avoid to extend the real PHT with additional
runtime memory areas. The technique does not break PaX
since all zones are allocated using the strict necessary
rights. However, if you want to redirect existing functions
on the newly injected functions from myputs.o, then you
will have to change some code in runtime, and then it
becomes necessary to disable mprotect option to avoid
breaking PaX.
---[ B. ET_REL relocation into ET_DYN
We ported the ET_REL injection and the EXTPLT technique to
ET_DYN files. The biggest difference is that ET_DYN files have
a relative address space ondisk. Of course, stripped binaries
have no effect on our algorithms and we dont need any
non-mandatory information such as debug sections or anything
(it may be obvious but some peoples really asked this).
Let's see what happens on this ET_DYN host file:
========= BEGIN DUMP 31 =========
elfsh@WTH $ file main
main: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV),
stripped
elfsh@WTH $ ./main
0x800008c8 main(argc=0xbfa238d0, argv=0xbfa2387c, envp=0xbfa23878,
auxv=0xbfa23874) __guard=0xb7ef4148
ssp-all (Stack) Triggering an overflow by copying [20] of data into [10]
of space
main: stack smashing attack in function main()
Aborted
elfsh@WTH $ ./main AAAAA
0x800008c8 main(argc=0xbf898e40, argv=0xbf898dec, envp=0xbf898de8,
auxv=0xbf898de4) __guard=0xb7f6a148
ssp-all (Stack) Copying [5] of data into [10] of space
elfsh@WTH $ ./main AAAAAAAAAAAAAAAAAAAAAAAAAAA
0x800008c8 main(argc=0xbfd3c8e0, argv=0xbfd3c88c, envp=0xbfd3c888,
auxv=0xbfd3c884) __guard=0xb7f0b148
ssp-all (Stack) Copying [27] of data into [10] of space
main: stack smashing attack in function main()
Aborted
========= END DUMP 31 =========
For the sake of fun, we decided to study in priority the
hardened gentoo binaries [11] . Those comes with PIE (Position
Independant Executable) and SSP (Stack Smashing Protection)
built in. It does not change a line of our algorithm. Here
are some tests done on a stack smashing protected binary
with an overflow in the first parameter, triggering the
stack smashing handler. We will redirect that handler
to show that it is a normal function that use classical
PLT mechanisms.
This is the code that we are going to inject :
========= BEGIN DUMP 32 =========
elfsh@WTH $ cat simple.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int fake_main(int argc, char **argv)
{
old_printf("I am the main function, I have %d argc and my "
"argv is %08X yupeelala \n",
argc, argv);
write(1, "fake_main is calling write ! \n", 30);
old_main(argc, argv);
return (0);
}
char* fake_strcpy(char *dst, char *src)
{
printf("The fucker wants to copy %s at address %08X \n", src, dst);
return ((char *) old_strcpy(dst, src));
}
void fake_stack_smash_handler(char func[], int damaged)
{
static int i = 0;
printf("calling printf from stack smashing handler %u\n", i++);
if (i>3)
old___stack_smash_handler(func, damaged);
else
printf("Same player play again [damaged = %08X] \n", damaged);
printf("A second (%d) printf from the handler \n", 2);
}
int fake_libc_start_main(void *one, void *two, void *three, void *four,
void *five, void *six, void *seven)
{
static int i = 0;
old_printf("fake_libc_start_main \n");
printf("start_main has been run %u \n", i++);
return (old___libc_start_main(one, two, three, four,
five, six, seven));
}
========= END DUMP 32 =========
The elfsh script that allow for the modification is :
========= BEGIN DUMP 33 =========
elfsh@WTH $ cat relinject.esh
#!../../../vm/elfsh
load main
load simple.o
reladd 1 2
redir main fake_main
redir __stack_smash_handler fake_stack_smash_handler
redir __libc_start_main fake_libc_start_main
redir strcpy fake_strcpy
save fake_main
quit
========= END DUMP 33 =========
Now let's see this in action !
========= BEGIN DUMP 34 =========
elfsh@WTH $ ./relinject.esh
The ELF shell 0.65 (32 bits built) .::.
.::. This software is under the General Public License V.2
.::. Please visit http://www.gnu.org
~load main
[*] Sun Jul 31 17:24:20 2005 - New object main loaded
~load simple.o
[*] Sun Jul 31 17:24:20 2005 - New object simple.o loaded
~reladd 1 2
[*] ET_REL simple.o injected succesfully in ET_DYN main
~redir main fake_main
[*] Function main redirected to addr 0x00005154 <fake_main>
~redir __stack_smash_handler fake_stack_smash_handler
[*] Function __stack_smash_handler redirected to addr
0x00005203 <fake_stack_smash_handler>
~redir __libc_start_main fake_libc_start_main
[*] Function __libc_start_main redirected to addr
0x00005281 <fake_libc_start_main>
~redir strcpy fake_strcpy
[*] Function strcpy redirected to addr 0x000051BD <fake_strcpy>
~save fake_main
[*] Object fake_main saved successfully
~quit
[*] Unloading object 1 (simple.o)
[*] Unloading object 2 (main) *
.:: Bye -:: The ELF shell 0.65
========= END DUMP 34 =========
What about the result ?
========= BEGIN DUMP 35 =========
elfsh@WTH $ ./fake_main
fake_libc_start_main
start_main has been run 0
I am the main function, I have 1 argc and my argv is BF9A6F54 yupeelala
fake_main is calling write !
0x800068c8 main(argc=0xbf9a6e80, argv=0xbf9a6e2c, envp=0xbf9a6e28,
auxv=0xbf9a6e24) __guard=0xb7f78148
ssp-all (Stack) Triggering an overflow by copying [20] of data into [10]
of space
The fucker wants to copy 01234567890123456789 at address BF9A6E50
calling printf from stack smashing handler 0
Same player play again [damaged = 39383736]
A second (2) printf from the handler
elfsh@WTH $ ./fake_main AAAA
fake_libc_start_main
start_main has been run 0
I am the main function, I have 2 argc and my argv is BF83A164 yupeelala
fake_main is calling write !
0x800068c8 main(argc=0xbf83a090, argv=0xbf83a03c, envp=0xbf83a038,
auxv=0xbf83a034) __guard=0xb7f09148
ssp-all (Stack) Copying [4] of data into [10] of space
The fucker wants to copy AAAA at address BF83A060
elfsh@WTH $ ./fake_main AAAAAAAAAAAAAAA
fake_libc_start_main
start_main has been run 0
I am the main function, I have 2 argc and my argv is BF8C7F24 yupeelala
fake_main is calling write !
0x800068c8 main(argc=0xbf8c7e50, argv=0xbf8c7dfc, envp=0xbf8c7df8,
auxv=0xbf8c7df4) __guard=0xb7f97148
ssp-all (Stack) Copying [15] of data into [10] of space
The fucker wants to copy AAAAAAAAAAAAAAA at address BF8C7E20
========= END DUMP 35 =========
No problem there : strcpy, main, libc_start_main and
__stack_smash_handler are redirected on our own routines
as the output shows. We also call write that was not available
in the original binary, which show that EXTPLT also works on
ET_DYN objects, the cool stuff beeing that it worked without
any modification.
In the current release (0.65rc1) there is a limitation on ET_DYN
however. We have to avoid non-initialized variables because
that would add some entries in relocation tables. This is not
a problem to add some since we also copy .rel.got (rel.dyn) in
EXTPLT on ET_DYN, but it is not implemented for now.
---[ C. Extending static executables
Now we would like to be able to debug static binary the same way
we do for dynamic ones. Since we cannot inject e2dbg using
DT_NEEDED dependances on static binaries, the idea is to inject
e2dbg as ET_REL into ET_EXEC since it is possible on static
binaries. E2dbg as many more dependancies than a simple host.c
program. The extended idea is to inject the missing part of
static libraries when it is necessary.
We have to resolve dependancies on-the-fly while ET_REL injection
is performed. For that we will use a simple recursive algorithm
on the existing relocation code : when a symbol is not found
at relocation time, either it is a old_* symbol so it is delayed
in a second stage relocation time (Indeed, old symbols appears
at redirection time, which is done after the injection of the
ET_REL file so we miss that symbol at first stage), or the
function symbol is definitely unknown and we need to add
information so that the rtld can resolve it as well.
To be able to find the suitable ET_REL to inject, ELFsh load all
the ET_REL from static library (.a) then the resolution is done
using this pool of binaries. The workspace feature of elfsh is
quite useful for this, when sessions are performed on more than
a thousand of ET_EXEC ELF files at a time (after extracting
modules from libc.a and others static librairies, for instance).
Circular dependancies are solved by using second stage relocation
when the required symbol is in a file that is being injected after
the current file. The same second stage relocation mechanism
is used when we need to relocate ET_REL objects that use OLD
symbols. Since OLD symbols are injected at redirection time and
ET_REL files should be injected before (so that we can use
functions from the ET_REL object as hook functions), we do not
have OLD symbols at relocation time. The second stage relocation
is then triggered at save time (for on disk modifications) or
recursively solved when injecting multiple ET_REL with circular
relocation dependances.
A problem is remaining, as for now we had one PT_LOAD by injected
section, we quickly reach more than 500 PT_LOAD. This seems to be
a bit too much for a regular ELF static file. We need to improve
the PT_LOAD allocation mechanism so that we can inject bigger
extension to such host binaries.
This technique provide the same features as EXTPLT but for static
binaries : we can inject what we want (regardless of what the host
binary contains).
So here is a smaller working example:
========= BEGIN DUMP 36 =========
elfsh@WTH $ cat host.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int legit_func(char *str)
{
puts("legit func !");
return (0);
}
int main()
{
char *str;
char buff[BUFSIZ];
read(0, buff, BUFSIZ-1);
puts("First_puts");
puts("Second_puts");
fflush(stdout);
legit_func("test");
return (0);
}
elfsh@WTH $ file a.out
a.out: ELF 32-bit LSB executable, Intel 80386, statically linked,
not stripped
elfsh@WTH $ ./a.out
First_puts
Second_puts
legit func !
========= END DUMP 36 =========
The injected file source code is as follow :
========= BEGIN DUMP 37 =========
elfsh@WTH $ cat rel2.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <netdb.h>
int glvar_testreloc = 42;
int glvar_testreloc_bss;
char glvar_testreloc_bss2;
short glvar_testreloc_bss3;
int hook_func(char *str)
{
int sd;
printf("hook func %s !\n", str);
return (old_legit_func(str));
}
int puts_troj(char *str)
{
int local = 1;
char *str2;
int fd;
char name[16];
void *a;
str2 = malloc(10);
*str2 = 'Z';
*(str2 + 1) = 0x00;
glvar_testreloc_bss = 43;
glvar_testreloc_bss2 = 44;
glvar_testreloc_bss3 = 45;
memset(name, 0, 16);
printf("Trojan injected ET_REL takes control now "
"[%s:%s:%u:%u:%hhu:%hu:%u] \n",
str2, str,
glvar_testreloc,
glvar_testreloc_bss,
glvar_testreloc_bss2,
glvar_testreloc_bss3,
local);
free(str2);
gethostname(name, 15);
printf("hostname : %s\n", name);
printf("printf called from puts_troj [%s] \n", str);
fd = open("/etc/services", 0, O_RDONLY);
if (fd)
{
if ((a = mmap(0, 100, PROT_READ, MAP_PRIVATE, fd, 0)) == (void *) -1)
{
perror("mmap");
close(fd);
printf("mmap failed : fd: %d\n", fd);
return (-1);
}
printf("-=-=-=-=-=- BEGIN /etc/services %d -=-=-=-=-=\n", fd);
printf("host : %.60s\n", (char *) a);
printf("-=-=-=-=-=- END /etc/services %d -=-=-=-=-=\n", fd);
printf("mmap succeed fd : %d\n", fd);
close(fd);
}
old_puts(str);
fflush(stdout);
return (0);
}
========= END DUMP 37 =========
The load_lib.esh script, generated using a small bash
script, looks like this :
========= BEGIN DUMP 38 =========
elfsh@WTH $ head -n 10 load_lib.esh
#!../../../vm/elfsh
load libc/init-first.o
load libc/libc-start.o
load libc/sysdep.o
load libc/version.o
load libc/check_fds.o
load libc/libc-tls.o
load libc/elf-init.o
load libc/dso_handle.o
load libc/errno.o
========= END DUMP 38 =========
Here is the injection ELFsh script:
========= BEGIN DUMP 39 =========
elfsh@WTH $ cat relinject.esh
#!../../../vm/elfsh
exec gcc -g3 -static host.c
exec gcc -g3 -static rel2.c -c
load a.out
load rel2.o
source ./load_lib.esh
reladd 1 2
redir puts puts_troj
redir legit_func hook_func
save fake_aout
quit
========= END DUMP 39 =========
Stripped output of the injection :
========= BEGIN DUMP 40 =========
elfsh@WTH $ ./relinject.esh
The ELF shell 0.65 (32 bits built) .::.
.::. This software is under the General Public License V.2
.::. Please visit http://www.gnu.org
~exec gcc -g3 -static host.c
[*] Command executed successfully
~exec gcc -g3 -static rel2.c -c
[*] Command executed successfully
~load a.out
[*] Sun Jul 31 16:37:32 2005 - New object a.out loaded
~load rel2.o
[*] Sun Jul 31 16:37:32 2005 - New object rel2.o loaded
~source ./load_lib.esh
~load libc/init-first.o
[*] Sun Jul 31 16:37:33 2005 - New object libc/init-first.o loaded
~load libc/libc-start.o
[*] Sun Jul 31 16:37:33 2005 - New object libc/libc-start.o loaded
~load libc/sysdep.o
[*] Sun Jul 31 16:37:33 2005 - New object libc/sysdep.o loaded
~load libc/version.o
[*] Sun Jul 31 16:37:33 2005 - New object libc/version.o loaded
[[... 1414 files later ...]]
[*] ./load_lib.esh sourcing -OK-
~reladd 1 2
[*] ET_REL rel2.o injected succesfully in ET_EXEC a.out
~redir puts puts_troj
[*] Function puts redirected to addr 0x080B7026 <puts_troj>
~redir legit_func hook_func
[*] Function legit_func redirected to addr 0x080B7000 <hook_func>
~save fake_aout
[*] Object fake_aout saved successfully
~quit
[*] Unloading object 1 (libpthreadnonshared/pthread_atfork.oS)
[*] Unloading object 2 (libpthread/ptcleanup.o)
[*] Unloading object 3 (libpthread/pthread_atfork.o)
[*] Unloading object 4 (libpthread/old_pthread_atfork.o)
[[... 1416 files later ...]]
.:: Bye -:: The ELF shell 0.65
========= END DUMP 40 =========
Does it works ?
========= BEGIN DUMP 41 =========
elfsh@WTH $ ./fake_aout
Trojan injected ET_REL takes control now [Z:First_puts:42:43:44:45:1]
hostname : WTH
printf called from puts_troj [First_puts]
-=-=-=-=-=- BEGIN /etc/services 3 -=-=-=-=-=
host : # /etc/services
#
# Network services, Internet style
#
# Not
-=-=-=-=-=- END /etc/services 3 -=-=-=-=-=
mmap succeed fd : 3
First_puts
Trojan injected ET_REL takes control now [Z:Second_puts:42:43:44:45:1]
hostname : WTH
printf called from puts_troj [Second_puts]
-=-=-=-=-=- BEGIN /etc/services 3 -=-=-=-=-=
host : # /etc/services
#
# Network services, Internet style
#
# Not
-=-=-=-=-=- END /etc/services 3 -=-=-=-=-=
mmap succeed fd : 3
Second_puts
hook func test !
Trojan injected ET_REL takes control now [Z:legit func !:42:43:44:45:1]
hostname : WTH
printf called from puts_troj [legit func !]
-=-=-=-=-=- BEGIN /etc/services 3 -=-=-=-=-=
host : # /etc/services
#
# Network services, Internet style
#
# Not
-=-=-=-=-=- END /etc/services 3 -=-=-=-=-=
mmap succeed fd : 3
legit func !
========= END DUMP 41 =========
Yes, It's working. Now have a look at the fake_aout static
file :
========= BEGIN DUMP 42 =========
elfsh@WTH $ ../../../vm/elfsh -f ./fake_aout -s
[*] Object ./fake_aout has been loaded (O_RDONLY)
[SECTION HEADER TABLE .::. SHT is not stripped]
[Object ./fake_aout]
[000] 0x00000000 ------- foff:000000 sz:00000
[001] 0x080480D4 a------ .note.ABI-tag foff:069844 sz:00032
[002] 0x08048100 a-x---- .init foff:069888 sz:00023
[003] 0x08048120 a-x---- .text foff:69920 sz:347364
[004] 0x0809CE10 a-x---- __libc_freeres_fn foff:417296 sz:02222
[005] 0x0809D6C0 a-x---- .fini foff:419520 sz:00029
[006] 0x0809D6E0 a------ .rodata foff:419552 sz:88238
[007] 0x080B2F90 a------ __libc_atexit foff:507792 sz:00004
[008] 0x080B2F94 a------ __libc_subfreeres foff:507796 sz:00036
[009] 0x080B2FB8 a------ .eh_frame foff:507832 sz:03556
[010] 0x080B4000 aw----- .ctors foff:512000 sz:00012
[011] 0x080B400C aw----- .dtors foff:512012 sz:00012
[012] 0x080B4018 aw----- .jcr foff:512024 sz:00004
[013] 0x080B401C aw----- .data.rel.ro foff:512028 sz:00044
[014] 0x080B4048 aw----- .got foff:512072 sz:00004
[015] 0x080B404C aw----- .got.plt foff:512076 sz:00012
[016] 0x080B4060 aw----- .data foff:512096 sz:03284
[017] 0x080B4D40 aw----- .bss foff:515380 sz:04736
[018] 0x080B5FC0 aw----- __libc_freeres_ptrs foff:520116 sz:00024
[019] 0x080B6000 aw----- rel2.o.bss foff:520192 sz:04096
[020] 0x080B7000 a-x---- rel2.o.text foff:524288 sz:04096
[021] 0x080B8000 aw----- rel2.o.data foff:528384 sz:00004
[022] 0x080B9000 a------ rel2.o.rodata foff:532480 sz:04096
[023] 0x080BA000 a-x---- .elfsh.hooks foff:536576 sz:00032
[024] 0x080BB000 aw----- libc/printf.o.bss foff:540672 sz:04096
[025] 0x080BC000 a-x---- libc/printf.o.text foff:544768 sz:04096
[026] 0x080BD000 aw----- libc/gethostname.o.bss foff:548864 sz:04096
[027] 0x080BE000 a-x---- libc/gethostname.o.text foff:552960 sz:04096
[028] 0x080BF000 aw----- libc/perror.o.bss foff:557056 sz:04096
[029] 0x080C0000 a-x---- libc/perror.o.text foff:561152 sz:04096
[030] 0x080C1000 a--ms-- libc/perror.o.rodata.str1.1 foff:565248 sz:04096
[031] 0x080C2000 a--ms-- libc/perror.o.rodata.str4.4 foff:569344 sz:04096
[032] 0x080C3000 aw----- libc/dup.o.bss foff:573440 sz:04096
[033] 0x080C4000 a-x---- libc/dup.o.text foff:577536 sz:04096
[034] 0x080C5000 aw----- libc/iofdopen.o.bss foff:581632 sz:04096
[035] 0x00000000 ------- .comment foff:585680 sz:20400
[036] 0x080C6000 a-x---- libc/iofdopen.o.text foff:585728 sz:04096
[037] 0x00000000 ------- .debug_aranges foff:606084 sz:00136
[038] 0x00000000 ------- .debug_pubnames foff:606220 sz:00042
[039] 0x00000000 ------- .debug_info foff:606262 sz:01600
[040] 0x00000000 ------- .debug_abbrev foff:607862 sz:00298
[041] 0x00000000 ------- .debug_line foff:608160 sz:00965
[042] 0x00000000 ------- .debug_frame foff:609128 sz:00068
[043] 0x00000000 ------- .debug_str foff:609196 sz:00022
[044] 0x00000000 ------- .debug_macinfo foff:609218 sz:28414
[045] 0x00000000 ------- .shstrtab foff:637632 sz:00632
[046] 0x00000000 ------- .symtab foff:640187 sz:30192
[047] 0x00000000 ------- .strtab foff:670379 sz:25442
[*] Object ./fake_aout unloaded
elfsh@WTH $ ../../../vm/elfsh -f ./fake_aout -p
[*] Object ./fake_aout has been loaded (O_RDONLY)
[Program Header Table .::. PHT]
[Object ./fake_aout]
[00] 0x8037000 -> 0x80B3D9C r-x memsz(511388) foff(000000) =>Loadable seg
[01] 0x80B4000 -> 0x80B7258 rw- memsz(012888) foff(512000) =>Loadable seg
[02] 0x80480D4 -> 0x80480F4 r-- memsz(000032) foff(069844) =>Aux. info.
[03] 0x0000000 -> 0x0000000 rw- memsz(000000) foff(000000) =>Stackflags
[04] 0x0000000 -> 0x0000000 --- memsz(000000) foff(000000) =>New PaXflags
[05] 0x80B6000 -> 0x80B7000 rwx memsz(004096) foff(520192) =>Loadable seg
[06] 0x80B7000 -> 0x80B8000 rwx memsz(004096) foff(524288) =>Loadable seg
[07] 0x80B8000 -> 0x80B8004 rwx memsz(000004) foff(528384) =>Loadable seg
[08] 0x80B9000 -> 0x80BA000 rwx memsz(004096) foff(532480) =>Loadable seg
[09] 0x80BA000 -> 0x80BB000 rwx memsz(004096) foff(536576) =>Loadable seg
[10] 0x80BB000 -> 0x80BC000 rwx memsz(004096) foff(540672) =>Loadable seg
[11] 0x80BC000 -> 0x80BD000 rwx memsz(004096) foff(544768) =>Loadable seg
[12] 0x80BD000 -> 0x80BE000 rwx memsz(004096) foff(548864) =>Loadable seg
[13] 0x80BE000 -> 0x80BF000 rwx memsz(004096) foff(552960) =>Loadable seg
[14] 0x80BF000 -> 0x80C0000 rwx memsz(004096) foff(557056) =>Loadable seg
[15] 0x80C0000 -> 0x80C1000 rwx memsz(004096) foff(561152) =>Loadable seg
[16] 0x80C1000 -> 0x80C2000 rwx memsz(004096) foff(565248) =>Loadable seg
[17] 0x80C2000 -> 0x80C3000 rwx memsz(004096) foff(569344) =>Loadable seg
[18] 0x80C3000 -> 0x80C4000 rwx memsz(004096) foff(573440) =>Loadable seg
[19] 0x80C4000 -> 0x80C5000 rwx memsz(004096) foff(577536) =>Loadable seg
[20] 0x80C5000 -> 0x80C6000 rwx memsz(004096) foff(581632) =>Loadable seg
[21] 0x80C6000 -> 0x80C7000 rwx memsz(004096) foff(585728) =>Loadable seg
[SHT correlation]
[Object ./fake_aout]
[*] SHT is not stripped
[00] PT_LOAD .note.ABI-tag .init .text __libc_freeres_fn .fini
.rodata __libc_atexit __libc_subfreeres .eh_frame
[01] PT_LOAD .ctors .dtors .jcr .data.rel.ro .got .got.plt
.data
.bss __libc_freeres_ptrs
[02] PT_NOTE .note.ABI-tag
[03] PT_GNU_STACK
[04] PT_PAX_FLAGS
[05] PT_LOAD rel2.o.bss
[06] PT_LOAD rel2.o.text
[07] PT_LOAD rel2.o.data
[08] PT_LOAD rel2.o.rodata
[09] PT_LOAD .elfsh.hooks
[10] PT_LOAD libc/printf.o.bss
[11] PT_LOAD libc/printf.o.text
[12] PT_LOAD libc/gethostname.o.bss
[13] PT_LOAD libc/gethostname.o.text
[14] PT_LOAD libc/perror.o.bss
[15] PT_LOAD libc/perror.o.text
[16] PT_LOAD libc/perror.o.rodata.str1.1
[17] PT_LOAD libc/perror.o.rodata.str4.4
[18] PT_LOAD libc/dup.o.bss
[19] PT_LOAD libc/dup.o.text
[20] PT_LOAD libc/iofdopen.o.bss |.comment
[21] PT_LOAD libc/iofdopen.o.text
[*] Object ./fake_aout unloaded
========= END DUMP 42 =========
We can notice the ET_REL really injected : printf.o@libc,
dup.o@libc, gethostname.o@libc, perror.o@libc and
iofdopen.o@libc.
Each injected file create several PT_LOAD segments. For this
example it is okay, but for injecting E2dbg that is really too
much.
This technique will be improved as soon as possible by reusing
PT_LOAD entry when this is possible.
----[ D. Architecture independant algorithms
In this part, we give all the architecture independent algorithms
that were developed for the new residency techniques in memory,
ET_DYN libraries, or static executables.
The new generic ET_REL injection algorithm is not that different
from the one presented in the first Cerberus Interface article [0],
that is why we only give it again in its short form. However, the
new algorithm has improved in modularity and portability. We will
detail some parts of the algorithm that were not explained in
previous articles. The implementation mainly takes place in
elfsh_inject_etrel() in the relinject.c file :
New generic relocation algorithm
+--------------------------------+
1/ Inject ET_REL BSS after the HOST BSS in a dedicated section (new)
2/ FOREACH section in ET_REL object
[
IF [ Section is allocatable and Section is not BSS ]
[
- Inject section in Host file or memory
]
]
3/ Fuze ET_REL and host file symbol tables
4/ Relocate the ET_REL object (STAGE 1)
5/ At save time, relocate the ET_REL object
(STAGE 2 for old symbols relocations)
We only had one relocation stage in the past. We had to use another
one since not all requested symbols are available (like old symbols
gained from CFLOW redirections that may happen after the ET_REL
injection). For ondisk modifications, the second stage relocation
is done at save time.
Some steps in this algorithm are quite straightforward, such as
step 1 and step 3. They have been explained in the first Cerberus
article [0], however the BSS algorithm has changed for compatibility
with ET_DYN files and multiple ET_REL injections. Now the BSS is
injected just as other sections, instead of adding a complex BSS
zones algorithm for always keeping one bss in the program.
ET_DYN / ET_EXEC section injection algorithm
+--------------------------------------------+
Injection algorithm for DATA sections does not change between ET_EXEC
and ET_DYN files. However, code sections injection slighly changed
for supporting both binaries and libraries host files. Here is the
new algorithm for this operation :
* Find executable PT_LOAD
* Fix injected section size for page size congruence
IF [ Hostfile is ET_EXEC ]
[
* Set injected section vaddr to lowest mapped section vaddr
* Substract new section size to new section virtual address
]
ELSE IF [ Hostfile is ET_DYN ]
[
* Set injected section vaddr to lowest mapped section vaddr
]
* Extend code segment size by newly injected section size
IF [ Hostfile is ET_EXEC ]
[
* Substract injected section vaddr to executable PT_LOAD vaddr
]
FOREACH [ Entry in PHT ]
[
IF [ Segment is PT_PHDR and Hostfile is ET_EXEC ]
[
* Substract injected section size to segment p_vaddr / p_paddr
]
ELSE IF [ Segment stands after extended PT_LOAD ]
[
* Add injected section size to segment p_offset
IF [ Hostfile is ET_DYN ]
[
* Add injected section size to segment p_vaddr and p_paddr
]
]
]
IF [ Hostfile is ET_DYN ]
[
FOREACH [ Relocation entry in every relocation table ]
[
IF [ Relocation offset points after injected section ]
[
* Shift relocation offset from injected section size
]
]
* Shift symbols from injected section size when pointing after it
* Shift dynamic syms from injected section size (same condition)
* Shift dynamic entries D_PTR's from injected section size
* Shift GOT entries from injected section size
* If existing, Shift ALTGOT entries from injected section size
* Shift DTORS and CTORS the same way
* Shift the entry point in ELF header the same way
]
* Inject new SECTION symbol on injected code
Static ET_EXEC section injection algorithm
+------------------------------------------+
This algorithm is used to insert sections inside static binaries. It
can be found in libelfsh/inject.c in elfsh_insert_static_section() :
* Pad the injected section size to stay congruent to page size
* Create a new PT_LOAD program header whoose bounds match the
new section bounds.
* Insert new section using classical algorithm
* Insert new program header in PHT
Runtime section injection algorithm in memory
+---------------------------------------------+
This algorithm can be found in libelfsh/inject.c in the function
elfsh_insert_runtime_section() :
* Create a new PT_LOAD program header
* Insert SHT entry for new runtime section
(so we keep a static map up-to-date)
* Insert new section using the classical algorithm
* Insert new PT_LOAD in Runtime PHT table (RPHT) with same bounds
Runtime PHT is a new table that we introduced so that we can
separate segments regulary mapped by the dynamic linker (original
PHT segments) from runtime injected segments. This may lead to an
easier algorithm for binary reconstruction from its memory image
in the future.
We will detail now the core (high level) relocation algorithm as
implemented in elfsh_relocate_object() and
elfsh_relocate_etrel_section() functions in libelfsh/relinject.c .
This code is common for all types of host files and for all
relocation stages. It is used at STEP 4 of the general algorithm:
Core portable relocation algorithm
+----------------------------------+
This algorithm has never been explained in any paper. Here it is :
FOREACH Injected ET_REL sections inside the host file
[
FOREACH relocation entry in ET_REL file
[
* Find needed symbol in ET_REL for this relocation
IF [ Symbol is COMMON or NOTYPE ]
[
* Find the corresponding symbol in Host file.
IF [ Symbol is NOT FOUND ]
[
IF [ symbol is OLD and RELOCSTAGE == 1 ]
[
* Delay relocation for it
]
ELSE
[
IF [ ET_REL symbol type is NOTYPE ]
[
* Request a new PLT entry and use its address
for performing relocation (EXTPLT algorithm)
]
ELSE IF [ Host file is STATIC ]
[
* Perform EXTSTATIC technique (next algorithm)
]
ELSE
[
* Algorithm failed, return ERROR
]
]
]
ELSE
[
* Use host file's symbol value
]
]
ELSE
[
* Use injected section base address as symbol value
]
- Relocate entry (switch/case architecture dependant handler)
]
]
EXTSTATIC relocation extension algorithm
+----------------------------------------+
In case the host file is a static file, we can try to get the
unknown symbol from relocatables files from static libraries that
are available on disk. An example of use of this EXTSTATIC technique
is located in the testsuite/etrel_inject/ directory.
Here is the EXTSTATIC algorithm that comes at the specified place
in the previous algorithm for providing the same functionality as
EXTPLT but for static binaries :
FOREACH loaded ET_REL objects in ELFSH
[
IF [ Symbol is found anywhere in current analyzed ET_REL ]
[
IF [ Found symbol is strongest than current result ]
[
* Update best symbol result and associated ET_REL file
]
ELSE
[
* Discard current iteration result
]
]
]
* Inject the ET_REL dependency inside Host file
* Use newly injected symbol in hostfile as relocation symbol in core
relocation algorithm.
Strongest symbol algorithm
+--------------------------+
When we have to choose between multiple symbols that have the same
name in different objects (either during static or runtime
injection), we use this simple algorithm to determine which one
to use :
IF [ Current chosen symbol has STT_NOTYPE ]
[
* Symbol becomes temporary choice
]
ELSE IF [ Candidate symbol has STT_NOTYPE ]
[
* Symbol becomes temporary choice
]
ELSE IF [ Candidate symbol binding > Chosen symbol binding ]
[
* Candidate symbol becomes Chosen symbol
]
-------[ VI. Past and present
In the past we have shown that ET_REL injection into
non-relocatable ET_EXEC object is possible. This paper presented
multiple extensions and ports to this residency technique
(ET_DYN and static executables target). Coupled to the EXTPLT
technique that allow for a complete post-linking of the host
file, we can add function definitions and use unknown functions
in the software extension. All those static injection
techniques worse when all PaX options are enabled on the
modified binary. Of course, the position independant and stack
smashing protection features of hardened Gentoo does not protect
anything when it comes to binary manipulation, either performed
on disk or at runtime.
We have also shown that it is possible to debug without using
the ptrace system call, which open the door for new reverse
engineering and embedded debugging methodology that bypass known
anti-debugging techniques. The embedded debugger is not
completely PaX proof and it is still necessary to disable the
mprotect flag. Even if it does not sound like a real problem,
we are still investigating on how to put breakpoints (e.g.
redirections) without disabling it.
Our core techniques are portable to many architectures (x86,
alpha, mips, sparc) on both 32bits and 64bits files. However
our proof of concept debugger was done for x86 only. We believe
that our techniques are portable enough to be able to provide
the debugger for other architectures without much troubles.
Share and enjoy the framework, contributions are welcome.
-------[ VII. Greetings
We thank all the peoples at the WhatTheHack party 2005 in
Netherlands. We add much fun with you guys and again we will
come in the future.
Special thanks go to andrewg for teaching us the sigaction
technique, dvorak for his interest in the optimization on the
the ALTPLT technique version 2 for the SPARC architecture,
sk for libasm, and solar for providing us the ET_DYN pie/ssp
testsuite.
Respects go to Devhell Labs, the PaX team, Phrackstaff, GOBBLES,
MMHS, ADM, and Synnergy Networks. Final shoutouts to s/ash from
RTC for driving us to WTH and the Coconut Crew for everything
and the rest, you know who you are.
-------[ VIII. References
[0] The Cerberus ELF Interface mayhem
http://www.phrack.org/show.php?p=61&a=8
[1] The GNU debugger GNU project
http://www.gnu.org/software/gdb/
[2] PaX / grsecurity The PaX team
http://pax.grsecurity.net/
[3] binary reconstruction from a core image Silvio Cesare
http://vx.netlux.org/lib/vsc03.html
[4] Antiforensic evolution: Self Ripe & Pluf
http://www.phrack.org/show.php?p=63&a=11
[5] Next-Gen. Runtime binary encryption Zeljko Vbra
http://www.phrack.org/show.php?p=63&a=13
[6] Fenris Michal Zalewski
http://lcamtuf.coredump.cx/fenris/
[7] Ltrace Ltrace team
http://freshmeat.net/projects/ltrace/
[8] The dude (replacement to ptrace) Mammon
http://www.eccentrix.com/members/mammon/Text/d\
ude_paper.txt
[9] Binary protection schemes Andrewg
http://www.codebreakers-journal.com/viewar\
ticle.php?id=51&layout=abstract
[10] ET_REL injection in memory JP
http://www.whatever.org.ar/~cuco/MERCANO.TXT
[11] Hardened Gentoo project Hardened team
http://www.gentoo.org/proj/en/hardened/
[12] Unpacking by Code Injection Eduardo Labir
http://www.codebreakers-journal.com/viewart\
icle.php?id=36&layout=abstract