Skip to content

SECURE_CONNECTION//PRESS[CTRL+J]FOR ROOT ACCESS

BACK TO INTEL
ReverseEasy

Lament Of The Seraph

CTF writeup for Lament Of The Seraph from niteCTF

//Lament of the Seraph

//Lament of the Seraph Write-up

>Challenge Summary

  • Binary: seraphs_lance.exe (PE32, packed inside seraphslance.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

  1. wine, wine64, wine32 – to run the PE and reproduce its filesystem expectations.
  2. radare2 + rabin2 – fast disassembly, section mapping, and string extraction.
  3. Python 3 – ad-hoc tooling for MD5 searches and XOR decoding.
  4. electrum data (recent_servers, mainnet_servers.json) – needed because the binary hashes Electrum hostnames.

>Step 1 – Initial Recon

  1. Unzip seraphslance.zip and examine sections with rabin2 -S seraphs_lance.exe.
  2. Identify an unusual RW .data block at 0x0040a020—contains interleaved bytes that resemble shellcode.
  3. Extract printable strings using r2 -q -c "izz" seraphs_lance.exe; only minimal hints, so deeper reversing required.

>Step 2 – Trace the Control Flow

  1. Run r2 -q -c "aaa" seraphs_lance.exe for full analysis.
  2. Entry point (entry0) quickly jumps into fcn.00402531, which orchestrates the Electrum wallet crawl and encryption routine.
  3. Within fcn.00402531, note the call at 0x402937 into fcn.00401e9d. This function is crucial—it
    • Lazily resolves multiple kernel32 APIs via the hashed import helper fcn.004019b1.
    • Calls into the raw blob at 0x40a020 via a pair of crafted WoW64 far returns, effectively executing embedded shellcode.
  4. fcn.00401d72 sets up vectored exception handlers and stores pointers at globals 0x40e044, 0x40e048, 0x40e04c. These form the trampolines used by the shellcode.

>Step 3 – Understand the Hash Check

  1. Inside fcn.00401fd1, the binary iterates over Electrum's recent_servers. For each hostname it:
    • Builds an MD5 digest via dynamically-resolved CryptoAPI calls.
    • Compares the hex digest to the hardcoded value 5fc442554f245b0a023771a80c66e09c.
  2. 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

  1. Disassemble the blob at 0x40a020 as 64-bit code (r2 -q -c "s 0x40a020; e asm.bits=64; pd 80").
  2. The first gadget builds a WoW64 retf stub, switching into 64-bit mode. Real logic begins at 0x40a054:
    • r9 is filled with the ciphertext (32 bytes).
    • r8 points 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.
  3. 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

  1. Populate Wine's recent_servers with the discovered hostname so the binary no longer displays the ransom hint but proceeds to decrypt.
  2. Run the executable under wine64 seraphs_lance.exe; confirm the decrypted string matches the Python solver output.
  3. Cross-check that no further transformations occur—the shellcode writes the plaintext directly next to its buffers, so extraction is straightforward.

>Flag

nite{CRYPT0BR0Sn4NG3LS4tTH3g4t3}