Analyse des fichiers PST Outlook pour la forensique des courriels
Vue d'ensemble
Les fichiers PST (Personal Storage Table) et OST (Offline Storage Table) de Microsoft Outlook sont des sources de preuves critiques dans les enquêtes de criminalistique numérique. Les fichiers PST stockent les messages électroniques, les événements de calendrier, les contacts, les tâches et les notes dans un format binaire propriétaire basé sur le système de propriétés MAPI (Messaging Application Programming Interface). L'analyse forensique de ces fichiers permet de récupérer les courriels supprimés (à partir du dossier Éléments récupérables), d'extraire les en-têtes des courriels pour tracer les routes des messages, d'analyser les pièces jointes pour détecter des logiciels malveillants ou des données exfiltrées, et de reconstruire les schémas de communication. Les fichiers PST modernes utilisent le format Unicode avec des pages de 4 Ko et peuvent atteindre 50 Go, tandis que le format ANSI hérité est limité à 2 Go.
Quand l'utiliser
- Lors d'enquêtes sur des incidents de sécurité nécessitant l'analyse forensique de fichiers PST Outlook
- Lors de l'élaboration de règles de détection ou de requêtes de chasse aux menaces pour ce domaine
- Lorsque les analystes SOC ont besoin de procédures structurées pour ce type d'analyse
- Lors de la validation de la couverture de surveillance de sécurité pour les techniques d'attaque associées
Prérequis
- libpff/pffexport (analyseur PST open source)
- Python 3.8+ avec les bibliothèques pypff ou libratom
- MailXaminer, Forensic Email Collector, ou SysTools PST Forensics (commercial)
- Microsoft Outlook (optionnel, pour un accès PST natif)
- Espace disque suffisant pour le contenu extrait
Emplacements des fichiers PST
| Source | Chemin |
|---|---|
| Outlook 2016+ par défaut | %USERPROFILE%\Documents\Outlook Files*.pst |
| Outlook hérité | %LOCALAPPDATA%\Microsoft\Outlook*.pst |
| Cache OST | %LOCALAPPDATA%\Microsoft\Outlook*.ost |
| Archive | %USERPROFILE%\Documents\Outlook Files\archive.pst |
Analyse avec des outils open source
libpff / pffexport
# Exporter tous les éléments d'un fichier PST
pffexport -m all evidence.pst -t exported_pst
# Exporter uniquement les messages électroniques
pffexport -m items evidence.pst -t exported_emails
# Exporter les éléments récupérés/supprimés
pffexport -m recovered evidence.pst -t recovered_items
# Obtenir les informations du fichier PST
pffinfo evidence.pst
Analyse PST avec Python
import pypff
import os
import json
import hashlib
import email
import sys
from datetime import datetime
from collections import defaultdict
class PSTForensicAnalyzer:
"""Analyse forensique des fichiers PST/OST Outlook."""
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 = ""):
"""Parcourt récursivement les dossiers PST et extrait les messages."""
folder_name = folder.name or "Racine"
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:
"""Extrait les métadonnées forensiques d'un seul message électronique."""
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""),
}
# Extraire les en-têtes de transport pour l'analyse du routage
headers = message.transport_headers
if headers:
msg_data["headers_present"] = True
msg_data["headers_size"] = len(headers)
# Analyser les en-têtes clés
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", [])
# Traiter les pièces jointes
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):
"""Exporte les pièces jointes sur le disque pour analyse."""
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"inconnu_{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:
"""Génère un rapport complet d'analyse forensique 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"[*] Nombre total de messages : {self.stats['total_messages']}")
print(f"[*] Nombre total de pièces jointes : {self.stats['total_attachments']}")
print(f"[*] Erreurs d'analyse : {self.stats['parse_errors']}")
return report_path
def close(self):
self.pst.close()
def main():
if len(sys.argv) < 3:
print("Utilisation : python process.py <fichier_pst> <répertoire_sortie>")
sys.exit(1)
analyzer = PSTForensicAnalyzer(sys.argv[1], sys.argv[2])
analyzer.generate_report()
analyzer.close()
if __name__ == "__main__":
main()
Analyse des en-têtes de courrier électronique
En-têtes clés pour l'investigation forensique :
| En-tête | Valeur forensique |
|---|---|
| Received | Chaîne de routage des messages (lire de bas en haut) |
| X-Originating-IP | Adresse IP réelle de l'expéditeur |
| Message-ID | Identifiant unique pour la corrélation |
| Date | Horodatage d'envoi |
| Return-Path | Adresse de rebond (peut différer de From) |
| DKIM-Signature | Signature d'authentification de domaine |
| Authentication-Results | Résultats de vérification SPF, DKIM, DMARC |
| X-Mailer | Client de messagerie utilisé |
Références
- MailXaminer PST Forensics : https://www.mailxaminer.com/blog/outlook-pst-file-forensics/
- Documentation libpff : https://github.com/libyal/libpff
- Spécification du format de fichier PST : https://docs.microsoft.com/en-us/openspecs/office_file_formats/ms-pst/
- SANS Email Forensics : https://www.sans.org/blog/email-forensics/
Exemple de sortie
$ pffexport /evidence/jsmith_archive.pst -t /analysis/pst_output
pffexport 20231205 - libpff Outil d'exportation PST/OST
=================================================
Entrée : /evidence/jsmith_archive.pst (2,3 Go)
Exportation du contenu PST...
Dossiers : 45
Messages : 12 456
Pièces jointes : 3 234
Contacts : 567
Calendrier : 234
Tâches : 89
Exportation terminée en 3 min 42 s.
$ python3 pst_analyzer.py /analysis/pst_output /analysis/email_report
Rapport d'analyse forensique PST
==============================
Source : jsmith_archive.pst ([email protected])
Plage de dates : 2023-06-01 à 2024-01-18
--- Statistiques de la boîte aux lettres ---
Total des courriels : 12 456
Envoyés : 4 567
Reçus : 7 889
Avec pièces jointes : 3 234
Supprimés (récupérés) : 234
--- Courriels de phishing / suspects ---
Courriel n°8923
Date : 2024-01-15 14:30:22 UTC
De : "IT Support" <[email protected]>
À : [email protected]
Objet : Urgent : Réinitialisation de mot de passe requise
En-têtes :
Return-Path : [email protected]
X-Originating-IP : 203.0.113.55
Received : from mail-relay.c0rporate-help.com (203.0.113.55)
SPF : ÉCHEC (domaine c0rporate-help.com)
DKIM : AUCUN
DMARC : ÉCHEC
Pièces jointes :
- Password_Reset_Form.xlsm (245 Ko) SHA-256 : 7a3b8c9d...e1f2a3b4
Aperçu du corps : "Cher employé, votre mot de passe expirera dans 24 heures.
Veuillez ouvrir le formulaire ci-joint pour réinitialiser vos identifiants..."
--- Indicateurs d'exfiltration de données ---
Courriel n°9102
Date : 2024-01-16 03:15:45 UTC
De : [email protected]
À : [email protected]
Objet : (sans objet)
Pièces jointes :
- archive_part1.7z (24,5 Mo) - chiffré
- archive_part2.7z (24,5 Mo) - chiffré
Courriel n°9103
Date : 2024-01-16 03:18:22 UTC
De : [email protected]
À : [email protected]
Objet : Re :
Pièces jointes :
- archive_part3.7z (18,2 Mo) - chiffré
--- Correspondances de mots-clés ---
"confidentiel" : 45 courriels
"mot de passe" : 23 courriels
"transfert" : 12 courriels
"démission" : 3 courriels
"supprimer les preuves" : 1 courriel (Courriel n°9200, 2024-01-17 22:30:00 UTC)
Résumé :
Courriels de phishing détectés : 1 (vecteur de compromission initial)
Courriels envoyés suspects : 5 (vers des comptes personnels avec pièces jointes)
Pièces jointes chiffrées : 3 (67,2 Mo au total - exfiltration possible)
Rapport : /analysis/email_report/pst_forensic_report.json