Imported from at-src/cold-box (
cold-box-room/skills/library/cb-static-malware-analysis-with-pe-studio/SKILL.md). Install upstream withnpx skills add at-src/cold-box --skill cb-static-malware-analysis-with-pe-studio. Copyright stays with the author.
Static Malware Analysis With Pe Studio (cold-box)
Journal ID:
CB-SKL-326— cite this in every journal entry while following this playbook.
Cold-box constraints (always)
- Evidence lives on the sealed operation table — read only through the viewport (
input_relpath). - Never write on
operation-table/; tool output goes torecords/{case_id}/scratch/. - Log this playbook in the case journal with
CB-SKL-326(log_skill) when you adopt it and at each major step. - Every SIFT execution:
run_sift_toolwithpurpose,why, and cite returnedtool_id+audit_idin the journal. - Prefer
list_sift_tools(verified_only=true)— skip[BAD — DO NOT USE]tools. - Load this playbook with
get_skill("cb-static-malware-analysis-with-pe-studio"); do not treat markdown code blocks as direct shell commands.
Agent loop
suggest_skills(case_profile=...)orget_skill("cb-static-malware-analysis-with-pe-studio")→ noteCB-SKL-326log_skill(case_id, journal_id="CB-SKL-326", action="adopted", note="...")list_sift_tools(verified_only=true)→ pick tools from the map belowdescribe_sift_tool(tool_id)→run_sift_tool(...)→ journal stdout withCB-SKL-326+audit_idlog_skill(..., action="finding", note="...")when recording conclusions; self-correct if outputs conflict
When to use
- A suspicious Windows executable has been collected and needs initial triage before sandbox execution
- You need to identify imports, strings, and resources that reveal malware functionality without running the sample
- Determining whether a PE file is packed, obfuscated, or contains anti-analysis techniques
- Extracting indicators of compromise (hashes, URLs, IPs, registry keys) embedded in a binary
- Classifying a sample's capabilities based on its import table and section characteristics
Tool map (SIFT via MCP)
Execution mode: sift_runnable — SIFT tools below are available on this host.
| binary | tool_id | verified | available |
|---|---|---|---|
sha256sum |
SIFT-018 |
yes | yes |
strings |
SIFT-044 |
yes | yes |
sha1sum |
SIFT-017 |
yes | yes |
md5sum |
SIFT-015 |
yes | yes |
grep |
SIFT-010 |
yes | yes |
yara |
SIFT-045 |
no | no |
file |
SIFT-008 |
yes | yes |
cut |
SIFT-006 |
yes | yes |
jq |
SIFT-013 |
yes | yes |
Cold-box MCP examples
Use these instead of running shell blocks directly. Always log {journal_id} in purpose/why or via log_skill.
sha256sum → SIFT-018
{
"tool_id": "SIFT-018",
"case_id": "{case_id}",
"input_relpath": "{artifact_relpath}",
"purpose": "[CB-SKL-326] sha256sum per playbook step",
"why": "Executing cb-static-malware-analysis-with-pe-studio \u2014 see Procedure section",
"extra_args": []
}
strings → SIFT-044
{
"tool_id": "SIFT-044",
"case_id": "{case_id}",
"input_relpath": "{artifact_relpath}",
"purpose": "[CB-SKL-326] strings per playbook step",
"why": "Executing cb-static-malware-analysis-with-pe-studio \u2014 see Procedure section",
"extra_args": []
}
sha1sum → SIFT-017
{
"tool_id": "SIFT-017",
"case_id": "{case_id}",
"input_relpath": "{artifact_relpath}",
"purpose": "[CB-SKL-326] sha1sum per playbook step",
"why": "Executing cb-static-malware-analysis-with-pe-studio \u2014 see Procedure section",
"extra_args": []
}
md5sum → SIFT-015
{
"tool_id": "SIFT-015",
"case_id": "{case_id}",
"input_relpath": "{artifact_relpath}",
"purpose": "[CB-SKL-326] md5sum per playbook step",
"why": "Executing cb-static-malware-analysis-with-pe-studio \u2014 see Procedure section",
"extra_args": []
}
grep → SIFT-010
{
"tool_id": "SIFT-010",
"case_id": "{case_id}",
"input_relpath": "{artifact_relpath}",
"purpose": "[CB-SKL-326] grep per playbook step",
"why": "Executing cb-static-malware-analysis-with-pe-studio \u2014 see Procedure section",
"extra_args": []
}
yara → SIFT-045
{
"tool_id": "SIFT-045",
"case_id": "{case_id}",
"input_relpath": "{artifact_relpath}",
"purpose": "[CB-SKL-326] yara per playbook step",
"why": "Executing cb-static-malware-analysis-with-pe-studio \u2014 see Procedure section",
"extra_args": []
}
file → SIFT-008
{
"tool_id": "SIFT-008",
"case_id": "{case_id}",
"input_relpath": "{artifact_relpath}",
"purpose": "[CB-SKL-326] file per playbook step",
"why": "Executing cb-static-malware-analysis-with-pe-studio \u2014 see Procedure section",
"extra_args": []
}
cut → SIFT-006
{
"tool_id": "SIFT-006",
"case_id": "{case_id}",
"input_relpath": "{artifact_relpath}",
"purpose": "[CB-SKL-326] cut per playbook step",
"why": "Executing cb-static-malware-analysis-with-pe-studio \u2014 see Procedure section",
"extra_args": []
}
Journal logging
When you adopt this playbook, append to records/{case_id}/{case_id}.md:
## {timestamp} — skill `CB-SKL-326` (`cb-static-malware-analysis-with-pe-studio`)
- **action:** adopted | step | finding | deferred | completed
- **note:** What you did or concluded under this playbook
- **related_audit_ids:** (optional) CB-… from run_sift_tool
Or call MCP: log_skill(case_id, journal_id="{journal_id}", action="adopted", note="...")
Cold-box path translation
When the procedure below uses host paths, translate as follows:
| Procedure path | Cold-box equivalent |
|---|---|
C:\Evidence\... / /cases/... |
{input_relpath} on the sealed table (via viewport) |
C:\Output\... / /analysis/... |
records/{case_id}/scratch/ (tool stdout/files) |
| Live SIEM / cloud console steps | Reference only on cold-box — note capability gap in journal |
Do not copy evidence off the table except into records/{case_id}/scratch/ via run_sift_tool.
Procedure
When to Use
- A suspicious Windows executable has been collected and needs initial triage before sandbox execution
- You need to identify imports, strings, and resources that reveal malware functionality without running the sample
- Determining whether a PE file is packed, obfuscated, or contains anti-analysis techniques
- Extracting indicators of compromise (hashes, URLs, IPs, registry keys) embedded in a binary
- Classifying a sample's capabilities based on its import table and section characteristics
Do not use for dynamic behavioral analysis requiring execution; use a sandbox (Cuckoo, ANY.RUN) for runtime behavior observation.
Prerequisites
- PEStudio (free edition from https://www.winitor.com/) installed on an isolated analysis workstation
- Python 3.8+ with
pefilelibrary for scripted PE analysis (pip install pefile) - CFF Explorer or PE-bear as supplementary PE analysis tools
- Access to VirusTotal API for hash lookups and community intelligence
- Isolated analysis VM with no network connectivity to production systems
- FLOSS (FireEye Labs Obfuscated String Solver) for extracting obfuscated strings
Workflow
Step 1: Compute File Hashes and Verify Sample Integrity
Generate cryptographic hashes for identification and intelligence lookup:
# Generate MD5, SHA-1, and SHA-256 hashes
md5sum suspect.exe
sha1sum suspect.exe
sha256sum suspect.exe
# Check hash against VirusTotal
curl -s -X GET "https://www.virustotal.com/api/v3/files/$(sha256sum suspect.exe | cut -d' ' -f1)" \
-H "x-apikey: $VT_API_KEY" | jq '.data.attributes.last_analysis_stats'
# Get file type with magic bytes verification
file suspect.exe
Step 2: Examine PE Headers and Section Table
Open the sample in PEStudio and inspect structural properties:
PEStudio Analysis Points:
━━━━━━━━━━━━━━━━━━━━━━━━━
File Header: Compilation timestamp, target architecture (x86/x64)
Optional Header: Entry point address, image base, subsystem (GUI/console)
Section Table: Section names, virtual/raw sizes, entropy values
High entropy (>7.0) in .text/.rsrc suggests packing
Signatures: Authenticode signature presence and validity
Scripted PE Header Analysis with pefile:
import pefile
import hashlib
import math
pe = pefile.PE("suspect.exe")
# Compilation timestamp
import datetime
timestamp = pe.FILE_HEADER.TimeDateStamp
compile_time = datetime.datetime.utcfromtimestamp(timestamp)
print(f"Compile Time: {compile_time} UTC")
# Section analysis with entropy calculation
for section in pe.sections:
name = section.Name.decode().rstrip('\x00')
entropy = section.get_entropy()
raw_size = section.SizeOfRawData
virtual_size = section.Misc_VirtualSize
ratio = virtual_size / raw_size if raw_size > 0 else 0
print(f"Section: {name:8s} Entropy: {entropy:.2f} Raw: {raw_size:>10} Virtual: {virtual_size:>10} Ratio: {ratio:.2f}")
if entropy > 7.0:
print(f" [!] HIGH ENTROPY - likely packed or encrypted")
if ratio > 10:
print(f" [!] HIGH V/R RATIO - unpacking stub likely present")
Step 3: Analyze Import Address Table (IAT)
Identify suspicious API imports that indicate malware capabilities:
# Extract and categorize imports
suspicious_imports = {
"Process Injection": ["VirtualAllocEx", "WriteProcessMemory", "CreateRemoteThread", "NtCreateThreadEx"],
"Keylogging": ["GetAsyncKeyState", "SetWindowsHookExA", "GetKeyState"],
"Persistence": ["RegSetValueExA", "CreateServiceA", "SchTasksCreate"],
"Evasion": ["IsDebuggerPresent", "CheckRemoteDebuggerPresent", "NtQueryInformationProcess"],
"Network": ["InternetOpenA", "HttpSendRequestA", "URLDownloadToFileA", "WSAStartup"],
"File Operations": ["CreateFileA", "WriteFile", "DeleteFileA", "MoveFileA"],
"Crypto": ["CryptEncrypt", "CryptDecrypt", "CryptAcquireContextA"],
}
for entry in pe.DIRECTORY_ENTRY_IMPORT:
dll_name = entry.dll.decode()
for imp in entry.imports:
if imp.name:
func_name = imp.name.decode()
for category, funcs in suspicious_imports.items():
if func_name in funcs:
print(f"[!] {category}: {dll_name} -> {func_name}")
Step 4: Extract and Analyze Strings
Use FLOSS for obfuscated strings and standard strings extraction:
# Standard strings extraction (ASCII and Unicode)
strings -a suspect.exe > strings_ascii.txt
strings -el suspect.exe > strings_unicode.txt
# FLOSS for decoded/deobfuscated strings
floss suspect.exe --output-json floss_output.json
# Search for network indicators in strings
grep -iE "(http|https|ftp)://" strings_ascii.txt
grep -iE "([0-9]{1,3}\.){3}[0-9]{1,3}" strings_ascii.txt
grep -iE "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}" strings_ascii.txt
# Search for registry keys
grep -i "HKLM\\|HKCU\\|SOFTWARE\\|CurrentVersion\\Run" strings_ascii.txt
# Search for file paths and extensions
grep -iE "\.(exe|dll|bat|ps1|vbs|tmp)" strings_ascii.txt
Step 5: Inspect Resources and Embedded Data
Examine the PE resource section for embedded payloads or configuration:
# Extract resources from PE file
if hasattr(pe, 'DIRECTORY_ENTRY_RESOURCE'):
for resource_type in pe.DIRECTORY_ENTRY_RESOURCE.entries:
if hasattr(resource_type, 'directory'):
for resource_id in resource_type.directory.entries:
if hasattr(resource_id, 'directory'):
for resource_lang in resource_id.directory.entries:
data = pe.get_data(resource_lang.data.struct.OffsetToData,
resource_lang.data.struct.Size)
entropy = calculate_entropy(data)
print(f"Resource Type: {resource_type.id} Size: {len(data)} Entropy: {entropy:.2f}")
if entropy > 7.0:
print(f" [!] High entropy resource - possible embedded payload")
# Check for PE signature in resource (embedded executable)
if data[:2] == b'MZ':
print(f" [!] Embedded PE detected in resource")
with open(f"extracted_resource_{resource_type.id}.bin", "wb") as f:
f.write(data)
Step 6: Check for Packing and Protection
Determine if the binary is packed or protected:
# Detect packer with Detect It Easy (DIE)
diec suspect.exe
# Check with PEiD signatures (command-line version)
python3 -c "
import pefile
pe = pefile.PE('suspect.exe')
# Check for common packer section names
packer_sections = {'.upx0': 'UPX', '.aspack': 'ASPack', '.adata': 'ASPack',
'.nsp0': 'NsPack', '.vmprotect': 'VMProtect', '.themida': 'Themida'}
for section in pe.sections:
name = section.Name.decode().rstrip('\x00').lower()
if name in packer_sections:
print(f'[!] Packer detected: {packer_sections[name]} (section: {name})')
# Check import table size (very few imports suggest packing)
import_count = sum(len(entry.imports) for entry in pe.DIRECTORY_ENTRY_IMPORT)
if import_count < 10:
print(f'[!] Only {import_count} imports - likely packed')
"
Step 7: Generate Static Analysis Report
Compile all findings into a structured triage report:
Document the following for each analyzed sample:
- File identification (hashes, file type, size, compile timestamp)
- Packing/protection status and identified packer
- Suspicious imports categorized by capability
- Network indicators extracted from strings (IPs, domains, URLs)
- Embedded resources and their characteristics
- Overall threat assessment and recommended next steps (sandbox execution, YARA rule creation)
Key Concepts
| Term | Definition |
|---|---|
| PE (Portable Executable) | The file format for Windows executables (.exe, .dll, .sys) containing headers, sections, imports, and resources that define how the OS loads the binary |
| Import Address Table (IAT) | PE structure listing external DLL functions the executable calls at runtime; reveals program capabilities and intent |
| Section Entropy | Statistical measure of randomness in a PE section; values above 7.0 (out of 8.0) indicate compression, encryption, or packing |
| FLOSS | FireEye Labs Obfuscated String Solver; automatically extracts and decodes obfuscated strings that standard strings misses |
| Packing | Compression or encryption of a PE file's code section to hinder static analysis; requires runtime unpacking stub to execute |
| PE Resources | Data section within a PE file that can contain icons, dialogs, version info, or attacker-embedded payloads and configuration data |
| Compilation Timestamp | Timestamp in the PE header indicating when the binary was compiled; can be forged but often reveals development timeline |
Tools & Systems
- PEStudio: Free Windows tool for static analysis of PE files providing indicators, imports, strings, and resource inspection in a single interface
- pefile (Python): Python library for parsing and analyzing PE file structures programmatically for automated analysis pipelines
- FLOSS: FireEye tool that extracts obfuscated strings from malware using static analysis techniques including stack string decoding
- Detect It Easy (DIE): Packer and compiler detection tool that identifies protectors, compilers, and linkers used to build PE files
- CFF Explorer: Advanced PE editor and viewer for detailed inspection of PE headers, sections, imports, and resource directories
Common Scenarios
Scenario: Triaging a Suspicious Email Attachment
Context: SOC receives an alert on a suspicious executable attached to a phishing email. The file needs rapid triage to determine if it is malicious before committing sandbox resources.
Approach:
- Compute SHA-256 hash and query VirusTotal for existing detections and community comments
- Open in PEStudio and check the indicators tab for red/yellow flagged items
- Verify compile timestamp (future dates or dates from 1970 indicate timestamp manipulation)
- Check imports for VirtualAllocEx, CreateRemoteThread (injection), URLDownloadToFileA (downloader)
- Extract strings and search for C2 URLs, IP addresses, and file paths
- Check resources for embedded PE files or high-entropy data blobs
- Assess packing status; if packed, note the packer and plan for unpacking before deeper analysis
Pitfalls:
- Trusting the PE compile timestamp without corroborating evidence (timestamps are trivially forged)
- Concluding a file is benign because it has few suspicious imports (packed malware hides real imports)
- Missing Unicode strings by only running ASCII string extraction
- Not checking overlay data appended after the last PE section (common hiding spot for configuration data)
Output Format
STATIC MALWARE ANALYSIS REPORT
=================================
Sample: suspect.exe
MD5: d41d8cd98f00b204e9800998ecf8427e
SHA-256: e3b0c44298fc1c149afbf4c8996fb924...
File Size: 245,760 bytes
File Type: PE32 executable (GUI) Intel 80386
Compile Time: 2025-09-14 08:23:15 UTC
PACKING STATUS
Packer Detected: None (native binary)
Section Entropy: .text=6.42 .rdata=4.89 .data=3.21 .rsrc=7.81
Note: .rsrc section entropy elevated - check resources
SUSPICIOUS IMPORTS
[INJECTION] kernel32.dll -> VirtualAllocEx
[INJECTION] kernel32.dll -> WriteProcessMemory
[INJECTION] kernel32.dll -> CreateRemoteThread
[EVASION] kernel32.dll -> IsDebuggerPresent
[NETWORK] wininet.dll -> InternetOpenA
[NETWORK] wininet.dll -> HttpSendRequestA
[PERSISTENCE] advapi32.dll -> RegSetValueExA
EXTRACTED INDICATORS
URLs: hxxps://update.malicious[.]com/gate.php
IPs: 185.220.101[.]42, 91.215.85[.]17
Registry Keys: HKCU\Software\Microsoft\Windows\CurrentVersion\Run\svchost
File Paths: C:\Users\Public\svchost.exe
EMBEDDED RESOURCES
Resource 101: Size=98304 Entropy=7.89 [!] Embedded PE detected
Resource 102: Size=4096 Entropy=2.14 (configuration XML)
ASSESSMENT
Threat Level: HIGH
Classification: Dropper with process injection capabilities
Recommended: Execute in sandbox, extract embedded PE for separate analysis
Self-correction
- If a SIFT tool errors, read
describe_sift_tooland pick an alternate from the tool map. - If procedure steps require SIEM/cloud/live APIs, log
{journal_id}+action=deferredwith the gap. - Do not override
[BAD — DO NOT USE]tools. - Mark inference vs confirmed in journal; tie confirmed findings to
audit_id.
Limits
- Playbooks guide reasoning; confirmed findings require tool output in scratch with matching
audit_id. - Reference-mode steps are not executable on cold-box until equivalent SIFT tooling exists.