with one click
ctf-malware
// Malware & network analysis: obfuscated JS/PowerShell/VBA, PE/.NET analysis, PyInstaller/PyArmor, custom C2 protocols (RC4, AES, DNS), YARA rules, shellcode, memory-forensics IOCs, anti-sandbox/anti-VM bypass.
// Malware & network analysis: obfuscated JS/PowerShell/VBA, PE/.NET analysis, PyInstaller/PyArmor, custom C2 protocols (RC4, AES, DNS), YARA rules, shellcode, memory-forensics IOCs, anti-sandbox/anti-VM bypass.
[HINT] Download the complete skill directory including SKILL.md and all related files
| name | ctf-malware |
| description | Malware & network analysis: obfuscated JS/PowerShell/VBA, PE/.NET analysis, PyInstaller/PyArmor, custom C2 protocols (RC4, AES, DNS), YARA rules, shellcode, memory-forensics IOCs, anti-sandbox/anti-VM bypass. |
Quick reference for malware analysis CTF challenges. Each technique has a one-liner here; see supporting files for full details with code.
Dispatch on byte/file signals, never the sample's reputation.
| Signal | Technique โ file |
|---|---|
File starts with PK + contains package.json + .vsix extension | VSCode extension activation-event exfil โ scripts-and-obfuscation.md |
High-entropy extension.js/index.js > 50 KB with child_process | JS obfuscator.io / JSFuck deobfuscation โ scripts-and-obfuscation.md |
PowerShell -enc <b64> or IEX in parent process logs | PS script decode + reflection assembly โ scripts-and-obfuscation.md |
| PCAP with custom TCP port, payload bytes distributed uniformly | Custom crypto / RC4 / AES session key recovery โ c2-and-protocols.md |
PCAP with DNS TXT records carrying base32/64 blobs | DNS-based C2 exfil โ c2-and-protocols.md |
| PCAP where specific User-Agent receives non-default responses | UA-gated C2 path-hex-XOR โ (see ctf-forensics/network-advanced.md) |
PE32/PE64 with .text sections having packer signatures (UPX, VMProtect, Themida) | Unpacking + dump-after-decrypt โ pe-and-dotnet.md |
| .NET assembly with obfuscated strings + reflection calls | dnSpy + de4dot + string decryption โ pe-and-dotnet.md |
PyInstaller binary (MEIPASS, _MEIxxxx strings) | pyinstxtractor + pycdc โ pe-and-dotnet.md |
Process-injection IoCs in memory dump (RWX allocations, NtUnmapViewOfSection) | Volatility malfind + process hollowing detection โ scripts-and-obfuscation.md |
Anti-VM checks (cpuid hypervisor bit, RDTSC timing, specific drivers) | VM-detection bypass in analysis sandbox โ scripts-and-obfuscation.md |
| WebSocket frame with visible RC4 key-setup or KSA signatures | RC4 WebSocket reversing โ c2-and-protocols.md |
Recognize the artefact or protocol pattern. The sample's name is not a signal.
For inline snippets and quick-reference tables, see quickref.md. The Pattern Recognition Index above is the dispatch table โ always consult it first.
tshark -r file.pcap -Y "tcp.stream eq X" -T fields -e tcp.payload
Look for C2 communication patterns on unusual ports (e.g., port 21 not for FTP).
.rodata# Extract IPs/domains
strings malware | grep -E '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}'
strings malware | grep -E '[a-zA-Z0-9.-]+\.(com|net|org|io)'
# DNS queries
tshark -r capture.pcap -Y "dns.qry.name" -T fields -e dns.qry.name | sort -u
Pattern (Tampered Seal): Malware uses WSS over non-standard port with RC4 encryption.
Decryption workflow:
tcprewrite so Wireshark decodes TLSMalware communication patterns:
Pattern: C2 uses rotating passwords based on time/sequence
Analysis:
def get_current_password(timestamp):
# Password changes every hour
hour_bucket = timestamp // 3600
return hashlib.sha256(f"seed_{hour_bucket}".encode()).digest()
Common key derivation:
Analysis approach:
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
import hashlib
# Common pattern: key = MD5(password)
password = b"hardcoded_password"
key = hashlib.md5(password).digest()
# IV often first 16 bytes of ciphertext
iv = ciphertext[:16]
ct = ciphertext[16:]
cipher = AES.new(key, AES.MODE_CBC, iv)
plaintext = unpad(cipher.decrypt(ct), 16)
By constants:
0x637c777b, 0x63636363 (S-box)expand 32-byte k or 0x617078650x9E3779B9 (golden ratio)By structure:
Pattern (Stomaker): Malware uses Telegram bot to exfiltrate stolen data.
Recover exfiltrated data via bot token:
# If you have the bot API token from malware source:
import requests
TOKEN = "bot_token_here"
# Get updates (message history)
r = requests.get(f"https://api.telegram.org/bot{TOKEN}/getUpdates")
# Download files sent to bot
file_id = "..."
r = requests.get(f"https://api.telegram.org/bot{TOKEN}/getFile?file_id={file_id}")
file_path = r.json()['result']['file_path']
requests.get(f"https://api.telegram.org/file/bot{TOKEN}/{file_path}")
Pattern: PCAP contains Poison Ivy RAT (Remote Access Trojan) traffic. Poison Ivy uses Camellia cipher with the key derived from an attacker-supplied password (null-padded to key length). The default password is "admin".
# Decrypt using MITRE's ChopShop framework + FireEye Poison Ivy module
chopshop -f capture.pcap -s ./output/ "poisonivy_23x -c -w admin"
Identification:
Alternative decryption (Python):
from Crypto.Cipher import Camellia
password = b"admin"
key = password.ljust(32, b'\x00')[:32] # null-pad to 256 bits
cipher = Camellia.new(key, Camellia.MODE_ECB)
plaintext = cipher.decrypt(encrypted_data)
Key insight: Poison Ivy's encryption key is derived solely from the attacker password with null-byte padding โ no key derivation function. The default password "admin" is commonly left unchanged. ChopShop with poisonivy_23x module automates full session reconstruction (screenshots, file listings, keystrokes). Also try common passwords: "password", "p0ison", or challenge-provided hints.
peframe malware.exe # Quick triage
pe-sieve # Runtime analysis
pestudio # Static analysis (Windows)
Look for:
Common storage locations:
Extraction tools:
# PE resources
wrestool -x -t 10 malware.exe -o config.bin
# .NET resources
monodis --mresources malware.exe
# Strings in .rdata/.data
objdump -s -j .rdata malware.exe
Pattern: Deobfuscated .NET malware with DNS C2
Analysis with dnSpy:
AsmResolver for programmatic analysis:
using AsmResolver.DotNet;
var module = ModuleDefinition.FromFile("malware.dll");
foreach (var type in module.GetAllTypes()) {
foreach (var method in type.Methods) {
// Analyze method body
}
}
Tools: ILSpy, dnSpy, dotPeek
LimeRAT C2 extraction (Whisper Of The Pain):
from Crypto.Cipher import AES
import hashlib, base64
key_source = '${8\',`d0}n,~@J;oZ"9a'
md5 = hashlib.md5(key_source.encode()).hexdigest()
# Key = first 15 bytes of MD5 + full 16 bytes + null (64 hex chars -> 32 bytes)
key = bytes.fromhex(md5[:30] + md5 + '00')[:32]
cipher = AES.new(key, AES.MODE_ECB)
plaintext = cipher.decrypt(base64.b64decode(encrypted_b64))
# Step 1: Extract PyInstaller archive
python pyinstxtractor.py malware.exe
# Look for main .pyc file in extracted directory
# Step 2: If PyArmor-protected, use unpacker
# github.com/Svenskithesource/PyArmor-Unpacker
# Three methods available; choose based on PyArmor version
# Step 3: Clean up deobfuscated source
# Remove fake/dead-code functions (confusion code)
# Identify core encryption/exfiltration logic
Inline code / one-liners / common payloads. Loaded on demand from SKILL.md. Detailed techniques live in the category-specific support files listed in SKILL.md.
eval/bash with echo to print underlying code; extract base64/hex blobs and analyze with file. See scripts-and-obfuscation.md.eval with console.log, decode unescape(), atob(), String.fromCharCode().-enc base64, replace IEX with output. See scripts-and-obfuscation.md.call targets. See scripts-and-obfuscation.md.tshark -r file.pcap -Y "tcp.stream eq X" -T fields -e tcp.payload
Look for C2 on unusual ports. Extract IPs/domains with strings | grep. See c2-and-protocols.md.
tcprewrite, add RSA key for TLS decryption, find RC4 key in binary. See c2-and-protocols.md.0x637c777b S-box; ChaCha20: expand 32-byte k; TEA/XTEA: 0x9E3779B9; RC4: sequential S-box init. See c2-and-protocols.md.peframe malware.exe # Quick triage
pe-sieve # Runtime analysis
pestudio # Static analysis (Windows)
See pe-and-dotnet.md.
VM detection (CPUID, MAC prefix, registry, disk size), timing evasion (sleep/RDTSC sandbox detection), API hashing (ROR13/DJB2/CRC32 + hashdb lookup), process injection (hollowing, APC, CreateRemoteThread), environment checks. See scripts-and-obfuscation.md.
pyinstxtractor.py to extract, PyArmor-Unpacker for protected code. See pe-and-dotnet.md.getUpdates and getFile APIs. See c2-and-protocols.md.ar -x package.deb && tar -xf control.tar.xz # Check postinst scripts
See scripts-and-obfuscation.md.
Write YARA rules to match byte patterns, strings, and regex against files or memory dumps. Detect XOR loops ({31 ?? 80 ?? ?? 4? 75}), base64 blobs, encoded PowerShell. Use yarac to compile for faster scanning. See scripts-and-obfuscation.md.
Disassemble with objdump -b binary -m i386:x86-64, emulate with Unicorn Engine (hook syscalls safely), or use Capstone for programmatic disassembly. Look for XOR decoder stubs. See scripts-and-obfuscation.md.
vol3 windows.malfind detects injected code (PAGE_EXECUTE_READWRITE without mapped file). windows.pstree reveals suspicious parent-child relationships. YARA scan memory with yarascan.YaraScan. See scripts-and-obfuscation.md.
strings malware | grep -E '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}'
tshark -r capture.pcap -Y "dns.qry.name" -T fields -e dns.qry.name | sort -u
eval/bash with echo to print underlying codefile// Replace eval with console.log
eval = console.log;
// Then run the obfuscated code
// Common patterns
unescape() // URL decoding
String.fromCharCode() // Char codes
atob() // Base64
# Common obfuscation
-enc / -EncodedCommand # Base64 encoded
IEX / Invoke-Expression # Eval equivalent
[System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String($encoded))
Pattern: Obfuscation adds meaningless instructions around real code
Identification:
Filtering technique:
# Identify real calls by looking for patterns
# junk, junk, junk, CALL target, junk, junk
# Extract call targets, ignore surrounding noise
def extract_real_calls(disassembly):
calls = []
for instr in disassembly:
if instr.mnemonic == 'call' and not is_junk_target(instr.operand):
calls.append(instr)
return calls
ar -x package.deb # Unpack debian package
tar -xf control.tar.xz # Check control files
# Look for postinst scripts that execute payloads
# Behavioral monitoring with strace/ltrace
strace -f -e trace=network,file -o trace.log ./malware
ltrace -f -o ltrace.log ./malware
# Network monitoring during execution
# Terminal 1: capture traffic
sudo tcpdump -i any -w malware_traffic.pcap &
# Terminal 2: DNS monitoring
sudo tcpdump -i any port 53 -l | tee dns_queries.log &
# Terminal 3: run sample
timeout 60 ./malware
# File system monitoring (Linux)
inotifywait -m -r /tmp /var/tmp --format '%T %w%f %e' --timefmt '%H:%M:%S' &
./malware
# Process monitoring
watch -n 1 'ps aux | grep -v grep | grep malware'
# Memory string extraction during runtime
# Run malware, then dump strings from its memory
pid=$(pgrep malware)
strings /proc/$pid/maps
cat /proc/$pid/mem 2>/dev/null | strings | grep -i flag
# Or use gdb: gdb -p $pid -batch -ex 'dump memory dump.bin 0x400000 0x500000'
# Automated sandbox execution with timeout
import subprocess, os, tempfile
def run_sample(path, timeout=30):
"""Run malware sample with monitoring"""
with tempfile.NamedTemporaryFile(suffix='.pcap', delete=False) as pcap:
# Start packet capture
tcpdump = subprocess.Popen(
['sudo', 'tcpdump', '-i', 'any', '-w', pcap.name],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
try:
# Run with strace
result = subprocess.run(
['strace', '-f', '-e', 'trace=network,file', path],
capture_output=True, text=True, timeout=timeout)
print("STDOUT:", result.stdout[:500])
print("STDERR (syscalls):", result.stderr[:2000])
except subprocess.TimeoutExpired:
print(f"Sample ran for {timeout}s (killed)")
finally:
tcpdump.terminate()
print(f"PCAP saved: {pcap.name}")
Key insight: Dynamic analysis reveals runtime behavior that static analysis misses: actual C2 domains resolved, encryption keys in memory, dropped files, and anti-analysis checks that were bypassed. Always run in an isolated environment (VM snapshot, Docker container) and monitor network, filesystem, and process activity simultaneously.
# Basic YARA rule structure
cat > detect_malware.yar << 'EOF'
rule SuspiciousStrings {
meta:
description = "Detect common malware indicators"
strings:
$s1 = "cmd.exe /c" nocase
$s2 = "powershell -enc" nocase
$s3 = {4D 5A 90 00} // MZ header (hex pattern)
$s4 = /https?:\/\/[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/ // IP-based URL
$xor_loop = {31 ?? 80 ?? ?? 4? 75} // XOR decode loop pattern
condition:
2 of ($s*) or $xor_loop
}
EOF
# Scan files
yara detect_malware.yar suspicious_file.exe
yara -r detect_malware.yar /path/to/directory/ # Recursive scan
# Scan memory dump
yara detect_malware.yar memory.dmp
Common YARA patterns for CTFs:
rule Base64_PowerShell {
strings:
$enc = "powershell" nocase
$b64 = /[A-Za-z0-9+\/]{50,}={0,2}/
condition:
$enc and $b64
}
rule XOR_Encrypted_PE {
strings:
$mz = {4D 5A}
condition:
not $mz at 0 and filesize < 1MB
// PE without MZ header = likely XOR encrypted
}
Key insight: YARA rules match byte patterns, strings, and regex against files or memory. In CTFs, write rules to detect specific obfuscation patterns (XOR loops, base64 blobs, encoded PowerShell), then apply to memory dumps or malware samples. Use yarac to compile rules for faster scanning.
# Extract shellcode from binary
objdump -d shellcode.bin -b binary -m i386:x86-64 -M intel
# Emulate shellcode with unicorn engine
python3 << 'PYEOF'
from unicorn import *
from unicorn.x86_const import *
shellcode = open('shellcode.bin', 'rb').read()
mu = Uc(UC_ARCH_X86, UC_MODE_64)
BASE = 0x400000
STACK = 0x7fff0000
mu.mem_map(BASE, 0x1000)
mu.mem_map(STACK - 0x1000, 0x2000)
mu.mem_write(BASE, shellcode)
mu.reg_write(UC_X86_REG_RSP, STACK)
# Hook syscalls to trace behavior
def hook_syscall(mu, user_data):
rax = mu.reg_read(UC_X86_REG_RAX)
print(f"syscall: {rax}")
mu.hook_add(UC_HOOK_INSN, hook_syscall, None, 1, 0, UC_X86_INS_SYSCALL)
mu.emu_start(BASE, BASE + len(shellcode))
PYEOF
# Disassemble with capstone
python3 -c "
from capstone import *
md = Cs(CS_ARCH_X86, CS_MODE_64)
code = open('shellcode.bin','rb').read()
for i in md.disasm(code, 0x0):
print(f'{i.address:#x}: {i.mnemonic} {i.op_str}')
"
# Quick analysis with scdbg (Windows shellcode emulator)
scdbg /f shellcode.bin
Key insight: Shellcode in CTF malware challenges is often XOR-encoded or staged. Look for decoder stubs (short loops with XOR), then extract and decode the payload. Unicorn Engine emulation is safer than running shellcode โ it intercepts syscalls without executing them.
# Volatility 3 โ analyze memory dump for malware indicators
# List processes (look for suspicious names, unusual parents)
vol3 -f memory.dmp windows.pslist
vol3 -f memory.dmp windows.pstree
# Detect hidden/unlinked processes
vol3 -f memory.dmp windows.psscan
# Dump suspicious process memory
vol3 -f memory.dmp windows.memmap --pid PID --dump
# Extract injected code (process hollowing, DLL injection)
vol3 -f memory.dmp windows.malfind
# Network connections from malware
vol3 -f memory.dmp windows.netscan
# Command-line arguments (reveals malware parameters)
vol3 -f memory.dmp windows.cmdline
# DLL list per process (detect injected DLLs)
vol3 -f memory.dmp windows.dlllist --pid PID
# YARA scan on memory dump
vol3 -f memory.dmp yarascan.YaraScan --yara-rules "rule test { strings: $s = \"flag{\" condition: $s }"
Key insight: windows.malfind detects injected code by finding memory regions with PAGE_EXECUTE_READWRITE protection and no corresponding mapped file โ the hallmark of process injection. Combine with windows.pstree to find processes with unexpected parent-child relationships (e.g., svchost.exe spawned by cmd.exe).
Malware uses runtime checks to detect analysis environments and alter behavior. Bypass these to reach the actual malicious functionality.
Pattern: Malware checks for virtualization artifacts before executing payload. In CTFs, the "real" flag logic is behind these checks.
Key insight: Identify the detection method, then patch the check or fake the environment.
# Common VM detection checks and bypasses:
# 1. CPUID check (hypervisor bit 31 of ECX after CPUID leaf 1)
# Bypass: patch JNZ after CPUID to JMP, or run in bare metal
# In GDB: set $ecx = $ecx & ~(1<<31)
# 2. MAC address prefix (VMware: 00:0C:29, 00:50:56; VBox: 08:00:27)
# Bypass: change VM NIC MAC to real hardware prefix
# 3. Registry keys (Windows)
# HKLM\SOFTWARE\VMware, Inc.\VMware Tools
# HKLM\SYSTEM\CurrentControlSet\Services\VBoxGuest
# Bypass: delete keys or patch registry check
# 4. File/process checks
VM_ARTIFACTS = [
'vmtoolsd.exe', 'vmwaretray.exe', 'VBoxService.exe',
'qemu-ga.exe', 'sandboxie', 'wireshark.exe',
'/sys/class/dmi/id/product_name', # "VMware Virtual Platform"
'C:\\windows\\system32\\drivers\\vmmouse.sys',
]
# 5. Disk size check (VMs often have small disks)
# if total_disk < 60GB: exit()
# Bypass: expand VM disk or patch comparison
# 6. CPU count / RAM check
# if cpu_count < 2 or ram < 2GB: exit()
# Bypass: allocate more resources to VM
Pattern: Malware uses sleep(), GetTickCount(), or RDTSC to detect accelerated execution in sandboxes.
# Detection: large sleep followed by time check
# import time
# start = time.time()
# time.sleep(300) # 5 minutes
# if time.time() - start < 290: sys.exit() # Sandbox fast-forwarded sleep
# Bypass approaches:
# 1. Patch sleep to NOP: elf.asm(elf.symbols['sleep'], 'ret')
# 2. Hook GetTickCount/time() to return expected values
# 3. In GDB: set breakpoint after sleep, manually advance
# 4. Binary patching: change sleep(300) to sleep(0)
Key insight: Look for calls to sleep, time.sleep, NtDelayExecution, GetTickCount64, QueryPerformanceCounter. If the sample just sits there doing nothing, it's likely in a sleep-based anti-sandbox check.
Pattern: Instead of importing functions by name (visible in strings/imports), malware resolves API addresses at runtime by hashing function names and comparing to hardcoded hash values.
# Common hash algorithms for API resolution:
# ROR13 (rotate-right 13) โ most common, used by Metasploit
def ror13_hash(name):
h = 0
for c in name:
h = ((h >> 13) | (h << 19)) & 0xFFFFFFFF
h = (h + ord(c)) & 0xFFFFFFFF
return h
# DJB2 hash
def djb2_hash(name):
h = 5381
for c in name:
h = ((h * 33) + ord(c)) & 0xFFFFFFFF
return h
# CRC32-based
import binascii
def crc32_hash(name):
return binascii.crc32(name.encode()) & 0xFFFFFFFF
# Reversing: build lookup table from Windows API names
# hashdb.openanalysis.net โ online API hash lookup
# ShellcodeHasher โ matches hashes against known Windows APIs
# In Ghidra: find the hash comparison constant, look up in hashdb
# Pattern: loop over PEBโLdrโInMemoryOrderModuleList, hash each export name
Key insight: When strings output shows almost no readable API names but the binary clearly does complex operations, suspect API hashing. Look for the hash function (small loop with XOR/rotate/add), then use hashdb or build a rainbow table against kernel32.dll and ntdll.dll exports.
Pattern: Malware injects code into legitimate processes to evade detection. Understanding the injection method helps extract the actual payload.
# Classic injection chain:
# 1. OpenProcess(target_pid)
# 2. VirtualAllocEx(remote, ..., PAGE_EXECUTE_READWRITE)
# 3. WriteProcessMemory(remote, shellcode)
# 4. CreateRemoteThread(remote, shellcode_addr)
# Process hollowing:
# 1. CreateProcess(legitimate.exe, CREATE_SUSPENDED)
# 2. NtUnmapViewOfSection(hollow out the image)
# 3. VirtualAllocEx + WriteProcessMemory (write malicious PE)
# 4. SetThreadContext (point EIP/RIP to new entry)
# 5. ResumeThread
# Detection in memory dumps:
vol3 -f memory.dmp windows.malfind # PAGE_EXECUTE_READWRITE without file backing
vol3 -f memory.dmp windows.hollowfind # Hollowed processes (VAD vs PEB mismatch)
# APC injection (no new thread):
# QueueUserAPC(shellcode_addr, target_thread, ...)
# Thread executes shellcode on next alertable wait
# For CTF: dump the injected code region and analyze separately
vol3 -f memory.dmp windows.malfind --dump --pid <PID>
Pattern: Malware checks for specific environment conditions (hostname, username, domain, locale) to target specific victims or avoid analysis labs.
# Common checks:
# - Hostname matches target: if socket.gethostname() != 'TARGET-PC': exit()
# - Username: if os.getlogin() in ['admin', 'sandbox', 'malware']: exit()
# - Domain membership: if 'WORKGROUP' in os.environ.get('USERDOMAIN', ''): exit()
# - Locale/language: WinAPI GetUserDefaultLangID()
# - Specific file must exist: if not os.path.exists('C:\\Users\\victim\\document.xlsx'): exit()
# Bypass: set environment variables before running
# export COMPUTERNAME=TARGET-PC
# Or patch the comparison in the binary
Key insight: If a malware sample exits immediately or behaves differently than expected, trace its API calls with strace/ltrace or step through with a debugger. Look for string comparisons against environment values early in execution.
.vsix onStartupFinished Activation โ Obfuscated child_process Exfil (source: HTB University 2025 Snowy Extension)Trigger: forensics artefact: a .vsix file (VSCode extension) with package.json listing "activationEvents": ["onStartupFinished"]; extension.js contains base64-heavy globals + child_process.
Signals: unzip .vsix (it's a ZIP) โ extension.js > 50 KB with high-entropy string blobs; onStartupFinished activation event.
Mechanic: extension auto-runs at VSCode launch without user interaction (differs from classic npm supply-chain). Reverse steps:
unzip x.vsix -d ext/extension.js with webcrack or hand-rolled AST walker (usually JSFuck / obfuscator.io layers)context.globalState.update('key', ...), and any shelled-out child_process.spawn calls# yara
rule vsix_startup_child_process {
strings:
$a = "onStartupFinished"
$b = "child_process"
$c = "globalState.update"
condition:
uint32(0) == 0x04034b50 and 2 of them
}