Communitygithub.com

Outlook PST 파일 이메일 포렌식 분석

libpff와 pst-utils를 사용하여 Microsoft Outlook PST 및 OST 파일을 파싱하여 메시지 콘텐츠, 헤더, 첨부 파일, 삭제된 항목 및 MAPI 메타데이터를 추출하고, 복구 가능한 항목 폴더에서 항목을 복구합니다. 이메일 포렌식 조사, 법적 증거 개시 또는 인시던트 대응 시 Outlook 아카이브에서 통신 패턴 재구성 또는 메시지 라우팅 추적이 필요할 때 사용하세요.

Outlook PST 파일 이메일 포렌식 분석란 무엇인가요?

이 스킬은 디지털 포렌식 조사에서 핵심적인 증거 소스인 Microsoft Outlook PST(개인 저장소 테이블) 및 OST(오프라인 저장소 테이블) 파일을 분석하는 방법을 제공합니다. PST 파일은 MAPI 속성 시스템을 기반으로 한 독점 바이너리 형식으로 이메일, 일정, 연락처, 작업 및 메모를 저장하며, 이 스킬은 libpff/pffexport와 같은 오픈 소스 도구와 Python(pypff)을 사용하여 메시지 콘텐츠, 헤더, 첨부 파일, 삭제된 항목(복구 가능한 항목 폴더 포함) 및 MAPI 메타데이터를 추출할 수 있습니다. 또한, SPF, DKIM, DMARC를 포함한 이메일 헤더 분석을 통해 메시지 라우팅 체인을 추적하고 피싱 시도나 데이터 유출 징후를 식별하는 데 유용합니다. 이 스킬은 Windows, Linux, macOS를 포함한 모든 주요 플랫폼에서 사용할 수 있으며, 사고 대응팀, 법무팀, SOC 분석가가 이메일 기반 공격을 조사하거나 e-디스커버리 절차를 수행해야 할 때 특히 유용합니다.

지원 대상~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/mukul975/Anthropic-Cybersecurity-Skills/tree/main/skills/analyzing-outlook-pst-for-email-forensics

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

이메일 포렌식을 위한 Outlook PST 분석

개요

Microsoft Outlook PST(Personal Storage Table) 및 OST(Offline Storage Table) 파일은 디지털 포렌식 조사에서 중요한 증거 소스입니다. PST 파일은 MAPI(Messaging Application Programming Interface) 속성 시스템을 기반으로 한 독점 바이너리 형식으로 이메일, 일정 이벤트, 연락처, 작업 및 메모를 저장합니다. 이러한 파일의 포렌식 분석을 통해 삭제된 이메일(복구 가능한 항목 폴더에서) 복구, 메시지 라우팅 추적을 위한 이메일 헤더 추출, 맬웨어 또는 유출 데이터 분석을 위한 첨부 파일 분석, 통신 패턴 재구성이 가능합니다. 최신 PST 파일은 4KB 페이지의 유니코드 형식을 사용하며 최대 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

# 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반송 주소 (보낸사람과 다를 수 있음)
DKIM-Signature도메인 인증 서명
Authentication-ResultsSPF, DKIM, DMARC 확인 결과
X-Mailer사용된 이메일 클라이언트

참고 자료

예제 출력

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

pffexport 20231205 - libpff PST/OST 내보내기 도구
=================================================
입력: /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 포렌식 분석 보고서
==============================
출처: 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 지원" <[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:            실패 (도메인 c0rporate-help.com)
    DKIM:           없음
    DMARC:          실패
  첨부 파일:
    - Password_Reset_Form.xlsm (245 KB) SHA-256: 7a3b8c9d...e1f2a3b4
  본문 미리보기:  "직원 여러분, 비밀번호가 24시간 이내에 만료됩니다.
                  첨부된 양식을 열어 자격 증명을 재설정하세요..."

--- 데이터 유출 징후 ---
이메일 #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) - 암호화됨

--- 키워드 적중 ---
  "기밀":     45개 이메일
  "비밀번호":   23개 이메일
  "이체":     12개 이메일
  "사직":     3개 이메일
  "증거 삭제":  1개 이메일 (이메일 #9200, 2024-01-17 22:30:00 UTC)

요약:
  탐지된 피싱 이메일:    1 (초기 침해 벡터)
  의심스러운 발신 이메일:    5 (개인 계정으로 첨부 파일 포함)
  암호화된 첨부 파일:       3 (총 67.2 MB - 유출 가능성)
  보고서: /analysis/email_report/pst_forensic_report.json

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

abusing-dpapi-for-credential-access

Extract and decrypt Windows DPAPI-protected secrets (Credential Manager, browser logins/cookies, Wi-Fi credentials, KeePass keys) online or offline using SharpDPAPI, SharpChrome, Mimikatz, or Impacket

abusing-shadow-credentials-for-privesc

Take over Active Directory accounts by writing attacker-controlled public keys to msDS-KeyCredentialLink (Shadow Credentials) with pyWhisker, Whisker, or Certipy, then authenticate via PKINIT to recover the target

achieving-cmmc-level-2-compliance

>-

acquiring-disk-image-with-dd-and-dcfldd

Create forensically sound bit-for-bit disk images with dd or dcfldd on a Linux forensic workstation, preserving evidence integrity through hash verification (MD5/SHA) during acquisition. Use when imaging a suspect drive, USB device, or memory card for investigation, preserving volatile disk evidence during incident response, or producing a verified copy for legal or law-enforcement proceedings before any destructive analysis.

analyzing-active-directory-acl-abuse

Detect dangerous ACL misconfigurations in Active Directory using ldap3

analyzing-android-malware-with-apktool

Perform static analysis of Android APK malware using apktool for resource decompilation, jadx for Java source recovery, and androguard for manifest inspection, dangerous permission-combination detection, and identification of obfuscated code, dynamic code loading, and reflection-based API calls. Use to statically triage a suspicious APK without executing it or to build mobile malware detection rules.

analyzing-api-gateway-access-logs

Parses API Gateway access logs (AWS API Gateway, Kong, Nginx) to detect

analyzing-apt-group-with-mitre-navigator

Query ATT&CK data with attackcti, mitreattack-python, and stix2, then build MITRE ATT&CK Navigator layers and multi-layer heatmap overlays mapping one or more APT groups

analyzing-azure-activity-logs-for-threats

Queries Azure Monitor activity logs and sign-in logs via azure-monitor-query

analyzing-bootkit-and-rootkit-samples

Analyzes bootkit and advanced rootkit malware infecting the Master

analyzing-browser-forensics-with-hindsight

Parse Chromium-based browser databases with Hindsight to extract and correlate browsing history, downloads, cookies, cached content, autofill data, saved passwords, and extensions from Chrome, Edge, Brave, Opera, and Vivaldi into a unified timeline (XLSX, JSON, or SQLite output). Use during incident response, insider-threat investigations, or criminal cases when you need to reconstruct a user

analyzing-campaign-attribution-evidence

Systematically evaluate cyber-campaign evidence to attribute an operation to a threat actor, using the Diamond Model and Analysis of Competing Hypotheses (ACH) to weigh infrastructure overlaps, TTP consistency, malware code similarity, and timing/language artifacts into confidence-weighted attribution assessments. Use when an incident investigation needs a defensible attribution confidence level.

analyzing-certificate-transparency-for-phishing

Monitor Certificate Transparency logs using crt.sh and Certstream to

analyzing-cloud-storage-access-patterns

Detect abnormal access in AWS S3, GCS, and Azure Blob Storage by analyzing CloudTrail Data Events, GCS audit logs, and Azure Storage Analytics for after-hours bulk downloads, new-IP access, and API-call spikes (e.g. GetObject) via statistical baselines and time-series anomaly detection. Use when investigating suspected cloud data exfiltration or building related detection rules.

analyzing-cobalt-strike-beacon-configuration

Extract and analyze Cobalt Strike beacon configuration from PE files

analyzing-cobaltstrike-malleable-c2-profiles

Parse and analyze Cobalt Strike Malleable C2 profiles with dissect.cobaltstrike (profiles and beacon-payload configs) and pyMalleableC2 (AST parsing) to extract HTTP/DNS transforms, URIs, headers, sleep/jitter, and injection behavior, then generate network detection signatures. Use when reverse-engineering a captured malleable profile or building detections against Cobalt Strike Beacon traffic.

analyzing-command-and-control-communication

Analyzes malware C2 communication over HTTP, HTTPS, DNS, and custom

analyzing-cyber-kill-chain

Analyzes intrusion activity against the Lockheed Martin Cyber Kill Chain

analyzing-disk-image-with-autopsy

Perform comprehensive forensic analysis of raw (dd), E01, or AFF disk images with Autopsy and The Sleuth Kit, recovering deleted files, examining metadata and embedded artifacts, keyword searching, and building investigation timelines with visual reports. Use for structured analysis of a forensic disk image or when stakeholders need visual reports from evidence.

analyzing-dns-logs-for-exfiltration

Analyzes DNS query logs to detect data exfiltration via DNS tunneling,

관련 스킬