malware analysis

Analyzing Malicious PDF with peepdf

Perform static analysis of malicious PDF documents using peepdf, pdfid, and pdf-parser to extract embedded JavaScript, shellcode, and suspicious objects.

dfirmalware-analysispdfpdf-parserpdfidpeepdfreverse-engineeringstatic-analysis
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

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

  1. Triage with pdfid: Scan PDF for suspicious keywords (/JS, /JavaScript, /OpenAction, /Launch, /EmbeddedFile).
  2. Interactive Analysis: Open PDF in peepdf interactive mode to explore object structure.
  3. Identify Suspicious Objects: Locate objects containing JavaScript, streams, or encoded data.
  4. Extract Content: Dump suspicious streams and decode filters (FlateDecode, ASCIIHexDecode).
  5. Deobfuscate JavaScript: Analyze extracted JS for shellcode, heap sprays, or exploit code.
  6. Check VirusTotal: Use peepdf vtcheck to cross-reference file hash with AV detections.
  7. 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.txt

peepdf 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.pdf

peepdf 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.pdf

Common 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/72

Scripts 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