Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-SkillsFramework mappings
MITRE ATT&CK
When to Use
- When triaging suspicious PDF attachments from phishing emails
- During malware analysis of PDF-based exploit documents
- When extracting embedded JavaScript, shellcode, or executables from PDFs
- For forensic examination of weaponized document artifacts
- When building detection signatures for PDF-based threats
Prerequisites
- Python 3.8+ with peepdf-3 installed (pip install peepdf-3)
- pdfid.py and pdf-parser.py from Didier Stevens suite
- Isolated analysis environment (VM or sandbox)
- Optional: PyV8 for JavaScript emulation within peepdf
- Optional: Pylibemu for shellcode analysis
Workflow
- Triage with pdfid: Scan PDF for suspicious keywords (/JS, /JavaScript, /OpenAction, /Launch, /EmbeddedFile).
- Interactive Analysis: Open PDF in peepdf interactive mode to explore object structure.
- Identify Suspicious Objects: Locate objects containing JavaScript, streams, or encoded data.
- Extract Content: Dump suspicious streams and decode filters (FlateDecode, ASCIIHexDecode).
- Deobfuscate JavaScript: Analyze extracted JS for shellcode, heap sprays, or exploit code.
- Check VirusTotal: Use peepdf vtcheck to cross-reference file hash with AV detections.
- Generate IOCs: Extract URLs, domains, hashes, and shellcode signatures.
Key Concepts
| Concept | Description |
|---|---|
| /OpenAction | Automatic action executed when PDF is opened |
| /JavaScript /JS | Embedded JavaScript code in PDF objects |
| /Launch | Action that launches external applications |
| /EmbeddedFile | File embedded within the PDF structure |
| FlateDecode | zlib compression filter used to hide content |
| Object Streams | PDF objects stored in compressed streams |
Tools & Systems
| Tool | Purpose |
|---|---|
| peepdf / peepdf-3 | Interactive PDF analysis with JS emulation |
| pdfid.py | Quick triage scanning for suspicious keywords |
| pdf-parser.py | Deep object-level PDF parsing |
| VirusTotal | Hash lookup and AV detection cross-reference |
| CyberChef | Decode and transform extracted payloads |
Output Format
Analysis Report: PDF-MAL-[DATE]-[SEQ]
File: [filename.pdf]
SHA-256: [hash]
Suspicious Keywords: [/JS, /OpenAction, etc.]
Objects with JavaScript: [Object IDs]
Extracted URLs: [List]
Shellcode Detected: [Yes/No]
Embedded Files: [Count and types]
VirusTotal Detections: [X/Y engines]
Risk Level: [Critical/High/Medium/Low]Source materials
References and resources
Everything below is rendered for inspection. Script files are read-only and never run.
References 1
api-reference.md3.2 KB
Malicious PDF Analysis Reference
peepdf Installation
# Python 3 version
pip install peepdf-3
# From source
git clone https://github.com/jesparza/peepdf.git
cd peepdf && pip install -r requirements.txtpeepdf CLI Usage
# Basic analysis (loose mode, force parsing)
peepdf -f -l malicious.pdf
# Interactive mode
peepdf -i malicious.pdf
# Batch script execution
peepdf -s commands.txt malicious.pdf
# JSON output
peepdf -j malicious.pdfpeepdf Interactive Commands
| Command | Description |
|---|---|
info |
Display document summary and suspicious elements |
tree |
Show object tree structure |
object <id> |
Display raw content of object |
stream <id> |
Decode and display stream content |
rawstream <id> |
Display raw (encoded) stream |
js_analyse <id> |
Analyze JavaScript in object |
js_eval <id> |
Evaluate JavaScript (requires PyV8) |
vtcheck |
Check file hash on VirusTotal |
extract uri |
Extract all URIs from document |
search <string> |
Search for string across objects |
offsets <id> |
Show byte offsets of object in file |
metadata |
Display document metadata |
pdfid.py Usage
# Basic scan
pdfid.py malicious.pdf
# Additional disarm indicators
pdfid.py -e malicious.pdf
# Scan directory
pdfid.py -r /samples/pdfid Suspicious Keywords
| Keyword | Risk | Significance |
|---|---|---|
| /JS | High | JavaScript object reference |
| /JavaScript | High | JavaScript action |
| /OpenAction | High | Automatic execution on open |
| /AA | High | Additional actions trigger |
| /Launch | Critical | Launch external application |
| /EmbeddedFile | High | Embedded file (dropper) |
| /XFA | High | XML Forms Architecture (exploit surface) |
| /JBIG2Decode | Medium | Image decoder (CVE-2009-0658) |
| /AcroForm | Medium | Interactive form (potential exploit) |
| /ObjStm | Low | Object stream (can hide objects) |
| /URI | Low | External URL reference |
pdf-parser.py Usage
# Document statistics
pdf-parser.py --stats malicious.pdf
# Extract specific object
pdf-parser.py -o 10 malicious.pdf
# Extract and decode filters
pdf-parser.py -o 10 -f malicious.pdf
# Dump decoded stream to file
pdf-parser.py -o 10 -f -d extracted.bin malicious.pdf
# Search for keyword
pdf-parser.py --search "/JavaScript" malicious.pdf
# Search by type
pdf-parser.py --type "/Action" malicious.pdfCommon CVEs in PDF Exploits
| CVE | Component | Description |
|---|---|---|
| CVE-2009-0658 | JBIG2 | Buffer overflow in JBIG2 decoder |
| CVE-2009-4324 | Doc.media | Use-after-free via newplayer |
| CVE-2010-0188 | LibTIFF | TIFF image handling overflow |
| CVE-2013-0640 | XFA | Memory corruption in XFA |
| CVE-2017-11882 | Equation Editor | Stack buffer overflow |
Shellcode Detection Patterns
| Pattern | Indicator |
|---|---|
%u9090%u9090 |
NOP sled (Unicode) |
\x90\x90\x90 |
NOP sled (hex) |
unescape() |
Shellcode decoding |
String.fromCharCode |
Character code assembly |
eval() |
Dynamic code execution |
new ActiveXObject |
COM object instantiation |
spray variable name |
Heap spray technique |
VirusTotal Check via peepdf
PPDF> vtcheck
MD5: abc123...
Detections: 45/72Scripts 1
agent.py8.2 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""Malicious PDF Analysis Agent - static analysis using peepdf, pdfid, and pdf-parser for threat detection."""
import json
import argparse
import logging
import subprocess
import hashlib
import os
import re
from datetime import datetime
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger(__name__)
SUSPICIOUS_KEYWORDS = [
"/JS", "/JavaScript", "/OpenAction", "/AA", "/Launch", "/EmbeddedFile",
"/RichMedia", "/XFA", "/AcroForm", "/JBIG2Decode", "/URI", "/SubmitForm",
"/ImportData", "/Names", "/ObjStm",
]
HIGH_RISK_KEYWORDS = ["/JS", "/JavaScript", "/OpenAction", "/Launch", "/EmbeddedFile", "/XFA"]
def compute_hashes(filepath):
"""Compute MD5 and SHA-256 hashes of the PDF file."""
md5 = hashlib.md5()
sha256 = hashlib.sha256()
with open(filepath, "rb") as f:
for chunk in iter(lambda: f.read(65536), b""):
md5.update(chunk)
sha256.update(chunk)
return {"md5": md5.hexdigest(), "sha256": sha256.hexdigest()}
def run_pdfid(filepath):
"""Run pdfid.py to triage PDF for suspicious keywords."""
cmd = ["python3", "-m", "pdfid", filepath]
alt_cmd = ["pdfid.py", filepath]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
if result.returncode != 0:
result = subprocess.run(alt_cmd, capture_output=True, text=True, timeout=120)
keywords = {}
for line in result.stdout.strip().split("\n"):
line = line.strip()
for kw in SUSPICIOUS_KEYWORDS:
if kw.lower() in line.lower():
parts = line.rsplit(None, 1)
if len(parts) == 2:
try:
count = int(parts[1])
keywords[kw] = count
except ValueError:
pass
return keywords
def run_peepdf_analysis(filepath):
"""Run peepdf for detailed PDF object analysis."""
cmd = ["peepdf", "-f", "-l", filepath]
alt_cmd = ["python3", "-m", "peepdf", "-f", "-l", filepath]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
if result.returncode != 0:
result = subprocess.run(alt_cmd, capture_output=True, text=True, timeout=120)
analysis = {
"versions": 0,
"objects": 0,
"streams": 0,
"encoded_streams": 0,
"suspicious_objects": [],
"js_objects": [],
"vulns": [],
"urls": [],
"raw_output": result.stdout[:2000],
}
for line in result.stdout.split("\n"):
line = line.strip()
if "Version" in line and "Objects" in line:
nums = re.findall(r"\d+", line)
if nums:
analysis["objects"] = int(nums[-1]) if nums else 0
if "Suspicious" in line or "suspicious" in line:
analysis["suspicious_objects"].append(line)
if "/JS" in line or "/JavaScript" in line:
obj_ids = re.findall(r"(\d+)", line)
analysis["js_objects"].extend(obj_ids)
if "CVE" in line.upper():
cves = re.findall(r"CVE-\d{4}-\d{4,}", line, re.IGNORECASE)
analysis["vulns"].extend(cves)
urls = re.findall(r"https?://[^\s\"'<>]+", line)
analysis["urls"].extend(urls)
return analysis
def run_pdf_parser(filepath, object_id=None):
"""Run pdf-parser.py to extract specific objects."""
if object_id:
cmd = ["pdf-parser.py", "-o", str(object_id), "-f", "-d", filepath]
else:
cmd = ["pdf-parser.py", "--stats", filepath]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
return result.stdout[:3000]
def extract_javascript(filepath, peepdf_analysis):
"""Extract JavaScript content from identified objects."""
js_content = []
for obj_id in peepdf_analysis.get("js_objects", []):
cmd = ["pdf-parser.py", "-o", str(obj_id), "-f", "-w", filepath]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
if result.stdout:
js_content.append({
"object_id": obj_id,
"content_preview": result.stdout[:1000],
"length": len(result.stdout),
})
return js_content
def detect_shellcode_patterns(content):
"""Detect common shellcode patterns in extracted content."""
patterns = {
"heap_spray": r"(%u[0-9a-fA-F]{4}){4,}",
"nop_sled": r"(\\x90){8,}|(%u9090){4,}",
"unescape_chain": r"unescape\s*\(",
"shellcode_var": r"shellcode|payload|sc\s*=\s*[\"']",
"fromcharcode": r"String\.fromCharCode",
"eval_call": r"eval\s*\(",
"activex": r"new\s+ActiveXObject",
}
detected = {}
for name, pattern in patterns.items():
matches = re.findall(pattern, content, re.IGNORECASE)
if matches:
detected[name] = len(matches)
return detected
def calculate_risk_score(pdfid_results, peepdf_analysis, shellcode_patterns):
"""Calculate overall risk score for the PDF."""
score = 0
for kw, count in pdfid_results.items():
if count > 0:
if kw in HIGH_RISK_KEYWORDS:
score += count * 20
else:
score += count * 5
score += len(peepdf_analysis.get("vulns", [])) * 30
score += len(peepdf_analysis.get("js_objects", [])) * 15
score += sum(shellcode_patterns.values()) * 10
risk_level = "critical" if score >= 80 else "high" if score >= 50 else "medium" if score >= 20 else "low"
return {"score": min(score, 100), "risk_level": risk_level}
def generate_report(filepath, hashes, pdfid_results, peepdf_analysis, js_content, shellcode, risk):
"""Generate comprehensive PDF malware analysis report."""
report = {
"timestamp": datetime.utcnow().isoformat(),
"file": os.path.basename(filepath),
"file_size": os.path.getsize(filepath),
"hashes": hashes,
"risk_assessment": risk,
"pdfid_keywords": pdfid_results,
"suspicious_keyword_count": sum(1 for v in pdfid_results.values() if v > 0),
"peepdf_analysis": {
"objects": peepdf_analysis.get("objects", 0),
"js_objects": peepdf_analysis.get("js_objects", []),
"cve_references": peepdf_analysis.get("vulns", []),
"extracted_urls": list(set(peepdf_analysis.get("urls", []))),
},
"javascript_content": js_content[:5],
"shellcode_indicators": shellcode,
"iocs": {
"sha256": hashes["sha256"],
"urls": list(set(peepdf_analysis.get("urls", []))),
"cves": peepdf_analysis.get("vulns", []),
},
}
return report
def main():
parser = argparse.ArgumentParser(description="Malicious PDF Analysis Agent")
parser.add_argument("file", help="Path to PDF file to analyze")
parser.add_argument("--extract-js", action="store_true", help="Extract JavaScript objects")
parser.add_argument("--output", default="pdf_analysis_report.json")
args = parser.parse_args()
if not os.path.exists(args.file):
logger.error("File not found: %s", args.file)
return
logger.info("Analyzing: %s (%d bytes)", args.file, os.path.getsize(args.file))
hashes = compute_hashes(args.file)
logger.info("SHA-256: %s", hashes["sha256"])
pdfid_results = run_pdfid(args.file)
peepdf_analysis = run_peepdf_analysis(args.file)
js_content = []
shellcode = {}
if args.extract_js or peepdf_analysis.get("js_objects"):
js_content = extract_javascript(args.file, peepdf_analysis)
all_js = " ".join(j["content_preview"] for j in js_content)
shellcode = detect_shellcode_patterns(all_js)
risk = calculate_risk_score(pdfid_results, peepdf_analysis, shellcode)
report = generate_report(args.file, hashes, pdfid_results, peepdf_analysis, js_content, shellcode, risk)
with open(args.output, "w") as f:
json.dump(report, f, indent=2, default=str)
logger.info("Risk: %s (score %d), %d suspicious keywords, %d JS objects, %d CVEs",
risk["risk_level"], risk["score"], report["suspicious_keyword_count"],
len(peepdf_analysis.get("js_objects", [])), len(peepdf_analysis.get("vulns", [])))
print(json.dumps(report, indent=2, default=str))
if __name__ == "__main__":
main()
Keep exploring