I Love Bacon!
I Love Bacon!
Platform: CUSTOM | Category: Forensics | Type: Challenge | Difficulty: Easy | OS: N/A | **Author: XESXOR Techniques: anomaly_hunting, base32_decoding, dns_traffic_triage, ttl_based_filtering
Summary
Walkthrough of the CUSTOM Forensics I Love Bacon!. Task: analyze a DNS-only PCAP with suspicious request/response pairs that look like encoded C2 traffic. Solution: ignore the noisy base32-like data, isolate the three anomalous responses with TTL 1337
Description
PCAP containing suspicious DNS request activity to a C2.
English summary: the challenge provides a packet capture of DNS traffic that appears to carry encoded command-and-control data. The goal is to identify which packets contain the meaningful signal, reconstruct the hidden message, and recover the flag.
Analysis
The first pass over dns_c2.pcap shows a very regular pattern:
- 2000 packets total
- only UDP/DNS traffic
- 1000 DNS
Aqueries from10.67.0.2to10.1.1.53 - 1000 matching responses
- each response carries one
TXTanswer - both the queried subdomain labels and TXT values look like uppercase RFC4648 base32 strings under
*.dawg.cwa.sec
That strongly suggests DNS-based exfiltration or C2. However, decoding everything is a trap: most entries decode to high-entropy junk and do not produce useful plaintext.
The real clue is an anomaly in the DNS responses. Three packets stand out because their response TTL is 1337, while the rest use ordinary-looking values. Those three responses also have an exact equality between the TXT answer and the query label, which does not happen in the surrounding noise.
The key packets are:
- frame
534 - frame
910 - frame
1824
Using TTL as the hunting pivot isolates the signal immediately. Extracting only those responses yields three base32 fragments:
IRQXOZ2DKRDHW4ZRPJ5GY2LOL5ZXKY3DOVWDG3TUL5RTEX3CGRRW63T5
Base32-decoding them gives readable ASCII chunks:
DawgCTF{s1zzlin_succul3nt_c2_b4con}
Concatenating the chunks reconstructs the full flag.
Solution
- Inspect the PCAP and confirm it contains only DNS over UDP.
- Notice the repetitive pattern of 1000 queries and 1000 TXT-bearing responses.
- Test the obvious hypothesis that all base32-looking labels contain data, then observe that most decode to junk.
- Hunt for anomalies instead of decoding everything blindly.
- Filter responses by TTL and isolate the three packets with
dns.resp.ttl == 1337. - Verify that in those packets the TXT answer exactly matches the query label.
- Base32-decode the three extracted labels and join the fragments.
Packet extraction command:
bash read-only • copy allowedtshark -r "dns_c2.pcap" -Y 'dns.flags.response==1 && dns.resp.ttl==1337' -T fields -e frame.number -e dns.qry.name -e dns.txt -e dns.resp.ttl
Expected output shape:
text read-only • copy allowed534 IRQXOZ2DKRDHW4ZRPJ5GY2LO.dawg.cwa.sec IRQXOZ2DKRDHW4ZRPJ5GY2LO 1337 910 L5ZXKY3DOVWDG3TU.dawg.cwa.sec L5ZXKY3DOVWDG3TU 1337 1824 L5RTEX3CGRRW63T5.dawg.cwa.sec L5RTEX3CGRRW63T5 1337
Minimal decode snippet:
python read-only • copy allowedimport base64 parts = [ "IRQXOZ2DKRDHW4ZRPJ5GY2LO", "L5ZXKY3DOVWDG3TU", "L5RTEX3CGRRW63T5", ] for s in parts: pad = '=' * ((8 - len(s) % 8) % 8) print(base64.b32decode(s + pad).decode()) print(''.join(base64.b32decode(s + '=' * ((8 - len(s) % 8) % 8)).decode() for s in parts))
Full working solver:
python read-only • copy allowed#!/usr/bin/env python3 import base64 import subprocess import sys def b32decode_nopad(s: str) -> str: pad = "=" * ((8 - len(s) % 8) % 8) return base64.b32decode(s + pad).decode() def main() -> None: if len(sys.argv) != 2: print(f"usage: {sys.argv[0]} dns_c2.pcap") raise SystemExit(1) pcap = sys.argv[1] cmd = [ "tshark", "-r", pcap, "-Y", "dns.flags.response==1 && dns.resp.ttl==1337", "-T", "fields", "-e", "frame.number", "-e", "dns.txt", ] out = subprocess.check_output(cmd, text=True) parts = [] for line in out.splitlines(): if not line.strip(): continue frame, txt = line.split("\t", 1) parts.append(txt.strip()) print(f"frame {frame}: {txt.strip()} -> {b32decode_nopad(txt.strip())}") flag = "".join(b32decode_nopad(part) for part in parts) print(f"flag = {flag}") if __name__ == "__main__": main()
Example output:
text read-only • copy allowedframe 534: IRQXOZ2DKRDHW4ZRPJ5GY2LO -> DawgCTF{s1zzlin frame 910: L5ZXKY3DOVWDG3TU -> _succul3nt frame 1824: L5RTEX3CGRRW63T5 -> _c2_b4con} flag = DawgCTF{REDACTED}
Key Takeaways / Lessons
- Mastering anomaly_hunting, base32_decoding, dns_traffic_triage, ttl_based_filtering was key to this challenge.
- Always check for common misconfigurations and verify assumptions.
Crafted by XESXOR Research • 2026-04-10 • CUSTOM • I Love Bacon! • Difficulty: Easy