Google CTF - Wiki Writeup

I began my Google CTF experience with this challenge which was categorized as a medium difficulty pwn task, but I have found it significantly easier than the inst-prof challenge which I solved later during the CTF. Without further ado, let’s dig right into it.

The challenge binary is fairly simple, it is an x86_64 linux executable, with NX and PIE enabled, but without stack canaries (all of these details are relevant for solving the challenge). Reversing the binary is straightforward, it simply reads a line from the user and depending on what the input is it can execute three different commands:

  • LIST: simply lists the files in the ./db directory. These file names correspond to the user names of the “legitim” users of the service and store their passwords
  • USER: reads a username and tries to open the associated file in the ./db folder, if the file is present it reads the password to a heap buffer and returns a pointer to it. This menu point can only be called once
  • PASS: reads the password form the user and compares it with the password stored in the file, if they match system("cat flag.txt") is called, otherwise the program exits.

The BUG

I started my investigation at the read username function, wanting to open some file that has easy to guess content, but the ‘/’ characters are properly filtered. Unlucky for us there is no path traversal opportunity. There is one bug in the read line function, it does not append a \0 character to the received string, but I have found no means to exploit this vulnerability. Every time this function is called from the program a larger buffer is passed to it, which is zeroed out beforehand.

The most exciting bug, however, is a trivial buffer overflow in the password check function. It reads up to 4096 characters into a 128 bytes long stack buffer. As we noted at the beginning, stack protection is not enabled which makes this bug “easily” exploitable. All what is needed to complete the exploit is to leak the program base or a libc address. However, looking at the binary it quickly becomes obvious that there is no opportunity to leak anything. The only place the program sends data is when the files (usernames) are listed, and we do not have any control over what gets printed there.

The Exploit

What we have at this point is RIP control in a form of a buffer overflow and a sort of “win address” in the PIE binary, that prints us the flag, but we do not have any address leaks. My first thought was to do a partial overwrite of the return address, preserving the original return address besides its LSB. This would be feasible as the target address is really close to the original return address, but the problem is if the input length is not multiple of 8, the program exits. Below is the relevant part of the password compare function:

char buffer[128]; // [sp+0h] [bp-98h]@1
if ( readLine(0, buffer, 4096LL) & 7 )
LABEL_7:
    _exit(v4);
result = strCmp(buffer, read_line_1);
if ( (_DWORD)result )
{
    v4 = system("cat flag.txt");
    goto LABEL_7;
}
return result;

There is only one constant segment in the virtual address space of PIE binary when ASLR is enabled, which is the [vsyscall] region. It provides access to three system calls that do not need to actually run in kernel mode. There is newer mechanism called vdso, which is affected by ASLR, but the vsyscall page is still present on modern systems for compatibility reasons (see this LWN article for more). What is important for us, is that there is a read-execute page in the memory at a constant address which contain stubs for three syscalls (sys_gettimeofday, sys_time, sys_getcpu). These syscall stubs all follow the same pattern:

mov eax, sycall_number
syscall
ret

The vsyscalls are executed in a way, that when they are called a page fault is generated and caught and the address of this page fault determines which syscall is going to be called (emulated). What this means is that only the “entry point”, the beginning, of the system call can be called so we cannot play with misaligned instructions or call directly the return.

This still provides us with a way to traverse the stack for a more useful address already on it. Which can be achieved by overwriting the stack with the address of one of the vsyscalls multiple times. These syscalls also provide some control of the memory content pointed by the RDI and RSI registers. Breaking at the end of the password check function (where the return overflow is going to be triggered), we can observe that RDI points to the beginning of our stack buffer while RSI points to the heap buffer, where the original password is stored. All that is required to complete the exploit is useful address on the stack to return to.

At the beginning of the main function the program copies the address of the three command functions from the .data section to the stack and passes it, as an argument, to the command loop. This means we can call any of those functions (with our stack traversal) including the password check. But this time the argument register of the function (RDI) points to the output of the sys_gettimeofday call instead of the original password. The actual steps of the exploit:

  • List the users
  • Open one of the users
  • Overflow the stack with the address of sys_gettimeofday until the address of the password check function is reached
  • Guess the returned time (epoch time in seconds), when the password function is called the second time
  • Profit

And the actual code of the solution (the complete exploit is available here):

m.c.sendline("LIST")
dirs = m.c.recv()
print "Users:"
print dirs
name = dirs.splitlines()[0]
m.c.sendline("USER")
print "Login as: "+name
m.c.sendline(name)

buff = "A"*0x88 # The actual buffer
rbx = "B" * 8   # pop rbx
rbp = "C" * 8   # pop rbp
ret = p64(0xffffffffff600400) # sys_gettimeofday
# The stack needs to be traversed by 24*8 bytes
payload = buff + rbx + rbp + ret *24
m.c.sendline("PASS")
m.c.sendline(payload)
guess = p64(int(time.time()))
m.c.sendline(guess)

m.c.interactive()

This simple exploit yielded the flag:

CTF{NoLeaksFromThisPipe}

This task was fairly simple to solve, especially since I have seen this trick being used in previous CTF challenges. Still I think it was a well constructed and thought out challenge kudos to the creator!

0ctf 2018 - Black Hole Theory

Writeup for the 2018 0ctf pwn challenge Black Hole Theory Continue reading