Skip to content

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

BACK TO INTEL
ForensicsMedium

Trojan Echoes Forensic

CTF writeup for Trojan Echoes Forensic from deadface

//Trojan Echoes Forensic

>Overview

Trojan Echoes is a Windows x86-64 PE malware sample designed to exfiltrate a sequence of hidden flags. The executable was compiled with MinGW/GCC 8.1.0 and stores most of its payload inside the .rdata section as encoded string fragments. This writeup documents the complete reverse-engineering process used to recover all flags (deadface{...}) distributed throughout the binary. Each step includes the relevant tooling, methodology, and Python helper scripts so the analysis can be reproduced.

>Tooling and Setup

  • Operating System: Linux
  • Python Environment: Local virtualenv (.venv) with pefile and capstone installed
  • Disassembly: objdump and Capstone
  • String Recon: strings dump (strings.txt)
  • Decoding Utilities: Custom Python scripts (shown inline)

Activate the virtual environment before running any Python helper scripts:

zsh
source /home/noigel/Desktop/deadface/forensics/Trojan/.venv/bin/activate

>Artifact Reconnaissance

The primary artifact is Trojan_Echoes/sample_01E9.exe.exe. Initial strings analysis highlighted human-readable clues such as deadface, multiple base64-looking fragments, and slash-delimited hexadecimal strings. Mapping the executable with pefile confirmed that all encoded payloads reside in .rdata, while the .text section contains routines that concatenate and decode them at runtime.

>Flag 00 – deadface{Hello_my_friend}

Discovery

Near the beginning of .rdata, the strings deadface, Hello, my, and friend appear as individual fragments. A short walk through the disassembly shows that these pieces are concatenated and written to %USERPROFILE%\flag.txt.

Reproduction

python
from pathlib import Path
import pefile

path = Path('sample_01E9.exe.exe')
pe = pefile.PE(str(path))
section = next(sec for sec in pe.sections if sec.Name.rstrip(b'\x00') == b'.rdata')
start = section.PointerToRawData
end = start + section.SizeOfRawData
rdata = path.read_bytes()[start:end]
print(rdata[0x5:0x23].decode('ascii'))

Output confirms the concatenated result: deadfaceHello_my_friend. Formatting matches the flag convention: deadface{Hello_my_friend}.

>Flag 01 – deadface{We_meet_again}

Discovery

Immediately after the first flag fragments, more plaintext blocks (We, meet, again) are stored contiguously. Runtime analysis shows the malware reuses the same concatenation buffers to emit the second message.

Reproduction

python
print(rdata[0x12:0x23].decode('ascii'))  # 'meetagain'

Combining these with the known prefix yields the confirmed flag: deadface{We_meet_again}.

>Flag 02 – deadface{Its_been_a_while}

Discovery

Beginning at offset 0x51, the binary switches to base64 stores. A sequence of five-character chunks (e.g., aWxl, bl9h, dHNf, …) is concatenated in order before decoding. Earlier attempts misordered the fragments, producing invalid output. Inspecting the disassembly clarified the runtime ordering, enabling correct reconstruction.

Reproduction

python
import base64
fragments = [
    'aWxl', 'bl9h', 'dHNf', 'LSBk', 'ZmFj', 'ZXtJ', 'ZWFk', 'YmVl', 'X3do', 'MDIg', 'fQ=='
]
payload = ''.join(fragments)
print(base64.b64decode(payload))

Decoded bytes: b'deadface{Its_been_a_while}'.

>Flag 03 – deadface{Where_should_we_begin}

Discovery

Additional base64 fragments appear later in .rdata. Disassembly around 0x40195f–0x401c20 shows the same concatenation routine, this time writing the result to the registry via RegSetValueExA. Ordering the fragments according to the lea instructions feeding the buffer restores a valid base64 string.

Reproduction

python
import base64
fragments = [
    'aG91', 'aGVy', 'ZXtX', 'ZV9z', 'd2Vf', 'YmVn', 'bGRf', 'MDMg', 'aW59'
]
decoded = base64.b64decode(''.join(fragments))
print(decoded.decode('ascii'))

Output: deadface{Where_should_we_begin}.

>Flag 04 – deadface{Feels_like_forever}

Discovery

Starting at .rdata+0xe4, the sample introduces slash-delimited hex fragments (e.g., /4c/53, /5a/58). Disassembly from 0x401c70 onward reveals that two dozen pointers to these fragments are stored on the stack, then concatenated via a long strcat chain. The resulting string acts as a URL path appended to https://www.totallynothackers.org. Capturing the exact concatenation order is essential: the pointers are stored non-sequentially, so manual visual ordering from strings.txt will fail.

Extracting the Order with Capstone

python
import pefile
from capstone import *
from capstone.x86 import *
from pathlib import Path

path = Path('sample_01E9.exe.exe')
pe = pefile.PE(str(path))
text = next(sec for sec in pe.sections if sec.Name.rstrip(b'\x00') == b'.text')
text_data = path.read_bytes()[text.PointerToRawData:text.PointerToRawData+text.SizeOfRawData]
start, end = 0x401c70, 0x401fac
offset_lo = start - (pe.OPTIONAL_HEADER.ImageBase + text.VirtualAddress)
offset_hi = end   - (pe.OPTIONAL_HEADER.ImageBase + text.VirtualAddress)
code = text_data[offset_lo:offset_hi]

md = Cs(CS_ARCH_X86, CS_MODE_64)
md.detail = True
order = []
for ins in md.disasm(code, start):
    if ins.mnemonic == 'mov' and len(ins.operands) == 2:
        op0, op1 = ins.operands
        if op0.type == X86_OP_REG and op0.reg == X86_REG_RDX and op1.type == X86_OP_MEM:
            mem = op1.mem
            if mem.base == X86_REG_RBP and mem.index == 0:
                order.append(mem.disp)
print(order)

order contains the stack displacements accessed prior to each strcat. Mapping these displacements back to .rdata string offsets yields the correct fragment order.

Reconstructing the Payload

python
import binascii
fragments = [
    '/4d/44', '/51/67', '/4c/53', '/42/6b', '/5a/57', '/46/6b', '/5a/6d', '/46/6a',
    '/5a/58', '/74/47', '/5a/57', '/56/73', '/63/31', '/39/73', '/61/57', '/74/6c',
    '/58/32', '/5a/76', '/63/6d', '/56/32', '/5a/58', '/4a/39'
]
hex_string = ''.join(part.replace('/', '') for part in fragments)
raw_bytes = binascii.unhexlify(hex_string)
print(raw_bytes)

Output: b'MDQgLSBkZWFkZmFjZXtGZWVsc19saWtlX2ZvcmV2ZXJ9'. One final base64 decode completes the recovery:

python
import base64
print(base64.b64decode('MDQgLSBkZWFkZmFjZXtGZWVsc19saWtlX2ZvcmV2ZXJ9'))

Result: b'04 - deadface{Feels_like_forever}'.

>Final Flag List

  1. deadface{Hello_my_friend}
  2. deadface{We_meet_again}
  3. deadface{Its_been_a_while}
  4. deadface{Where_should_we_begin}
  5. deadface{Feels_like_forever}

>Key Takeaways

  • Fragment Ordering Matters: Both base64 and slash-hex payloads relied on runtime concatenation order, not textual ordering in the binary. Static extraction without disassembly leads to incorrect results.
  • Slash-Hex Double Encoding: Each /hh/HH fragment represents two hex bytes. The malware concatenates them into a base64 string, so decoding requires both hex and base64 passes.
  • Dual Delivery Channels: Flags were written to disk, to the registry, and embedded inside a URL destined for totallynothackers.org. Monitoring these I/O paths would expose the malware’s intent even without full reverse engineering.
  • Capstone Automation: Automating the extraction of pointer access order with Capstone avoids human error when tracing long concatenation sequences.

This methodology provides a repeatable blueprint for tackling similarly structured malware challenges: start with strings, confirm runtime ordering via disassembly, and automate decoding workflows with lightweight scripts.