Lesson 1 of 2 · 25 min read

Static Analysis — PE Header, Strings, and Import Analysis

Malware triage starts before any tool runs — a 30-second visual inspection of the file’s static properties often tells you which tools to run and what questions to ask.


Static Triage Checklist

File received: update.exe (SHA256: 4a3f7e2c8b1d5e9f3c7a2e6b4d8f1a5c)

Step 1: File identification                    Time: 30 seconds
  └── file update.exe                          (OS file type detection)
  └── xxd update.exe | head -2                 (check MZ header)
  └── sha256sum update.exe                     (hash for VT lookup)

Step 2: VirusTotal hash lookup                 Time: 2 minutes
  └── curl -s https://www.virustotal.com/... (or browser)
  └── If VT match → family identified, skip steps 3-5
  └── If no VT match → new or modified sample, continue

Step 3: DIE / PEStudio metadata               Time: 3 minutes
  └── Compilation timestamp (forged?)
  └── Architecture (x86/x64)
  └── Entropy per section
  └── Packer detection
  └── Compiler/language identification

Step 4: Import analysis                        Time: 5 minutes
  └── Which DLLs imported?
  └── Which functions — network? injection? anti-debug?
  └── Few imports → dynamic resolution → packed

Step 5: Strings analysis                       Time: 5 minutes
  └── strings -a update.exe | grep -E "http|https|[0-9]{1,3}\.[0-9]+"
  └── Registry keys, file paths, mutex names
  └── User-Agent strings, encoded data

Total: ~15 minutes before sandbox detonation

PE Header Analysis

# Basic file identification
file update.exe
# PE32+ executable (DLL) (console) x86-64, for MS Windows

# PE header examination with pecheck (Python)
# Install: pip install pefile
python3 << 'EOF'
import pefile
pe = pefile.PE("update.exe")

# Compilation timestamp (may be forged)
import datetime
ts = pe.FILE_HEADER.TimeDateStamp
print(f"Compile time: {datetime.datetime.utcfromtimestamp(ts)} UTC")

# Architecture
print(f"Machine: {hex(pe.FILE_HEADER.Machine)}")
# 0x8664 = x64, 0x14c = x86

# Entry point
print(f"Entry point: {hex(pe.OPTIONAL_HEADER.AddressOfEntryPoint)}")

# Section entropy
for section in pe.sections:
    name = section.Name.decode().rstrip('\x00')
    entropy = section.get_entropy()
    size = section.SizeOfRawData
    print(f"Section: {name:<10} Size: {size:>8}  Entropy: {entropy:.2f}")

EOF

Import Table Analysis

# Extract all imported functions using pefile
python3 << 'EOF'
import pefile
pe = pefile.PE("update.exe")

suspicious_imports = {
    # Process injection
    "OpenProcess", "VirtualAllocEx", "WriteProcessMemory", "CreateRemoteThread",
    "NtWriteVirtualMemory", "RtlCreateUserThread",
    # Privilege escalation
    "OpenProcessToken", "AdjustTokenPrivileges", "LookupPrivilegeValue",
    # Anti-analysis
    "IsDebuggerPresent", "CheckRemoteDebuggerPresent", "NtQueryInformationProcess",
    # Network
    "WSAStartup", "connect", "InternetOpen", "HttpSendRequest",
    # Persistence
    "RegSetValueEx", "RegCreateKeyEx", "CreateService",
}

print("=== IMPORT ANALYSIS ===")
if hasattr(pe, 'DIRECTORY_ENTRY_IMPORT'):
    for lib in pe.DIRECTORY_ENTRY_IMPORT:
        dll_name = lib.dll.decode()
        print(f"\n[DLL] {dll_name}")
        for imp in lib.imports:
            if imp.name:
                func_name = imp.name.decode()
                flag = " ⚠ SUSPICIOUS" if func_name in suspicious_imports else ""
                print(f"  {func_name}{flag}")
else:
    print("No import table found — binary may be packed or shellcode")
EOF

Strings Analysis

# Extract strings (ASCII + Unicode)
strings -a -n 8 update.exe > strings_output.txt       # ASCII, min 8 chars
strings -a -n 8 -e l update.exe >> strings_output.txt  # Unicode (LE 16-bit)

# IOC extraction from strings
echo "=== NETWORK IOCs ==="
grep -E "https?://|[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}" strings_output.txt

echo "=== REGISTRY KEYS ==="
grep -i "HKCU\|HKLM\|SOFTWARE\\\\Microsoft" strings_output.txt | head -10

echo "=== STAGING PATHS ==="
grep -iE "\\\\temp\\\\|\\\\tmp\\\\|appdata|programdata|\\\\users\\\\public" strings_output.txt

echo "=== BASE64 BLOBS (length > 40 chars) ==="
grep -E "^[A-Za-z0-9+/]{40,}={0,2}$" strings_output.txt | head -10

echo "=== MUTEX/EVENT NAMES ==="
grep -iE "Global\\\\|mutex|event" strings_output.txt | head -10

echo "=== COMMON C2 INDICATORS ==="
grep -E "User-Agent|Mozilla|Content-Type|Authorization" strings_output.txt

Identifying Common Malware Families from Static Analysis

Family          | Key Static Indicators
────────────────────────────────────────────────────────────────────
Cobalt Strike   | Very few imports (.text high entropy), no readable strings
beacon          | → Shellcode-like structure, requires dynamic analysis
                |
Meterpreter     | reverse_tcp/reverse_https strings, PAYLOAD_UUID
(staged)        | Few imports if staged shellcode
                |
Go-based        | No Windows API imports (Go uses syscall directly)
implants        | Strings: golang runtime, main.main, net/http
(Sliver, etc.)  | File signature: Go binary detected by DIE
                |
Njrat/AsyncRAT  | .NET binary (detected by DIE), Confuser obfuscator
(RATs)          | Strings: mutex name, registry key for persistence
                |
Emotet/Qakbot   | Packed (.text high entropy), minimal imports
(loaders)       | Forged compilation timestamp, drop path strings
                |
Mimikatz        | Strings: sekurlsa, lsadump, privilege::debug
                | Imports: LsaEnumerateLogonSessions, NtQueryVirtualMemory