We hired somebody to gather intelligence on an enemy party. But apparently they managed to lose the secret document they extracted. They just sent us this and said we should be able to recover everything we need from it.
Can you help?
In this challenge we are given a PCAP file
dump.pcap and a Python source code for the server -
PCAP file contains a recorded conversation between a DNS client and a server, where DNS queries and CNAME responses seem to contain encoded messages:
Close examination of the server code reveals that DNS queries and responses are used as transport for a remote shell session. DNS client initiates the conversation and the server responds by sending shell commands, for which the client then sends output. Commands are sent in CNAME responses to DNS queries, while the output is encoded in subdomain names in DNS queries. Pretty neat so far…
Each payload is encoded in Base32 and split into 62-character chunks to account for the maximum length of the domain name. The chunks are separated with periods and end in
.eat-sleep-pwn-repeat.de. A typical payload would look like this:
Since DNS packets go over UDP, the protocol includes special handling for things like duplicate packets. To account for that the first 6 bytes in each payload contain the conversation ID, sequence number, and the acknowledgement. There is no time to develop a fully robust decoding solution, but at the very least it would be necessary to account for duplicate packets.
Based on the the information gathered so far (and much trial and error ) I wrote the following script. It goes through all packets in PCAP file, extracts and decodes payloads, discards duplicate packets, and dumps the output to the screen:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 import base64 import struct import dpkt import sys # packet sequence numbers that we will keep track of sseq = -1 dseq = -1 def decode_b32(s): s = s.upper() for i in range(10): try: return base64.b32decode(s) except: s += b'=' raise ValueError('Invalid base32') def parse(name): # split payload data at periods, remove the top # level domain name, and decode the data data = decode_b32(b''.join(name.split('.')[:-2])) (conn_id, seq, ack) = struct.unpack('<HHH', data[:6]) return (seq, data[6:]) def handle(val, port): global sseq, dseq (seq,data) = parse(val) # remove empty packets if len(data) == 0: return #remove duplicates if port == 53: if sseq < seq: sseq = seq else: return else: if dseq < seq: dseq = seq else: return sys.stdout.write(data) # main execution loop - go through all DNS packets, # decode payloads and dump them to the screen for ts, pkt in dpkt.pcap.Reader(open('dump.pcap','r')): eth = dpkt.ethernet.Ethernet(pkt) if eth.type == dpkt.ethernet.ETH_TYPE_IP: ip = eth.data if ip.p == dpkt.ip.IP_PROTO_UDP: udp = ip.data dns = dpkt.dns.DNS(udp.data) # extract commands from CNAME records and # output from queries if udp.sport == 53: for rr in dns.an: if rr.type == dpkt.dns.DNS_CNAME: handle(rr.cname, udp.sport) else: if dns.opcode == dpkt.dns.DNS_QUERY: handle(dns.qd.name, udp.sport)
Running it (
python decode.py > output.bin) gives us the output file.
The output is a treasure trove of information:
- There is a public and private key. We save them to local file
- There are commands the user executed to encrypt a document
- And there is the encrypted document itself, written to stdout. The document body is output between tags
END_OF_FILE. We use a binary editor (e.g. HxD) to extract its body to
Now all that is left is to backtrack the user’s steps from the output log and decrypt the document:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 root@kali:/33c3/exfil# gpg --import key.txt gpg: key D43CC062D0D8161F: public key "operator from hell <firstname.lastname@example.org>" imported gpg: key D43CC062D0D8161F: "operator from hell <email@example.com>" not changed gpg: key D43CC062D0D8161F: secret key imported gpg: Total number processed: 2 gpg: imported: 1 gpg: unchanged: 1 gpg: secret keys read: 1 gpg: secret keys imported: 1 root@kali:/33c3/exfil# gpg --decrypt --recipient firstname.lastname@example.org --trust-model always secret.docx.gpg > secret.docx gpg: encrypted with 2048-bit RSA key, ID 4C2B141BBF30A26A, created 2016-12-11 "operator from hell <email@example.com>"
The resulting file
secret.docx contains the key:
1 2 3 The secret codeword is 33C3_g00d_d1s3ct1on_sk1llz_h0mie