🔬 Reverse Engineering
Malware Triage Workflow — Static Analysis
Step-by-step static analysis workflow for suspicious files. From file identification to YARA scanning — with every command copy-paste ready.
🔬 Static Analysis Workflow — 8 Steps
Safety first: All static analysis steps below are safe to run without executing the malware. Never double-click a suspicious file. Work in an isolated VM with host-guest file sharing disabled.
1
File Type Identification
Goal: confirm what you are actually dealing with — file extension lies
Never trust the file extension. Magic bytes (file header) tell the truth. A .pdf that starts with MZ is a Windows executable. A .jpg that starts with PK is a ZIP archive.
# Linux / Mac
file suspicious.pdf
xxd suspicious.pdf | head -4
# Windows (PowerShell)
$bytes = [System.IO.File]::ReadAllBytes("suspicious.pdf")
"{0:X2} {1:X2} {2:X2} {3:X2}" -f $bytes[0],$bytes[1],$bytes[2],$bytes[3]
# Common magic bytes
# MZ (4D 5A) = Windows PE (EXE/DLL)
# PK (50 4B) = ZIP / DOCX / XLSX / JAR
# %PDF = PDF
# D0 CF 11 E0 = OLE2 (DOC/XLS/PPT — older Office)
# 7z BC AF 27 = 7-Zip archive
# Rar! (52 61 72) = RAR archive
2
Hash & Reputation Lookup
Goal: check if this file is already known malicious — saves analysis time
# Generate hashes
md5sum suspicious.exe
sha256sum suspicious.exe
# PowerShell
Get-FileHash suspicious.exe -Algorithm MD5
Get-FileHash suspicious.exe -Algorithm SHA256
# Check on VirusTotal (do NOT upload sensitive files)
# Use SHA256 to search: https://www.virustotal.com/gui/file/
# CLI lookup via VT API (if you have a key)
curl -s "https://www.virustotal.com/api/v3/files/$(sha256sum suspicious.exe | cut -d' ' -f1)" \
-H "x-apikey: YOUR_VT_KEY" | python3 -m json.tool | grep -E "malicious|suspicious|type_description"
# Also check: MalwareBazaar (no account needed)
curl -s "https://mb-api.abuse.ch/api/v1/" \
-d "query=get_info&hash=$(sha256sum suspicious.exe | cut -d' ' -f1)"
3
Metadata Extraction
Goal: compilation timestamp, original filename, author, version info
# Extract all metadata
exiftool suspicious.exe
# PE-specific metadata (Python)
pip install pefile
python3 -c "
import pefile, datetime
pe = pefile.PE('suspicious.exe')
# Compilation timestamp
ts = pe.FILE_HEADER.TimeDateStamp
print('Compile time:', datetime.datetime.fromtimestamp(ts))
# Version info
if hasattr(pe, 'VS_VERSIONINFO'):
for info in pe.FileInfo[0]:
if hasattr(info, 'StringTable'):
for st in info.StringTable:
for k,v in st.entries.items():
print(f'{k.decode()}: {v.decode()}')
"
# Suspicious indicators:
# Compile time before 2010 = likely timestomped
# Compile time in future = definitely timestomped
# Missing version info = uncommon for legitimate software
# Mismatched product name = impersonation attempt
4
String Extraction
Goal: find hardcoded C2 domains, IPs, registry keys, file paths, API names
# Basic string extraction (min 8 chars, reduces noise)
strings -n 8 suspicious.exe
# Unicode strings too (common in modern malware)
strings -n 8 -e l suspicious.exe # 16-bit little-endian (Unicode)
strings -n 8 -e b suspicious.exe # 16-bit big-endian
# Filter for IOCs immediately
strings -n 8 suspicious.exe | grep -iE \
"(http|https|ftp)://|\.onion|[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}|cmd\.exe|powershell|wscript|rundll32|regsvr32|HKEY_"
# FLOSS — extracts obfuscated strings too (better than strings)
# pip install flare-floss
floss suspicious.exe --no-static-strings # decoded strings only
floss suspicious.exe # all strings + decoded
# Python bulk extraction
python3 -c "
import re, sys
with open('suspicious.exe','rb') as f: data = f.read()
patterns = {
'URLs': rb'https?://[^\x00-\x1f\x7f-\xff]{8,}',
'IPs': rb'\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b',
'Domains': rb'[a-zA-Z0-9\-]{3,}\.(com|net|org|ru|cn|tk|xyz|top|info)\b',
'Registry': rb'HKEY_[A-Z_]+\\[^\x00]{4,}',
}
for name,pat in patterns.items():
matches = set(re.findall(pat,data))
if matches:
print(f'\n=== {name} ===')
for m in matches: print(m.decode(errors='replace'))
"
5
Import / Export Analysis
Goal: understand capability from Windows API usage
python3 -c "
import pefile
pe = pefile.PE('suspicious.exe')
print('=== IMPORTS ===')
if hasattr(pe,'DIRECTORY_ENTRY_IMPORT'):
for lib in pe.DIRECTORY_ENTRY_IMPORT:
dll = lib.dll.decode()
funcs = [i.name.decode() if i.name else str(i.ordinal) for i in lib.imports if i.name]
print(f'\n{dll}:')
for f in funcs[:20]: print(f' {f}')
print('\n=== EXPORTS ===')
if hasattr(pe,'DIRECTORY_ENTRY_EXPORT'):
for exp in pe.DIRECTORY_ENTRY_EXPORT.symbols:
if exp.name: print(f' {exp.name.decode()}')
"
# Suspicious import combinations:
# VirtualAlloc + WriteProcessMemory + CreateRemoteThread → Process injection
# CreateThread + LoadLibrary → DLL injection
# CryptEncrypt/CryptDecrypt → Encrypted payload/C2
# InternetOpen + InternetConnect + HttpSendRequest → HTTP C2
# RegSetValueEx + HKCU\Software\Microsoft\Windows\CurrentVersion\Run → Persistence
# GetAsyncKeyState / SetWindowsHookEx → Keylogger
# FindFirstFile + CopyFile + DeleteFile → File operations (ransomware)
# NtQuerySystemInformation → Anti-analysis/sandbox detection
6
Packer / Obfuscation Detection
Goal: determine if file is packed — affects all subsequent analysis
# Detect-It-Easy (die) — best packer identifier
die suspicious.exe
# or: diec suspicious.exe (command-line version)
# Section entropy — packed sections have entropy > 7.0
python3 -c "
import pefile, math
pe = pefile.PE('suspicious.exe')
print(f'{'Section':<12} {'Size':>8} {'Entropy':>8} {'Verdict'}')
print('-'*45)
for section in pe.sections:
name = section.Name.decode().rstrip('\x00')
data = section.get_data()
# Calculate entropy
counts = [0]*256
for b in data: counts[b]+=1
entropy = -sum((c/len(data))*math.log2(c/len(data)) for c in counts if c)
verdict = 'PACKED' if entropy > 7.0 else 'ENCRYPTED' if entropy > 6.5 else 'Normal'
print(f'{name:<12} {len(data):>8} {entropy:>8.4f} {verdict}')
"
# If packed: unpack before further analysis
# UPX: upx -d suspicious.exe -o unpacked.exe
# Generic: run in controlled sandbox, dump from memory
7
Antivirus & Signature Scan
Goal: coverage from multiple engines without uploading to cloud
# ClamAV — offline, free, no data leaves your network
sudo apt install clamav && freshclam
clamscan suspicious.exe --verbose
# YARA — pattern matching
yara -r /path/to/rules/ suspicious.exe
# Download community YARA rules
git clone https://github.com/Yara-Rules/rules
git clone https://github.com/Neo23x0/signature-base
# Run all rules
yara -r rules/ suspicious.exe 2>/dev/null | head -50
# Quick check against India-relevant families
cat > india_apt.yar << 'EOF'
rule Crimson_RAT_Strings {
strings:
$s1 = "CrimsonRAT" nocase
$s2 = "Screen Capture" nocase
$s3 = "GetDriveInfo" nocase
$s4 = { 43 72 69 6D 73 6F 6E }
condition: 2 of them
}
rule AllaKore_RAT {
strings:
$s1 = "AllaKore" nocase
$s2 = "rdp_client" nocase
$net = "hxxp" nocase
condition: 2 of them
}
EOF
yara india_apt.yar suspicious.exe
8
Triage Verdict & Report
Goal: classify sample and document findings for escalation
Based on steps 1-7, classify the sample and generate a triage report. If analysis is inconclusive, escalate to dynamic analysis (sandbox). If clearly malicious, move to IOC extraction and SIEM rule creation.
# Quick triage report template
cat > triage_report.txt << EOF
MALWARE TRIAGE REPORT
=====================
Analyst: $(whoami)
Date: $(date)
File: suspicious.exe
SHA256: $(sha256sum suspicious.exe | cut -d' ' -f1)
MD5: $(md5sum suspicious.exe | cut -d' ' -f1)
FILE IDENTIFICATION
Type: [PE32 executable / DLL / other]
Packer: [UPX / MPRESS / None detected]
Compile time: [from exiftool / pefile]
Timestamp legitimate: [Yes/No/Suspicious]
REPUTATION
VirusTotal: [X/70 detections]
MalwareBazaar: [Known / Unknown]
Family: [If identified]
CAPABILITY INDICATORS
Imports: [Key suspicious imports found]
Strings: [Key strings — C2, persistence, capability]
Network IOCs: [IPs, domains found]
File IOCs: [File paths, registry keys]
VERDICT
[ ] Malicious — confirmed
[ ] Suspicious — needs dynamic analysis
[ ] Clean — no indicators found
RECOMMENDED ACTION
[Next steps: sandbox / SIEM rules / block at perimeter / escalate]
EOF
cat triage_report.txt
🔤 String Analysis — What to Look For
| String Pattern | What It Suggests | Severity |
|---|---|---|
http:// or https:// hardcoded URLs | C2 communication, payload download, exfiltration endpoint | High |
.onion domains | Tor-based C2 — active evasion of network monitoring | High |
| Raw IP addresses (non-local) | Hardcoded C2 server — more reliable than domain-based | High |
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run | Registry-based persistence mechanism | High |
%APPDATA%, %TEMP%, %SYSTEMROOT% | Drop locations for malware components or persistence | Medium |
cmd.exe /c, powershell -enc | Shell command execution — often for second-stage payload | High |
wscript.exe, mshta.exe, rundll32.exe | Living-off-the-land binary (LOLBin) abuse | High |
Long Base64 strings ([A-Za-z0-9+/]{50,}={0,2}) | Encoded payload, config, or obfuscated command | High |
Hex-encoded data (0x prefixed) | Shellcode or encoded payload | Medium |
SELECT, INSERT, sqlite | Browser credential/data theft capability | High |
GetAsyncKeyState, keylog | Keylogger functionality | High |
CreateToolhelp32Snapshot, PROCESSENTRY32 | Process enumeration — reconnaissance or AV evasion | Medium |
| Version strings mismatching file metadata | Attempted impersonation of legitimate software | Medium |
| Mutex names (random-looking strings) | Mutex for single-instance check — documented in malware families | Medium |
Decode Base64 strings found in sample
# Decode Base64 string
echo "TVoAAA==" | base64 -d | xxd | head
# Find and decode all Base64 in file
python3 << 'EOF'
import re, base64
with open('suspicious.exe','rb') as f: data = f.read().decode('latin-1')
pattern = r'[A-Za-z0-9+/]{40,}={0,2}'
for match in set(re.findall(pattern, data)):
try:
decoded = base64.b64decode(match)
# Only print if decoded result looks readable or is PE
if decoded[:2] == b'MZ' or any(32 <= b < 127 for b in decoded[:20]):
print(f'[B64] {match[:40]}...')
print(f' → {decoded[:80]}')
except: pass
EOF
📦 PE Header Quick Analysis
Section entropy interpretation
| Entropy Range | Interpretation | Action |
|---|---|---|
| 0.0 – 1.0 | All zeros / uniform data | Normal (uninitialised data section) |
| 4.0 – 6.5 | Normal code/data | Normal — proceed with analysis |
| 6.5 – 7.0 | Compressed or dense data | Suspicious — check section name and size |
| 7.0 – 8.0 | Packed, encrypted, or compressed | Likely packed — unpack before analysis |
Full PE metadata dump
python3 << 'EOF'
import pefile, datetime, math
pe = pefile.PE('suspicious.exe')
h = pe.FILE_HEADER
oh = pe.OPTIONAL_HEADER
print("=== FILE HEADER ===")
print(f"Machine: {hex(h.Machine)} ({'x64' if h.Machine==0x8664 else 'x86'})")
print(f"Compile time: {datetime.datetime.fromtimestamp(h.TimeDateStamp)}")
print(f"Characteristics: {hex(h.Characteristics)}")
print(f" DLL: {bool(h.Characteristics & 0x2000)}")
print(f" Executable: {bool(h.Characteristics & 0x0002)}")
print("\n=== OPTIONAL HEADER ===")
print(f"Entry point: {hex(oh.AddressOfEntryPoint)}")
print(f"Image base: {hex(oh.ImageBase)}")
print(f"Subsystem: {oh.Subsystem} ({'GUI' if oh.Subsystem==2 else 'Console' if oh.Subsystem==3 else 'Other'})")
print("\n=== SECTIONS ===")
for s in pe.sections:
name = s.Name.decode().rstrip('\x00')
data = s.get_data()
counts = [0]*256
for b in data: counts[b]+=1
ent = -sum((c/len(data))*math.log2(c/len(data)) for c in counts if c) if data else 0
print(f" {name:<10} VA:{hex(s.VirtualAddress):<10} Size:{len(data):<8} Entropy:{ent:.2f}")
print("\n=== IMPORTS (first 5 DLLs) ===")
if hasattr(pe,'DIRECTORY_ENTRY_IMPORT'):
for lib in pe.DIRECTORY_ENTRY_IMPORT[:5]:
print(f" {lib.dll.decode()}")
for imp in lib.imports[:5]:
if imp.name: print(f" {imp.name.decode()}")
EOF
🎯 IOC Extraction — Static
python3 << 'EOF'
import re, pefile, sys
fname = 'suspicious.exe'
with open(fname,'rb') as f: raw = f.read()
text = raw.decode('latin-1')
iocs = {'IPs':set(),'Domains':set(),'URLs':set(),'Registry':set(),
'Files':set(),'Emails':set(),'Mutexes':set()}
iocs['IPs'] = set(re.findall(
r'\b(?!10\.|192\.168\.|172\.(1[6-9]|2\d|3[01])\.|127\.|0\.0\.0\.0)(?:\d{1,3}\.){3}\d{1,3}\b',text))
iocs['URLs'] = set(re.findall(
r'https?://[a-zA-Z0-9\-\._~:/?#\[\]@!$&\'()*+,;=%]{10,}',text))
iocs['Domains'] = set(re.findall(
r'\b[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.(?:com|net|org|ru|cn|tk|xyz|top|info|biz|io|cc)\b',
text)) - {u.split('/')[2] for u in iocs['URLs'] if '/' in u}
iocs['Registry'] = set(re.findall(
r'HKEY_[A-Z_]+\\[A-Za-z0-9\\_ ]{4,}',text))
iocs['Files'] = set(re.findall(
r'[A-Za-z]:\\[A-Za-z0-9\\_ \.\-]{6,}\.(?:exe|dll|bat|ps1|vbs|js|tmp|dat)',
text))
iocs['Emails'] = set(re.findall(
r'[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}',text))
for cat, items in iocs.items():
if items:
print(f'\n=== {cat} ({len(items)}) ===')
for item in sorted(items)[:20]:
print(f' {item}')
# Export to CSV
import csv
with open('iocs.csv','w',newline='') as f:
w = csv.writer(f)
w.writerow(['Type','Indicator'])
for cat, items in iocs.items():
for item in items: w.writerow([cat,item])
print('\n[+] IOCs saved to iocs.csv')
EOF