| name | analyzing-outlook-pst-for-email-forensics |
| description | 分析 Microsoft Outlook PST 和 OST 文件,提取电子邮件取证证据,包括邮件内容、邮件头、附件、已删除邮件及元数据,使用 libpff、pst-utils 和取证电子邮件分析工具,适用于法律调查和事件响应。 |
| domain | cybersecurity |
| subdomain | digital-forensics |
| tags | ["email-forensics","pst","ost","outlook","mapi","email-headers","attachments","deleted-emails","libpff","eml-extraction"] |
| version | 1.0 |
| author | mahipal |
| license | Apache-2.0 |
分析 Outlook PST 进行电子邮件取证
概述
Microsoft Outlook PST(个人存储表,Personal Storage Table)和 OST(离线存储表,Offline Storage Table)文件是数字取证调查中的关键证据来源。PST 文件以基于 MAPI(消息应用程序编程接口,Messaging Application Programming Interface)属性系统的专有二进制格式存储电子邮件、日历事件、联系人、任务和备注。对这些文件进行取证分析,可以恢复已删除的邮件(来自"可恢复邮件"文件夹)、提取邮件头以追踪邮件路由、分析附件中的恶意软件或外泄数据,以及重建通信模式。现代 PST 文件使用 Unicode 格式,页面大小 4KB,最大可达 50GB;而旧版 ANSI 格式限制为 2GB。
前置条件
- 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
pffexport -m all evidence.pst -t exported_pst
pffexport -m items evidence.pst -t exported_emails
pffexport -m recovered evidence.pst -t recovered_items
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 = .extract_message(message, current_path)
msg_data:
.messages.append(msg_data)
.stats[] +=
Exception e:
.stats[] +=
i (folder.number_of_sub_folders):
:
subfolder = folder.get_sub_folder(i)
.process_folder(subfolder, current_path)
Exception:
() -> :
msg_data = {
: folder_path,
: message.subject ,
: message.sender_name ,
: ,
: (message.creation_time) message.creation_time ,
: (message.delivery_time) message.delivery_time ,
: (message.modification_time) message.modification_time ,
: message.number_of_attachments > ,
: message.number_of_attachments,
: (message.plain_text_body ),
: (message.html_body ),
}
headers = message.transport_headers
headers:
msg_data[] =
msg_data[] = (headers)
parsed = email.message_from_string(headers)
msg_data[] = parsed.get(, )
msg_data[] = parsed.get(, )
msg_data[] = parsed.get(, )
msg_data[] = parsed.get(, )
msg_data[] = parsed.get(, )
msg_data[] = parsed.get_all(, [])
j (message.number_of_attachments):
:
attachment = message.get_attachment(j)
att_data = {
: msg_data[],
: attachment.name ,
: attachment.size,
: ,
}
.attachments.append(att_data)
.stats[] +=
Exception:
msg_data
():
att_dir = os.path.join(.output_dir, )
os.makedirs(att_dir, exist_ok=)
root = .pst.get_root_folder()
._save_attachments_recursive(root, att_dir, max_size_mb)
():
i (folder.number_of_sub_messages):
:
message = folder.get_sub_message(i)
j (message.number_of_attachments):
att = message.get_attachment(j)
att.size att.size < max_size_mb * * :
name = att.name
safe_name = .join(c c.isalnum() c c name)
path = os.path.join(att_dir, safe_name)
:
data = att.read_buffer(att.size)
(path, ) f:
f.write(data)
Exception:
Exception:
i (folder.number_of_sub_folders):
:
._save_attachments_recursive(folder.get_sub_folder(i), att_dir, max_size_mb)
Exception:
() -> :
root = .pst.get_root_folder()
.process_folder(root)
report = {
: datetime.now().isoformat(),
: .pst_path,
: os.path.getsize(.pst_path),
: (.stats),
: .messages[:],
: .attachments[:],
}
report_path = os.path.join(.output_dir, )
(report_path, ) f:
json.dump(report, f, indent=, default=)
()
()
()
report_path
():
.pst.close()
():
(sys.argv) < :
()
sys.exit()
analyzer = PSTForensicAnalyzer(sys.argv[], sys.argv[])
analyzer.generate_report()
analyzer.close()
__name__ == :
main()
电子邮件头分析
取证调查的关键头字段:
| 头字段 | 取证价值 |
|---|
| Received | 邮件路由链(从下到上阅读) |
| X-Originating-IP | 发件人实际 IP 地址 |
| Message-ID | 用于关联的唯一标识符 |
| Date | 发送时间戳 |
| Return-Path | 退信地址(可能与 From 不同) |
| DKIM-Signature | 域名认证签名 |
| Authentication-Results | SPF、DKIM、DMARC 验证结果 |
| X-Mailer | 使用的邮件客户端 |
参考资料