一、概述
邮件是企业取证里价值最高、也最难取到的一类证据。
它能直接回答"谁在什么时间与什么人就什么内容通信"——账号日志、聊天记录都替代不了这种直接证据。
一个人可以删掉浏览记录、清理浏览器存储,但邮件往来本身就构成"关系存在"和"内容知情"的证据。
难在哪儿?难在格式,不难在权限。
你完全有权限访问 ...\V15\MailDB\<GUID>\Mailbox Database.edb,但普通工具打不开它——它既不是 SQLite,也不是任何公开标准格式。
Exchange 从 2007 版起把邮箱数据存进 ESE 数据库(与 Windows 的 ESENT.DLL 同源),而它的 DATA/LVAL 页可以是 LZX 或 RLE 压缩存储的。
结果是:即便找到号称支持 ESE 的工具,面对压缩页时也可能只读得出结构、读不出内容。
这构成本篇的核心矛盾:数据就在那里(有权限、有文件),但读不出来(格式封闭 + 压缩)。
更危险的是——为了读出来而"修复"数据库的常规做法,会永久破坏证据。
在取证流程中的位置:本篇属于分析环的高价值支线,与 Web 攻击链的还原往往并行但独立。
它最大的特殊性在于体积:Exchange 的 .edb 是 P3 级工件(数十至数百 GB),必须单独规划提取,见 00 篇的取证优先级。上游依赖 NTFS 层面的定位与提取能力;下游支撑"通信关系图"与数据外传的认定。
涉及的证据形态:
| 形态 |
位置 |
取证价值 |
| 邮箱数据库 |
C:\Program Files\Microsoft\Exchange Server\V15\MailDB\<GUID>\Mailbox Database.edb |
★★★★★ 收发件人与时间 |
| 传输数据库 |
同目录 Transport Database.edb |
★★★ 队列与路由 |
| 事务日志 |
同 GUID 目录下的 Logs\($Upd、.log) |
★★★★ 未提交事务的恢复来源 |
| 传输日志 |
V15\TransportRoles\Logs\Role_*.log |
★★★★★ 比 .edb 易读得多 |
| 客户端 PST/OST |
%LOCALAPPDATA%\Microsoft\Outlook\ |
★★★★★ 不受 ESE 压缩影响,含完整附件 |
邮件头 Received: 链 |
任何留存的一封邮件原文 |
★★★★★ 独立于数据库,与压缩无关 |
| 网关/反垃圾日志 |
网关服务器、Anti-Spam 目录 |
★★★ 收发记录与拦截情况 |
| OWA 浏览器缓存 |
用户浏览器缓存 |
★★ 轻量版邮件内容 |
★★ 本篇最被低估的证据是邮件头。 Received: 链是投递过程中每跳追加的明文头,记录了每跳的服务器名、接收时间戳、客户端 IP。它不经过数据库压缩——所以即使 .edb 完全无法解析,只要有邮件原文(哪怕只是客户端里的邮件、转发件、打印件、网关日志里的一行),就能还原完整路径。 判读时注意:Received: 链自上而下是从发件方到收件方,时间戳递减(靠上更早)是伪造的迹象。
某人某时收发过某封邮件;邮件的正文与附件;完整的投递路径与每跳时间戳、客户端 IP(这一项独立于数据库);已删除邮件"曾经存在过"的事实;传输队列的投递结果与失败原因;邮件数据是否被外传。
附件常常无法完整恢复(在 LVAL/溢出页且可能压缩);已删除邮件可能只剩字节(slack 空间被复用后,能证明"曾经存在"不证明"内容是什么");服务器 .edb 未必覆盖全部邮件(用户可能用移动端或本地缓存,服务器只有副本);时间下限(循环日志最老的文件会被 checkpoint 覆盖)。
操作纪律(本篇最需要记住的一条):esentutl 的使用顺序是先 /mh 读状态(只读)→ 再 /y 复制工作副本 → 最后才在副本上考虑修复。
绝不直接对原始 .edb 执行任何写操作或修复。 详见 4.1 写操作边界。
内容边界(本篇的红线):邮件内容是企业个人信息中敏感度最高的一类。提取与呈现都要最小化——只取与委托范围相关的邮件,宁少勿全;报告中的正文与附件内容应脱敏呈现并登记密级;涉及第三方(收发件人不是本案当事人)的邮件内容,只保留证明案件事实所必需的部分。相关授权要求见扩展阅读中的合规条目。
读者前提:需要 Windows 文件系统取证能力(定位、复制、哈希固定)、数据库基础概念(页、事务日志、slack space)、邮件协议(SMTP/MAPI)基本原理。建议先按 3.6 第 6 步 的思路评估替代路径——很多案件的邮件证据在用户 PST、网关日志或某一封转发出去的邮件里,找到它可能只需要一次 grep,而解析 .edb 需要专用工具、几十 GB 空间和数天时间。
二、核心原理
2.1 Exchange 版本与数据库布局
Exchange 的数据库布局随版本变化很大。先确认版本,这是第一步。
| 版本 |
主要数据库文件 |
路径 |
说明 |
| Exchange 2013+ |
Mailbox Database.edb |
C:\Program Files\Microsoft\Exchange Server\V15\MailDB\<GUID>\ |
邮箱数据库(每个数据库组一个 GUID 目录) |
|
Transport Database.edb |
同上目录或独立路径 |
传输队列与路由 |
|
Logs\ 子目录 |
同一 GUID 目录下 |
$Upd 事务日志、.log 文件 |
|
WLSV*.edb |
索引相关 |
名称因角色而异 |
| Exchange 2010 |
Mailbox Database.edb |
V14\MailDB\<GUID>\ |
结构与 2013 接近 |
| Exchange 2007 |
Store01.edb 等 |
V12\MailDB\<store>\<GUID>\ |
文件名是 StoreNN.edb,与 2013+ 不同 |
| Exchange 2003 及更早 |
MDB.edb |
MailDB\<storage group>\<store>\MDB.edb |
旧结构 |
| PST(客户端) |
用户的 .pst |
%LOCALAPPDATA%\Microsoft\Outlook\ 或网络盘 |
PST 不是 ESE,是 PST 专有格式 |
★ 三个必须先确认的事实:
- 文件名随版本变:Exchange 2013+ 是
Mailbox Database.edb(有空格),2007 是 Store01.edb。按固定文件名搜索会漏。 用 *.edb 递归搜整个数据库目录。
- 每个数据库组有自己的 GUID 子目录:
MailDB\<GUID>\Mailbox Database.edb。GUID 随数据库组创建,重建数据库会换新 GUID——旧 GUID 目录还在但不再使用,本身是"数据库曾被重建"的线索。
- 日志在
Logs\ 子目录,且日志文件巨大(每个通常 1024 MB)。完整提取一个邮箱数据库(含日志)可能是几十到几百 GB(见 01 的 P3 优先级)。
2.2 ★ ESE 数据库机制:页、事务日志与 slack space
ESE 是一个分页(page-based)的嵌入式事务数据库。理解三个概念就够了,剩下的取证特性都由它们推出来。
① 页(Page)与页类型
| 页类型 |
作用 |
取证意义 |
| Database(DB)页 |
数据库头:格式版本、根页指针、LSN |
读出它就知道版本与状态(/mh 读的就是它) |
| Catalog(CAT)页 |
表目录:有哪些表、列定义、索引 |
不读它就无法知道 .edb 里有什么表 |
| Data(DATA)页 |
实际记录数据 |
邮件正文、收件人、附件元数据在此 |
| Index(INDX)页 |
B 树索引 |
索引损坏但数据页完好时,仍可能恢复数据 |
| Long Value(LVAL)页 |
大字段(正文、附件)的溢出存储 |
附件常在这里 |
② $Upd 事务日志:为什么 ESE 可以"回滚恢复"
ESE 采用预写日志(Write-Ahead Logging)机制:任何数据页被修改前,对应的修改记录先写入日志文件(E01.log、E02.log…),日志页就是所谓 $Upd 页。
这个机制决定了取证上的一个基本事实:数据页上的内容永远可能落后于日志。
修改数据页 → 先写 $Upd 日志页(记录旧值/新值)→ 再改数据页 → 日志标记提交
崩溃恢复 → 读日志,把未提交的修改回滚(UNDO)
取证含义:
| 事实 |
取证价值 |
| 日志先于数据页写入(预写日志) |
决定了"文件里的内容"是不是最新状态 |
| 异常关闭(dirty shutdown)后日志里可能含比数据页更新的数据 |
日志是独立证据源,可能含主库没有的记录 |
| 循环日志中最老的文件可能被 checkpoint 覆盖 |
决定可恢复的邮件时间下限 |
/mh 的 Log Time 与 Last Update Time 不一致 |
说明未正常关闭,可能需要修复 |
★ 关键推论:不要只提 .edb 文件。 同一目录下的 Logs\ 子目录是独立的、常常被忽略的证据源。在某些情况下,日志里能恢复出主库文件里已经没有的邮件记录。
③ Slack space:为什么"删掉的邮件还能找到一部分"
删除一封邮件不会擦除数据页上的原始字节,只是在记录层把它标记为"已删除"(tombstone / 变长字段的重置)。
原始字节通常仍留在页内的 slack 空间(未使用区),直到那个页被新数据覆写。
| 项目 |
说明 |
| 未分配页(unallocated page) |
整个页已不属于任何表,整页可雕刻 |
| slack space(页内未使用区) |
页已分配但内部有空洞,只能按页提取后手工搜索 |
| 复用的时机 |
页内新数据写入时才会覆写 slack。邮件删除后立即未复用 → 字节完好 |
| 取证手段 |
提取未分配页(blkls)或整页,按内容签名与关键字搜索 |
★ 与 Web 取证的连接:这就是 04 2.7 节说的同一件事——删除抹掉的是"目录项",不是"数据"。区不要在于 ESE 的 slack 在数据库文件内部,需要 ESE 专用工具才能按页提取;NTFS 的 slack 在磁盘未分配空间,用 blkls 就能拿到。
2.3 ★★ LZX / RLE 压缩:Exchange 数据恢复的主要障碍
这是本篇最需要提前认知的技术事实,因为它决定了你会不会在工具上浪费时间。
| 事实 |
影响 |
| ESE 的 DATA / LVAL 页可以配置为压缩存储(LZX 或 RLE) |
记录不是明文,而是压缩流 |
| Exchange 2013+ 邮箱数据库默认启用压缩 |
主流生产环境几乎必然遇到 |
| 压缩页无法用通用工具解析 |
用 SQLite 工具打开只会得到"文件不是数据库" |
| 支持 ESE ≠ 支持 LZX |
能读表结构和部分记录,但长正文/附件为空或乱码 |
这就是"为什么通用数据库工具完全无效"的技术原因:
| 工具类型 |
结果 |
sqlite3、DB Browser 类 |
完全打不开——ESE 文件头与 SQLite 完全不同 |
| 十六进制编辑器 |
能看到明文片段,但结构被打散,无法确定边界与含义 |
通用文本搜索(grep/strings) |
只能命中未压缩页里的记录;压缩页里的正文全部漏掉 |
| 支持 ESE 但不支持 LZX 的解析器 |
能读表结构、记录头,但正文字段为空 |
★ 实务建议:在报告和方案阶段就要明确写清"本环境是否支持 LZX 解压"。如果工具链不支持,应尽早转向 2.7 节的替代证据路径(客户端 PST、邮件网关日志、Received: 链),而不是反复在不同解析器之间碰壁。邮件正文的取证不应该是"工具试错"的过程。
2.4 ★ esentutl 详解:先读状态,再复制,最后才谈修复
esentutl.exe(%windir%\System32\)是 Windows 自带的 ESE 维护工具。
它同时具备读取和改写能力——这正是它的取证风险所在。
| 开关 |
作用 |
是否修改数据库 |
取证用法 |
/mh <file> |
读数据库头,打印状态报告(/m = File Dump,h = header) |
否(只读) |
★★★ 第一步必做:是否干净关闭、页大小、版本 |
/y <src> /d <dst> |
复制数据库文件 |
源否 |
★★★ 第二步必做:制作工作副本 |
/k <file> |
Checksum:逐页校验和,验证物理完整性 |
否(只读) |
★★★ 在副本上做;报页级错误说明底层损坏 |
/g <file> |
Integrity:完整性检查,查逻辑与结构错误 |
否(只读) |
★★★ 在副本上做;Dirty Shutdown 时应先 /r 再 /g |
/r <file> |
Recovery:软修复(重放日志、回滚未提交事务) |
是,明显修改文件 |
★ 在副本上做,且必须先留哈希 |
/p <file> |
Repair:硬修复,删除无法读取的页 |
是,且可能丢数据 |
✗ 取证高危(见陷阱表) |
/d |
Defragmentation:碎片整理 |
是(重写全库) |
✗ 取证禁用 |
★ 三条模式名极易记混,这是本节最需要记住的:
| 开关 |
名字 |
记忆锚点 |
/k |
Checksum |
K = 校验和(check) |
/g |
Go/integrity |
G = 全库结构检查 |
/p |
Patch/repair |
P 是破坏性修复,会删页 |
/r |
Recovery |
软修复,不删数据 |
/d |
Defragment |
碎片整理,重写全库 |
只读的三条是 /mh、/k、/g——取证早期只应使用这三者。凡涉及 /r、/p、/d 的操作,一律先复制副本。
★ 四个关键判读点(/mh 输出的核心字段):
| 字段 |
健康值 |
异常含义 |
State: |
Clean Shutdown |
Dirty Shutdown = 未正常关闭,日志与数据页可能不一致 |
Log Time: vs Last Update Time: |
两者接近 |
差距大 → 日志更新,可能有未回滚的修改,可考虑 /r |
Format Version: |
与目标 Exchange 版本匹配 |
版本跨度过大 → 需确认是否升过级 |
Last Update Time: |
越新越好 |
过旧 = 数据库可能长时间未更新甚至已废弃 |
★ 最关键的操作纪律(务必写进工作记录):
提取路径:/mh(只读)→ /y 复制出工作副本 → 在副本上做 /k、/g,确有必要时才 /r。
绝对顺序错误:esentutl /r "D:\Exchange\Mailbox Database.edb" 直接对原始文件操作 → 修改原始数据库、破坏证据完整性、且不可逆。即便现场是从镜像里提取出来的文件,它仍是原始证据。
"只在副本上操作、把原始状态单独存档"这个规矩并非 ESE 独有。
InnoDB 的 redo/undo 与 binlog 如何在不写回原始文件的前提下取证,可参考 MySQL 数据库取证。
每次操作前后都要记录 SHA-256,否则无法证明副本与原件的对应关系。
2.5 标准流程:先 /mh 读状态,再复制,需要时才修复
① 确认版本与路径
→ 递归找 *.edb(不按固定文件名搜)
② 只读状态检查 esentutl /mh <file>
→ 看 State / Log Time / Last Update Time / Format Version
→ 记录输出(★ 这是原始健康状态,修复后就没了)
③ 复制工作副本 esentutl /y <src> /d <dst>
→ 对副本计算 SHA-256,与原件比对必须一致
④ 只读校验(副本)esentutl /k <副本> ← 逐页校验和
esentutl /g <副本> ← 完整性检查
→ 两者的区别见 2.4 记忆锚点表
⑤ 软修复(可选) esentutl /r <副本> ← ★ Dirty Shutdown 时的标准处置
→ 修复后重新 /mh + 重算哈希(★ 证明副本已被修改)
⑥ 专用工具解析 支持 ESE + LZX 的工具
→ 导出可引用的证据格式(CSV / 报告)
★ 为什么第 ② 步的输出要单独存档:/r 修复会抹掉"原始状态是 Dirty Shutdown"这个事实。而"数据库异常关闭"本身就是一条证据——它可能意味着管理员强杀了服务、断电、或攻击者关停 Exchange 规避日志。修复前的状态报告是原始证据的一部分。
★ 注意顺序:官方建议在 Dirty Shutdown 状态下先 /r 再 /g,因为不先完成恢复就直接做完整性检查,结果不可靠。/k(逐页校验和)不受此限制,可随时做。
2.6 可用工具与其能力边界
| 工具 |
适用对象 |
能力与边界 |
libesedb(esedbexport / esedbinfo) |
.edb(ESE) |
跨平台;esedbinfo 列表与记录数,esedbexport 按表导出。对 LZX 支持有限,长正文/附件常为空 |
PST 解析器(如 pffexport 一类) |
.pst |
专处理 PST(非 ESE)。能完整导出邮件与附件,常比服务器 .edb 更实用 |
Exchange 原生命令(Get-Mailbox 等) |
活系统 |
只能在运行中的服务器上导出 PST,离线镜像不可用 |
| 十六进制 + 关键字搜索 |
任意 |
只能命中未压缩页,作交叉验证的补充手段 |
| VEST 类 ESE 工具 |
.edb / .pst |
商业取证套件中的 ESE 组件,通常对 LZX 支持较完整;需在方案阶段确认授权 |
★ 选型原则:先确认"是否支持 LZX",再选工具。选一个不支持压缩的解析器,然后花三天分析"为什么附件都是空的",是这类案件最常见的时间浪费。
2.7 ★ 取证价值与限度
能证明什么(服务器 .edb 可用时):
| 结论 |
证据强度 |
| 某人某时收到/发送了某封邮件 |
★★★★(时间、收发件人、主题可固定) |
| 邮件的正文内容 |
★★★(取决于压缩支持,见 2.3) |
| 邮件的附件内容 |
★★(常因压缩/溢出页难以完整恢复) |
| 邮件的传输路径 |
★★★★★(★ Received: 链,独立于数据库,与压缩无关) |
★★ 邮件头是本篇最被低估的证据。 Received: 链是投递过程中每跳追加的明文头,记录了:每一跳的服务器名/主机名、接收时间戳、客户端 IP。**它不经过数据库压缩,所以即使 .edb 完全无法解析,只要有邮件原文(哪怕只是客户端里的邮件、转发件、打印件、网关日志里的一行),就能还原完整路径。
判读方法:Received: 链自上而下是从发件方到收件方(最上面是最后接收方)。每跳的时间戳可构成完整时间线;**时间戳递减(靠上更早)是伪