CTFするぞ

CTF以外のことも書くよ

Insomni'hack teaser 2020 Writeup

I played Insomni'hack teaser 2020 in shibad0gs. There're only 1 misc, 1 rev, 1 forensics, 3 crypto and 4 web. We solved 4 tasks in the first 4 hours and that's it lol.

f:id:ptr-yudai:20200119181050p:plain

As the web tasks are too guessy, I just solved the misc, forensics and the easiest web.

[misc] welcome

We're given a PoW script, which also sends the user data to server and overwrite bashrc while calculating. I just removed the malicious line and run it, sent the result to the server and got the flag.

[web] LowDeep

It's a ping service online. As it has obvious OS command injection, I run ls -lha and found ./print_flag, which seems an ELF binary but not executable. I dropped it through browser (since it's in /var/www/html) and found the flag using string command.

[forensics] getdents

Description: Oh shit! Data have been stolen from my computer... I looked for malicious activity but found nothing suspicious. Could ya give me a hand and find the malware and how it's hiding?
File: memory.vmem

This is the only fun challenge. We're given a memory dump of an Ubuntu machine.

At first I dumped the file list.

$ vol.py -f memory.vmem --profile=LinuxUbuntu_4_15_0-72-generic_profilex64 linux_enumerate_files

Looking over the list, I found some curious files.

0xffff8a9dc38422e8                   2363716 /home/julien/Downloads
               0x0 ------------------------- /home/julien/Downloads/.hidden
0xffff8a9dd42755e8                   2359303 /home/julien/Downloads/rkit.ko
0xffff8a9d948151a8                   2367790 /home/julien/Downloads/meterpreter

Let's dump and analyse rkit.ko.

$ vol.py -f memory.vmem --profile=LinuxUbuntu_4_15_0-72-generic_profilex64 linux_find_file -i 0xffff8a9dd42755e8 -O rkit.ko

This function is suspicious because it's alternating getdents syscall to something new.

f:id:ptr-yudai:20200119162000p:plain

Let's check sub_80005EE and sub_8000266. sub_80005EE is pretty incomprehensive.

f:id:ptr-yudai:20200119162204p:plain

However, it seems a base64 encoder since it's using a table abcd...789+/ inside the function. The return value of this function is the kmalloced pointer. So, we can guess something like this is happening.

ep = base64(s, strlen(s), &epl);

Let's check sub_8000266 now.

This is another encoder. I implemented this encoder in Python.

pk = "wC4jSbGOktXTIfdsHFuKoU7nZVvLq0eWl1mBQr9P5JEpMyN832AY6chRigDaxz"
sk = ""
for i in range(62):
    sk += chr(ord(pk[i]) ^ key)

At first glance the xor key is variable but actually constant.

Where is ep used? There's another suspicious function sub_80005F9.

f:id:ptr-yudai:20200119163018p:plain

This is also an encoder which uses sk as the key. The code looks like this:

for i in range(len(encoded_flag)):
    encoded_flag[i] ^= sk[j % 62]

Okay, now we have every piece. Next thing we need to do is find the encoded flag.

Let's dump the memory around this kernel module using volshell. Before that we have to find the address where rkit.ko is loaded.

$ vol.py -f memory.vmem --profile=LinuxUbuntu_4_15_0-72-generic_profilex64 linux_check_modules
Volatility Foundation Volatility Framework 2.6.1
    Module Address       Core Address       Init Address Module Name             
------------------ ------------------ ------------------ ------------------------
0xffffffffc0943080 0xffffffffc0941000                0x0 rkit

You see the address of the syscall table is fixed.

f:id:ptr-yudai:20200119163753p:plain

Also, sk, epl and ep are followed by syscall_table.

f:id:ptr-yudai:20200119164432p:plain

In volshell you can find those data here:

>>> db(0xffffffffc09433e0, 128)
0xffffffffc09433e0  e5 d1 a6 f8 c1 f0 d5 dd f9 e6 ca c6 db f4 f6 e1   ................
0xffffffffc09433f0  da d4 e7 d9 fd c7 a5 fc c8 c4 e4 de e3 a2 f7 c5   ................
0xffffffffc0943400  fe a3 ff d0 c3 e0 ab c2 a7 d8 d7 e2 df eb dc aa   ................
0xffffffffc0943410  a1 a0 d3 cb a4 f1 fa c0 fb f5 d6 f3 ea e8 00 00   ................
0xffffffffc0943420  16 00 00 00 00 00 00 00 a0 12 fc a1 9d 8a ff ff   ................
0xffffffffc0943430  40 02 60 ab ff ff ff ff 00 00 00 00 00 00 00 00   @.`.............
0xffffffffc0943440  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
0xffffffffc0943450  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................

Let's dump the pointer to the encoded flag.

>>> db(0xffff8a9da1fc12a0, 128)
0xffff8a9da1fc12a0  ac 9f f5 83 93 c0 e5 a9 b2 d7 be 80 eb 86 a4 8e   ................
0xffff8a9da1fc12b0  ea bf 8e bc 8e ba 00 00 60 da af aa ff ff ff ff   ........`.......
0xffff8a9da1fc12c0  7a 73 84 f8 09 cc dd c4 d0 9b aa aa ff ff ff ff   zs..............
0xffff8a9da1fc12d0  b0 9b aa aa ff ff ff ff b0 4e b0 aa ff ff ff ff   .........N......
0xffff8a9da1fc12e0  c0 f0 68 c0 51 d7 ff ff 80 f0 68 c0 51 d7 ff ff   ..h.Q.....h.Q...
0xffff8a9da1fc12f0  00 f1 68 c0 51 d7 ff ff 00 00 00 00 00 00 00 00   ..h.Q...........
0xffff8a9da1fc1300  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
0xffff8a9da1fc1310  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................

Cool. As we have sk and ep, we can easily recover the original flag.

import base64

encrypted = "\xac\x9f\xf5\x83\x93\xc0\xe5\xa9\xb2\xd7\xbe\x80\xeb\x86\xa4\x8e\xea\xbf\x8e\xbc\x8e\xba"
sk = b'\xe5\xd1\xa6\xf8\xc1\xf0\xd5\xdd\xf9\xe6\xca\xc6\xdb\xf4\xf6\xe1\xda\xd4\xe7\xd9\xfd\xc7'

flag = ""
for i in range(len(encrypted)):
    flag += chr(ord(encrypted[i]) ^ ord(sk[i % 62]))
print(flag)
$ python solve.py
INS{R00tK1tF0rRo0kies}