🔬 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 PatternWhat It SuggestsSeverity
http:// or https:// hardcoded URLsC2 communication, payload download, exfiltration endpointHigh
.onion domainsTor-based C2 — active evasion of network monitoringHigh
Raw IP addresses (non-local)Hardcoded C2 server — more reliable than domain-basedHigh
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunRegistry-based persistence mechanismHigh
%APPDATA%, %TEMP%, %SYSTEMROOT%Drop locations for malware components or persistenceMedium
cmd.exe /c, powershell -encShell command execution — often for second-stage payloadHigh
wscript.exe, mshta.exe, rundll32.exeLiving-off-the-land binary (LOLBin) abuseHigh
Long Base64 strings ([A-Za-z0-9+/]{50,}={0,2})Encoded payload, config, or obfuscated commandHigh
Hex-encoded data (0x prefixed)Shellcode or encoded payloadMedium
SELECT, INSERT, sqliteBrowser credential/data theft capabilityHigh
GetAsyncKeyState, keylogKeylogger functionalityHigh
CreateToolhelp32Snapshot, PROCESSENTRY32Process enumeration — reconnaissance or AV evasionMedium
Version strings mismatching file metadataAttempted impersonation of legitimate softwareMedium
Mutex names (random-looking strings)Mutex for single-instance check — documented in malware familiesMedium

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 RangeInterpretationAction
0.0 – 1.0All zeros / uniform dataNormal (uninitialised data section)
4.0 – 6.5Normal code/dataNormal — proceed with analysis
6.5 – 7.0Compressed or dense dataSuspicious — check section name and size
7.0 – 8.0Packed, encrypted, or compressedLikely 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

✅ Static Analysis Triage Checklist