//Lament of the Seraph
//Lament of the Seraph Write-up
>Challenge Summary
- Binary:
seraphs_lance.exe(PE32, packed insideseraphslance.zip) - Goal: Reverse the ransomware-lite binary and recover the true flag
nite{...}. - Environment: Linux host (Ubuntu), Wine with WoW64 support for execution, Radare2 for static analysis, Python for scripting.
>Tooling Checklist
wine,wine64,wine32– to run the PE and reproduce its filesystem expectations.radare2+rabin2– fast disassembly, section mapping, and string extraction.Python 3– ad-hoc tooling for MD5 searches and XOR decoding.electrumdata (recent_servers,mainnet_servers.json) – needed because the binary hashes Electrum hostnames.
>Step 1 – Initial Recon
- Unzip
seraphslance.zipand examine sections withrabin2 -S seraphs_lance.exe. - Identify an unusual RW
.datablock at0x0040a020—contains interleaved bytes that resemble shellcode. - Extract printable strings using
r2 -q -c "izz" seraphs_lance.exe; only minimal hints, so deeper reversing required.
>Step 2 – Trace the Control Flow
- Run
r2 -q -c "aaa" seraphs_lance.exefor full analysis. - Entry point (
entry0) quickly jumps intofcn.00402531, which orchestrates the Electrum wallet crawl and encryption routine. - Within
fcn.00402531, note the call at0x402937intofcn.00401e9d. This function is crucial—it- Lazily resolves multiple
kernel32APIs via the hashed import helperfcn.004019b1. - Calls into the raw blob at
0x40a020via a pair of crafted WoW64 far returns, effectively executing embedded shellcode.
- Lazily resolves multiple
fcn.00401d72sets up vectored exception handlers and stores pointers at globals0x40e044,0x40e048,0x40e04c. These form the trampolines used by the shellcode.
>Step 3 – Understand the Hash Check
- Inside
fcn.00401fd1, the binary iterates over Electrum'srecent_servers. For each hostname it:- Builds an MD5 digest via dynamically-resolved CryptoAPI calls.
- Compares the hex digest to the hardcoded value
5fc442554f245b0a023771a80c66e09c.
- Reproducing this logic externally confirms the match is
electrum.blockstream.info.
MD5 Scanner Script
python
#!/usr/bin/env python3
import hashlib, json
with open("mainnet_servers.json", "r", encoding="utf-8") as fh:
servers = json.load(fh)
target = "5fc442554f245b0a023771a80c66e09c"
for host in servers:
digest = hashlib.md5(host.encode()).hexdigest()
if digest == target:
print("Match:", host)
break
Output: Match: electrum.blockstream.info
>Step 4 – Decode the Shellcode Payload
- Disassemble the blob at
0x40a020as 64-bit code (r2 -q -c "s 0x40a020; e asm.bits=64; pd 80"). - The first gadget builds a WoW64
retfstub, switching into 64-bit mode. Real logic begins at0x40a054:r9is filled with the ciphertext (32 bytes).r8points to an alphabet-like table but effectively supplies the Electrum hostname as the key (after host discovery).- A loop divides the index by 25 (length of key), fetches one byte, XORs with ciphertext, and writes result to
r10.
- Dumping the data block reveals the ciphertext
0b0511060f3127347e365c2d315b201a462b265e623a5a123b2d5f0257004108.
Shellcode XOR Solver
python
#!/usr/bin/env python3
cipher = bytes.fromhex(
"0b0511060f3127347e365c2d315b201a"
"462b265e623a5a123b2d5f0257004108"
)
key = b"electrum.blockstream.info" # 25 bytes
plain = bytes(c ^ key[i % len(key)] for i, c in enumerate(cipher))
print(plain.decode())
Output: nite{CRYPT0BR0Sn4NG3LS4tTH3g4t3}
![[Pasted image 20251214081923.png]]
>Step 5 – Validate End-to-End
- Populate Wine's
recent_serverswith the discovered hostname so the binary no longer displays the ransom hint but proceeds to decrypt. - Run the executable under
wine64 seraphs_lance.exe; confirm the decrypted string matches the Python solver output. - Cross-check that no further transformations occur—the shellcode writes the plaintext directly next to its buffers, so extraction is straightforward.
>Flag
nite{CRYPT0BR0Sn4NG3LS4tTH3g4t3}