Welcome back. Today we’re going to be looking at the IO wargame from Smash The Stack. Specifically, we will be looking at level 3. If you’re interested in following along, go a head and ssh into IO as level3 with the password you got from completing level2.
Once in, we should check the /levels directory as that is where the executables for this wargame reside. Upon doing this we can see two files, an executable called level03 and a file called level03.c. Let’s look at the C file and see if we can find something to exploit:
level3@io:~$ more /levels/level03.c
#include <stdio.h>
#include <unistd.h>
#include <string.h>int good(int addr) {
printf(“Address of hmm: %p\n”, addr);
}int hmm() {
printf(“Win.\n”);
execl(“/bin/sh”, “sh”, NULL);
}extern char **environ;
int main(int argc, char **argv) {
int i, limit;
for(i = 0; environ[i] != NULL; i++)
memset(environ[i], 0x00, strlen(environ[i]));int (*fptr)(int) = good;
char buf[32];if(strlen(argv[1]) <= 40) limit = strlen(argv[1]);
for(i = 0; i <= limit; i++) {
buf[i] = argv[1][i];
if(i < 36) buf[i] = 0x41;
}int (*hmmptr)(int) = hmm;
(*fptr)((int)hmmptr);
return 0;
}
Looking at the code, we can see there is a character buffer that gets filled with input from the first command line argument. This seems like a good place to start looking. Further analyzing the code shows that if we want to fill this buffer, we’re going to be limited in the number of bytes we can write by the variable “limit”, which controls the buffer writing for-loop. Looking one line up, we can see the only place where the limit variable is set. Here, it is set to the length of the first command line argument only if the length of the first command line argument is less than or equal to 40 characters.
So far, his seems promising. We can see there is a 32 bit buffer and we can control the program to write 40 bytes to it. This means that the program is vulnerable to a buffer overflow. However, only being able to overflow it by 8 bytes is a restriction in that it won’t be enough to overwrite the return address on the stack. Now would be a good time to try and figure out what the stack looks like, so we know what we can overwrite with this buffer overflow, and how it might effect the program.
Looking at the order in which variables are defined in the source code, we can assume the stack has a layout similar to the following:
4B 32B 4B 4B 4B 4B 4B 4B 4B
[*hmmptr][buf][*fptr][limit][i][argv][argc][sfp][ra]
Judging by the above layout and the program, we have an interesting conundrum. The variable *fptr is a function pointer. Also, if we look at the bottom of the code before the final return 0, we can see that this function pointer is used to call the function it points to. This simply means, if we can get *fptr to point to a function which does something useful, we could benefit. Also since *fptr is after buf, it looks like it may be in range of the overflow. However, if we look at the code again, we see the final line of the for-loop doesn’t let us control what is written to memory during the first 36 bytes of writing. Looking at the above memory layout, that means we might not be able to control *fptr.
However, let’s get a more detailed view of memory in this program instead of our guess. Let’s load the program in gdb and dissassemble main. While I won’t post all the assembly code here, I will post a few lines of importance. Essentially what we want to do is read through the assembly and understand where each variable is on the stack. To do that, we can look for the instructions that use our variables of interest and see at what offset from the base pointer the variables reside. Below are the lines we’re looking for with some added comments:
0x08048524 <main+117>: movl $0x8048464,-0x14(%ebp) –int (*fptr)(int) = good;
0x0804855d <main+174>: cmp -0x10(%ebp),%eax –i <= limit;–buf[i] = 0x41;
0x08048582 <main+211>: lea -0x38(%ebp),%eax –move address of start of buf into eax
0x08048585 <main+214>: add -0xc(%ebp),%eax –add value of i to eax
0x08048588 <main+217>: movb $0x41,(%eax) –move hex value 0x41 into memory address stored in eax0x08048592 <main+227>: movl $0x804847f,-0x3c(%ebp) –int (*hmmptr)(int) = hmm;
Looking at the above lines of assembly we can figure out where on the call stack the variables for the program reside, specifically what is located 36 bytes after the start of buf. This is an important question because it seems as though we’re only able to control what is written to the four bytes starting at buf+36; Analyzing the above we can pull out the following offsets for variables:
-0x3c(%ebp) => hmmptr
-0x38(%ebp) => buf
-0x14(%ebp) => fptr
-0x10(%ebp) => limit
-0x0c(%ebp) => i
Finally, doing a little math we can calculate the difference between the memory location of buf and of fptr:
0x38 – 0x14 = 0x24
Which, when we convert it to decimal, is 36. Interestingly enough, that means that fptr is located starting at the memory location 36 bytes past the start of buf. So, if we use a 40 character string we can exploit the buffer overflow to change the value of fptr. Now we need to determine what to change fptr to. Luckily enough, the function “hmm” looks like a good target! Hmm does exactly what we want, it spawns a shell. So using the information we had from the assembly lines above, we can see that the hmm function starts at memory location 0x0804847f. So, let’s attempt to write a 40 character string which will overwrite fptr with the location of hmm. Remember to write the bytes in little-endian (reverse) order:
level3@io:~$ /levels/level03 `perl -e ‘print “A”x36,”\x7f\x84\x04\x08″‘`
Win.
sh-4.1$ whoami
level4
sh-4.1$ more /home/level4/.pass
YYYYYYYYYYYY
There we have it. In this example we found a vulnerable buffer overflow which allowed us to re-write a function pointer and simply wait for the function to be called. This is slightly different from many of the other common buffer overflows which simply smash through the entire stack frame to overwrite the return address at the end. It just goes to show that buffer overflows are a serious security vulnerabilities which need to be taken seriously.