4

Example Using find.c Using find.c as an example, how would this manual source code auditing process work? We need to start with user data entering the program. As seen in the preceding ITS4 output, a recvfrom() function call accepts an incoming UDP packet. The code surrounding the call looks like this:

char buf[65536];   // buffer to receive incoming udp packet
int sock, pid;     // socket descriptor and process id
sockaddr_in fsin;  // internet socket address information

//...
// Code to take care of the socket setup
//...

while(1){ // loop forever
    unsinged int alen = sizeof(fsin);
    // now read the next incoming UPD packet
    if(recvfrom(sock, buf, sizeof(buf), 0, (struct sockaddr *)&fsin, &alen) < 0){
        // exit the program if an error occurred
        errexit("recvfrom: %s\n", strerror(errno));
    }

    pid = fork();       // fork a child to process the packet
    if(pid == 0){       // Then this must be the child
         manage_request(buf, sock, &fsin);  // child handles packet
         exit(0);           // child exits after packet is processed
    }
}

The preceding code shows a parent process looping to receive incoming UDP packets using the recvfrom() function. Following a successful recvfrom(), a child process is forked and the manage_request() function is called to process the received packet. We need to trace into manage_request() to see what happens with the user’s input. We can see right off the bat that none of the parameters passed in to manage_request() deals with the size of buf, which should make the hair on the back of our necks stand up. The manage_request() function starts out with a number of data declarations, as shown here:

void manage_request(char *buf, int sock, struct sockaddr_in* addr){

  char init_cwd[1024];
  char cmd[512];
  char outf[512];
  char replybuf[65536];
  char *user;
  char *password;
  char *filename;
  char *keyword;
  char *envstrings[16];
  char *id;
  char *field;
  char *p;
  int i;

Here, we see the declaration of many of the fixed-size buffers noted earlier by RATS. We know that the input parameter buf points to the incoming UDP packet, and the buffer may contain up to 65,535 bytes of data (the maximum size of a UDP packet). There are two interesting things to note here: First, the length of the packet is not passed into the function, so bounds checking will be difficult and perhaps completely dependent on well-formed packet content. Second, several of the local buffers are significantly smaller than 65,535 bytes, so the function had better be very careful how it copies information into those buffers. Earlier, it was mentioned that the buffer at line 172 is vulnerable to an overflow. That seems a little difficult given that there is a 64KB buffer sitting between it and the return address.

The function proceeds to set some of the pointers by parsing the incoming packet, which is expected to be formatted as follows:

id some_id_value\n
user some_user_name\n
password some_users_password\n
filename some_filename\n
keyword some_keyword\n
environ key=value key=value key=value ...\n

The pointers in the stack are set by locating the key name, searching for the following space, and incrementing by one character position. The values become null terminated when the trailing \n is located and replaced with \0. If the key names are not found in the order listed, or trailing \n characters fail to be found, the input is considered malformed and the function returns. Parsing the packet goes well until processing of the optional environ values begins. The environ field is processed by the following code (note, the pointer p at this point is positioned at the next character that needs parsing within the input buffer):

  envstrings[0] = NULL;   // assume no environment strings
  if(!strncmp("environ", p, strlen("environ"))){
      field = memchr(p, ' ', strlen(p));  // find trailing space
      if(field == NULL){  // error if no trailing space
          reply(id, "missing environment value", sock, addr);
          return;
      }
      field++;   // increment to first character of key
      i = 0;     // init our index counter into envstrings
      while(1){  // loop as long as we need to
          envstrings[i] = field;   // save the next envstring ptr
          p = memchr(field, ' ', strlen(field)); // trailing space
          if(p==NULL){  // if no space then we need a newline
              p = memchr(field, '\n', strlen(field)); 
              if (p==NULL){
                  reply(id, "malformed environment value", sock, addr);
                  return;
              }
              *p = '\0';    // found newline terminate last envstring
              i++;              // count the envstring
              break;            // newline marks the end so break
          }
          *p = '\0';      // terminate the envstring
          field = p + 1;  // point to start of next envstring 
          i++; // count the envstring

      }
      envstrings[i] = NULL;   // terminate the list           
  }

Following the processing of the environ field, each pointer in the envstrings array is passed to the putenv() function, so these strings are expected to be in the form key=value. In analyzing this code, note that the entire environ field is optional, but skipping it wouldn’t be any fun for us. The problem in the code results from the fact that the while loop that processes each new environment string fails to do any bounds checking on the counter i, but the declaration of envstrings only allocates space for 16 pointers. If more than 16 environment strings are provided, the variables below the envstrings array on the stack will start to get overwritten. We have the makings of a buffer overflow at this point, but the question becomes: “Can we reach the saved return address?” Performing some quick math tells us that there are about 67,600 bytes of stack space between the envstrings array and the saved frame pointer/saved return address. Because each member of the envstrings array occupies 4 bytes, if we add 67,600/4 = 16,900 additional environment strings to our input packet, the pointers to those strings will overwrite all of the stack space up to the saved frame pointer. Two additional environment strings will give us an overwrite of the frame pointer and the return address. How can we include 16,918 environment strings if the form key=value is in our packet? If a minimal environment string, say x=y, consumes 4 bytes counting the trailing space, then it would seem that our input packet needs to accommodate 67,672 bytes of environment strings alone. Because this is larger than the maximum UDP packet size, we seem to be out of luck. Fortunately for us, the preceding loop does no parsing of each environment string, so there is no reason for a malicious user to use properly formatted (key=value) strings. It is left to you to verify that placing approximately 16,919 space characters between the keyword environ and the trailing carriage return should result in an overwrite of the saved return address. Since an input line of that size easily fits in a UDP packet, all we need to do now is consider where to place our shellcode. The answer is to make it the last environment string, and the nice thing about this vulnerability is that we don’t even need to determine what value to overwrite the saved return address with, as the preceding code handles it for us. Understanding that point is also left to you as an exercise.

Above is the the exact extract from the book. I was trying to minimize it and I didn't have much time to post so yeah the code was incorrect. It's corrected now.

he code handles this because when the function memchr returns a pointer, that pointer is pushed on the stack and the address pushed on the stack will be the starting point of our shellcode due the space before our shellcode?

  • Did you copy that out exactly? Because as written it shouldn't loop long enough; the while loop has an unconditional break right at the end of its statement list, and there's no continue earlier in the loop. It should only find one environment variable. Otherwise, I can see how it'd be exploitable. – Parthian Shot Jul 20 '15 at 19:54
  • Actually, scratch that, I'm almost positive you copied it incorrectly; the lines which mention newline in the comments (with the break should probably be in the first if(p==NULL), and the "terminate the envstring" part should be in the braces for the while, and the "terminate the list" part should be in the if(!strcmp... block. i doesn't even get initialized. Please make sure you copied it correctly and, if that's really what it says, I'll help you with that. – Parthian Shot Jul 20 '15 at 20:03
1

I'm assuming that my comment about the code being copied incorrectly is accurate, and as such I'm giving the solution that would work if the code were moved around.

The value returned by memchr isn't pushed on the stack. All the variable declarations happen at the beginning of manage_request, so after that the stack frame for that function should be constant in size.

The thing to notice is that envstrings is an array of pointers, and that inner while loop is advancing the index into that array. However, as you've noticed, there are no checks to determine whether the index is out of bounds. Which means that, if you include more than 16 envstrings to decode, the pointers to subsequent strings will be written to locations deeper in the stack; first local variables, then the previous base pointer value, then the return pointer.

It's unlikely you'll ever get to the return pointer, because unfortunately you've got more local variables (which are never used, by the way. So... I suppose you omitted a lot of this function as well?) than you have UDP packet bytes. Here's the good news: those local variables you can modify (filename, user, password, replybuf) are the kinds of things that look pretty damned important (or, at least, they would be if they were used later). You could use filename to do a directory traversal, user and password to either simply crash the program, or try running it as another user, and replybuf to determine the location of the stack (if it's sending you a response with replybuf as contents, you can make sure replybuf contains the address of one of your envstrings. Some simple arithmetic later, you have the start of the local stack frame.

The beauty is that the loop handles memory addresses for you; it finds the memory locations for the beginning of space-delimited strings, and then writes those pointers into the local variables.

EDIT!

OH! Wait. Nevermind- that was flawed. You can overwrite the return pointer because the replybuf is a bunch of chars, not pointers or ints. Which means that, for each space character in the env part of the original packet, you write 4 bytes of data onto the stack (because you're writing the pointer to the data, and we're on a 32-bit machine). Which means that you only need 1 space for every 4 characters in replybuf to fill it entirely.

So, you need to 4 spaces to overwrite the keyword, filename, password, and user pointers, and then 65536 / 4 = 2 ** 16 / 2 ** 2 = 1 ** 14 = 16384 spaces to fill up replybuf, then 64 spaces each for cmd and outf, and and another 128 spaces for init_cwd.

Getting a tally of the number of spaces we need in our initial packet's environ section so far...

4 + 16384 + 64 + 64 + 128 = 16388 + 256 = 1644 spaces

Now, that's not quite it, yet. We also need to overwrite the saves %ebp from the previous frame (because the first instructions for a function will be pushl %ebp, movl %esp, %ebp), which is just another pointer (so, another space), bringing out total tally of padding to 1645 spaces.

And then, after all those spaces, you'd put your shellcode payload (ensuring it doesn't contain any spaces or newlines, hoping the compiler didn't do any weird optimizations, and hoping the binary wasn't compiled with support for stack protection / canaries).

  • I have adjusted my post so you can see the correct book extract. Sorry for the wrong wording not push on the stack yea that I can see is wrong. But its writing past the reserve stack frame as u mentioned and what is writing over the stack frame is pointers envstrings pointers so if we (as the book said) put our shellcode as the last envstrings we would overwrite the return address with the address of our shellcode right? – span1223 Jul 21 '15 at 1:30
  • @span1223 But its writing past the reserve stack frame as u mentioned and what is writing over the stack frame is pointers envstrings pointers so if we (as the book said) put our shellcode as the last envstrings we would overwrite the return address with the address of our shellcode right? That we would, with a bit of additional padding. :) – Parthian Shot Jul 21 '15 at 20:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.