Wednesday, November 16, 2005

Adding Back Doors to the Standard C Library

Adding Back Doors to the Standard C Library

Hacked by chrootstrap December 2003

(GNU Free Documentation License)

In computer terms, a library is an archive of reusable functions, data, and types. When a program uses parts of a library, the library is said to be statically linked when the library's parts are copied into the program and dynamically linked when the parts are loaded when the program are running. Libraries which support dynamic linking are said to be shared libraries because their parts may be used by many different programs, even at the same time. Only one copy of the parts needs to be on the system and any updates to a part apply to all programs using the part. Because of these advantages, shared libraries are very popular on many operating systems including Linux.

Normally when a shared library is updated or changed, it has to be rebuilt from all of the original parts and the new library simply replaces the old library. It is possible, however, to modify the library without the original objects. This is usually done with the BFD library, which allows the internal structures of programs and libraries to be operated in a fairly high level way. However, BFD does not allow for the alteration of a library in place; it is necessary to create a new library from the previous one while, perhaps, changing some of its properties. The code to this (viz. object_copy in objcopy.c of binutils) is fairly laborious and heavy handed.

I will explain the use of a somewhat different technique in order to modify the operations of an existing library, the standard C library, in place without changing the file size or internal structure. The end result will be used to make a 'back door' in the open function that will allow us to handle any file name that starts with "http://" by return the file handle of a socket ready for the reading of the file requested by HTTP. Any program that uses the shared library and calls open will have this functionality added to it.

Our example will be for x86 ISA Linux. The code is easily adapted to other architectures (assuming a familiarity with elementary assembly language) and the ideas can be generally applied to systems with ELF shared libraries. To begin, I write the specialized handler for the open function with typical Posix library calls. Please study this version first to get a quick understanding of how the operation is accomplished:

openbackdoor_library.c

However, within the C standard library we should not be loading other libraries and can make things simpler by foregoing the use of standard library calls as well. Furthermore, we are not going to insert from any other section that .text and therefore may not overly use string constants or global variables. In order to meet these criteria we must rewrite the function using system calls and restricted techniques. The most complex rewrite has to do with providing basic gethostbyname functionality. Here is the version that is completely independent of position and other functions:

openbackdoor_systemcalls.c

You'll notice that at the very end of our function we handle the case of a pathname that doesn't start with "http://". We simply use the open system call.

Now we are ready to compile the function and prepare it for insertion into the library. This done simply by entering:

gcc -c openbackdoor_systemcalls.c

Now we need to extract our code into the raw function data:

objcopy -O binary openbackdoor_systemcalls.o

openbackdoor_systemcalls.o is now purely the function's contents. All ELF structures and additional sections have been stripped away. It is ready to be introduced into the standard library. It is time now to prepare the library for insertion. We will test our changes on a disposable copy of the library. Begin by making an empty directory, e.g. one called bdoor. Inside the new directory make two subdirectories, one named lib and one named etc. Into etc copy resolv.conf from /etc/resolv.conf. Now make a small test program named test.c. The test program should have these contents:

#include 
#include

int main (int argc, char **argv)
{
int fd;
char buf [1024];

if ((fd = open("test.c", O_RDONLY, 0)) < fd =" open(">

Compile this program ordinarily ("gcc test.c"). Into the subdirectory, lib, copy two files, ld-linux.so.2 and libc.so.6. These two libraries will be somewhere on your system already, probably in /lib. libc.so.6 is the standard C library and ld-linux.so.2 is the dynamic linker which will our test program will need to load the standard library. Right above the bdoor directory you can switch to using this library copy with your test program by chrooting (you will need to be root to this):

chroot bdoor /a.out

If you do this before patching the standard library, it should simply print out the source code for test.c Now, we need to discover some details about the standard library. We need to learn the location of the current open function, the location of the vfwprintf function, the location of the dynsym symbol, and the location of the open symbol. The vfwprintf function is used because we are going to commandeer its location as it's a bloated function that is never called by anything on nearly every system. Start by getting a print out of the dynamic symbol table of libc.so.6 by doing this:

objdump -T libc.so.6 > symboldump

Open symboldump and remove the first four lines to reach the point where the SYMBOL TABLE listings begin. Search for dynsym. Record the leftmost number; it is the location of what this symbol points to (the dynamic symbols), probably between 0x3000 and 0x4000. Now search for open . You will find many instances of open, but you need the one that is only "open" and doesn't have any other text in its identifier. Now record the location (leftmost entry) along line number at which this symbol occurs in the symboldump file. The line number will be used to calculate where it is in the dynsym listing. Finally, find and record the location of vfwprintf. The size is listed after what section is in (.text). Your inserted function can only be up to this size. For example, here are the values in my library:

dynsym symbol location = 0x339C
open function location = 0xD5A20
open line number = 1997
vfwprintf function location = 0x51E10

Now we need to calculate the location of the open symbol. This is (open_line_number * 0x10 + dynsym_symbol_location). In my example it is 0xb06c. Finally, you will need the size of your new function, which is its file size after objcopy -O binary. In my case the new size is 4631. Now write an inserter program using your values in the define statements:

#include 
#include

#define OPEN_SYM 0xb06c - 128
#define OPEN_FUNC 0xd5a20
#define NEW_OPEN_SIZE 4631
#define VFWPRINTF_FUNC 0x51e10

int main ()
{
int x, fd;
char buf [2048];
char c = 0xe9;

fd = open("libc.so.6", O_RDWR, 0);
lseek(fd, OPEN_FUNC, SEEK_SET);
write(fd, &c, 1);
x = VFWPRINTF_FUNC - OPEN_FUNC + 4;
write(fd, &x, 4);
lseek(fd, OPEN_SYM, SEEK_SET);
x = OPEN_FUNC;
x = (int)memmem(buf, read(fd, buf, 2048), &x, 4) - (int)buf;
lseek(fd, OPEN_SYM + x, SEEK_SET);
x = VFWPRINTF_FUNC;
write(fd, &x, 4);
x = NEW_OPEN_SIZE;
write(fd, &x, 4);
lseek(fd, VFWPRINTF_FUNC, SEEK_SET);
while((x = fread(buf, 1, 2048, stdin)) > 0) {
write(fd, buf, x);
}
close(fd);
return 0;
}

What we have done is changed the description of the v symbol's contents. They describe the location and size of our code which we copy into the vfwprintf location. Calculating the location of the open symbol from the line number of the print out gives a result that varies a few bytes more or less from the actual location. This is why we back track a little from the estimate and then search to find the precise location. We also change the contents of the existing open function to simply jump to our back door. This way any standard library function which uses the open function (and has already had its address hard-coded into it) will be redirected to our back door. Cool beans!

Now compile it and run it in the lib directory as:

./a.out <>

Now try your test program again through chroot and ensure that everything works correctly. If everything went correctly, you should now have a back door in the open function that handles pathnames that start with "http://". You can put several functions in vfwprintf, ensuring correct offsets each time. If you want to be nice, you can add a small bit of code to return -1 at the start of vfwprintf and put your functions after that. If you're brave, copy your new library to where the original was, backing up the original first, and give it a try. Now all your binaries that use the shared open function can easily work with HTTP addresses. Happy hacking!

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.