Outlook PSTメールフォレンジック解析 は何をしますか?
概要
Microsoft Outlook PST(Personal Storage Table)およびOST(Offline Storage Table)ファイルは、デジタルフォレンジック調査において重要な証拠源です。PSTファイルは、MAPI(Messaging Application Programming Interface)プロパティシステムに基づく独自のバイナリ形式で、電子メールメッセージ、カレンダーイベント、連絡先、タスク、メモを保存します。これらのファイルのフォレンジック分析により、削除済みメール(復元可能アイテムフォルダから)の復元、メッセージルート追跡のためのメールヘッダー抽出、マルウェアや漏洩データのための添付ファイル分析、通信パターンの再構築が可能になります。最新のPSTファイルは4KBページのUnicode形式を使用し、最大50GBまで拡張可能ですが、従来のANSI形式は2GBに制限されています。
使用すべき場面
- セキュリティインシデント調査においてOutlook PSTのメールフォレンジック解析が必要な場合
- このドメインの検出ルールや脅威ハンティングクエリを構築する場合
- SOCアナリストがこの分析タイプに対して構造化された手順を必要とする場合
- 関連する攻撃手法に対するセキュリティモニタリングカバレッジを検証する場合
前提条件
- libpff/pffexport(オープンソースPSTパーサー)
- Python 3.8+とpypffまたはlibratomライブラリ
- MailXaminer、Forensic Email Collector、SysTools PST Forensics(商用)
- Microsoft Outlook(オプション、ネイティブPSTアクセス用)
- 抽出コンテンツ用の十分なディスク容量
PSTファイルの保存場所
| ソース | パス |
|---|---|
| Outlook 2016+ デフォルト | %USERPROFILE%\Documents\Outlook Files*.pst |
| Outlook レガシー | %LOCALAPPDATA%\Microsoft\Outlook*.pst |
| OSTキャッシュ | %LOCALAPPDATA%\Microsoft\Outlook*.ost |
| アーカイブ | %USERPROFILE%\Documents\Outlook Files\archive.pst |
オープンソースツールによる解析
libpff / pffexport
# PSTファイルからすべてのアイテムをエクスポート
pffexport -m all evidence.pst -t exported_pst
# 電子メールメッセージのみエクスポート
pffexport -m items evidence.pst -t exported_emails
# 復元/削除済みアイテムをエクスポート
pffexport -m recovered evidence.pst -t recovered_items
# PSTファイル情報を取得
pffinfo evidence.pst
Python PST解析
import pypff
import os
import json
import hashlib
import email
import sys
from datetime import datetime
from collections import defaultdict
class PSTForensicAnalyzer:
"""Outlook PST/OSTファイルのフォレンジック解析クラス"""
def __init__(self, pst_path: str, output_dir: str):
self.pst_path = pst_path
self.output_dir = output_dir
os.makedirs(output_dir, exist_ok=True)
self.pst = pypff.file()
self.pst.open(pst_path)
self.messages = []
self.attachments = []
self.stats = defaultdict(int)
def process_folder(self, folder, folder_path: str = ""):
"""PSTフォルダを再帰的に処理し、メッセージを抽出します。"""
folder_name = folder.name or "Root"
current_path = f"{folder_path}/{folder_name}" if folder_path else folder_name
for i in range(folder.number_of_sub_messages):
try:
message = folder.get_sub_message(i)
msg_data = self.extract_message(message, current_path)
if msg_data:
self.messages.append(msg_data)
self.stats["total_messages"] += 1
except Exception as e:
self.stats["parse_errors"] += 1
for i in range(folder.number_of_sub_folders):
try:
subfolder = folder.get_sub_folder(i)
self.process_folder(subfolder, current_path)
except Exception:
continue
def extract_message(self, message, folder_path: str) -> dict:
"""単一の電子メールメッセージからフォレンジックメタデータを抽出します。"""
msg_data = {
"folder": folder_path,
"subject": message.subject or "",
"sender": message.sender_name or "",
"sender_email": "",
"creation_time": str(message.creation_time) if message.creation_time else None,
"delivery_time": str(message.delivery_time) if message.delivery_time else None,
"modification_time": str(message.modification_time) if message.modification_time else None,
"has_attachments": message.number_of_attachments > 0,
"attachment_count": message.number_of_attachments,
"body_size": len(message.plain_text_body or b""),
"html_size": len(message.html_body or b""),
}
# ルーティング解析のためのトランスポートヘッダーを抽出
headers = message.transport_headers
if headers:
msg_data["headers_present"] = True
msg_data["headers_size"] = len(headers)
# 主要ヘッダーを解析
parsed = email.message_from_string(headers)
msg_data["from_header"] = parsed.get("From", "")
msg_data["to_header"] = parsed.get("To", "")
msg_data["date_header"] = parsed.get("Date", "")
msg_data["message_id"] = parsed.get("Message-ID", "")
msg_data["x_originating_ip"] = parsed.get("X-Originating-IP", "")
msg_data["received_headers"] = parsed.get_all("Received", [])
# 添付ファイルを処理
for j in range(message.number_of_attachments):
try:
attachment = message.get_attachment(j)
att_data = {
"message_subject": msg_data["subject"],
"name": attachment.name or f"attachment_{j}",
"size": attachment.size,
"content_type": "",
}
self.attachments.append(att_data)
self.stats["total_attachments"] += 1
except Exception:
continue
return msg_data
def save_attachments(self, max_size_mb: int = 100):
"""添付ファイルをディスクにエクスポートして解析します。"""
att_dir = os.path.join(self.output_dir, "attachments")
os.makedirs(att_dir, exist_ok=True)
root = self.pst.get_root_folder()
self._save_attachments_recursive(root, att_dir, max_size_mb)
def _save_attachments_recursive(self, folder, att_dir, max_size_mb):
for i in range(folder.number_of_sub_messages):
try:
message = folder.get_sub_message(i)
for j in range(message.number_of_attachments):
att = message.get_attachment(j)
if att.size and att.size < max_size_mb * 1024 * 1024:
name = att.name or f"unknown_{i}_{j}"
safe_name = "".join(c if c.isalnum() or c in ".-_" else "_" for c in name)
path = os.path.join(att_dir, safe_name)
try:
data = att.read_buffer(att.size)
with open(path, "wb") as f:
f.write(data)
except Exception:
continue
except Exception:
continue
for i in range(folder.number_of_sub_folders):
try:
self._save_attachments_recursive(folder.get_sub_folder(i), att_dir, max_size_mb)
except Exception:
continue
def generate_report(self) -> str:
"""包括的なPSTフォレンジック解析レポートを生成します。"""
root = self.pst.get_root_folder()
self.process_folder(root)
report = {
"analysis_timestamp": datetime.now().isoformat(),
"pst_file": self.pst_path,
"pst_size_bytes": os.path.getsize(self.pst_path),
"statistics": dict(self.stats),
"messages": self.messages[:500],
"attachments": self.attachments[:200],
}
report_path = os.path.join(self.output_dir, "pst_forensic_report.json")
with open(report_path, "w") as f:
json.dump(report, f, indent=2, default=str)
print(f"[*] メッセージ総数: {self.stats['total_messages']}")
print(f"[*] 添付ファイル総数: {self.stats['total_attachments']}")
print(f"[*] 解析エラー数: {self.stats['parse_errors']}")
return report_path
def close(self):
self.pst.close()
def main():
if len(sys.argv) < 3:
print("使用方法: python process.py <pst_file> <output_dir>")
sys.exit(1)
analyzer = PSTForensicAnalyzer(sys.argv[1], sys.argv[2])
analyzer.generate_report()
analyzer.close()
if __name__ == "__main__":
main()
メールヘッダー解析
フォレンジック調査における主要なヘッダー:
| ヘッダー | フォレンジック上の価値 |
|---|---|
| Received | メッセージルーティングチェーン(下から上に読む) |
| X-Originating-IP | 送信者の実際のIPアドレス |
| Message-ID | 相関分析のための一意識別子 |
| Date | 送信タイムスタンプ |
| Return-Path | バウンスアドレス(Fromと異なる場合あり) |
| DKIM-Signature | ドメイン認証署名 |
| Authentication-Results | SPF、DKIM、DMARC検証結果 |
| X-Mailer | 使用されたメールクライアント |
参考資料
- MailXaminer PST Forensics: https://www.mailxaminer.com/blog/outlook-pst-file-forensics/
- libpff ドキュメント: https://github.com/libyal/libpff
- PSTファイル形式仕様: https://docs.microsoft.com/en-us/openspecs/office_file_formats/ms-pst/
- SANS Email Forensics: https://www.sans.org/blog/email-forensics/
出力例
$ pffexport /evidence/jsmith_archive.pst -t /analysis/pst_output
pffexport 20231205 - libpff PST/OST Export Tool
=================================================
入力: /evidence/jsmith_archive.pst (2.3 GB)
PST内容をエクスポート中...
フォルダ: 45
メッセージ: 12,456
添付ファイル: 3,234
連絡先: 567
カレンダー: 234
タスク: 89
エクスポート完了(3分42秒)
$ python3 pst_analyzer.py /analysis/pst_output /analysis/email_report
PST Forensic Analysis Report
==============================
ソース: jsmith_archive.pst ([email protected])
日付範囲: 2023-06-01 ~ 2024-01-18
--- メールボックス統計 ---
メール総数: 12,456
送信: 4,567
受信: 7,889
添付ファイル付き: 3,234
削除済み(復元): 234
--- フィッシング/不審なメール ---
メール #8923
日付: 2024-01-15 14:30:22 UTC
差出人: "IT Support" <[email protected]>
宛先: [email protected]
件名: 【緊急】パスワードリセットが必要です
ヘッダー:
Return-Path: [email protected]
X-Originating-IP: 203.0.113.55
Received: from mail-relay.c0rporate-help.com (203.0.113.55)
SPF: FAIL (ドメイン c0rporate-help.com)
DKIM: NONE
DMARC: FAIL
添付ファイル:
- Password_Reset_Form.xlsm (245 KB) SHA-256: 7a3b8c9d...e1f2a3b4
本文プレビュー: "Dear Employee, Your password will expire in 24 hours.
Please open the attached form to reset your credentials..."
--- データ漏洩の兆候 ---
メール #9102
日付: 2024-01-16 03:15:45 UTC
差出人: [email protected]
宛先: [email protected]
件名: (件名なし)
添付ファイル:
- archive_part1.7z (24.5 MB) - 暗号化
- archive_part2.7z (24.5 MB) - 暗号化
メール #9103
日付: 2024-01-16 03:18:22 UTC
差出人: [email protected]
宛先: [email protected]
件名: Re:
添付ファイル:
- archive_part3.7z (18.2 MB) - 暗号化
--- キーワードヒット ---
"confidential": 45通のメール
"password": 23通のメール
"transfer": 12通のメール
"resign": 3通のメール
"delete evidence": 1通のメール(メール #9200、2024-01-17 22:30:00 UTC)
要約:
検出されたフィッシングメール: 1(初期侵害ベクター)
不審な送信メール: 5(個人アカウントへの添付ファイル付き)
暗号化された添付ファイル: 3(合計67.2 MB - 漏洩の可能性)
レポート: /analysis/email_report/pst_forensic_report.json