|
==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. Altenate ondisk and memory ELF scripting (feat. linkmap) c. Real debugging : stepping and backtracing d. Dynamic analyzers generation III. Better multiarchitecture ELF redirections a. CFLOW: PaX-safe static functions redirection b. ALTPLT technique revisited 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 VI. Past and present VII. Greetings VIII. References [NOTE of ELFsh crew] [This article is in beta version. The final release is for this week] -------[ I. Hardened software debugging introduction In the past, binary manipulation work has focussed on virii writing, backdoors deployment, or creation of tiny or obfuscated executables. Besides the tools from the GNU project such as the GNU debugger [1] (which focuss more on portability than functionalities), no major binary manipulation framework does exist. For the last ten years, the ELF format has been a succes and most UNIX Operating System and distributions rely on it. However, the existing tools do not take advantage of the format and most of the reverse engineering related software 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 on advances on ELF techniques. We will explain in great details the 8 new reverse engineering functionalities that cut with the existing methodology of binary manipulation based reverse engineering. Those techniques allow for a new type of approach on debugging and extending closed source software. We worked on many architectures (x86, alpha, sparc, mips) and focussed on hardened environment where binaries are linked with security protections enabled (such as hardened gentoo binaries) in PaX [2] protected machines. ----[ 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 inject 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 we claim portability on those as well. A major innovation of our 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 process manipulations such as dumping memory, putting breakpoing, doing step by step analysis, and so on. We propose the same features without using the system call and we added new major contributions to that common interface. 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 or phone systems whoose Operating System is ELF based but without a ptrace interface. The second major reason of 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 stage of a penetration test when debugging sensible 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 possible, we only use ELF techniques that reduce forensics evidence 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, wheter 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 though many rewrites and now allow a complete architecture independant implementation. We completed ALTPLT by a new technique called ALTGOT so that hijacking function and calling back the original one 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) 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, but you may encounted a problem. In that case drop us an email so that we can go further and you can get 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. However the debugger 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. Then you can start a debugging session using the reconstructed binary. We do not provide binary reconstruction facilities but other related work suggest that it is really usable. The central communication language in the Embedded ELF Debugger (e2dbg) framework is our scripting facilities. We augmented the scripting capabilities with loop, transparent support for lazy typed variables (like in bash or perl). The source command and user defined macros are also supported. We also developed a peer2peer stack so called Distributed Updates Management Protocol - DUMP - that allow for linking multiple debugger instances using the network. For completeness, we also now support multiuser (parallel or shared) sessions, and workspaces support. We will go through the use of such interface in the first part of the paper, besides the use of all the new techniques. 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. -------[ 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, which are : (libelfsh/section.c) /* Fill an anonymous (unknown content) section */ void *elfsh_get_anonymous_section(elfshobj_t *file, elfshsect_t *sect) { ELFSH_PROFILE_IN(__FILE__, __FUNCTION__, __LINE__); /* Bad parameter checking */ if (file == NULL || sect == NULL) ELFSH_PROFILE_ERR(__FILE__, __FUNCTION__, __LINE__, "Invalid NULL parameter", NULL); /* If the section is already loaded */ if (sect->data != NULL) ELFSH_PROFILE_ROUT(__FILE__, __FUNCTION__, __LINE__, (elfsh_get_raw(sect))); sect->data = elfsh_load_section(file, sect->shdr); ELFSH_PROFILE_ROUT(__FILE__, __FUNCTION__, __LINE__, (elfsh_get_raw(sect))); } What is the technique about ? It is quite simple : if the debugger internal flag is set to static mode (ondisk modification), then we return the pointer on the data cache for the section we want to modify. 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. <2 outputs : by ld_preload, by static injection> ---[ B. Altenate ondisk and memory ELF scripting (feat. linkmap) Let's see how to use the embedded debugger and its mode command that does the memory/disk selection. We there print the Global Offset Table (.got). First the memory got is displayed, then we get back in static mode and the ondisk GOT is displayed: ========= BEGIN EXAMPLE 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 [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 .::. 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) 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 EXAMPLE 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 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. Section .got handles the pointer for external variables, while .got.plt handles the external function pointers. In earlier versions of LD, those 2 sections were merged. 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 external function without losing the real function address. We will develop both of these techniques in the next parts. ---[ C. Real debugging : backtrace, breakpoints, and step When performing debugging using a debugger embedded in the debuggee process, we are not using 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 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 (ondisk 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 (which is incompatible with the mprotect option of PaX). 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. ========= BEGIN EXAMPLE 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 EXAMPLE 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 : ========= BEGIN EXAMPLE 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 EXAMPLE 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 available for x86 and MIPS architecture : ========= BEGIN EXAMPLE 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 EXAMPLE 4 ========= Let's now execute the debuggee program, in which the debugger was injected. ========= BEGIN EXAMPLE 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/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> [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 EXAMPLE 5 ========= As you see, the use of the debugger is quite similar to other debugger. 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 mentioning. ---[ 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]. 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, we presented a redirection technique that we called ALTPLT. This technique is not enough since it allows only for PLT redirection. 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. Let's see the host file that we use for this test: ========= BEGIN EXAMPLE 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 EXAMPLE 6 ========= Let's look at the relocatable file that we are going to inject in the above binary. ========= BEGIN EXAMPLE 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 EXAMPLE 7 ========= As you can see, the relocatable object use of unknown functions like write and putchar. Those functions do not have an 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 let's focuss on the CFLOW technique that allow for redirection of the legit_func by 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. Let's see more: ========= BEGIN EXAMPLE 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 result of the ORIGINAL binary is as follow: ========= BEGIN EXAMPLE 9 ========= elfsh@WTH $ ./a.out First_printf test First_puts Second_printf test Second_puts LEGIT FUNC legit func (test) ! ========= END EXAMPLE 9 =========== Now let's inject the stuff: ========= BEGIN EXAMPLE 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 EXAMPLE 10 ========= Let's now execute the modified binary. ========= BEGIN EXAMPLE 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 EXAMPLE 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. ========= BEGIN EXAMPLE 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 EXAMPLE 12 ========= Now the modified code: ========= BEGIN EXAMPLE 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 EXAMPLE 13 ========= We create a new section .elfsh.hooks whoose data is array of hook code stubs like this one: ========= BEGIN EXAMPLE 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 EXAMPLE 14 ========= The MIPS logs are on Devhell Labs machine which is down as we are at WTH and we will put them in the updated version of that article as soon as we get back. ---[ B. ALTPLT technique revisited ALTPLT technique v1 was presented in the Cerberus ELF Interface paper. As already stated, it was not satisfying and we finally found the .dynamic DT_PLTREL trick: =============== BEGIN EXAMPLE 16 ================ [SECTION HEADER TABLE .::. SHT is not stripped] [Object fake_aout] [000] 0x00000000 ------- foff:00000000 sz:00000000 link:00 [001] 0x08042134 a-x---- .elfsh.hooks foff:00000308 sz:00000016 link:00 [002] 0x08043134 a-x---- .elfsh.extplt foff:00004404 sz:00000048 link:00 [003] 0x08044134 a-x---- .elfsh.altplt foff:00008500 sz:00004096 link:00 [004] 0x08045134 a--ms-- rel.o.rodata.str1.32 foff:12596 sz:04096 link:00 [005] 0x08046134 a--ms-- rel.o.rodata.str1.1 foff:16692 sz:04096 link:00 [006] 0x08047134 a-x---- rel.o.text foff:00020788 sz:00004096 link:00 [007] 0x08048134 a------ .interp foff:00024884 sz:00000019 link:00 [008] 0x08048148 a------ .note.ABI-tag foff:00024904 sz:00000032 link:00 [009] 0x08048168 a------ .hash foff:00024936 sz:00000064 link:10 [010] 0x080481A8 a------ .dynsym foff:00025000 sz:00000176 link:11 [011] 0x08048258 a------ .dynstr foff:00025176 sz:00000112 link:00 [012] 0x080482C8 a------ .gnu.version foff:00025288 sz:00000022 link:10 [013] 0x080482E0 a------ .gnu.version_r foff:00025312 sz:00000032 link:11 [014] 0x08048300 a------ .rel.dyn foff:00025344 sz:00000016 link:10 [015] 0x08048310 a------ .rel.plt foff:00025360 sz:00000056 link:10 [016] 0x08048348 a-x---- .init foff:00025416 sz:00000023 link:00 [017] 0x08048360 a-x---- .plt foff:00025440 sz:00000128 link:00 [018] 0x08048400 a-x---- .text foff:00025600 sz:00000736 link:00 [019] 0x080486E0 a-x---- .fini foff:00026336 sz:00000027 link:00 [020] 0x080486FC a------ .rodata foff:00026364 sz:00000116 link:00 [021] 0x08048770 a------ .eh_frame foff:00026480 sz:00000004 link:00 [022] 0x08049774 aw----- .ctors foff:00026484 sz:00000008 link:00 [023] 0x0804977C aw----- .dtors foff:00026492 sz:00000008 link:00 [024] 0x08049784 aw----- .jcr foff:00026500 sz:00000004 link:00 [025] 0x08049788 aw----- .dynamic foff:00026504 sz:00000200 link:11 [026] 0x08049850 aw----- .got foff:00026704 sz:00000004 link:00 [027] 0x08049854 aw----- .got.plt foff:00026708 sz:00000040 link:00 [028] 0x0804987C aw----- .data foff:00026748 sz:00000012 link:00 [029] 0x08049888 aw----- .bss foff:00026760 sz:00000008 link:00 [030] 0x08049890 aw----- rel.o.bss foff:00026768 sz:00004096 link:00 [031] 0x0804A890 aw----- rel.o.data foff:00030864 sz:00000004 link:00 [032] 0x0804A894 aw----- .elfsh.altgot foff:00030868 sz:00000048 link:00 [033] 0x0804A8E4 aw----- .elfsh.dynsym foff:00030948 sz:00000208 link:34 [034] 0x0804AA44 aw----- .elfsh.dynstr foff:00031300 sz:00000127 link:33 [035] 0x0804AB24 aw----- .elfsh.reldyn foff:00031524 sz:00000016 link:00 [036] 0x0804AB34 aw----- .elfsh.relplt foff:00031540 sz:00000072 link:00 [037] 0x00000000 ------- .comment foff:00031652 sz:00000665 link:00 [038] 0x00000000 ------- .debug_aranges foff:00032324 sz:00000120 link:00 [039] 0x00000000 ------- .debug_pubnames foff:00032444 sz:00000042 link:00 [040] 0x00000000 ------- .debug_info foff:00032486 sz:00006871 link:00 [041] 0x00000000 ------- .debug_abbrev foff:00039357 sz:00000511 link:00 [042] 0x00000000 ------- .debug_line foff:00039868 sz:00000961 link:00 [043] 0x00000000 ------- .debug_frame foff:00040832 sz:00000072 link:00 [044] 0x00000000 ---ms-- .debug_str foff:00040904 sz:00008067 link:00 [045] 0x00000000 ------- .debug_macinfo foff:00048971 sz:00029295 link:00 [046] 0x00000000 ------- .shstrtab foff:00078266 sz:00000507 link:00 [047] 0x00000000 ------- .symtab foff:00080736 sz:00002368 link:48 [048] 0x00000000 ------- .strtab foff:00083104 sz:00001785 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 => 0000072 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 => 0x080482E0 {DT_VERNEED} [17] SUN needed version number => 0001 {DT_VERNEEDNUM} [18] GNU version VERSYM => 0x080482C8 {DT_VERSYM} =============== END EXAMPLE 16 ================ 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. The specific ALTPLT technique allow still for function redirection with the capability of calling back the original function without erasing the hook as you call it. The output of that binary was already pasted and we wont copy it again here. ---[ C. ALTGOT technique : the RISC complement On ALPHA and 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. When the dynamic linker is called the GOT entries are filled and then the indirect jump instruction on MIPS and ALPHA do 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. Let's see how to developed the ALTGOT technique on the ALPHA architecture: ========= BEGIN EXAMPLE 17 ========= 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 EXAMPLE 17 ========= The original binary executes: ========= BEGIN EXAMPLE 18 ========= elfsh@alpha$ ./a.out First_printf test First_puts Second_printf 42 Second_puts ========= END EXAMPLE 18 ========== Let's look again the relocatable object we are injecting: ========= BEGIN EXAMPLE 19 ========= 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 EXAMPLE 19 ========= Now we inject the stuff: ========= BEGIN EXAMPLE 20 ========= elfsh@alpha$ ./etrel_injecter [*] ET_REL injected 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 EXAMPLE 20 ========== ========= BEGIN EXAMPLE 21 ========= 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 EXAMPLE 21 ========= Segments are extended the good way. Everything works well. ---[ 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 .got, .dynsym, and .dynstr mirroring extension, 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 the original binary: ========= BEGIN EXAMPLE 22 ========= [*] 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 EXAMPLE 22 ========= Let's now see the new relocation tables: ========= BEGIN EXAMPLE 23 ========= [*] 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 EXAMPLE 23 ========= As you see, _IO_putc 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. ---[ E. IA32, SPARC32/64, ALPHA64, MIPS32 compliant algorithms We did not have time to write the pseudo-algorithms for all these techniques. Since it is something we care about particulary, the article will be updated in the week with all pseudo-algorithms information as soon as we return from WTH. Stay tuned. -------[ 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 has 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 with the EXTPLT technique directly in memory. A last technique that we wanted to develop was about extending and debugging static executables. We developed a 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 (wether or not they are done using the 0xCC ia32 opcode or the more portable redirection function), 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. Let's look at some simple binary that does just use printf and puts. ========= BEGIN EXAMPLE 24 ========= 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 EXAMPLE 24 ========= 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 EXAMPLE 25 ========= 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 EXAMPLE 25 ========= We then execute the modified binary. ========= BEGIN EXAMPLE 26 ========= 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/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 EXAMPLE 26 ========= 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 debugging problems inherent to the debugger. ========= BEGIN EXAMPLE 27 ========= 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 EXAMPLE 27 ========= Really cool. We hijacked 2 functions (puts and legit_func) using the 2 different (ALTPLT and CFLOW) techniques. ---[ 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. Let's see what happens on this ET_DYN files: ========= BEGIN EXAMPLE 28 ========= 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 EXAMPLE 28 ========= 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. ========= BEGIN EXAMPLE 29 ========= 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 EXAMPLE 29 ========= The elfsh script that allow for the modification is : ========= BEGIN EXAMPLE 30 ========= 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 EXAMPLE 30 ========= Now let's see this in action ! ========= BEGIN EXAMPLE 31 ========= 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 EXAMPLE 31 ========= What about the result ? ========= BEGIN EXAMPLE 32 ========= 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 EXAMPLE 32 ========= No problem there : strcpy, main, libc_start_main and __stack_smash_handler are redirected on our own routines as the output shows. ---[ C. Extending static executables Now we would like to be able to debug static binary the same way we do for dynamics. But we can't inject e2dbg using DT_NEEDED dependances. So the idea is to inject e2dbg as an ET_REL as ET_REL into ET_EXEC is possible on static binaries. E2dbg as many more dependancy than a simple host.c program. So the idea is to inject the missing part of static library when it is necessary. So we have to resolve dependancy on-the-fly while ET_REL injection is performed. 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 binary. Circular dependancy are solved by using second stage relocation when the required symbol is in a file that is being injected. 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. 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 an smaller (working) example: ========= BEGIN EXAMPLE 33 ========= 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 EXAMPLE 33 ========= The injected file source code is as follow : ========= BEGIN EXAMPLE 34 ========= 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 EXAMPLE 34 ========= The load_lib.esh generated script looks like this : ========= BEGIN EXAMPLE 34 ========= 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 EXAMPLE 34 ========= Here is the injection ELFsh script: ========= BEGIN EXAMPLE 35 ========= 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 EXAMPLE 35 ========= Stripped output of the injection : ========= BEGIN EXAMPLE 36 ========= 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 EXAMPLE 36 ========= Does it works ? ========= BEGIN EXAMPLE 37 ========= 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 EXAMPLE 37 ========= Yes, It's working. Now have a look at the fake_aout file: ========= BEGIN EXAMPLE 38 ========= 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 EXAMPLE 38 ========= 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. Here it's ok but for injecting E2dbg that's really too much. This technique will be improved as soon as possible by reusing PT_LOAD entry when this is possible. -------[ 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. 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 problem, we are investigating on how to put breakpoints (e.g. redirections) without disabling it. Our ground 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 port the debugger without much troubles. We wrote that article at the WTH conference in Netherland. Due to Devhell Labs. machine crash during the meeting, we will publish the e2dbg source code tomorrow evening (August 1st 2005) when we get back at home and reboot the webs server machine. You can find it on : http://elfsh.devhell.org as well as on the official mirror http://elfsh.segfault.net. Share and enjoy. -------[ VII. Greetings We thank all the peoples at the WhatTheHack party 2005 in Netherland. 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 early interest in the improvement and optimization of the ALTPLT technique on the SPARC architecture, sk from Devhell Labs. for libasm, solar from the Hardened Gentoo project for providing us the ET_DYN pie/ssp testsuite, the PaX team for the awesome entertaining kernel protection patch, the phrack staff for delaying the Phrack #63 electronic release for us. 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 [1] The GNU debugger http://www.gnu.org/software/gdb/ [2] PaX http://pax.grsecurity.net/ [3] Silvio: binary reconstruction from a core image http://vx.netlux.org/lib/vsc03.html [4] Ripe & Pluf : Antiforensic evolution: Self http://www.phrack.org (Phrack #63) [5] Zeljko Vbra : Next-Gen. Runtime binary encryption using on-demand function extraction http://www.phrack.org (Phrack #63) [6] lcamtuf : fenris http://lcamtuf.coredump.cx/fenris/ [7] Ltrace team : ltrace http://freshmeat.net/projects/ltrace/ [8] mammon : The dude (replacement to ptrace) http://www.eccentrix.com/members/mammon/Text/dude_paper.txt [9] andrewg : Binary protection schemes http://felinemenace.org/papers/Binary_protection_schemes-1.00\ -prerelease.tar.gz [10] jp : ET_REL injection in memory Secret URL [11] Hardened Gentoo project http://www.gentoo.org/proj/en/hardened/