Silk Road
Silk Road
Platform: hackerlab | Category: Forensics | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2026-04-05 | Status: Solved Techniques: vmdk_extraction_with_7z, ext4_analysis_without_mounting, bash_history_forensics, png_text_chunk_metadata_extraction, firefox_password_decryption_firepwd, multi_fragment_flag_assembly, trash_artifact_analysis, binary_file_comparison
Summary
Task: a 4 GB VirtualBox VM disk image (Debian, user 'ross') themed around Silk Road with 3 hidden flag fragments. Solution: extract ext4 from VMDK with 7z, recover fragment 1 from bash_history (failed deletion), fragment 2 from PNG tEXt Author metadata in Trash, fragment 3 from Firefox backup logins decrypted with firepwd.py.
Silk Road — HackerLab
Description
Шёлковый путь. Не повторяйте чужих ошибок.
English summary: a 3.9 GB ZIP archive containing a VirtualBox VM (Debian 64-bit). The VM belongs to user "ross" — a reference to Ross Ulbricht, creator of the Silk Road darknet marketplace. The flag is split into 3 fragments hidden across different forensic artifacts. The subtitle "Don't repeat other people's mistakes" hints at Ulbricht's OPSEC failures that led to his arrest.
Analysis
Initial reconnaissance
The archive task.zip contains two files:
vm.ovf— VirtualBox VM descriptor (Debian_64)vm-disk001.vmdk— virtual disk (~4 GB)
Using 7z to inspect the VMDK without mounting reveals 3 partitions:
0.img(18 GB) — ext4 main filesystem1.img(1 GB) — swap2(1 MB) — boot
bash read-only • copy allowed7z l vm-disk001.vmdk # Shows 3 partitions inside the VMDK 7z x vm-disk001.vmdk "0.img" -y file 0.img # Linux rev 1.0 ext4 filesystem
Filesystem exploration
Listing the ext4 contents with 7z l 0.img reveals a single user ross with a rich set of forensic artifacts:
| Path | Significance |
|---|---|
home/ross/.bash_history | Command history with flag fragment |
home/ross/Desktop/.desktop_config | 2 MB VeraCrypt encrypted volume |
home/ross/Desktop/org.keepassxc.KeePassXC.desktop | KeePassXC shortcut |
home/ross/Desktop/veracrypt.desktop | VeraCrypt shortcut |
home/ross/.\./.config.kdbx | KeePassXC database in hidden dot-backslash directory |
home/ross/.local/share/Trash/files/config.kdbx | KeePassXC database in Trash |
home/ross/.local/share/Trash/files/avatar_forum.png | Modified avatar in Trash |
home/ross/.local/share/Trash/files/avatar_forum.png_original | Original avatar in Trash |
home/ross/.mozilla/firefox/u0kkkne8.default-esr/logins.json | Firefox saved passwords |
home/ross/.mozilla/firefox/u0kkkne8.default-esr/logins-backup.json | Firefox logins backup |
home/ross/.mozilla/firefox/u0kkkne8.default-esr/key4.db | Firefox NSS key database |
home/ross/.config/VeraCrypt/Configuration.xml | VeraCrypt config |
home/ross/.config/keepassxc/keepassxc.ini | KeePassXC config |
| |
The presence of KeePassXC, VeraCrypt, Firefox with saved passwords, and deleted files in Trash all point to multiple potential hiding spots for flag fragments.
Solution
Fragment 1: bash_history (failed deletion)
bash read-only • copy allowed7z x 0.img "home/ross/.bash_history" -oextracted -y cat extracted/home/ross/.bash_history
Contents:
CODEBY{w4s_
ls
cat ~/.bash_history
nano .bash_history
ls -ahli
unlink .bash_history
ls -ahli
nano .bash_history
ls
cat .bash_history
shutdown
Ross accidentally typed the flag start CODEBY{w4s_ as a shell command. He then noticed it in his history and tried to delete it with unlink .bash_history, but subsequent commands created a new history file that still contained the original entry. This mirrors the real Ross Ulbricht's OPSEC failures — he was caught partly because of digital traces he failed to properly clean up.
Fragment 1: CODEBY{w4s_
Fragment 2: PNG metadata steganography
Two avatar images were found in Trash with slightly different file sizes:
avatar_forum.png_original— 55,487 bytesavatar_forum.png— 55,516 bytes (29 bytes larger)
bash read-only • copy allowed7z x 0.img "home/ross/.local/share/Trash/files/avatar_forum.png" -oextracted -y 7z x 0.img "home/ross/.local/share/Trash/files/avatar_forum.png_original" -oextracted -y cmp -l avatar_forum.png_original avatar_forum.png
Binary comparison showed the IDAT (pixel data) chunks are byte-for-byte identical. The only difference: the modified file has an extra tEXt PNG chunk containing an Author field.
python read-only • copy allowedfrom PIL import Image img = Image.open("avatar_forum.png") print(img.text) # {'Author': 'th3_s1lly_'}
The tEXt chunk is a standard PNG metadata chunk — not visible when viewing the image, but trivially readable with any PNG metadata tool (exiftool, PIL, pngcheck).
Fragment 2: th3_s1lly_
Fragment 3: Firefox saved passwords
Extracted the Firefox profile artifacts:
bash read-only • copy allowed7z x 0.img "home/ross/.mozilla/firefox/u0kkkne8.default-esr/key4.db" -oextracted -y 7z x 0.img "home/ross/.mozilla/firefox/u0kkkne8.default-esr/logins.json" -oextracted -y 7z x 0.img "home/ross/.mozilla/firefox/u0kkkne8.default-esr/logins-backup.json" -oextracted -y
The logins.json and logins-backup.json contain encrypted credentials for http://sinkroad.cdb (a play on "Silk Road" + CODEBY). The username is DreadPirateRoberts — Ulbricht's famous alias.
Key technical details:
key4.dbuses PBES2 (PBKDF2 + HMAC-SHA256 + AES256-CBC) to protect the master key- No master password was set (empty string)
- Login credentials are encrypted with 3DES-CBC using the first 24 bytes of the 32-byte master key
Used firepwd.py (pure Python Firefox password decryptor) since native firefox_decrypt failed due to NSS library issues on macOS:
bash read-only • copy allowedpython firepwd.py -d extracted/home/ross/.mozilla/firefox/u0kkkne8.default-esr/
Custom solve script based on firepwd approach:
python read-only • copy allowed#!/usr/bin/env python3 """Decrypt Firefox saved passwords from key4.db and logins.json""" import json, sqlite3 from base64 import b64decode from hashlib import pbkdf2_hmac, sha1 from pathlib import Path from Crypto.Cipher import AES, DES3 from Crypto.Util.Padding import unpad from pyasn1.codec.der import decoder PROFILE = Path("extracted/home/ross/.mozilla/firefox/u0kkkne8.default-esr") def decryptPBE(decodedItem, masterPassword, globalSalt): entrySalt = decodedItem[0][0][1][0][1][0].asOctets() iterations = int(decodedItem[0][0][1][0][1][1]) keyLength = int(decodedItem[0][0][1][0][1][2]) k = sha1(globalSalt + masterPassword).digest() key = pbkdf2_hmac("sha256", k, entrySalt, iterations, dklen=keyLength) iv = b"\x04\x0e" + decodedItem[0][0][1][1][1].asOctets() cipherT = decodedItem[0][1].asOctets() return AES.new(key, AES.MODE_CBC, iv).decrypt(cipherT) def getKey(masterPassword=b""): conn = sqlite3.connect(str(PROFILE / "key4.db")) c = conn.cursor() c.execute("SELECT item1, item2 FROM metadata WHERE id='password'") globalSalt, item2 = c.fetchone() # Verify empty master password decoded = decoder.decode(item2) check = decryptPBE(decoded, masterPassword, globalSalt) assert b"password-check" in check, "Wrong master password!" # Extract master key c.execute("SELECT a11 FROM nssPrivate") a11 = c.fetchone()[0] conn.close() return decryptPBE(decoder.decode(a11), masterPassword, globalSalt) def decrypt_logins(key, path): final_key = key[:24] # 3DES uses first 24 of 32 bytes with open(path) as f: data = json.load(f) for login in data["logins"]: for field in ["encryptedUsername", "encryptedPassword"]: seq = decoder.decode(b64decode(login[field]))[0] iv, ct = bytes(seq[1][1]), bytes(seq[2]) plain = unpad(DES3.new(final_key, DES3.MODE_CBC, iv).decrypt(ct), 8) label = "Username" if "User" in field else "Password" print(f" {label}: {plain.decode()}") key = getKey() for f in ["logins.json", "logins-backup.json"]: print(f"\n=== {f} ===") print(f" Hostname: http://sinkroad.cdb") decrypt_logins(key, PROFILE / f)
Results:
| File | Username | Password |
|---|---|---|
logins.json (current) | DreadPirateRoberts | fa1r?} (truncated) |
logins-backup.json (backup) | DreadPirateRoberts | REDACTED} (complete) |
The current logins.json has a truncated password — the password was changed at some point, but the backup retains the full version.
Fragment 3: REDACTED}
Flag assembly
Combining all 3 fragments in order:
| # | Source | Fragment |
|---|---|---|
| 1 | bash_history | CODEBY{w4s_ |
| 2 | PNG tEXt Author metadata | th3_s1lly_ |
| 3 | Firefox backup logins | REDACTED} |
Red herrings and alternative paths
- KeePassXC databases (both in hidden
.\./directory and Trash) — had 76M+ iterations for key derivation, not quickly crackable. Likely a red herring or an alternative harder path. - VeraCrypt volume (
.desktop_config, 2 MB on Desktop) — encrypted container, password possibly stored in KeePassXC. Not needed for the flag. - VeraCrypt Configuration.xml — showed
SaveHistory=0,UseKeyfiles=0, no history saved.