Chapter 12

Email Artifacts

Email forensics covers local client artifacts (Outlook PST/OST, mail rules), server-side artifacts (Exchange/Microsoft 365 audit logs), and email header analysis. Email is the primary initial access vector — forensically recovering what was sent, received, and clicked is often central to reconstructing an intrusion.

Scenario

A spear-phishing email delivered an LNK file attachment that triggered a PowerShell payload when opened. The email was deleted by the user after clicking (inadvertently, or by the attacker's cleanup). The email body no longer exists in the user's mailbox. But the PST file on disk may have the deleted message in recoverable form (items aren't immediately purged from PST — they go to Deleted Items, then remain in the PST's unallocated internal space). The Exchange audit log shows the message was received, and the Message Tracking Log shows its full path through the mail system. The email headers in those logs reveal the originating infrastructure. This chapter covers how to get all of that.

Local Email Artifacts (Outlook PST/OST)

ArtifactLocationForensic value
PST file (Personal Storage Table)%USERPROFILE%\Documents\Outlook Files\ or custom location visible in Outlook settingsEntire local email archive — all folders, messages, attachments, contacts. Contains deleted items until permanently purged.
OST file (Offline Storage Table)%LOCALAPPDATA%\Microsoft\Outlook\Local cache of Exchange/M365 mailbox. Contains email that has since been deleted from the server if purge hasn't happened.
Outlook NK2/Autocomplete%APPDATA%\Microsoft\Outlook\*.dat or %LOCALAPPDATA%\Microsoft\Outlook\RoamCache\Email address autocomplete cache — shows all addresses the user has ever emailed, including those not in contacts
Outlook mail rulesStored inside PST/OST (Rules folder) and in registry at HKCU\Software\Microsoft\Office\<version>\Outlook\RulesAttacker-created mail rules (auto-forward, auto-delete) are a key BEC indicator

Analyzing PST/OST Files

Bashpst-analysis.sh
# Analyze Outlook PST files with libpff tools (available in SIFT)
# libpff provides pffexport — extracts PST contents to readable files

# Export entire PST to directory (messages as .eml, attachments preserved)
pffexport -f text -t all -o /cases/CASE001/pst-export/ \
  "/evidence/jsmith.pst"

# -f text: export as plain text
# -t all: export all folders including Deleted Items, Recoverable Items
# Output directory: one folder per PST folder, one file per message

# List folder structure without exporting
pffinfo "/evidence/jsmith.pst"

# Alternatively: readpst (another libpst tool, more Unix-friendly)
readpst -u -o /cases/CASE001/pst-export/ "/evidence/jsmith.pst"
# -u: unicode mode
# -o: output directory
Pythonsearch-pst.py
"""Search PST file for specific content using pypff."""
import pypff
import sys
from datetime import datetime

def search_messages(pst_path: str, search_term: str):
    pst = pypff.file()
    pst.open(pst_path)
    root = pst.get_root_folder()
    results = []
    _walk_folder(root, search_term.lower(), results)
    pst.close()
    return results

def _walk_folder(folder, term, results, path=""):
    for i in range(folder.number_of_sub_folders):
        subfolder = folder.get_sub_folder(i)
        _walk_folder(subfolder, term, results, path + "/" + subfolder.name)

    for i in range(folder.number_of_sub_messages):
        msg = folder.get_sub_message(i)
        subject = msg.subject or ""
        sender = msg.sender_name or ""
        body = ""
        try:
            body = msg.plain_text_body or ""
            if isinstance(body, bytes):
                body = body.decode("utf-8", errors="replace")
        except Exception:
            pass

        if (term in subject.lower() or term in sender.lower() or
                term in body.lower()):
            dt = msg.client_submit_time
            results.append({
                "folder": path,
                "subject": subject,
                "sender": sender,
                "time": str(dt),
                "body_preview": body[:200]
            })

if __name__ == "__main__":
    pst_path = sys.argv[1]
    search_term = sys.argv[2]
    for r in search_messages(pst_path, search_term):
        print(f"[{r['time']}] {r['folder']} | {r['sender']} | {r['subject']}")
        print(f"  Preview: {r['body_preview'][:100]}")
        print()

Email Header Analysis

Email headers contain the routing history of an email — every server it passed through, originating IP, and authentication results. For phishing investigations, headers reveal the attacker's sending infrastructure.

  Reading Email Headers for Forensic Investigation
  ═══════════════════════════════════════════════════════════════════

  Relevant headers (read bottom-to-top for chronological order):

  Authentication-Results:
    spf=fail (IP not authorized to send for this domain)
    dkim=none (no DKIM signature — suspicious for corporate email)
    dmarc=fail (failed both SPF and DKIM checks)
    → Authentication failures = likely spoofed sender address

  Received: from mail.evil-domain.com ([185.220.101.47])
            by inbound.corp.com with ESMTP id abc123
            → Originating IP is the first "Received: from" in the chain
            → Resolve via whois, geolocation, VirusTotal

  X-Originating-IP: 185.220.101.47
    → Some mail systems add this explicitly (very valuable)

  Message-ID: 
    → Message ID includes the sending server's domain
    → Non-matching domain vs From: address is suspicious

  X-Mailer / User-Agent:
    → Reveals what software sent the email
    → "PHPMailer" or unusual mail clients suggest bulk phishing tools

  Return-Path: attacker@different-domain.com
    → Where bounces go — often reveals the real sending domain
    → Should match From: in legitimate email

  Analysis tools:
    MXToolbox Email Header Analyzer (paste headers, get visual routing)
    Google Admin Toolbox Messageheader Analyzer
    Both provide visual timelines and highlight authentication failures

Microsoft 365 / Exchange Audit Logs

For cloud-hosted email (Microsoft 365), server-side logs provide evidence that survives local PST deletion and show attacker actions in the mailbox:

PowerShellm365-mailbox-audit.ps1
# Microsoft 365 mailbox audit log queries
# Requires: Exchange Online PowerShell module, Global Admin or Compliance role

Connect-ExchangeOnline

# Search unified audit log for mailbox access
Search-UnifiedAuditLog -StartDate "2026-09-15" -EndDate "2026-09-20" `
  -UserIds "jsmith@corp.com" `
  -Operations "MailItemsAccessed" `
  -ResultSize 1000 |
  Select-Object CreationDate, UserIds, Operations,
    @{n="Details"; e={$_.AuditData | ConvertFrom-Json}} |
  Export-Csv "mailbox-access.csv" -NoTypeInformation

# Hunt for suspicious inbox rules (attacker auto-forward/delete rules)
Get-InboxRule -Mailbox "jsmith@corp.com" |
    Where-Object {
        $_.ForwardTo -or $_.ForwardAsAttachmentTo -or
        $_.DeleteMessage -or $_.MoveToFolder -or $_.RedirectTo
    } |
    Select-Object Name, ForwardTo, DeleteMessage, MoveToFolder |
    Format-Table -AutoSize

# Check for email forwarding rules (SMTP forwarding at account level)
Get-Mailbox "jsmith@corp.com" |
    Select-Object ForwardingAddress, ForwardingSmtpAddress, DeliverToMailboxAndForward

# Message Trace — track specific email's delivery path
Get-MessageTrace -SenderAddress "external@domain.com" `
    -StartDate "2026-09-15" -EndDate "2026-09-20" |
    Select-Object Received, SenderAddress, RecipientAddress, Subject, Status, MessageId |
    Format-Table -AutoSize

BEC-Specific Email Forensics Workflow

QuestionEvidence source
When did the attacker first access the mailbox?Unified Audit Log: MailItemsAccessed events; M365 Sign-in logs showing successful auth from attacker IP
What emails did the attacker read?MailItemsAccessed in UAL — shows specific message IDs accessed, timestamped
Did the attacker create inbox rules?New-InboxRule / Set-InboxRule events in UAL; Get-InboxRule on the live mailbox
What emails did the attacker send as the victim?Send and SendAs operations in UAL; Sent Items folder in PST/OST (if not deleted)
What phishing email triggered the initial access?Incoming message in PST, Message Trace from M365, header analysis for originating IP

Q & A

Q: The attacker deleted all sent items and inbox rules before you could export them. Are they recoverable?

In Microsoft 365, yes — the Recoverable Items folder (also called the "dumpster") retains deleted items for 14-30 days by default, and for much longer if a Litigation Hold or In-Place Hold is enabled. In Exchange Online, Compliance Search via the Security and Compliance Center can search the Recoverable Items folder even after the user has permanently deleted items. Use: New-ComplianceSearch -ContentMatchQuery "..." -ExchangeLocation jsmith@corp.com -AllowNotFoundExchangeLocationsEnabled $true followed by New-ComplianceSearchAction -SearchName ... -Purge -PurgeType HardDelete (to preserve) or Export. The UAL will also show the DeleteMessage events — even if the content is gone, the audit log records that deletion happened and when. Inbox rules deleted via the API also leave UAL events: Remove-InboxRule records are evidence the rule existed even if the rule itself is gone.