Flare's Darkroom is a free dark web intelligence training lab, and its Matryoshka track is exactly what the name promises: a nested intrusion you peel one layer at a time. Each flag hands you the thread for the next mission, starting from a single malicious ad and ending with root on the operator's own server.
This is the story of that chain. A fake Notepad++ download turned into a full takedown of a ransomware crew called SkullProxy, and every step turned on a mistake the operator made.
Along the way you get PCAP forensics, dark web pivoting, a bit of social engineering, an offline password-vault crack, and a Linux privilege escalation, all in one continuous storyline.
The entry point was a browser capture of a malvertising attack aimed at UK GCHQ staff. A user searched for "notepad++ download," clicked the sponsored result, and got trojanized.
The PCAP held the whole redirect chain, so no internet pivot was needed. It bounced through a fake Google page, a spoofed googleadservices domain, a fake doubleclick host, a tracking CDN, and finally a typo-squat download page at notepadd-plus.ctf, spelled with two d's.
The cleverness was in the payload. The last redirect carried a JWT, and the malicious JavaScript pulled the XOR key straight out of that token's middle section. The 75-byte payload was then wrapped in five layers: XOR, gzip, hex, Base64, and a reversed string. The final flag reads d3sr3v3r on purpose, which is "reversed" written backwards. A small joke that tells you the last layer.
Flags: the impersonated software was notepad++, the obfuscation key was n0tap4dp1us_2o26, and the decrypted payload was FLAG{y0u_d3sr3v3r_ads_dr1veby}.
Mission 1 gave us the delivery domain, cdn-files.ctf. Mission 2 turned that domain into a person.
Sellers who run loader and stealer infrastructure advertise it on forums like BreachForums, and they name the CDN their builds ship from. So the move was to search the forum for the domain, not the thread titles, since the search matches post content.
That post gave up the seller's handle. From there, their profile pointed to two more pivots: an escrow reference on their sales listing, and a dedicated leak site advertised so buyers could verify the crew was real. That .onion address is what carried us into the next mission.
Takeaway: operators are careless with their own naming. One reused domain string in a sales post is enough to tie infrastructure to an identity.
The onion address resolved to SkullProxy's real leak site, live on Tor. This one had four flags, and the operators left every one of them lying around.
The first was an HTML comment sitting at the end of the page's head section: FLAG{SkullProxy_dedicated_leak_site_view_source_1337}. View source, and it was right there.
The second was a proof-of-breach file listing. One victim, a German pharma company, had a downloadable listing at /file_list.txt, about 557,000 lines. Only nine files carried the .SKULLPROXY ransomware extension, and exactly one had anything after it: SKULLPROXY(5qarlnf2dm83coi9u8yp03temw2v1nll). A quick filter found the needle in a 60MB haystack.
The third flag is the one worth framing. The site exposed Apache's /server-status page to the public, which printed the server's own address: 204.34.131.21. That is how you deanonymize a hidden service, not by breaking Tor, but by catching the operator leaving a door open.
The fourth was the Telegram contact glowing at the bottom of the page: luckyslavik. That handle is how we reached the operator directly in the next mission.
This mission was live human intelligence against an AI threat-actor persona on Telegram. No leverage, no exploit. Just a convincing cover story.
The operator is in it for money, so the cover was a paying customer: an initial access broker with access to EU pharma and logistics companies, looking to run under a solid affiliate program. The opening message referenced his leak site and a specific victim, which signals you are already in the scene rather than a stranger fishing.
He bit. He named his split, 70/30 in the affiliate's favor, and after a bit of back and forth about the quality of the access, he handed over where to register and pull builds.
The lesson for anyone doing this work: money-motivated actors respond to a profitable-looking opportunity and almost nothing else. Show up looking like revenue and they open the door themselves.
Flags: the revenue split was 70/30, and the affiliate panel was at https://panel.skullproxy.ctf/login.
The panel URL resolved to a login form we had no credentials for. The objective was to bypass it, not brute-force it, and land as the owner.
This was a classic SQL injection auth bypass. Putting admin' -- in the username field, with anything in the password, comments out the rest of the query and logs you straight in as the first account. We landed as the operator's own panel account.
From inside, the account settings held the real prize: a personal recovery address, ebogachev@protonmail.com. That is the pivot from a criminal alias to a person, and it set up the next two missions.
Takeaway: a login form that trusts user input in its query is still one of the most common ways in. One apostrophe and two dashes is all it took here.
With a personal email in hand, the next step was a credential-exposure search, the kind of stealer-log and breach lookup Flare's platform is built around.
Running ebogachev@protonmail.com through it surfaced his own infected-device record. The operator's personal machine had been hit by a stealer at some point, and his data was now in the same kind of dump he sells.
Two things came off that record. His personal credential pair, and the exact moment the stealer executed. A stealer log is different from a plain breach entry: it timestamps when the malware ran, down to the second.
Flags: the credential pair was ebogachev@protonmail.com:Anapa1980!Lucky, and the infection fired at 10/28/2023 10:44:17 AM.
The poetic part: the man who runs the infrastructure got caught by the exact tooling he profits from.
The stealer log did not just carry credentials. It grabbed the files off his machine too, including a KeePass vault. With the encrypted file in hand, this became an offline attack: no rate limit, no lockout, no one watching. The only defense left was the master password.
KDBX4 uses Argon2id, a deliberately slow key-derivation function, so blind brute force is painful. The better play was to profile the operator. His known password, Anapa1980!Lucky, follows a pattern: a place, a year, a symbol, a word. So I seeded a wordlist from his own habits instead of throwing rockyou at it.
The crack landed, but not on the obvious guess. The master password was qTGmcRWzzL5uHK!, a random-looking string that was also sitting in his own browser password dump. He had saved his KeePass master password in his browser, so the stealer captured it. That is the reuse mistake that ends the game.
Inside the vault, the entry titled "skullproxy private C2" gave up the SSH target ssh://ebogachev@100.60.31.30:2222 and its password QwMeQWFc*M24q##w^#Ja. A note on it read "deploy via sudo, do not connect without vpn."
The log had one more gift. His Authenticator browser extension stored an unencrypted TOTP secret labeled "SkullProxy Private C2 sudo." That two-factor seed became the key to root two missions later.
The final mission was login, escalate, and destroy. We SSH'd into the C2 with the vault credentials and landed as ebogachev.
The home directory flag came first: FLARE{you_l0gg3d_1n_but_c4n_y0u_r00t?}. A notes file next to it spelled out the path. The operator could run one command as root, sudo deploy, and security had put that sudo rule behind a rotating 2FA code back in June.
That rotating code was the TOTP seed we pulled from his Authenticator extension in Mission 7. Generating the live code got us past the sudo prompt.
The escalation itself was a sudoers misconfiguration. The sudo policy kept the user's PATH with env_keep+=PATH, and the deploy script called its helper binaries by bare name. Drop a malicious binary earlier in PATH, run sudo deploy, and your code runs as root. From there, the root flag in /root/flag.txt closed out the chain.
That is the full Matryoshka: one malicious ad, peeled all the way down to root on the operator's own server.
The one place we got stuck is the part most worth writing down. The vault C2 password was correct, the platform had already accepted it as a flag, yet SSH kept refusing it from an outside network.
The instinct in that spot is to assume a typo and hammer the login harder. That was the wrong instinct, and worth resisting for two reasons. First, the C2 address sits in normal routable space, so blindly retrying risks pounding on some unrelated stranger's server. Second, a correct credential that gets refused is telling you something about where you are connecting from, not what you are typing.
The box only trusted connections coming from inside the lab environment. The operator's own note, "do not connect without vpn," was the hint. Once the connection came from the right place, the same password worked on the first try.
The lesson holds well beyond a CTF: when a known-good credential fails, read the environment before you blame the secret.
Every flag in this chain was an operator mistake. The same mistakes show up in real environments, which is what makes the exercise useful.
The theme across all of it: attackers get compromised the same way their victims do. Good operational hygiene is the defense on both sides of the fence.
How did we do, #1! This was a bit more guided tho so not full competition mode. After Flare had some AMAZING visuals to go with this, here is the intro for the event.
| Mission | Flags |
|---|---|
| 1. The Drive-By | notepad++n0tap4dp1us_2o26FLAG{y0u_d3sr3v3r_ads_dr1veby} |
| 2. Find the Seller | vor_v_zakoneESCROW-773-VOR2aujhpoyrbfxrdcrqzqxkj7gs2co37hoahryrf2viqjcxbqju7kukeid.onion |
| 3. The Leak Site | FLAG{SkullProxy_dedicated_leak_site_view_source_1337}SKULLPROXY(5qarlnf2dm83coi9u8yp03temw2v1nll)204.34.131.21luckyslavik |
| 4. Talking to the Operator | 70/30https://panel.skullproxy.ctf/login |
| 5. Into the Panel | vor_adminebogachev@protonmail.com |
| 6. Unmasking | ebogachev@protonmail.com:Anapa1980!Lucky10/28/2023 10:44:17 AM |
| 7. The Vault | qTGmcRWzzL5uHK!ssh://ebogachev@100.60.31.30:2222QwMeQWFc*M24q##w^#Ja |
| 9. The Takedown | FLARE{you_l0gg3d_1n_but_c4n_y0u_r00t?}FLARE{con4ts_y0u_pwn3d!} |
That is the full flag set for the Matryoshka track, start to finish.
For the readers who want the how. The toolkit was small: tshark for the PCAP, Python for the decode chain, ripgrep for the big file listing, pykeepass for the vault, pyotp for the 2FA, and plain ssh.
Mission 1, peel the payload. Pull the XOR key from the JWT, then unwrap the five layers.
# the key lives in the JWT's middle (payload) segment
echo '<jwt-payload-segment>' | base64 -d | jq -r .k # n0tap4dp1us_2o26
import gzip, base64
key = b"n0tap4dp1us_2o26"
data = open("get_9f83c1a7", "rb").read()
stage = bytes(b ^ key[i % len(key)] for i, b in enumerate(data)) # 1: XOR
hexstr = gzip.decompress(stage).decode() # 2: gunzip
raw = bytes.fromhex(hexstr) # 3: hex
print(base64.b64decode(raw)[::-1].decode()) # 4: base64, 5: reverse
# FLAG{y0u_d3sr3v3r_ads_dr1veby}
Mission 3, find the needle and the real IP. One rg pass beats scrolling 557k lines.
rg -n 'SKULLPROXY\(' file_list.txt # the only extension line with a suffix
# http://<onion>/server-status -> "Apache Status for 204.34.131.21"
Mission 5, the auth bypass. Username field only.
username: admin' --
password: anything
Mission 7, open the vault. The master password was reused and sitting in the same log's browser dump.
from pykeepass import PyKeePass
kp = PyKeePass("Vault.kdbx", password="qTGmcRWzzL5uHK!")
for e in kp.entries:
print(e.title, e.username, e.password, e.url)
import pyotp # TOTP seed from the Authenticator extension's leveldb
print(pyotp.TOTP("MFRGG2LTMVZW63TF").now()) # live sudo code
Mission 9, login to root. The escalation rides env_keep+=PATH: deploy calls a helper by bare name, so a fake one earlier in PATH runs as root. Swap git for whatever the script actually calls.
ssh ebogachev@100.60.31.30 -p 2222 # password from the vault
cat ~/flag.txt
sudo -l # (root) /usr/local/bin/deploy, env_keep+=PATH
cd /tmp
printf '#!/bin/bash\n/bin/bash\n' > git
chmod +x git
PATH=/tmp:$PATH sudo deploy # enter the live TOTP at the prompt
cat /root/flag.txt
< RETURN TO CTF ARCHIVE