mcpField
A
analyzing-outlook-pst-for-email-forensics

analyzing-outlook-pst-for-email-forensics

github.com/mukul975セキュリティ35.2k308 回インストール8 週間前に更新
リポジトリ

analyzing-outlook-pst-for-email-forensics について

デジタルフォレンジック担当者向けに、Outlook の PST と OST ファイルを調べるスキルです。メールボックスデータのエクスポート、削除項目の復元、ヘッダーによる経路や通信の追跡を扱います。

主な機能

  1. 01PST と OST の内容をエクスポート
  2. 02削除項目の復元
  3. 03メール経路ヘッダーの確認
  4. 04添付ファイルとメールの分析
  5. 05libpff と Python ツールの利用

活用シーン

  1. 01インシデントでのメールボックス証拠の調査
  2. 02調査用の削除メールの復元
  3. 03ヘッダーからメール経路を追跡
  4. 04添付ファイルのマルウェアやデータの確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/mukul975/anthropic-cybersecurity-skills --skill analyzing-outlook-pst-for-email-forensics

インストール後、Claude Code で/analyzing-outlook-pst-for-email-forensicsを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

LICENSE
references/
api-reference.md
standards.md
workflows.md
scripts/
agent.py

詳細

メールフォレンジックのための 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 パーサー)
  • pypff または libratom ライブラリを使用する Python 3.8 以降
  • 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
# Export all items from PST file
pffexport -m all evidence.pst -t exported_pst

# Export only email messages
pffexport -m items evidence.pst -t exported_emails

# Export recovered/deleted items
pffexport -m recovered evidence.pst -t recovered_items

# Get PST file information
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:
    """Forensic analysis of Outlook PST/OST files."""

    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 = ""):
        """Recursively process PST folders and extract messages."""
        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:
        """Extract forensic metadata from a single email message."""
        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""),
        }

        # Extract transport headers for routing analysis
        headers = message.transport_headers
        if headers:
            msg_data["headers_present"] = True
            msg_data["headers_size"] = len(headers)
            # Parse key 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", [])

        # Process attachments
        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):
        """Export attachments to disk for analysis."""
        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:
        """Generate comprehensive PST forensic analysis report."""
        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"[*] Total messages: {self.stats['total_messages']}")
        print(f"[*] Total attachments: {self.stats['total_attachments']}")
        print(f"[*] Parse errors: {self.stats['parse_errors']}")
        return report_path

    def close(self):
        self.pst.close()


def main():
    if len(sys.argv) < 3:
        print("Usage: 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-ResultsSPF、DKIM、DMARC の検証結果
X-Mailer使用されたメールクライアント

参考資料

出力例

$ pffexport /evidence/jsmith_archive.pst -t /analysis/pst_output

pffexport 20231205 - libpff PST/OST Export Tool
=================================================
Input: /evidence/jsmith_archive.pst (2.3 GB)

Exporting PST contents...
  Folders:       45
  Messages:      12,456
  Attachments:   3,234
  Contacts:      567
  Calendar:      234
  Tasks:         89

Export completed in 3m 42s.

$ python3 pst_analyzer.py /analysis/pst_output /analysis/email_report

PST Forensic Analysis Report
==============================
Source: jsmith_archive.pst ([email protected])
Date Range: 2023-06-01 to 2024-01-18

--- Mailbox Statistics ---
  Total Emails:       12,456
  Sent:               4,567
  Received:           7,889
  With Attachments:   3,234
  Deleted (recovered): 234

--- Phishing / Suspicious Emails ---
Email #8923
  Date:        2024-01-15 14:30:22 UTC
  From:        "IT Support" <[email protected]>
  To:          [email protected]
  Subject:     Urgent: Password Reset Required
  Headers:
    Return-Path:    [email protected]
    X-Originating-IP: 203.0.113.55
    Received:       from mail-relay.c0rporate-help.com (203.0.113.55)
    SPF:            FAIL (domain c0rporate-help.com)
    DKIM:           NONE
    DMARC:          FAIL
  Attachments:
    - Password_Reset_Form.xlsm (245 KB) SHA-256: 7a3b8c9d...e1f2a3b4
  Body Preview:  "Dear Employee, Your password will expire in 24 hours.
                  Please open the attached form to reset your credentials..."

--- Data Exfiltration Indicators ---
Email #9102
  Date:        2024-01-16 03:15:45 UTC
  From:        [email protected]
  To:          [email protected]
  Subject:     (no subject)
  Attachments:
    - archive_part1.7z (24.5 MB) - encrypted
    - archive_part2.7z (24.5 MB) - encrypted

Email #9103
  Date:        2024-01-16 03:18:22 UTC
  From:        [email protected]
  To:          [email protected]
  Subject:     Re:
  Attachments:
    - archive_part3.7z (18.2 MB) - encrypted

--- Keyword Hits ---
  "confidential":     45 emails
  "password":         23 emails
  "transfer":         12 emails
  "resign":           3 emails
  "delete evidence":  1 email (Email #9200, 2024-01-17 22:30:00 UTC)

Summary:
  Phishing emails detected:    1 (initial compromise vector)
  Suspicious sent emails:      5 (to personal accounts with attachments)
  Encrypted attachments:       3 (67.2 MB total - possible exfiltration)
  Report: /analysis/email_report/pst_forensic_report.json

コメント