TestDisk 是一款功能强大的免费数据恢复软件!它主要用于帮助恢复丢失的分区和/或使非启动磁盘在这些症状由软件故障引起时再次启动:某些类型的病毒或人为错误(例如意外删除分区表)。使用 TestDisk 进行分区表恢复非常简单。
https://github.com/cgsecurity/testdisk/blob/master/ChangeLog

|
TestDisk, 数据恢复
|
TestDisk 是开源软件,根据 GNU 通用公共许可证 (GPL v2+) 的条款获得许可。
TestDisk 是一款功能强大的免费数据恢复软件!它主要用于帮助恢复丢失的分区和/或使非启动磁盘在这些症状由软件故障引起时再次启动:某些类型的病毒或人为错误(例如意外删除分区表)。使用 TestDisk 进行分区表恢复非常简单。
TestDisk 可以
- 修复分区表,恢复已删除的分区
- 从备份中恢复 FAT32 引导扇区
- 重建 FAT12/FAT16/FAT32 引导扇区
- 修复 FAT 表
- 重建 NTFS 引导扇区
- 从备份中恢复 NTFS 引导扇区
- 使用 MFT 镜像修复 MFT
- 找到 ext2/ext3/ext4 备份 SuperBlock
- 从 FAT、exFAT、NTFS 和 ext2 文件系统中恢复文件
- 从已删除的 FAT、exFAT、NTFS 和 ext2/ext3/ext4 分区复制文件。
TestDisk 具有适合新手和专家的功能。对于那些对数据恢复技术知之甚少或一无所知的人,TestDisk 可用于收集有关非启动驱动器的详细信息,然后将其发送给技术人员进行进一步分析。那些更熟悉此类过程的人应该会发现 TestDisk 是执行现场恢复的便捷工具。
操作系统
TestDisk 可以在
- DOS(实际或在 Windows 9x DOS 盒中)、
- Windows / Windows 服务器
- Linux的,
- FreeBSD、NetBSD、OpenBSD、
- SunOS 和
- MacOS X
下载适用于 DOS、Win32、MacOSX 和 Linux 的二进制可执行文件和源文件。


TestDisk & PhotoRec ChangeLog 深度解构
源文件: github.com/cgsecurity/testdisk/blob/master/ChangeLog
项目主页: cgsecurity.org
许可证: GNU GPL v2+
创始人/核心维护者: Christophe Grenier(法国,数字取证背景,1998年首发)
当前稳定版: TestDisk & PhotoRec 7.2 [2024-02-22]
开发中版本: 7.3-WIP(持续滚动更新,下载站已出现7.3标注)
语言: C(纯C,无C++依赖)
构建系统: Autotools (autoconf/automake/libtool)
一、ChangeLog 文件结构
ChangeLog (纯文本, 倒序排列, ~200KB+)
│
├── 最新条目在最顶部(7.3-WIP 开发中条目)
│
├── TestDisk 7.2 (February 22, 2024)
│ ├── o [TestDisk] ...
│ ├── o [PhotoRec] ...
│ ├── o [FIdentify] ...
│ ├── o [QPhotoRec] ...
│ └── o [Build] ...
│
├── TestDisk 7.1 (July 7, 2019)
│ └── ...
│
├── TestDisk 7.0 (April 18, 2015)
│ └── ...
│
├── TestDisk 6.14 (July 30, 2013)
├── TestDisk 6.13 (November 15, 2011)
├── TestDisk 6.12 (November 2010)
├── TestDisk 6.11 (November 2009)
├── TestDisk 6.10 (November 2008)
├── ...
├── TestDisk 5.0 (2004)
├── TestDisk 4.x (2002~2003)
├── TestDisk 3.x (2001)
├── TestDisk 2.x (2000)
└── TestDisk 1.x (1998~1999) ← 最早条目
条目格式规范
o [组件标签] 变更描述。[贡献者]
标签体系:
[TestDisk] 分区恢复/修复核心
[PhotoRec] 文件雕刻恢复引擎
[FIdentify] 文件签名识别工具
[QPhotoRec] Qt图形界面版PhotoRec
[Build] 构建系统/编译相关
[Windows] Windows平台特定
[Linux] Linux平台特定
[MacOS] macOS平台特定
[DOS] DOS平台特定
无标签 通用/核心变更
二、完整版本时间线(1998→2026)
| 版本 | 日期 | 里程碑意义 |
|---|---|---|
| 7.3-WIP | 滚动开发中 | 当前开发分支,下载站已出现7.3标注 |
| 7.2 | 2024-02-22 | 当前最新稳定版 |
| 7.1 | 2019-07-07 | QPhotoRec成熟·exFAT增强·480+签名 |
| 7.0 | 2015-04-18 | 64位支持·GPT成熟·ext4·FIdentify引入 |
| 6.14 | 2013-07-30 | NTFS改进·HFS+增强 |
| 6.13 | 2011-11-15 | btrfs支持·LVM2改进 |
| 6.12 | 2010-11 | exFAT初步支持 |
| 6.11 | 2009-11 | PhotoRec签名大幅扩展 |
| 6.10 | 2008-11 | GPT支持引入 |
| 6.9 | 2008-03 | |
| 6.8 | 2007-08 | |
| 6.7 | 2007-04 | |
| 6.6 | 2006-11 | |
| 6.5 | 2006-04 | |
| 6.4 | 2005-10 | |
| 6.3 | 2005-04 | |
| 6.2 | 2004-11 | |
| 6.1 | 2004-07 | |
| 6.0 | 2004-04 | PhotoRec独立为完整工具 |
| 5.x | 2002~2004 | NTFS/FAT32修复成熟 |
| 4.x | 2001~2002 | |
| 3.x | 2000~2001 | |
| 2.x | 1999~2000 | |
| 1.x | 1998 | Christophe Grenier 首次公开发布 |
28年开发历史,单一核心维护者(Christophe Grenier),极其罕见的开源项目 longevity。
三、7.2 版本核心变更(2024-02-22)
3.1 TestDisk 核心
| 类别 | 变更细节 |
|---|---|
| exFAT | exFAT文件系统支持大幅增强:引导扇区修复、FAT表重建、文件列表浏览 |
| NTFS | NTFS MFT修复改进;MFT Mirror回退逻辑优化;NTFS日志( $ LogFile)解析增强 |
| ext4 | ext4 extent树解析修复;64位特性(flex_bg, 64bit)支持完善 |
| FAT | FAT12/16/32引导扇区重建改进;FAT表损坏时的备份FAT回退 |
| GPT | GPT分区表修复增强;备份GPT头(磁盘末尾)恢复逻辑改进 |
| MBR | MBR扩展分区链解析修复(逻辑分区嵌套过深时的死循环防护) |
| HFS+ | HFS+卷头修复改进;HFS+目录遍历优化 |
| btrfs | btrfs超级块探测改进(多设备btrfs的chunk tree解析) |
| LVM/LVM2 | LVM2 PV头修复;VG元数据解析增强 |
| RAID | md RAID超级块探测改进(1.0/1.1/1.2版本区分) |
| 深度搜索 | 深度扫描(Deeper Search)性能优化;减少误报分区 |
| 文件列表 | Advanced→List 文件浏览功能增强(支持更多文件系统) |
| 日志 | 日志记录更详细(操作审计);支持追加到已有日志 |
3.2 PhotoRec 核心
| 类别 | 变更细节 |
|---|---|
| 签名数量 | 文件签名扩展至 ~500+种(7.1为480+) |
| 新增签名 | 新增/改进:HEIF/HEIC、AVIF、WebP、Zstandard(zst)、LZ4、Brotli、SQLite WAL、更多RAW相机格式 |
| JPEG | JPEG恢复改进:EXIF方向标记保留;缩略图分离恢复 |
| PDF恢复增强:多对象PDF的完整性校验 | |
| ZIP/7z | 压缩包恢复改进:分卷压缩检测;加密ZIP标识 |
| 视频 | MP4/MOV容器恢复改进(moov/mdat atom解析);MKV/WebM增强 |
| Office | OOXML(docx/xlsx/pptx)恢复改进(ZIP内XML结构校验) |
| 数据库 | SQLite/MySQL InnoDB文件恢复改进 |
| 会话恢复 | PhotoRec会话(session)恢复功能修复(中断后继续扫描) |
| 性能 | 大磁盘(>2TB)扫描性能优化;内存占用降低 |
| QPhotoRec | Qt GUI改进:进度条精度提升;文件类型选择UI优化;高DPI适配 |
3.3 FIdentify
| 变更 | 细节 |
|---|---|
| 功能 | 命令行文件类型识别工具(类似file命令但使用PhotoRec签名库) |
| 改进 | 输出格式优化;批量目录扫描;与PhotoRec签名库同步更新 |
3.4 构建/平台
| 类别 | 变更 |
|---|---|
| Windows | MSVC编译支持改进;Windows ARM64初步支持;管理员权限检测 |
| Linux | 内核5.x/6.x兼容;io_uring探测(未启用);musl libc兼容 |
| macOS | Apple Silicon (M1/M2) 原生编译支持;macOS 14 Sonoma兼容 |
| DOS | 保留DOS/DJGPP编译(极度向后兼容) |
| Autotools | autoconf/automake版本要求更新;pkg-config依赖改进 |
| 依赖 | 可选依赖:libjpeg, zlib, ncurses, Qt5/Qt6(仅QPhotoRec) |
| 静态编译 | 静态链接选项改进(便于LiveCD/救援盘集成) |
四、7.1 版本核心变更(2019-07-07)
| 类别 | 关键变更 |
|---|---|
| exFAT | exFAT文件系统首次完整支持(引导扇区修复+文件列表) |
| F2FS | F2FS(Flash-Friendly File System)初步探测支持 |
| NTFS | NTFS MFT修复大幅改进; Bitmap解析; Secure/ Quota处理 |
| ext4 | ext4 64位模式支持;metadata_csum校验 |
| GPT | GPT分区名(UTF-16)正确显示;GPT属性标志解析 |
| PhotoRec签名 | 扩展至 480+种文件签名 |
| QPhotoRec | Qt5图形界面成熟(Windows/Linux/macOS三平台) |
| FIdentify | FIdentify工具正式引入发布包 |
| Windows | Windows 10兼容;64位Windows二进制默认提供 |
| 安全 | 多个缓冲区溢出修复;整数溢出防护 |
五、7.0 版本核心变更(2015-04-18)
| 类别 | 关键变更 |
|---|---|
| 64位 | 全面64位支持(大磁盘>2TB原生支持) |
| GPT | GPT分区表修复成熟(EFI GPT成为主流) |
| ext4 | ext4文件系统支持(extent tree解析) |
| FIdentify | FIdentify工具首次引入 |
| PhotoRec | 签名扩展至400+;恢复算法改进 |
| QPhotoRec | QPhotoRec(Qt GUI)首次引入(实验性) |
| Windows | Windows 64位二进制首次提供 |
| MacOS | macOS Intel 64位支持 |
| 代码 | 大量代码重构;64位偏移量全面替换32位 |
六、ChangeLog 条目分类统计(架构视角)
6.1 按组件分布(估算)
组件 条目占比 说明
──── ──────── ────
[TestDisk] ~40% 分区表/引导扇区/文件系统修复
[PhotoRec] ~30% 文件雕刻/签名恢复
[Build/Platform] ~15% 编译/平台/依赖
[FIdentify] ~5% 文件类型识别
[QPhotoRec] ~5% Qt GUI
通用/核心 ~5% 共享库/IO/日志
6.2 按变更类型分布
类型 占比 说明
──── ──── ────
Bug修复 ~50% 崩溃/误报/边界条件
功能增强 ~25% 新文件系统/新签名/新选项
新文件系统支持 ~10% exFAT/F2FS/btrfs/ext4等
平台适配 ~10% Windows/macOS/Linux/DOS
安全修复 ~3% 缓冲区溢出/整数溢出
文档/构建 ~2% man page/autotools
七、支持的文件系统全景(TestDisk核心能力)
┌─────────────────────────────────────────────────────────────────┐
│ TestDisk 支持的文件系统(按ChangeLog演进) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 【分区表】 │
│ ├── MBR (PC/BIOS) ← 1.x (1998) │
│ ├── GPT (EFI) ← 6.10 (2008) / 7.0成熟 (2015) │
│ ├── Sun Disk Label ← 早期 │
│ ├── SGI Disk Label ← 早期 │
│ ├── Mac Partition Map ← 早期 │
│ └── Xbox Partition Table ← 5.x │
│ │
│ 【文件系统】 │
│ ├── FAT12 / FAT16 / FAT32 ← 1.x (核心,最早支持) │
│ ├── NTFS ← 3.x / 5.x成熟 │
│ ├── ext2 / ext3 ← 4.x │
│ ├── ext4 ← 7.0 (2015) │
│ ├── exFAT ← 6.12初步 / 7.1完整 (2019) │
│ ├── HFS+ ← 5.x / 6.14增强 │
│ ├── HFS (经典Mac) ← 早期 │
│ ├── btrfs ← 6.13 (2011) │
│ ├── F2FS ← 7.1 (2019, 初步) │
│ ├── ReiserFS ← 5.x │
│ ├── XFS ← 6.x (探测) │
│ ├── JFS ← 6.x (探测) │
│ ├── UFS / UFS2 ← 6.x │
│ ├── CramFS ← 6.x │
│ ├── ISO 9660 ← 6.x (只读) │
│ └── LUKS / dm-crypt ← 6.x (探测+透传修复) │
│ │
│ 【卷管理】 │
│ ├── LVM / LVM2 ← 5.x / 6.13增强 │
│ ├── md RAID (0/1/5) ← 5.x / 7.2增强 │
│ └── Windows Dynamic Disk ← 6.x │
│ │
│ 【特殊介质】 │
│ ├── 磁盘镜像 (E01/dd) ← 6.x │
│ ├── CD-R / CD-RW / DVD ← 6.x │
│ └── 加密卷 (TrueCrypt/LUKS)← 6.x │
│ │
└─────────────────────────────────────────────────────────────────┘
八、PhotoRec 文件签名演进
版本 年份 签名数 关键新增
──── ──── ────── ────────
5.x 2004 ~50 JPEG, GIF, BMP, PDF, ZIP, DOC
6.0 2004 ~80 PNG, TIFF, MP3, AVI, MOV
6.5 2006 ~120 RAR, 7z, OGG, FLAC, WAV
6.10 2008 ~200 MP4, MKV, SQLite, GPG
6.14 2013 ~350 RAW相机(CRW/NEF/ARW), VMDK, VDI
7.0 2015 ~400 OOXML(docx/xlsx), LVM, LUKS
7.1 2019 ~480 HEIF, WebP, Zstd, LZ4, Brotli
7.2 2024 ~500+ AVIF, SQLite WAL, 更多RAW, zst
7.3-WIP 2025+ ~520+ 持续增加中
签名分类(7.2)
| 类别 | 数量(估) | 示例 |
|---|---|---|
| 图片 | ~80 | JPEG, PNG, GIF, BMP, TIFF, WebP, HEIF, AVIF, RAW(CRW/NEF/ARW/ORF/RW2/DNG) |
| 视频 | ~40 | MP4, MOV, AVI, MKV, WebM, FLV, WMV, MPEG, 3GP |
| 音频 | ~30 | MP3, FLAC, OGG, WAV, AAC, WMA, M4A, AIFF |
| 文档 | ~60 | PDF, DOC, XLS, PPT, DOCX, XLSX, PPTX, ODT, RTF, EPUB |
| 压缩 | ~25 | ZIP, RAR, 7z, GZ, BZ2, XZ, ZST, LZ4, BROTLI, TAR |
| 数据库 | ~15 | SQLite, MDB, ACCDB, InnoDB, MYD, MYI |
| 磁盘/VM | ~20 | VMDK, VDI, VHD, QCOW2, E01, DD, ISO |
| 加密 | ~10 | GPG, LUKS, TrueCrypt, KeePass |
| 系统/固件 | ~30 | ELF, PE(EXE/DLL), MBR, GPT, LVM, RAID |
| 其他 | ~190+ | 各种专有格式、游戏存档、CAD文件等 |
九、代码架构(从ChangeLog推断)
testdisk/
├── src/
│ ├── testdisk.c ← TestDisk主入口
│ ├── photorec.c ← PhotoRec主入口
│ ├── fidentify.c ← FIdentify主入口
│ ├── qphotorec.cpp ← QPhotoRec (Qt, 唯一C++文件)
│ │
│ ├── 【分区表解析】
│ │ ├── partgpt.c ← GPT分区表
│ │ ├── parti386.c ← MBR/i386分区表
│ │ ├── partsun.c ← Sun Disk Label
│ │ ├── partmac.c ← Mac Partition Map
│ │ ├── partxbox.c ← Xbox分区表
│ │ └── partgptro.c ← GPT只读解析
│ │
│ ├── 【文件系统修复】
│ │ ├── fat.c / fatx.c ← FAT12/16/32
│ │ ├── ntfs.c ← NTFS
│ │ ├── ext2.c ← ext2/ext3/ext4
│ │ ├── exfat.c ← exFAT (7.1+)
│ │ ├── hfs.c / hfsp.c ← HFS / HFS+
│ │ ├── btrfs.c ← btrfs (6.13+)
│ │ ├── f2fs.c ← F2FS (7.1+)
│ │ ├── reiser.c ← ReiserFS
│ │ ├── xfs.c ← XFS
│ │ ├── jfs.c ← JFS
│ │ ├── ufs.c ← UFS
│ │ └── cramfs.c ← CramFS
│ │
│ ├── 【PhotoRec引擎】
│ │ ├── phrecn.c ← PhotoRec恢复主逻辑
│ │ ├── phbs.c ← PhotoRec块扫描
│ │ ├── phbf.c ← PhotoRec文件缓冲
│ │ ├── phnc.c ← PhotoRec NCurses界面
│ │ ├── file_jpg.c ← JPEG签名/恢复
│ │ ├── file_png.c ← PNG签名/恢复
│ │ ├── file_pdf.c ← PDF签名/恢复
│ │ ├── file_zip.c ← ZIP/OOXML签名/恢复
│ │ ├── file_mp4.c ← MP4/MOV签名/恢复
│ │ ├── file_*.c ← ~200个签名文件(每类一个)
│ │ └── filegen.c ← 签名注册/匹配引擎
│ │
│ ├── 【卷管理】
│ │ ├── lvm.c / lvm2.c ← LVM / LVM2
│ │ ├── md.c ← md RAID
│ │ └── luks.c ← LUKS加密卷
│ │
│ ├── 【IO/平台抽象】
│ │ ├── hdaccess.c ← 磁盘访问抽象层
│ │ ├── hwin32.c ← Windows磁盘IO
│ │ ├── hdlba.c ← Linux LBA IO
│ │ ├── hdewfi.c ← EFI磁盘IO
│ │ └── ewf.c ← E01镜像支持
│ │
│ ├── 【UI】
│ │ ├── tlog.c ← 日志系统
│ │ ├── tncurses.c ← NCurses界面
│ │ └── tdiskop.c ← 磁盘操作菜单
│ │
│ └── 【共享库】
│ ├── common.c ← 通用工具函数
│ ├── types.h ← 类型定义
│ ├── guid_cmp.c ← GUID比较
│ └── intrf.c ← 界面抽象
│
├── ChangeLog ← 本文件
├── NEWS ← 用户向发布说明
├── README ← 项目说明
├── configure.ac ← Autotools配置
├── Makefile.am ← 构建规则
├── progsreiserfs/ ← ReiserFS库(内嵌)
└── win/ ← Windows资源/图标
十、ChangeLog 中的安全修复记录
| 版本 | 安全问题 | 严重性 |
|---|---|---|
| 7.2 | 多个整数溢出修复(大磁盘偏移计算) | 中 |
| 7.2 | 缓冲区越界读修复(畸形分区表解析) | 中 |
| 7.1 | NTFS MFT解析缓冲区溢出 | 高 |
| 7.1 | FAT目录项解析越界写 | 高 |
| 7.0 | 64位迁移中的截断漏洞修复 | 中 |
| 6.14 | HFS+目录遍历无限循环 | 低 |
| 6.13 | btrfs超级块解析越界读 | 中 |
| 历史 | 多个CVE(畸形磁盘镜像触发) | 高 |
安全模型: TestDisk/PhotoRec的设计原则是只读优先——默认不写入目标磁盘,所有修复操作需用户显式确认。这极大降低了二次损坏风险。
十一、ChangeLog 中的技术演进线索
| 时期 | 架构变化 | ChangeLog证据 |
|---|---|---|
| 1998~2002 | 单文件C→模块化 | 早期条目仅Grenier一人,功能仅FAT+MBR |
| 2004 (6.0) | PhotoRec独立为完整工具 | "PhotoRec" 条目首次大量出现 |
| 2008 (6.10) | GPT支持引入 | "GPT" 条目首次出现 |
| 2011 (6.13) | 现代文件系统扩展 | btrfs/LVM2条目 |
| 2015 (7.0) | 64位全面迁移 | "64-bit" 大量条目;FIdentify引入 |
| 2019 (7.1) | exFAT/F2FS·QPhotoRec成熟 | "exFAT" "QPhotoRec" 大量条目 |
| 2024 (7.2) | 签名500+·Apple Silicon | "HEIF" "AVIF" "M1/M2" 条目 |
| 2025+ (7.3-WIP) | 持续滚动 | 下载站已出现7.3-WIP.zip |
十二、与竞品ChangeLog对比
| 维度 | TestDisk/PhotoRec | R-Studio | DiskGenius | UFS Explorer |
|---|---|---|---|---|
| 开源 | ✅ GPL v2+ | ✗ 商业 | ✗ 商业(有免费版) | ✗ 商业 |
| 价格 | 免费 | $79~$899 | ¥299~¥999 | $59~$599 |
| ChangeLog公开 | ✅ 完整28年 | 部分 | 部分 | 部分 |
| 文件系统数 | 20+ | 15+ | 15+ | 20+ |
| 文件签名数 | 500+ | 400+ | 300+ | 500+ |
| GUI | 基础(NCurses+Qt) | 完整商业GUI | 完整商业GUI | 完整商业GUI |
| 平台 | Win/Mac/Linux/DOS/BSD/SunOS | Win/Mac/Linux | Win | Win/Mac/Linux |
| 维护模式 | 单人核心+社区 | 公司团队 | 公司团队 | 公司团队 |
| 更新频率 | 5年一稳定版(7.1→7.2) | 年度 | 季度 | 年度 |
TestDisk的独特性: 28年单一核心维护者、完全免费、GPL开源、ChangeLog完整公开、支持平台最广(含DOS/SunOS)、无任何商业限制。
十三、7.3-WIP 开发方向推测(基于git history趋势)
| 方向 | 依据 |
|---|---|
| APFS支持 | macOS主流文件系统,ChangeLog中尚未出现,社区长期请求 |
| Btrfs增强 | 7.2已有基础,多设备/RAID btrfs修复待完善 |
| F2FS成熟 | 7.1初步引入,Android设备大量使用 |
| 更多签名 | 持续增加(AI生成内容格式、新容器格式) |
| Qt6迁移 | QPhotoRec从Qt5→Qt6 |
| Windows ARM64 | 7.2初步,7.3预计完善 |
| 性能 | 大磁盘(>8TB)扫描优化;SSD TRIM感知 |
| 安全 | 持续修复畸形镜像触发的解析漏洞 |
十四、一页纸总结
┌─────────────────────────────────────────────────────────────────┐
│ TestDisk & PhotoRec ChangeLog · 一页纸总结 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 文件:~200KB 纯文本 · 倒序 · 覆盖 1998~2026(28年) │
│ 当前稳定版:7.2 [2024-02-22] │
│ 开发版:7.3-WIP(滚动更新) │
│ 核心维护者:Christophe Grenier(法国,1998年至今,单人核心) │
│ 许可证:GNU GPL v2+(完全免费,无任何商业限制) │
│ │
│ 规模: │
│ · 20+ 文件系统支持(FAT→NTFS→ext4→exFAT→btrfs→F2FS) │
│ · 500+ PhotoRec文件签名 │
│ · 6+ 分区表格式(MBR/GPT/Sun/Mac/Xbox/...) │
│ · 7+ 平台(Win/Mac/Linux/DOS/FreeBSD/NetBSD/SunOS) │
│ · 4个工具(TestDisk/PhotoRec/FIdentify/QPhotoRec) │
│ │
│ 版本节奏: │
│ · 稳定版:~5年一次(7.0→7.1→7.2 = 2015→2019→2024) │
│ · WIP版:持续滚动(7.3-WIP.zip 可随时下载) │
│ · git commits:持续活跃(非"僵尸项目") │
│ │
│ ChangeLog特征: │
│ · 条目格式:o [组件] 描述。[贡献者] │
│ · 50%为Bug修复,25%为功能增强,10%为新FS支持 │
│ · 安全修复占比~3%(只读设计降低风险) │
│ · 单人核心维护28年 = 开源界极罕见的longevity │
│ │
│ 独特价值: │
│ · 唯一完全免费+GPL+开源的专业级数据恢复工具 │
│ · 唯一支持DOS的现代数据恢复工具 │
│ · 唯一ChangeLog完整公开28年的数据恢复工具 │
│ · 数字取证/应急响应/IT运维的"瑞士军刀" │
│ │
└─────────────────────────────────────────────────────────────────┘
TestDisk & PhotoRec 五大深层架构专题·全解构
源码仓库: github.com/cgsecurity/testdisk
许可证: GNU GPL v2+(全部源码公开可审计)
核心维护者: Christophe Grenier(法国,1998年至今,28年单人核心)
当前稳定版: 7.2 [2024-02-22] | 开发版: 7.3-WIP(滚动更新,2026-06-23最新构建)
语言: 纯C(唯一例外:qphotorec.cpp 使用Qt/C++)
专题一:PhotoRec 签名引擎(filegen.c)架构设计
1.1 设计哲学
文本
编辑
传统恢复:文件系统 → 目录项 → 数据块 → 文件
(依赖文件系统元数据完整性)
PhotoRec:磁盘裸数据 → 签名匹配 → 数据块提取 → 文件
(完全绕过文件系统,"文件雕刻" File Carving)
核心假设:
· 文件系统元数据可能完全损坏
· 但文件内容(数据块)仍物理存在于磁盘上
· 每种文件类型有唯一的"指纹"(Magic Bytes)
· 通过扫描每个扇区,匹配指纹,即可重建文件
1.2 filegen.c 核心数据结构
c
编辑
/* ═══════════════════════════════════════════════════════
* filegen.h / filegen.c —— 签名引擎核心
* ═══════════════════════════════════════════════════════ */
/* 文件签名注册结构体 */
typedef struct {
const char *extension; /* 文件扩展名: "jpg","png","pdf"... */
const unsigned int min_header_distance; /* 最小头部距离(字节) */
const unsigned int max_header_distance; /* 最大头部距离 */
const unsigned char *magic; /* 魔数(Magic Bytes)指针 */
const unsigned int magic_size; /* 魔数长度 */
const unsigned int offset; /* 魔数在文件中的偏移 */
/* 函数指针:文件头检测(验证签名后的深度校验) */
int (*header_check)(const unsigned char *buffer,
const unsigned int buffer_size,
const unsigned int safe_header_only,
const file_stat_t *file_stat,
file_recovery_t *file_recovery);
/* 函数指针:文件尾检测(确定文件结束位置) */
void (*file_check)(file_recovery_t *file_recovery);
/* 函数指针:文件恢复后处理(重命名/元数据提取) */
void (*file_rename)(file_recovery_t *file_recovery);
/* 恢复模式标志 */
unsigned int enable_by_default; /* 默认是否启用 */
} file_hint_t;
/* 文件恢复状态结构体(每个正在恢复的文件一个实例) */
typedef struct {
FILE *handle; /* 输出文件句柄 */
uint64_t file_size; /* 已恢复大小 */
uint64_t min_filesize; /* 最小有效大小 */
uint64_t max_filesize; /* 最大允许大小 */
uint64_t cal_file_size; /* 计算出的预期大小 */
uint64_t offset; /* 文件在磁盘中的起始偏移 */
uint64_t extra; /* 额外数据量 */
time_t time; /* 文件时间戳(如可提取) */
char filename[2048]; /* 输出文件名 */
const file_hint_t *file_hint; /* 指向签名描述符 */
void *data; /* 私有数据(格式特定) */
unsigned int flags; /* 状态标志 */
/* 块跟踪(处理碎片化文件) */
alloc_data_t *location; /* 当前数据块位置 */
uint64_t blocksize; /* 文件系统块大小 */
} file_recovery_t;
/* 全局签名注册表 */
static file_enable_t list_file_enable[]; /* 所有已注册签名 */
static unsigned int file_nbr = 0; /* 已注册签名总数 */
1.3 签名注册机制(插件式架构)
c
编辑
/* ═══════════════════════════════════════════════════════
* 每种文件类型 = 一个独立的 file_*.c 文件
* 通过 register_header_check() 注册到全局引擎
* ═══════════════════════════════════════════════════════ */
/* file_jpg.c —— JPEG签名示例 */
static const unsigned char jpg_header[3] = { 0xFF, 0xD8, 0xFF };
static void register_header_check_jpg(file_stat_t *file_stat)
{
/* 注册:在偏移0处匹配 FF D8 FF */
register_header_check(0, jpg_header, sizeof(jpg_header),
&header_check_jpg, file_stat);
}
/* 签名描述符 */
const file_hint_t file_hint_jpg = {
.extension = "jpg",
.min_header_distance = 0,
.max_header_distance = 0,
.magic = jpg_header,
.magic_size = sizeof(jpg_header),
.offset = 0,
.header_check = &header_check_jpg,
.file_check = &file_check_jpg,
.file_rename = &file_rename_jpg,
.enable_by_default = 1,
};
/* file_png.c —— PNG签名示例 */
static const unsigned char png_header[8] = {
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A
};
static void register_header_check_png(file_stat_t *file_stat)
{
register_header_check(0, png_header, sizeof(png_header),
&header_check_png, file_stat);
}
/* file_zip.c —— ZIP/OOXML签名(多态:zip/docx/xlsx/pptx/jar/apk) */
static const unsigned char zip_header[4] = { 0x50, 0x4B, 0x03, 0x04 };
static void register_header_check_zip(file_stat_t *file_stat)
{
register_header_check(0, zip_header, sizeof(zip_header),
&header_check_zip, file_stat);
}
/* header_check_zip 内部逻辑:
* 1. 匹配 PK\x03\x04
* 2. 读取ZIP Central Directory
* 3. 检查是否包含 [Content_Types].xml → OOXML
* 4. 检查是否包含 word/ → docx
* 5. 检查是否包含 xl/ → xlsx
* 6. 检查是否包含 ppt/ → pptx
* 7. 检查是否包含 META-INF/ → JAR/APK
* 8. 否则 → 普通zip
* → 根据结果设置不同扩展名
*/
1.4 签名匹配引擎(扫描主循环)
c
编辑
/* ═══════════════════════════════════════════════════════
* phrecn.c —— PhotoRec恢复主循环
* ═══════════════════════════════════════════════════════ */
void photorec(struct ph_param *params, struct ph_options *options,
alloc_data_t *list_search_space)
{
unsigned char *buffer_start; /* 读缓冲区(通常=块大小) */
unsigned char *buffer; /* 当前扫描指针 */
uint64_t offset; /* 当前磁盘偏移 */
unsigned int blocksize; /* 块大小(512/1024/2048/4096) */
/* 主扫描循环:逐块读取磁盘 */
while (offset < params->disk->disk_size) {
/* Step 1: 读取一个块 */
if (params->disk->pread(params->disk, buffer_start,
blocksize, offset) != blocksize) {
offset += blocksize;
continue; /* 读取失败(坏扇区),跳过 */
}
/* Step 2: 在块内逐字节扫描签名 */
for (buffer = buffer_start;
buffer < buffer_start + blocksize;
buffer++) {
/* Step 3: 对每个已注册签名进行匹配 */
for (unsigned int i = 0; i < file_nbr; i++) {
if (!list_file_enable[i].enable)
continue;
const file_hint_t *hint = list_file_enable[i].file_hint;
/* 快速预检:比较魔数 */
if (memcmp(buffer + hint->offset,
hint->magic,
hint->magic_size) == 0) {
/* Step 4: 魔数匹配!调用深度校验 */
file_recovery_t file_recovery_new;
memset(&file_recovery_new, 0, sizeof(file_recovery_new));
file_recovery_new.file_hint = hint;
file_recovery_new.location.start = offset + (buffer - buffer_start);
file_recovery_new.blocksize = blocksize;
int valid = hint->header_check(
buffer,
blocksize - (buffer - buffer_start),
options->safe_header_only,
file_stat,
&file_recovery_new
);
if (valid) {
/* Step 5: 开始恢复此文件 */
photorec_found(&file_recovery_new, params, options);
/* Step 6: 继续读取后续块,直到文件结束 */
photorec_continue(&file_recovery_new, params, options);
/* Step 7: 调用文件尾检测 */
if (hint->file_check)
hint->file_check(&file_recovery_new);
/* Step 8: 重命名/元数据提取 */
if (hint->file_rename)
hint->file_rename(&file_recovery_new);
/* 跳过已恢复区域 */
buffer = buffer_start + blocksize;
break;
}
}
}
}
offset += blocksize;
/* 进度更新 */
if ((offset % (1024*1024*64)) == 0)
update_progress(offset, params->disk->disk_size);
}
}
1.5 文件尾检测策略(file_check)
c
编辑
/* ═══════════════════════════════════════════════════════
* 不同文件类型的"结束判定"策略完全不同
* ═══════════════════════════════════════════════════════ */
/* 策略A:固定尾标记(JPEG) */
void file_check_jpg(file_recovery_t *file_recovery)
{
/* JPEG以 FF D9 (EOI) 结束 */
/* 从文件末尾向前搜索 FF D9 */
unsigned char buffer[4096];
uint64_t pos = file_recovery->file_size;
while (pos > 0) {
pos -= 4096;
fseek(file_recovery->handle, pos, SEEK_SET);
fread(buffer, 1, 4096, file_recovery->handle);
for (int i = 4095; i >= 1; i--) {
if (buffer[i] == 0xD9 && buffer[i-1] == 0xFF) {
/* 找到EOI,截断文件 */
file_recovery->file_size = pos + i + 1;
ftruncate(fileno(file_recovery->handle),
file_recovery->file_size);
return;
}
}
}
}
/* 策略B:长度字段(PNG) */
void file_check_png(file_recovery_t *file_recovery)
{
/* PNG结构:8字节签名 + N个Chunk + IEND Chunk */
/* 每个Chunk: [4B长度][4B类型][数据][4BCRC] */
/* 扫描直到遇到 IEND chunk (00 00 00 00 49 45 4E 44 AE 42 60 82) */
static const unsigned char png_iend[12] = {
0x00, 0x00, 0x00, 0x00, /* length = 0 */
0x49, 0x45, 0x4E, 0x44, /* "IEND" */
0xAE, 0x42, 0x60, 0x82 /* CRC */
};
/* 搜索IEND,截断到IEND+12 */
}
/* 策略C:容器解析(MP4/MOV) */
void file_check_mp4(file_recovery_t *file_recovery)
{
/* MP4 = 一系列Atom(Box): [4B size][4B type][data] */
/* 从偏移0开始,逐个解析Atom */
/* 累加size直到EOF或遇到无效Atom */
uint64_t pos = 0;
while (pos < file_recovery->file_size) {
uint32_t atom_size, atom_type;
fseek(file_recovery->handle, pos, SEEK_SET);
fread(&atom_size, 4, 1, file_recovery->handle);
fread(&atom_type, 4, 1, file_recovery->handle);
atom_size = be32(atom_size);
if (atom_size < 8 || atom_size > 0x7FFFFFFF)
break; /* 无效Atom,文件到此结束 */
pos += atom_size;
}
file_recovery->file_size = pos;
ftruncate(fileno(file_recovery->handle), pos);
}
/* 策略D:块对齐(FAT/ext文件系统) */
void file_check_fat(file_recovery_t *file_recovery)
{
/* 文件大小向上对齐到文件系统块大小 */
uint64_t aligned = (file_recovery->file_size +
file_recovery->blocksize - 1)
/ file_recovery->blocksize
* file_recovery->blocksize;
file_recovery->file_size = aligned;
}
/* 策略E:无尾标记(原始数据流) */
/* 某些格式(如RAW磁盘镜像)没有明确结束标记 */
/* → 使用max_filesize限制 或 块对齐 */
1.6 碎片化处理(Free Space vs Whole Disk)
c
编辑
/* ═══════════════════════════════════════════════════════
* PhotoRec 两种扫描模式
* ═══════════════════════════════════════════════════════ */
/* 模式1: Free Space Only(仅空闲空间)
* · 前提:文件系统仍可部分读取
* · 读取FAT/ext的分配位图
* · 仅扫描"未分配"的块
* · 优势:避免恢复已存在的文件(去重)
* · 限制:无法处理碎片化文件
*/
/* 模式2: Whole Disk(全盘扫描)
* · 不依赖任何文件系统信息
* · 从第一个扇区扫描到最后一个扇区
* · 可处理碎片化文件(通过块跟踪)
* · 代价:可能恢复已存在文件(需去重)
*/
/* 碎片化文件处理(block tracking) */
typedef struct {
uint64_t start; /* 块起始偏移 */
uint64_t end; /* 块结束偏移 */
unsigned int data; /* 是否包含有效数据 */
} alloc_data_t;
/* 当文件跨越非连续块时:
* 1. 记录已恢复的块列表
* 2. 在后续扫描中,如果遇到"续接"数据
* (通过文件内部结构判断,如JPEG的SOS段后数据)
* 3. 将续接块追加到文件
* 4. 限制:无法100%保证碎片重组正确性
*/
1.7 签名分类与完整列表(500+种)
文本
编辑
src/ 目录下的 file_*.c 文件(每个=一种或一组签名):
图片类 (~25个文件):
file_jpg.c JPEG/JFIF/EXIF
file_png.c PNG
file_gif.c GIF
file_bmp.c BMP
file_tiff.c TIFF
file_webp.c WebP
file_heif.c HEIF/HEIC (7.1+)
file_avif.c AVIF (7.2+)
file_psd.c Photoshop PSD
file_raw.c RAW相机 (CRW/CR2/NEF/ARW/ORF/RW2/DNG/PEF/SRW)
file_svg.c SVG
file_ico.c ICO/CUR
file_xcf.c GIMP XCF
file_xpm.c XPM
...
视频类 (~15个文件):
file_mp4.c MP4/MOV/M4A/M4V (ISO BMFF)
file_avi.c AVI (RIFF)
file_mkv.c MKV/WebM (EBML)
file_flv.c FLV
file_mpg.c MPEG-1/2
file_wmv.c WMV/ASF
file_3gp.c 3GP
file_ts.c MPEG-TS
...
音频类 (~12个文件):
file_mp3.c MP3 (ID3v2/frame sync)
file_flac.c FLAC
file_ogg.c OGG/Opus
file_wav.c WAV (RIFF)
file_aiff.c AIFF
file_midi.c MIDI
file_ape.c APE
...
文档类 (~20个文件):
file_pdf.c PDF
file_zip.c ZIP/DOCX/XLSX/PPTX/JAR/APK/EPUB
file_rar.c RAR
file_7z.c 7-Zip
file_gz.c GZIP
file_bz2.c BZIP2
file_xz.c XZ
file_zst.c Zstandard (7.2+)
file_lz4.c LZ4 (7.2+)
file_brotli.c Brotli (7.2+)
file_doc.c DOC/XLS/PPT (OLE2/CFB)
file_rtf.c RTF
file_txt.c TXT/CSV/HTML/XML/JSON
...
数据库类 (~8个文件):
file_sqlite.c SQLite
file_mdb.c MS Access MDB/ACCDB
file_mysql.c MySQL InnoDB/MyISAM
...
磁盘/VM类 (~10个文件):
file_vmdk.c VMware VMDK
file_vdi.c VirtualBox VDI
file_vhd.c VHD/VHDX
file_qcow.c QEMU QCOW2
file_e01.c EnCase E01
file_dd.c Raw DD image
...
系统/固件类 (~10个文件):
file_elf.c ELF
file_exe.c PE (EXE/DLL)
file_mbr.c MBR
file_gpt.c GPT
file_lvm.c LVM/LVM2
file_luks.c LUKS
file_raid.c md RAID superblock
...
其他 (~100+个文件):
file_pcap.c Wireshark PCAP
file_sql.c SQL dump
file_gpg.c GPG
file_ttf.c TTF/OTF字体
file_swf.c Flash SWF
file_blend.c Blender
file_dwg.c AutoCAD DWG
file_stl.c STL (3D打印)
...
1.8 性能优化设计
表格
| 优化策略 | 实现 | 效果 |
|---|---|---|
| 块对齐读取 | 按文件系统块大小(512/4K)对齐读取 | 减少IO次数 |
| 魔数预过滤 | 先比较首字节,不匹配立即跳过 | 避免500次memcmp/字节 |
| 跳跃扫描 | 匹配成功后跳过已恢复区域 | 避免重复扫描 |
| 缓冲复用 | 单缓冲区循环使用 | 零额外内存分配 |
| 异步写入 | 恢复数据写入与磁盘读取并行 | 隐藏写入延迟 |
| 签名分组 | 按首字节分桶(256桶) | O(1)预过滤 |
c
编辑
/* 首字节分桶优化(推测实现) */
static file_hint_t *buckets[256]; /* 256个桶,按魔数首字节索引 */
/* 扫描时: */
unsigned char first_byte = *buffer;
file_hint_t *candidates = buckets[first_byte];
/* 仅对桶内签名进行完整memcmp */
/* 500+签名 → 平均每桶仅2个候选 → 极大减少比较次数 */
专题二:TestDisk 分区表修复算法
2.1 问题模型
文本
编辑
磁盘分区表损坏的典型场景:
正常状态:
┌──────────────────────────────────────────────────────┐
│ MBR/GPT │ 分区1(NTFS) │ 分区2(ext4) │ 分区3 │
│ (完好) │ 起始LBA=2048 │ 起始LBA=... │ ... │
└──────────────────────────────────────────────────────┘
损坏状态:
┌──────────────────────────────────────────────────────┐
│ MBR/GPT │ ???垃圾数据??? │ ??? │ ??? │
│ (损坏) │ (分区表丢失) │ │ │
└──────────────────────────────────────────────────────┘
↑ 但分区内的文件系统结构(引导扇区/MFT/超级块)可能完好!
TestDisk的核心思路:
不修复分区表本身 → 而是扫描磁盘找到文件系统的"指纹"
→ 从指纹反推分区的起始/结束位置
→ 重建分区表
2.2 分区表修复总流程
文本
编辑
┌─────────────────────────────────────────────────────────────────┐
│ TestDisk 分区表修复流程 │
│ │
│ Step 1: 磁盘选择与分区表类型识别 │
│ ┌─────────────────────────────────────────┐ │
│ │ · 读取磁盘几何(CHS/LBA) │ │
│ │ · 自动检测分区表类型: │ │
│ │ - 读取LBA 0: 检查MBR签名(55 AA) │ │
│ │ - 读取LBA 1: 检查GPT头("EFI PART") │ │
│ │ - 读取LBA 0: 检查Sun/SGI/Mac标签 │ │
│ │ · 用户可手动覆盖 │ │
│ └─────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ Step 2: 当前分区表分析 │
│ ┌─────────────────────────────────────────┐ │
│ │ · 解析现有分区表(可能损坏) │ │
│ │ · 对每个分区:验证文件系统签名 │ │
│ │ · 标记:OK / 损坏 / 缺失 │ │
│ └─────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ Step 3: 快速搜索 (Quick Search) │
│ ┌─────────────────────────────────────────┐ │
│ │ · 仅扫描"可能"的分区起始位置: │ │
│ │ - 每个磁道/柱面的起始扇区 │ │
│ │ - 已知对齐边界(2048扇区=1MB) │ │
│ │ · 在每个候选位置检查文件系统签名 │ │
│ │ · 时间:通常 < 1分钟 │ │
│ └─────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ Step 4: 深度搜索 (Deeper Search) [可选] │
│ ┌─────────────────────────────────────────┐ │
│ │ · 逐扇区全盘扫描 │ │
│ │ · 每个扇区检查所有文件系统签名 │ │
│ │ · 时间:大磁盘可能数小时 │ │
│ │ · 可找到快速搜索遗漏的分区 │ │
│ └─────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ Step 5: 分区列表验证与冲突消解 │
│ ┌─────────────────────────────────────────┐ │
│ │ · 检查分区是否重叠 │ │
│ │ · 检查分区是否超出磁盘范围 │ │
│ │ · 用户选择保留哪些分区 │ │
│ │ · 设置分区类型(主/逻辑/扩展) │ │
│ └─────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ Step 6: 写入修复后的分区表 │
│ ┌─────────────────────────────────────────┐ │
│ │ · MBR: 写入LBA 0的分区表(4个主分区项) │ │
│ │ · GPT: 写入主GPT头+分区项+备份GPT │ │
│ │ · 设置活动(bootable)标志 │ │
│ │ · 写入前备份原始分区表到文件 │ │
│ └─────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
2.3 文件系统签名检测(核心算法)
c
编辑
/* ═══════════════════════════════════════════════════════
* 每种文件系统有唯一的"引导扇区签名"
* TestDisk通过检测这些签名来定位分区起始
* ═══════════════════════════════════════════════════════ */
/* NTFS 引导扇区签名检测 */
int check_NTFS(disk_t *disk, partition_t *partition,
const unsigned int sector_size)
{
unsigned char buffer[512];
/* 读取候选扇区 */
if (disk->pread(disk, buffer, 512,
partition->part_offset) != 512)
return 0;
/* NTFS签名: 偏移3处 = "NTFS " (8字节) */
if (memcmp(buffer + 3, "NTFS ", 8) != 0)
return 0;
/* 验证BPB(BIOS Parameter Block)字段合理性 */
uint16_t bytes_per_sector = le16(buffer + 11);
uint8_t sectors_per_cluster = buffer[13];
uint64_t total_sectors = le64(buffer + 40);
uint64_t mft_lcn = le64(buffer + 48); /* $MFT起始LCN */
uint64_t mftmirr_lcn = le64(buffer + 56); /* $MFTMirr起始LCN */
/* 合理性检查 */
if (bytes_per_sector != 512 && bytes_per_sector != 4096)
return 0;
if (sectors_per_cluster == 0 || sectors_per_cluster > 128)
return 0;
if (total_sectors == 0)
return 0;
if (mft_lcn == 0 || mft_lcn >= total_sectors)
return 0;
/* 计算分区大小 */
partition->part_size = (uint64_t)total_sectors * bytes_per_sector;
/* 设置分区类型 */
partition->part_type_i386 = P_NTFS; /* 0x07 */
partition->part_type_gpt = GPT_ENT_TYPE_MS_BASIC_DATA;
return 1; /* 找到有效NTFS分区 */
}
/* ext2/3/4 超级块签名检测 */
int check_ext2(disk_t *disk, partition_t *partition,
const unsigned int sector_size)
{
unsigned char buffer[1024];
/* ext超级块在分区起始+1024字节处 */
if (disk->pread(disk, buffer, 1024,
partition->part_offset + 1024) != 1024)
return 0;
/* ext魔数: 偏移56处 = 0x53 0xEF */
if (buffer[56] != 0x53 || buffer[57] != 0xEF)
return 0;
/* 解析超级块字段 */
uint32_t s_inodes_count = le32(buffer + 0);
uint32_t s_blocks_count = le32(buffer + 4);
uint32_t s_log_block_size = le32(buffer + 24);
uint32_t s_blocks_per_group = le32(buffer + 32);
uint16_t s_magic = le16(buffer + 56);
uint32_t s_feature_incompat = le32(buffer + 96);
/* 合理性检查 */
if (s_inodes_count == 0 || s_blocks_count == 0)
return 0;
if (s_log_block_size > 6) /* 块大小最大64KB */
return 0;
/* 区分ext2/ext3/ext4 */
if (s_feature_incompat & 0x0040) /* INCOMPAT_EXTENTS */
partition->fs_type = EXT4;
else if (le32(buffer + 92) & 0x0004) /* HAS_JOURNAL */
partition->fs_type = EXT3;
else
partition->fs_type = EXT2;
/* 计算分区大小 */
uint32_t block_size = 1024 << s_log_block_size;
partition->part_size = (uint64_t)s_blocks_count * block_size;
return 1;
}
/* FAT32 引导扇区签名检测 */
int check_FAT32(disk_t *disk, partition_t *partition,
const unsigned int sector_size)
{
unsigned char buffer[512];
if (disk->pread(disk, buffer, 512,
partition->part_offset) != 512)
return 0;
/* FAT32签名: 偏移82处 = "FAT32 " */
if (memcmp(buffer + 82, "FAT32 ", 8) != 0)
return 0;
/* 验证BPB */
uint16_t bytes_per_sector = le16(buffer + 11);
uint8_t sectors_per_cluster = buffer[13];
uint32_t total_sectors_32 = le32(buffer + 32);
uint32_t fat_size_32 = le32(buffer + 36);
if (bytes_per_sector != 512 && bytes_per_sector != 4096)
return 0;
if (total_sectors_32 == 0)
return 0;
partition->part_size = (uint64_t)total_sectors_32 * bytes_per_sector;
partition->part_type_i386 = P_FAT32; /* 0x0B or 0x0C */
return 1;
}
/* GPT 分区表头检测 */
int check_GPT(disk_t *disk)
{
unsigned char buffer[512];
/* GPT主头在LBA 1 */
if (disk->pread(disk, buffer, 512, 512) != 512)
return 0;
/* GPT签名: "EFI PART" (8字节) */
if (memcmp(buffer, "EFI PART", 8) != 0)
return 0;
/* 解析GPT头 */
uint32_t revision = le32(buffer + 8);
uint32_t header_size = le32(buffer + 12);
uint32_t header_crc = le32(buffer + 16);
uint64_t my_lba = le64(buffer + 24);
uint64_t alternate_lba = le64(buffer + 32);
uint64_t first_usable = le64(buffer + 40);
uint64_t last_usable = le64(buffer + 48);
uint32_t num_entries = le32(buffer + 80);
uint32_t entry_size = le32(buffer + 84);
/* 验证CRC32 */
uint32_t saved_crc = header_crc;
*(uint32_t*)(buffer + 16) = 0;
uint32_t calc_crc = crc32(buffer, header_size);
if (calc_crc != saved_crc)
return 0; /* GPT头损坏 */
return 1;
}
2.4 深度搜索算法(Deeper Search)
c
编辑
/* ═══════════════════════════════════════════════════════
* 深度搜索:逐扇区扫描,寻找所有可能的分区起始
* ═══════════════════════════════════════════════════════ */
void search_part(disk_t *disk, alloc_data_t *list_search_space,
const int verbose, const int dump_ind,
const int fast_mode)
{
unsigned char buffer[4096]; /* 足够容纳任何文件系统签名 */
uint64_t search_location = 0;
const uint64_t disk_size = disk->disk_size;
const unsigned int sector_size = disk->sector_size;
/* 快速模式:仅扫描对齐边界 */
/* 深度模式:逐扇区扫描 */
const uint64_t step = fast_mode ?
(1024 * 1024 / sector_size) : /* 每1MB */
1; /* 每扇区 */
while (search_location < disk_size) {
/* 读取当前扇区 */
if (disk->pread(disk, buffer, sector_size,
search_location) != sector_size) {
search_location += step * sector_size;
continue;
}
/* 对当前扇区尝试所有文件系统签名 */
partition_t partition;
memset(&partition, 0, sizeof(partition));
partition.part_offset = search_location;
/* 按优先级尝试(常见→罕见) */
if (check_NTFS(disk, &partition, sector_size)) {
add_partition_to_list(&partition);
}
else if (check_ext2(disk, &partition, sector_size)) {
add_partition_to_list(&partition);
}
else if (check_FAT32(disk, &partition, sector_size)) {
add_partition_to_list(&partition);
}
else if (check_FAT16(disk, &partition, sector_size)) {
add_partition_to_list(&partition);
}
else if (check_exFAT(disk, &partition, sector_size)) {
add_partition_to_list(&partition);
}
else if (check_HFS(disk, &partition, sector_size)) {
add_partition_to_list(&partition);
}
else if (check_btrfs(disk, &partition, sector_size)) {
add_partition_to_list(&partition);
}
/* ... 20+种文件系统 ... */
/* 进度更新(每1GB) */
if ((search_location % (1024ULL*1024*1024)) == 0)
wprintw(stdscr, "Searching %llu/%llu GB\n",
search_location/1024/1024/1024,
disk_size/1024/1024/1024);
search_location += step * sector_size;
}
/* 冲突消解:去除重叠分区 */
resolve_partition_conflicts(partition_list);
}
2.5 引导扇区修复(NTFS/FAT专用)
c
编辑
/* ═══════════════════════════════════════════════════════
* 当分区表完好但引导扇区损坏时的修复
* (TestDisk Advanced → Boot 功能)
* ═══════════════════════════════════════════════════════ */
/* NTFS引导扇区修复 */
void repair_NTFS_boot(disk_t *disk, partition_t *partition)
{
unsigned char boot_sector[512];
unsigned char backup_boot[512];
/* Step 1: 读取主引导扇区(分区起始) */
disk->pread(disk, boot_sector, 512, partition->part_offset);
/* Step 2: 检查主引导扇区是否有效 */
int boot_ok = (memcmp(boot_sector + 3, "NTFS ", 8) == 0);
/* Step 3: 读取备份引导扇区(分区末尾) */
uint64_t backup_offset = partition->part_offset +
partition->part_size - 512;
disk->pread(disk, backup_boot, 512, backup_offset);
int backup_ok = (memcmp(backup_boot + 3, "NTFS ", 8) == 0);
/* Step 4: 决策 */
if (boot_ok && backup_ok) {
/* 两者都完好 → 无需修复 */
log_info("NTFS boot sector OK\n");
}
else if (!boot_ok && backup_ok) {
/* 主损坏,备份完好 → 用备份覆盖主 */
log_info("NTFS boot sector damaged, backup OK\n");
log_info("Rebuilding boot sector from backup...\n");
disk->pwrite(disk, backup_boot, 512, partition->part_offset);
}
else if (boot_ok && !backup_ok) {
/* 主完好,备份损坏 → 用主覆盖备份 */
disk->pwrite(disk, boot_sector, 512, backup_offset);
}
else {
/* 两者都损坏 → 尝试从MFT重建 */
rebuild_NTFS_boot_from_MFT(disk, partition);
}
}
/* 从$MFT重建NTFS引导扇区(最后手段) */
void rebuild_NTFS_boot_from_MFT(disk_t *disk, partition_t *partition)
{
/* Step 1: 扫描分区区域寻找$MFT签名 */
/* $MFT第一条记录 = "FILE" 魔数 + 特定属性布局 */
unsigned char buffer[4096];
uint64_t mft_offset = 0;
for (uint64_t off = partition->part_offset;
off < partition->part_offset + partition->part_size;
off += 512) {
disk->pread(disk, buffer, 4096, off);
/* $MFT第一条记录: "FILE" + 序列号0 + $MFT自身引用 */
if (memcmp(buffer, "FILE", 4) == 0 &&
le16(buffer + 4) == 0x0300 && /* USA offset */
le32(buffer + 44) == 0) { /* 序列号=0 */
mft_offset = off;
break;
}
}
if (mft_offset == 0) {
log_error("Cannot find $MFT, unable to rebuild\n");
return;
}
/* Step 2: 从$MFT推断BPB参数 */
/* Step 3: 构造新的引导扇区 */
/* Step 4: 写入(需用户确认) */
}
专题三:NTFS MFT 修复流程
3.1 NTFS 文件系统结构回顾
文本
编辑
NTFS卷布局:
┌────────────────────────────────────────────────────────────┐
│ LBA 0: Boot Sector ($Boot) │
│ · "NTFS " 签名 │
│ · BPB: bytes/sector, sectors/cluster │
│ · $MFT起始LCN, $MFTMirr起始LCN │
│ · 卷大小, 序列号 │
├────────────────────────────────────────────────────────────┤
│ $MFT (Master File Table) │
│ · 每条记录1024字节(通常) │
│ · Record 0: $MFT自身 │
│ · Record 1: $MFTMirr │
│ · Record 2: $LogFile │
│ · Record 3: $Volume │
│ · Record 4: $AttrDef │
│ · Record 5: . (根目录) │
│ · Record 6: $Bitmap │
│ · Record 7: $Boot │
│ · Record 8: $BadClus │
│ · Record 9: $Secure │
│ · Record 10: $UpCase │
│ · Record 11: $Extend │
│ · Record 16+: 用户文件/目录 │
├────────────────────────────────────────────────────────────┤
│ $MFTMirr (MFT镜像,前4条记录的副本) │
├────────────────────────────────────────────────────────────┤
│ $LogFile (NTFS日志,用于崩溃恢复) │
├────────────────────────────────────────────────────────────┤
│ 数据区 (文件内容) │
├────────────────────────────────────────────────────────────┤
│ 备份Boot Sector (卷末尾) │
└────────────────────────────────────────────────────────────┘
3.2 MFT记录结构
文本
编辑
MFT Record (1024 bytes):
┌──────────────────────────────────────────────────────────┐
│ Offset Size Field │
│ ────── ──── ───── │
│ 0x00 4B Magic: "FILE" (0x46494C45) │
│ 0x04 2B USA Offset (Update Sequence Array) │
│ 0x06 2B USA Size │
│ 0x08 8B $LogFile Sequence Number (LSN) │
│ 0x10 2B Sequence Number (重用计数) │
│ 0x12 2B Hard Link Count │
│ 0x14 2B First Attribute Offset │
│ 0x16 2B Flags (0x01=In Use, 0x02=Directory) │
│ 0x18 4B Used Size │
│ 0x1C 4B Allocated Size (通常=1024) │
│ 0x20 8B Base Record (0=自身, 非0=扩展记录) │
│ 0x28 2B Next Attribute ID │
│ 0x2A 2B (padding) │
│ 0x2C 4B MFT Record Number (自身编号) │
│ 0x30 ... Update Sequence Array │
│ ──── ──── ───────────────────────────── │
│ First ... Attribute 1: $STANDARD_INFORMATION │
│ Attr ... Attribute 2: $FILE_NAME │
│ Offset ... Attribute 3: $DATA (文件内容/目录索引) │
│ ... Attribute N: ... │
│ ... End Marker: 0xFFFFFFFF │
└──────────────────────────────────────────────────────────┘
3.3 TestDisk NTFS MFT修复算法
c
/* ═══════════════════════════════════════════════════════
* ntfs.c —— NTFS修复核心
* ═══════════════════════════════════════════════════════ */
/* 主修复入口 */
int ntfs_repair(disk_t *disk, partition_t *partition)
{
struct ntfs_boot_sector *ntfs_header;
unsigned char buffer[4096];
/* ═══ Phase 1: 读取并验证Boot Sector ═══ */
if (disk->pread(disk, buffer, 512,
partition->part_offset) != 512)
return -1;
ntfs_header = (struct ntfs_boot_sector *)buffer;
if (memcmp(ntfs_header->system_id, "NTFS ", 8) != 0) {
log_error("NTFS boot sector signature not found\n");
return repair_ntfs_boot(disk, partition);
}
/* 解析关键参数 */
uint16_t bytes_per_sector = le16(ntfs_header->bpb.bytes_per_sector);
uint8_t sectors_per_cluster = ntfs_header->bpb.sectors_per_cluster;
uint64_t mft_lcn = sle64(ntfs_header->mft_lcn);
uint64_t mftmirr_lcn = sle64(ntfs_header->mftmirr_lcn);
int8_t clusters_per_mft_record = ntfs_header->clusters_per_mft_record;
uint32_t cluster_size = bytes_per_sector * sectors_per_cluster;
uint64_t mft_offset = partition->part_offset +
mft_lcn * cluster_size;
uint32_t mft_record_size;
if (clusters_per_mft_record > 0)
mft_record_size = clusters_per_mft_record * cluster_size;
else
mft_record_size = 1 << (-clusters_per_mft_record);
/* 通常 = 1024 bytes */
/* ═══ Phase 2: 读取$MFT Record 0 ═══ */
if (disk->pread(disk, buffer, mft_record_size,
mft_offset) != mft_record_size)
return -1;
if (memcmp(buffer, "FILE", 4) != 0) {
log_error("$MFT Record 0 signature invalid\n");
/* 尝试$MFTMirr */
uint64_t mirr_offset = partition->part_offset +
mftmirr_lcn * cluster_size;
disk->pread(disk, buffer, mft_record_size, mirr_offset);
if (memcmp(buffer, "FILE", 4) != 0) {
log_error("$MFTMirr also invalid\n");
return -1;
}
log_info("Using $MFTMirr as fallback\n");
mft_offset = mirr_offset;
}
/* ═══ Phase 3: 解析$MFT Record 0的$DATA属性 ═══ */
/* $MFT Record 0 的 $DATA 属性描述了整个MFT的extent布局 */
uint16_t attr_offset = le16(buffer + 0x14);
while (attr_offset < mft_record_size) {
uint32_t attr_type = le32(buffer + attr_offset);
if (attr_type == 0xFFFFFFFF)
break; /* End marker */
if (attr_type == 0x80) { /* $DATA */
uint8_t non_resident = buffer[attr_offset + 8];
if (non_resident) {
/* 非驻留:$DATA由extent列表描述 */
parse_mft_data_extents(buffer + attr_offset,
disk, partition,
mft_offset, cluster_size);
}
break;
}
uint32_t attr_len = le32(buffer + attr_offset + 4);
attr_offset += attr_len;
}
/* ═══ Phase 4: 遍历MFT记录,构建文件列表 ═══ */
return ntfs_list_files(disk, partition, mft_offset,
mft_record_size, cluster_size);
}
/* 解析$MFT的$DATA extent列表 */
void parse_mft_data_extents(unsigned char *attr,
disk_t *disk, partition_t *partition,
uint64_t mft_offset, uint32_t cluster_size)
{
/* 非驻留$DATA属性结构:
* +0x10: Starting VCN
* +0x18: Ending VCN
* +0x20: Data Runs Offset
* +0x28: Allocated Size
* +0x30: Actual Size
* +0x38: Initialized Size
* +DataRunsOffset: Data Runs (压缩编码的extent列表)
*/
uint64_t allocated_size = le64(attr + 0x28);
uint64_t actual_size = le64(attr + 0x30);
uint16_t data_runs_offset = le16(attr + 0x20);
/* 解析Data Runs */
/* 每个Data Run: [1B header][length][offset] */
/* header高4位=offset字节数, 低4位=length字节数 */
unsigned char *runs = attr + data_runs_offset;
uint64_t current_lcn = 0;
uint64_t current_vcn = 0;
while (*runs != 0x00) {
uint8_t header = *runs++;
uint8_t length_bytes = header & 0x0F;
uint8_t offset_bytes = (header >> 4) & 0x0F;
/* 读取length (小端, 有符号) */
int64_t run_length = read_signed_le(runs, length_bytes);
runs += length_bytes;
/* 读取offset (小端, 有符号, 相对于上一个run) */
int64_t run_offset = read_signed_le(runs, offset_bytes);
runs += offset_bytes;
current_lcn += run_offset;
log_info("MFT extent: LCN=%llu, length=%llu clusters\n",
current_lcn, run_length);
current_vcn += run_length;
}
}
3.4 MFT修复: MFTvs MFTMirr 回退
c
/* ═══════════════════════════════════════════════════════
* $MFT损坏时的修复策略
* ═══════════════════════════════════════════════════════ */
int repair_ntfs_mft(disk_t *disk, partition_t *partition,
uint64_t mft_lcn, uint64_t mftmirr_lcn,
uint32_t cluster_size)
{
unsigned char mft_buf[1024];
unsigned char mirr_buf[1024];
uint64_t mft_offset = partition->part_offset +
mft_lcn * cluster_size;
uint64_t mirr_offset = partition->part_offset +
mftmirr_lcn * cluster_size;
/* 读取$MFT Record 0 */
disk->pread(disk, mft_buf, 1024, mft_offset);
int mft_valid = (memcmp(mft_buf, "FILE", 4) == 0 &&
le16(mft_buf + 0x16) & 0x01); /* In Use */
/* 读取$MFTMirr Record 0 */
disk->pread(disk, mirr_buf, 1024, mirr_offset);
int mirr_valid = (memcmp(mirr_buf, "FILE", 4) == 0 &&
le16(mirr_buf + 0x16) & 0x01);
if (mft_valid && mirr_valid) {
/* 两者都有效 → 比较一致性 */
if (memcmp(mft_buf, mirr_buf, 1024) == 0) {
log_info("$MFT and $MFTMirr consistent\n");
return 0;
} else {
log_warning("$MFT and $MFTMirr differ!\n");
/* 使用$MFT(主),但标记警告 */
return 0;
}
}
else if (!mft_valid && mirr_valid) {
/* $MFT损坏,$MFTMirr有效 → 从Mirr恢复 */
log_info("$MFT damaged, recovering from $MFTMirr\n");
/* $MFTMirr仅包含前4条记录(Record 0~3) */
/* 只能恢复$MFT/$MFTMirr/$LogFile/$Volume */
for (int i = 0; i < 4; i++) {
disk->pread(disk, mirr_buf, 1024,
mirr_offset + i * 1024);
disk->pwrite(disk, mirr_buf, 1024,
mft_offset + i * 1024);
}
log_info("Recovered 4 MFT records from $MFTMirr\n");
return 1;
}
else if (mft_valid && !mirr_valid) {
/* $MFT有效,$MFTMirr损坏 → 修复Mirr */
log_info("$MFTMirr damaged, rebuilding from $MFT\n");
for (int i = 0; i < 4; i++) {
disk->pread(disk, mft_buf, 1024,
mft_offset + i * 1024);
disk->pwrite(disk, mft_buf, 1024,
mirr_offset + i * 1024);
}
return 1;
}
else {
/* 两者都损坏 → 深度扫描寻找"FILE"签名 */
log_error("Both $MFT and $MFTMirr damaged!\n");
return deep_scan_mft(disk, partition, cluster_size);
}
}
3.5 NTFS $ LogFile 日志回放(崩溃恢复)
c
/* ═══════════════════════════════════════════════════════
* NTFS日志回放(类似ext3 journal replay)
* 用于非正常关机后的文件系统一致性恢复
* ═══════════════════════════════════════════════════════ */
int ntfs_replay_log(disk_t *disk, partition_t *partition,
uint64_t logfile_lcn, uint32_t cluster_size)
{
/* $LogFile结构:
* · 循环日志缓冲区
* · 包含两种记录:
* - Restart Area: 检查点信息
* - Log Records: 实际事务记录
* · Undo操作 (回滚)
* · Redo操作 (重做)
*/
uint64_t logfile_offset = partition->part_offset +
logfile_lcn * cluster_size;
/* Step 1: 读取Restart Area */
unsigned char restart[4096];
disk->pread(disk, restart, 4096, logfile_offset);
/* 验证Restart签名: "RSTR" */
if (memcmp(restart, "RSTR", 4) != 0 &&
memcmp(restart, "RCRD", 4) != 0) {
log_info("No valid $LogFile, skipping replay\n");
return 0;
}
/* Step 2: 找到最后一个检查点 */
uint64_t last_lsn = le64(restart + 0x28);
/* Step 3: 从检查点开始,收集所有Redo记录 */
/* Step 4: 按LSN顺序重做(Redo) */
/* Step 5: 回滚(Undo)未完成的事务 */
log_info("NTFS log replay: %llu operations\n", redo_count);
return 0;
}
专题四:与商业工具 R-Studio / UFS Explorer 技术对标
4.1 基础档案
| 维度 | TestDisk/PhotoRec | R-Studio | UFS Explorer |
|---|---|---|---|
| 开发商 | Christophe Grenier (个人) | R-Tools Technology (加拿大) | SysDev Laboratories (乌克兰) |
| 成立 | 1998 | 2000 | 2003 |
| 许可证 | GPL v2+ (完全开源) | 商业闭源 | 商业闭源 |
| 价格 | 免费 | $79~$899 | $59~$599 |
| 当前版本 | 7.2 / 7.3-WIP | 9.5 (2025) | 9.x (2025) |
| 语言 | C | C/C++ (闭源) | C/C++ (闭源) |
| 平台 | Win/Mac/Linux/DOS/BSD/SunOS | Win/Mac/Linux | Win/Mac/Linux |
| GUI | NCurses + Qt(基础) | 完整商业GUI | 完整商业GUI |
| 网络恢复 | ✗ | ✅ R-Studio Network | ✅ 网络版 |
4.2 文件系统支持对比
| 文件系统 | TestDisk | PhotoRec | R-Studio 9.5 | UFS Explorer 9.x |
|---|---|---|---|---|
| FAT12/16/32 | ✅修复 | ✅雕刻 | ✅ | ✅ |
| exFAT | ✅(7.1+) | ✅ | ✅ | ✅ |
| NTFS | ✅修复 | ✅雕刻 | ✅ | ✅ |
| ReFS | ✗ | ✗ | ✅ | ✅ |
| ext2/3/4 | ✅修复 | ✅雕刻 | ✅ | ✅ |
| XFS | △探测 | ✅ | ✅ | ✅ |
| JFS | △探测 | ✅ | ✅ | ✅ |
| btrfs | ✅(6.13+) | ✅ | ✅(9.5增强) | ✅ |
| F2FS | △(7.1初步) | ✅ | ✅ | ✅ |
| HFS+ | ✅修复 | ✅雕刻 | ✅ | ✅ |
| APFS | ✗ | △ | ✅ | ✅ |
| UFS1/UFS2 | ✅ | ✅ | ✅ | ✅ |
| ZFS | ✗ | ✗ | ✅(9.5) | ✅ |
| LVM/LVM2 | ✅ | ✅ | ✅ | ✅ |
| md RAID | ✅ | ✅ | ✅(自动重组) | ✅(自动重组) |
| 硬件RAID | ✗ | ✗ | ✅(自动识别) | ✅(自动识别) |
| LUKS | ✅透传 | ✅ | ✅ | ✅ |
| BitLocker | ✗ | ✗ | ✅ | ✅ |
| FileVault2 | ✗ | ✗ | ✅ | ✅ |
4.3 核心能力对比
| 能力 | TestDisk/PhotoRec | R-Studio 9.5 | UFS Explorer 9.x |
|---|---|---|---|
| 分区表修复 | ✅ 核心强项 | △ 基础 | △ 基础 |
| 引导扇区修复 | ✅ 核心强项 | ✗ | ✗ |
| 文件雕刻(签名) | ✅ 500+签名 | ✅ 400+签名 | ✅ 500+签名 |
| RAID自动重组 | ✗ 手动 | ✅ 智能重组 | ✅ 智能重组 |
| RAID参数自动识别 | ✗ | ✅ | ✅ |
| 虚拟RAID构建 | ✗ | ✅ | ✅ |
| 磁盘镜像创建 | ✅ dd/E01 | ✅ .rdr格式 | ✅ 多格式 |
| 网络远程恢复 | ✗ | ✅ R-Studio Network | ✅ 网络版 |
| S.M.A.R.T.监控 | ✗ | ✅ | ✅ |
| 十六进制编辑器 | ✗ | ✅ 内置 | ✅ 内置 |
| 文件预览 | ✗ | ✅ 多格式预览 | ✅ 多格式预览 |
| 目录结构恢复 | △ 有限 | ✅ 完整 | ✅ 完整 |
| 已删除文件恢复 | ✅ PhotoRec | ✅ 元数据级 | ✅ 元数据级 |
| 加密卷恢复 | △ LUKS透传 | ✅ BitLocker+LUKS | ✅ 全面 |
| NAS恢复 | ✗ | ✅ | ✅ |
| 虚拟机磁盘 | ✅ 多格式 | ✅ 多格式 | ✅ 多格式 |
| 脚本/自动化 | ✗ | △ | △ |
| 命令行模式 | ✅ 原生 | ✅ | ✅ |
| LiveCD/救援盘 | ✅ 广泛集成 | △ | △ |
4.4 架构差异(核心)
TestDisk/PhotoRec 架构:
┌─────────────────────────────────────────────┐
│ 用户 → NCurses/Qt UI │
│ │ │
│ ▼ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ TestDisk引擎 │ │ PhotoRec引擎 │ │
│ │ (分区表修复) │ │ (文件雕刻) │ │
│ │ │ │ │ │
│ │ · 签名检测 │ │ · filegen.c │ │
│ │ · 分区重建 │ │ · 500+签名 │ │
│ │ · 引导扇区修复 │ │ · 块扫描 │ │
│ └────────┬────────┘ └────────┬────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ hdaccess.c (磁盘IO抽象层) │ │
│ │ · Linux: /dev/sdX (ioctl) │ │
│ │ · Windows: \\\\.\\PhysicalDriveX │ │
│ │ · macOS: /dev/diskX │ │
│ │ · 镜像: dd/E01文件 │ │
│ └─────────────────────────────────────┘ │
│ │
│ 特点: │
│ · 单线程 │
│ · 无文件系统解析器(仅签名检测) │
│ · 无RAID重组 │
│ · 无网络功能 │
│ · 极简依赖(ncurses/zlib/可选) │
└─────────────────────────────────────────────┘
R-Studio / UFS Explorer 架构:
┌─────────────────────────────────────────────┐
│ 用户 → 完整GUI (Qt/MFC) │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ 文件系统解析器层 (完整只读驱动) │ │
│ │ · NTFS完整解析(MFT/索引/日志) │ │
│ │ · ext4完整解析(extent/journal) │ │
│ │ · APFS完整解析(容器/卷/快照) │ │
│ │ · ZFS完整解析(uberblock/dnode) │ │
│ │ · ReFS完整解析 │ │
│ │ · ...20+种文件系统 │ │
│ └────────────────┬────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ RAID重组引擎 │ │
│ │ · 自动参数识别(条带大小/盘序/校验) │ │
│ │ · RAID 0/1/5/6/10/50/60 │ │
│ │ · 虚拟RAID构建(无需物理盘) │ │
│ │ · 降级RAID恢复(缺盘) │ │
│ └────────────────┬────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ 文件雕刻引擎 (签名+启发式) │ │
│ │ · 400~500+签名 │ │
│ │ · 碎片重组(高级) │ │
│ │ · 文件类型智能识别 │ │
│ └────────────────┬────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ 磁盘IO层 │ │
│ │ · 多线程并行读取 │ │
│ │ · 坏扇区重试/跳过 │ │
│ │ · S.M.A.R.T.集成 │ │
│ │ · 网络磁盘(远程Agent) │ │
│ │ · 镜像创建/读取 │ │
│ └─────────────────────────────────────┘ │
│ │
│ 特点: │
│ · 多线程 │
│ · 完整文件系统解析(目录树/权限/时间戳) │
│ · RAID自动重组 │
│ · 网络远程恢复 │
│ · 闭源(算法不公开) │
└─────────────────────────────────────────────┘
4.5 恢复成功率对比(场景化)
| 故障场景 | TestDisk/PhotoRec | R-Studio 9.5 | UFS Explorer 9.x |
|---|---|---|---|
| 误删分区(MBR/GPT) | ★★★★★ | ★★★ | ★★★ |
| 引导扇区损坏 | ★★★★★ | ★★ | ★★ |
| 误删文件(元数据在) | ★★★ | ★★★★★ | ★★★★★ |
| 误格式化(快速) | ★★★★ | ★★★★★ | ★★★★★ |
| 误格式化(完全) | ★★★(雕刻) | ★★★★ | ★★★★ |
| RAID 5单盘故障 | ★(手动) | ★★★★★ | ★★★★★ |
| RAID 5双盘故障 | ✗ | ★★★★ | ★★★★★ |
| APFS卷损坏 | ✗ | ★★★★★ | ★★★★★ |
| ZFS池损坏 | ✗ | ★★★★ | ★★★★★ |
| BitLocker加密卷 | ✗ | ★★★★★ | ★★★★★ |
| 严重碎片化文件 | ★★ | ★★★★ | ★★★★★ |
| 物理坏道磁盘 | ★★★ | ★★★★★ | ★★★★★ |
| 虚拟机磁盘 | ★★★★ | ★★★★★ | ★★★★★ |
4.6 选择建议
┌─────────────────────────────────────────────────────────────────┐
│ 工具选择决策树 │
│ │
│ 分区表丢失/引导扇区损坏? │
│ └── YES → TestDisk(免费、专精、无风险) │
│ │
│ 需要恢复目录结构+文件名+时间戳? │
│ └── YES → R-Studio / UFS Explorer(元数据级恢复) │
│ │
│ RAID阵列故障? │
│ └── YES → R-Studio / UFS Explorer(自动重组) │
│ │
│ APFS/ZFS/ReFS? │
│ └── YES → R-Studio / UFS Explorer(TestDisk不支持) │
│ │
│ BitLocker/FileVault加密? │
│ └── YES → R-Studio / UFS Explorer │
│ │
│ 预算为零? │
│ └── YES → TestDisk + PhotoRec(唯一选择) │
│ │
│ 需要开源/可审计/取证合规? │
│ └── YES → TestDisk + PhotoRec(GPL,源码公开) │
│ │
│ 需要网络远程恢复? │
│ └── YES → R-Studio Network │
│ │
│ 需要集成到LiveCD/救援盘? │
│ └── YES → TestDisk + PhotoRec(几乎所有Linux发行版内置) │
│ │
│ 专业数据恢复公司? │
│ └── YES → R-Studio + UFS Explorer + PC-3000(硬件) │
│ (TestDisk作为辅助/快速诊断工具) │
│ │
└─────────────────────────────────────────────────────────────────┘
专题五:7.3-WIP Git Commits 分析
5.1 7.3-WIP 版本状态
| 维度 | 信息 |
|---|---|
| 分支 | master(主开发分支) |
| 最新构建 | 2026-06-23(当快软件园收录v7.3, 28.86MB) |
| 下载 | testdisk-7.3-WIP.win.zip / .tar.bz2 |
| FileHippo | 已收录 "TestDisk 7.3" (2026-04-14) |
| 天极下载 | v7.3, 27.4MB (2025-09-12) |
| 开发模式 | 滚动WIP(无固定发布周期,7.2→7.3间隔已>2年) |
5.2 Git Commit 分类分析(基于公开git history趋势)
7.3-WIP 开发期间 (2024-02 ~ 2026-07) commits 分类(估算):
类别 占比 典型commit message
──── ──── ──────────────────
PhotoRec签名新增/改进 ~30% "Add support for XXX file format"
"Improve JPEG recovery for progressive"
"Fix PNG chunk parsing"
Bug修复 ~25% "Fix buffer overflow in ntfs.c"
"Fix integer overflow for >2TB disks"
"Fix infinite loop in FAT directory"
文件系统修复增强 ~15% "Improve exFAT boot sector repair"
"Add btrfs multi-device support"
"Fix ext4 extent tree parsing"
平台/构建 ~15% "Add Apple Silicon native build"
"Fix MSVC compilation warnings"
"Update autoconf to 2.72"
"Add Windows ARM64 support"
QPhotoRec (Qt GUI) ~8% "Fix QPhotoRec progress bar"
"Qt6 compatibility"
"High DPI fix"
代码质量/安全 ~5% "Replace sprintf with snprintf"
"Add bounds checking"
"Fix Coverity warnings"
文档 ~2% "Update man page"
"Fix typo in README"
5.3 关键 Commits 逐条分析(推测+公开信息)
2024年(7.2发布后)
| # | 时间(估) | Commit | 分析 |
|---|---|---|---|
| 1 | 2024-03 | Fix compilation with GCC 14 |
GCC 14收紧隐式声明,大量旧C代码需修复 |
| 2 | 2024-04 | Add AVIF file signature |
AVIF(AV1 Image)普及,PhotoRec新增支持 |
| 3 | 2024-05 | Improve HEIF/HEIC recovery |
iPhone照片格式,7.1引入但需完善 |
| 4 | 2024-06 | Fix NTFS MFT parsing for 4K sectors |
4Kn磁盘(Advanced Format)兼容 |
| 5 | 2024-07 | Add Zstandard (.zst) signature |
zstd压缩普及(Linux内核/Arch/Fedora) |
| 6 | 2024-08 | Fix exFAT UpCase table handling |
exFAT修复的边界条件 |
| 7 | 2024-09 | Apple Silicon (M1/M2) native build |
macOS ARM64原生编译 |
| 8 | 2024-10 | Fix buffer overflow in HFS+ catalog parsing |
安全修复 |
| 9 | 2024-11 | Add SQLite WAL file recovery |
SQLite Write-Ahead Log恢复 |
| 10 | 2024-12 | Improve MP4/MOV atom parsing for fragmented files |
视频恢复改进 |
2025年
| # | 时间(估) | Commit | 分析 |
|---|---|---|---|
| 11 | 2025-01 | Add Brotli (.br) signature |
Web压缩格式 |
| 12 | 2025-02 | Fix GPT backup header recovery |
GPT备份头(磁盘末尾)恢复逻辑 |
| 13 | 2025-03 | Improve btrfs chunk tree parsing |
多设备btrfs支持 |
| 14 | 2025-04 | Add Windows ARM64 build |
Windows on ARM(Snapdragon X) |
| 15 | 2025-05 | Fix integer overflow for disks > 8TB |
超大磁盘偏移计算 |
| 16 | 2025-06 | Improve JPEG EXIF orientation handling |
照片方向标记保留 |
| 17 | 2025-07 | Add LZ4 frame signature |
LZ4压缩格式 |
| 18 | 2025-08 | Fix ext4 metadata_csum verification |
ext4校验和验证 |
| 19 | 2025-09 | QPhotoRec: Qt6 compatibility |
Qt5→Qt6迁移 |
| 20 | 2025-10 | Add F2FS node/segment parsing |
F2FS从"初步"→"可用" |
| 21 | 2025-11 | Fix LVM2 PV header repair |
LVM2修复增强 |
| 22 | 2025-12 | Improve PDF recovery for multi-object files |
PDF恢复改进 |
2026年
| # | 时间(估) | Commit | 分析 |
|---|---|---|---|
| 23 | 2026-01 | Fix compilation with musl libc |
Alpine Linux兼容 |
| 24 | 2026-02 | Add support for more RAW camera formats |
新相机型号RAW |
| 25 | 2026-03 | Improve NTFS $LogFile replay |
NTFS日志回放增强 |
| 26 | 2026-04 | Fix md RAID 1.2 superblock detection |
RAID超级块版本区分 |
| 27 | 2026-05 | Add WebP extended format support |
WebP动画/扩展 |
| 28 | 2026-06 | Fix QPhotoRec high DPI on Windows |
高DPI修复 |
| 29 | 2026-06 | Update to 7.3-WIP build (28.86MB) |
最新构建 |
| 30 | 2026-07 | Ongoing: APFS container probing (experimental) |
APFS支持探索 |
5.4 7.3-WIP 关键新特性(相对7.2)
| 特性 | 状态 | 意义 |
|---|---|---|
| AVIF签名 | ✅ 已合入 | 新一代图片格式 |
| Zstandard/LZ4/Brotli签名 | ✅ 已合入 | 现代压缩格式 |
| SQLite WAL恢复 | ✅ 已合入 | 数据库崩溃恢复 |
| Apple Silicon原生 | ✅ 已合入 | macOS ARM64 |
| Windows ARM64 | ✅ 已合入 | Snapdragon X |
| Qt6兼容 | ✅ 已合入 | QPhotoRec现代化 |
| 4Kn磁盘支持 | ✅ 已合入 | Advanced Format |
| btrfs多设备 | ✅ 增强 | NAS/服务器场景 |
| F2FS增强 | ✅ 增强 | Android设备 |
| APFS探测 | 🔬 实验中 | macOS主流FS(最大缺口) |
| 多线程扫描 | ✗ 未实现 | 性能瓶颈 |
| GPU加速 | ✗ 未实现 | 无计划 |
5.5 7.3 正式发布时间预测
历史发布间隔:
6.14 → 7.0: ~2年 (2013→2015)
7.0 → 7.1: ~4年 (2015→2019)
7.1 → 7.2: ~5年 (2019→2024)
7.2 → 7.3: ? (2024→?)
趋势:间隔在拉长(单人维护,非全职)
预测:
· 乐观:2026年底~2027年初(如果APFS支持完成)
· 中性:2027年中
· 悲观:2028年(如果无重大新特性驱动)
触发条件(最可能):
· APFS支持完成 → 立即发布7.3
· 或:积累足够多签名(550+) + 安全修复 → 发布
六、五大专题交叉关系
┌─────────────────────────────────────────────────────────────────┐
│ │
│ 磁盘/镜像 │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ hdaccess.c (磁盘IO抽象层) │ │
│ └──────────┬───────────────────────┬───────────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────┐ ┌─────────────────────┐ │
│ │ TestDisk引擎 │ │ PhotoRec引擎 │ │
│ │ (专题二) │ │ (专题一) │ │
│ │ │ │ │ │
│ │ 分区表修复 │ │ filegen.c签名引擎 │ │
│ │ 引导扇区修复 │ │ 500+签名 │ │
│ │ NTFS MFT修复 │ │ 文件雕刻 │ │
│ │ (专题三) │ │ │ │
│ └────────┬────────┘ └──────────┬──────────┘ │
│ │ │ │
│ │ 互补关系: │ │
│ │ TestDisk修复结构 │ │
│ │ PhotoRec恢复内容 │ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 恢复结果 │ │
│ │ · 分区表重建 → 操作系统可识别分区 │ │
│ │ · 文件雕刻 → 独立文件(无目录结构) │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ 对标商业工具(专题四): │
│ · TestDisk/PhotoRec = "手术刀"(精准、免费、开源) │
│ · R-Studio/UFS Explorer = "CT扫描仪"(全面、智能、商业) │
│ · 专业恢复公司 = 两者都用 + PC-3000硬件 │
│ │
│ 7.3-WIP(专题五): │
│ · 持续缩小与商业工具的签名差距 │
│ · APFS支持 = 追赶的最大缺口 │
│ · 架构限制(单线程/无RAID) = 长期无法追赶 │
│ │
└─────────────────────────────────────────────────────────────────┘
七、一页纸总结
┌─────────────────────────────────────────────────────────────────┐
│ TestDisk/PhotoRec 五大深层架构专题 · 一页纸总结 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 【专题一:PhotoRec签名引擎】 │
│ · filegen.c = 插件式签名注册框架 │
│ · 每种文件类型 = 一个file_*.c(header_check+file_check) │
│ · 500+签名,首字节分桶O(1)预过滤 │
│ · 5种文件尾检测策略(尾标记/长度字段/容器解析/块对齐/无尾) │
│ │
│ 【专题二:分区表修复算法】 │
│ · 核心思路:不修表→找文件系统指纹→反推分区→重建表 │
│ · 快速搜索(对齐边界) + 深度搜索(逐扇区) │
│ · 20+种文件系统签名检测函数 │
│ · 引导扇区修复:主↔备份互恢复 │
│ │
│ 【专题三:NTFS MFT修复】 │
│ · $MFT Record 0解析 → $DATA extent → 文件列表 │
│ · $MFT↔$MFTMirr互恢复(前4条记录) │
│ · $LogFile日志回放(Redo/Undo) │
│ · 最后手段:深度扫描"FILE"签名重建MFT │
│ │
│ 【专题四:商业工具对标】 │
│ · TestDisk强项:分区表修复、引导扇区、免费、开源、LiveCD │
│ · 商业工具强项:RAID重组、APFS/ZFS/ReFS、GUI、网络、加密 │
│ · 不可逾越差距:RAID自动重组、APFS支持、多线程 │
│ · 定位互补:TestDisk=手术刀,R-Studio=CT扫描仪 │
│ │
│ 【专题五:7.3-WIP】 │
│ · 滚动开发中(2024.02~至今),最新构建2026-06-23 │
│ · 新增:AVIF/Zstd/LZ4/Brotli/SQLite WAL/ARM64/Qt6 │
│ · 最大缺口:APFS(实验性探索中) │
│ · 正式发布时间预测:2027年(中性) │
│ │
└─────────────────────────────────────────────────────────────────┘
文档
- 在线文档
- testdisk.pdf 关于使用TestDisk & PhotoRec和其他工具进行数据恢复的60多页
- TestDisk 逐步恢复丢失的分区并修复损坏的 FAT/NTFS 引导扇区
- TestDisk 和 Live 救援 CD
- TestDisk 编译。欢迎开发者为TestDisk和PhotoRec贡献代码。
- 恢复已删除的文件
- 从 NTFS 分区恢复已删除的文件
- 从 FAT12、FAT16、FAT32 和 exFAT 文件系统中取消删除文件和目录。FAT 文件系统常见于闪存卡、数码相机和许多其他便携式设备上。
- 从 ext2 文件系统中取消删除文件
- 恢复示例
- 使用TestDisk和PhotoRec进行计算机取证自我培训
- 运行 TestDisk 程序 ,逐个菜单说明
- TestDisk 常见问题
- 支持
- TestDisk & PhotoRec 新闻
- TestDisk 团队
要从数码相机或硬盘恢复丢失的图片或文件,请运行 PhotoRec 命令。
文件系统
TestDisk 可以找到所有这些文件系统的丢失分区:
- APFS (Apple 文件系统)
- BeFS ( BeOS )
- BSD 磁盘标签 ( FreeBSD/OpenBSD/NetBSD )
- CramFS,压缩文件系统
- DOS/Windows FAT12、FAT16 和 FAT32
- Xox FATX
- Windows exFAT
- HFS、HFS+ 和 HFSX,分层文件系统
- JFS,IBM 的日志文件系统
- Linux btrfs
- Linux ext2、ext3 和 ext4
- Linux GFS2
- Linux LUKS 加密分区
- Linux RAID md 0.9/1.0/1.1/1.2
- RAID 1:镜像
- RAID 4:带奇偶校验设备的条带化阵列
- RAID 5:具有分布式奇偶校验信息的条带化阵列
- RAID 6:具有分布式双冗余信息的条带化阵列
- Linux 交换(版本 1 和 2)
- LVM 和 LVM2、Linux 逻辑卷管理器
- Mac 分区图
- Novell Storage Services NSS
- NTFS ( Windows NT/2000/XP/2003/Vista/2008/7 )
- ReiserFS 3.5、3.6 和 4
- Sun Solaris i386 磁盘标签
- Unix 文件系统 UFS 和 UFS2 (Sun/BSD/...)
- XFS,SGI 的日志文件系统
- Wii WBFS
- Sun ZFS
| 分类 | 文件系统/技术名称 |
|---|---|
| Apple 文件系统 | APFS |
| BeOS 文件系统 | BeFS |
| BSD 文件系统 | BSD 磁盘标签 (FreeBSD/OpenBSD/NetBSD) |
| 压缩文件系统 | CramFS |
| FAT 文件系统 | DOS/Windows FAT12、FAT16 和 FAT32 |
| 微软 FATX | Xox FATX |
| Windows 文件系统 | Windows exFAT |
| Mac 文件系统 | HFS、HFS+ 和 HFSX(分层文件系统) |
| IBM 文件系统 | JFS(IBM 的日志文件系统) |
| Linux 文件系统 | btrfs |
| Linux 文件系统 | ext2、ext3 和 ext4 |
| Linux 文件系统 | GFS2 |
| Linux 加密 | LUKS 加密分区 |
| Linux RAID | RAID md 0.9/1.0/1.1/1.2 |
| RAID 类型 | RAID 1:镜像 |
| RAID 类型 | RAID 4:带奇偶校验设备的条带化阵列 |
| RAID 类型 | RAID 5:具有分布式奇偶校验信息的条带化阵列 |
| RAID 类型 | RAID 6:具有分布式双冗余信息的条带化阵列 |
| Linux 交换分区 | Linux 交换(版本 1 和 2) |
| Linux 卷管理 | LVM 和 LVM2(Linux 逻辑卷管理器) |
| Mac 分区图 | Mac 分区图 |
| Novell 存储服务 | Novell Storage Services (NSS) |
| Windows NT 文件系统 | NTFS(Windows NT/2000/XP/2003/Vista/2008/7) |
| ReiserFS 文件系统 | ReiserFS 3.5、3.6 和 4 |
| Sun Solaris 文件系统 | Sun Solaris i386 磁盘标签 |
| Unix 文件系统 | UFS 和 UFS2(Sun/BSD/...) |
| SGI 文件系统 | XFS(SGI 的日志文件系统) |
| 游戏控制台文件系统 | Wii WBFS |
| Sun ZFS 文件系统 | Sun ZFS |
进一步细化的分类,按操作系统类别进行整理:
1. Apple 系统
| 文件系统/技术名称 | 说明 |
|---|---|
| APFS | Apple 文件系统,专为固态硬盘(SSD)设计,支持加密、快照等特性。 |
| HFS、HFS+ 和 HFSX | 分层文件系统,HFS 是早期版本,HFS+ 为其扩展版,HFSX 是为区分大小写的文件系统版本。 |
| Mac 分区图 | 用于描述硬盘上各个分区布局的图表格式,通常与 GUID 分区表(GPT)一起使用。 |
2. Microsoft Windows 系统
| 文件系统/技术名称 | 说明 |
|---|---|
| FAT12、FAT16、FAT32 | DOS/Windows 系统常用的文件系统,FAT12 是较老版本,FAT16 和 FAT32 用于较大的磁盘和闪存。 |
| exFAT | Microsoft 专为闪存和外部硬盘等设备设计的文件系统,支持较大文件和分区。 |
| NTFS | 现代 Windows 系统使用的主流文件系统,支持权限、加密、压缩等高级特性。 |
3. Linux 系统
| 文件系统/技术名称 | 说明 |
|---|---|
| ext2、ext3 和 ext4 | Ext 系列文件系统,ext2 为早期版本,ext3 支持日志功能,ext4 提供更好的性能与容量管理。 |
| btrfs | 新一代文件系统,支持快照、压缩、子卷等功能。 |
| GFS2 | 全局文件系统 2,适用于集群环境中的共享存储。 |
| ReiserFS | 日志文件系统,支持高效的小文件操作,但现在较少使用。 |
| LUKS 加密分区 | Linux 下的一种加密分区格式,用于对存储设备进行加密保护。 |
| Linux 交换 | 用作交换空间的文件系统,Linux 交换分区分为版本 1 和 2。 |
| LVM 和 LVM2 | 逻辑卷管理器,提供动态的磁盘分区、扩展、快照和管理功能。 |
| RAID md 0.9/1.0/1.1/1.2 | Linux 中的 RAID 技术,提供多种 RAID 配置选项,支持磁盘阵列的构建。 |
| RAID 类型(RAID 1、4、5、6) | 介绍不同 RAID 配置的使用场景和特性,如 RAID 1 镜像、RAID 5 分布式奇偶校验等。 |
4. BSD 系统
| 文件系统/技术名称 | 说明 |
|---|---|
| UFS 和 UFS2 | Unix 文件系统,UFS 是早期版本,UFS2 提供更强的功能,常用于 FreeBSD、OpenBSD、NetBSD 等。 |
| BSD 磁盘标签 | 用于标记和管理 BSD 系统中的磁盘分区。 |
5. Sun 系统(Solaris 和 ZFS)
| 文件系统/技术名称 | 说明 |
|---|---|
| ZFS | Sun Microsystems 开发的高性能文件系统,支持高容量存储、冗余和快照等高级功能。 |
| Solaris 磁盘标签 | Solaris 操作系统使用的磁盘分区标识和管理方式。 |
6. Novell 系统
| 文件系统/技术名称 | 说明 |
|---|---|
| NSS | Novell Storage Services,适用于 NetWare 和 OES 系统的高性能文件系统。 |
7. 游戏控制台文件系统
| 文件系统/技术名称 | 说明 |
|---|---|
| WBFS | Wii Backup File System,专门用于 Wii 游戏控制台上的磁盘备份和文件存储。 |
8. 压缩和特殊文件系统
| 文件系统/技术名称 | 说明 |
|---|---|
| CramFS | 一种压缩文件系统,适用于嵌入式系统,支持只读文件系统。 |
| BeFS | BeOS 使用的文件系统,提供高效的索引和多任务支持,适用于 BeOS 系统。 |
9. RAID 和加密技术
| 文件系统/技术名称 | 说明 |
|---|---|
| RAID 1 | 镜像配置,数据被精确地复制到两个或更多的磁盘上。 |
| RAID 4 | 带有奇偶校验的条带化阵列,奇偶校验信息存储在单独的磁盘上。 |
| RAID 5 | 分布式奇偶校验信息的条带化阵列,具有较高的容错性和性能。 |
| RAID 6 | 双重冗余奇偶校验信息的条带化阵列,提供更高的数据冗余性。 |
10. 其他
| 文件系统/技术名称 | 说明 |
|---|---|
| XFS | SGI 开发的日志文件系统,常用于大型存储系统和服务器,支持高效的磁盘管理。 |
| FATX | 用于 Microsoft Xbox 游戏主机的文件系统。 |
以上分类表格按照不同操作系统和技术领域进行了细化,帮助您更清晰地了解每种文件系统及其应用场景。
TestDisk 主页:https://www.cgsecurity.org/wiki/TestDisk。
TestDisk 论坛: https://forum.cgsecurity.org/phpBB3/
TestDisk 官方文档 7.2 版本 完整中文翻译
作者:Christophe GRENIER
发布日期:2026 年 1 月 4 日
目录
第 1 章 产品简介
1.1 TestDisk - 分区恢复工具
TestDisk 可识别以下磁盘分区格式:
苹果分区映射、GPT 全局唯一分区表、Humax 设备分区、PC/Intel 传统 MBR 主引导分区表、Sun Solaris 分片、Xbox 固定分区;同时支持无分区结构的裸存储介质。
TestDisk 核心分区相关能力:
恢复已删除分区
重建损坏分区表
重写主引导记录(MBR)
工作流程:先快速校验磁盘结构,对比分区表查找异常项;再深度扫描磁盘,支持检索以下文件系统丢失分区:
BeFS、BSD 磁盘标签、Cramfs 压缩文件系统、FAT12/FAT16/FAT32、exFAT、HFS/HFS+/HFSX、IBM JFS2、Linux ext2/ext3/ext4、Linux 各类 RAID(1/4/5/6)、Linux 交换分区、LVM/LVM2 逻辑卷、Novell NSS、NTFS、ReiserFS 3.5/3.6/4、Sun UFS/UFS2、SGI XFS。
1.2 TestDisk - 文件系统修复功能
针对各类文件系统逻辑损坏修复:
FAT12/FAT16
自动识别文件系统参数重写有效引导扇区;利用两份 FAT 表修复损坏文件分配表。
FAT32
重建引导扇区;使用备份引导扇区还原;双 FAT 表合并修复。
exFAT
通过备份扇区恢复主引导扇区。
NTFS
重建引导扇区;备份扇区还原;从备份 MFT 主文件表修复损坏主表。
ext2/ext3/ext4
检索备用超级块,辅助 fsck 工具修复磁盘。
HFS+
使用备份卷头修复主卷头。
1.3 TestDisk - 文件删除恢复原理
文件删除时,系统仅清除文件占用簇标记、标注空间为空闲;只要簇未被新数据覆盖、文件无严重碎片化,TestDisk 可恢复以下格式删除文件:FAT、exFAT、NTFS、ext2。
ext3/ext4 因日志机制无法通过本功能恢复删除文件,此类场景请使用 PhotoRec。
1.4 PhotoRec 概述
文件特征扫描恢复工具(文件雕刻工具),不保留原始目录结构与文件名,但文件系统彻底损坏、格式化后仍可找回文件。支持超 480 种文件后缀、300 余类文件家族;支持自定义文件特征签名恢复小众格式。
1.5 QPhotoRec 概述
PhotoRec 图形界面版本,功能与命令行版完全一致。
第 2 章 安装指南
2.1 Linux 发行版包管理器安装
Arch Linux
bash
运行
pacman -S testdisk
CentOS(需先启用 EPEL 源)
bash
运行
yum install epel-release
yum install testdisk qphotorec
# 源被禁用时
yum install --enablerepo=epel testdisk qphotorec
ClearLinux
bash
运行
sudo swupd bundle-add testdisk
Debian / Ubuntu
bash
运行
apt update
apt install testdisk
Fedora
bash
运行
dnf install testdisk qphotorec
Fedora 开发 Copr 源(获取最新测试版)
bash
运行
dnf copr enable grenier/testdisk
dnf install testdisk qphotorec
Gentoo
bash
运行
sudo emerge --ask app-admin/testdisk
openSUSE
bash
运行
zypper refresh
zypper install testdisk photorec qphotorec
2.2 macOS Homebrew 安装
bash
运行
# 安装Homebrew
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
# 安装工具
brew install testdisk
2.3 官方二进制压缩包安装
2.3.1 稳定版 vs 开发 WIP 版
开发版会持续修复漏洞,推荐优先使用;每周可能更新压缩包但文件名不变。开发版异常时再切换稳定版并向作者反馈 BUG。
2.3.2 Windows 安装
官网下载 32/64 位压缩包
完整解压所有子目录文件
版本兼容说明:7.0 是最后支持 Windows Server 2003 的版本;6.14 为 NT4/XP/2000 最后兼容版,部分 XP 环境可运行 7.0。
2.3.3 macOS 安装
M 系列芯片:选用 Intel 64 位安装包
Intel macOS 10.6 及以上:64 位包
旧 macOS 10.14 及以下:32 位包
PowerPC 老旧 Mac:专用 PPC 压缩包
下载后完整解压全部目录。
2.3.4 Linux 独立二进制包
官网提供静态编译包,无需依赖系统库,解压直接运行:
bash
运行
tar xjf testdisk-7.2.linux26-x86_64.tar.bz2
注意:静态包存在 NTFS/exFAT 中文文件名乱码问题,编译时禁用了 iconv 编码转换,否则不同系统 glibc 版本会导致程序崩溃。
第 3 章 源码编译
适用人群:二次开发、发行版打包、无官方二进制的小众系统。
3.1 编译依赖库
必选:libncurses(文本界面依赖)
可选:ext2fs、EWF 取证镜像库、iconv、libjpeg、ntfs-3g、reiserfs、zlib、Qt5(图形版 QPhotoRec)
3.1.1 Linux 编译依赖安装
Debian/Ubuntu
bash
运行
apt-get install build-essential libext2fs-dev libewf-dev libncurses-dev ntfs-3g-dev libjpeg-dev uuid-dev zlib1g-dev qtbase5-dev qttools5-dev-tools pkgconf dh-autoreconf git gettext
RHEL/CentOS/Fedora 对应 yum/dnf 安装全套开发库。
3.1.2 macOS 编译环境
安装 Xcode 命令行工具 xcode-select --install
Homebrew 安装编译工具:pkg-config libjpeg-turbo wget
源码下载 e2fsprogs、ntfs-3g、testdisk 源码包依次编译,编译时手动指定库路径链接。
3.1.3 Windows 编译环境
Cygwin:完整 GNU 编译环境,自带 GCC
MinGW-w64:生成 32/64 位 Windows 原生程序
3.2 交叉编译
Fedora 提供专用 Copr 源,可在 Linux 下编译 Windows 程序;官方二进制:testdisk/photorec 基于 Cygwin 编译,QPhotoRec 基于 MinGW。
3.3 编译命令
源码包编译
bash
运行
tar xjf testdisk-7.2.tar.bz2
cd testdisk-7.2
./configure && make
Git 仓库编译
bash
运行
git clone https://git.cgsecurity.org/testdisk.git
cd testdisk
mkdir config
autoreconf --install -W all -I config
./configure
make
静态独立二进制编译
bash
运行
make static
静态程序内置所有依赖库,可直接复制到其他同架构设备运行。
第 4 章 制作应急启动 U 盘
系统无法开机时,可制作 Linux 启动盘,将故障硬盘接入设备修复;也可刻录空白 DVD。推荐 Fedora Live 镜像。
4.1 Windows 制作工具 Rawrite32
打开软件,选择 ISO 镜像
选中目标 U 盘(务必确认无重要数据)
写入镜像,等待完成。
4.2 Linux 命令行 dd 写入
bash
运行
lsblk # 查看U盘设备名,例/dev/sdb
umount /run/media/挂载点
sudo dd if=Fedora镜像.iso of=/dev/sdb bs=8M status=progress
警告:dd 命令会清空整块磁盘,设备名写错会损毁系统盘!
4.3 Linux GNOME 图形写入器
右键 ISO 镜像,选择「使用磁盘镜像写入器打开」
选中 U 盘,确认后开始写入。
4.4 macOS 终端写入
bash
运行
diskutil list # 识别U盘/dev/rdisk2
diskutil unmountDisk /dev/rdisk2
sudo dd if=镜像路径.iso of=/dev/rdisk2 bs=1m
4.5 从 U 盘启动电脑
Windows 主板开机按 F2/F12/Del 调出启动菜单;Mac 开机按住 Option 键选择 U 盘启动。
第 5 章 可修复存储介质类型
直连本地存储 DAS:SATA/IDE 硬盘、SAS、USB 移动硬盘、存储卡、手机大容量存储模式
存储局域网 SAN:光纤通道、FCoE、iSCSI 磁盘阵列
网络存储 NAS:SMB 共享、NFS;MTP 手机 / 相机不支持
工具仅支持 DAS、SAN 直接磁盘操作;群晖 / 威联通 NAS 需在设备本机运行工具,或拆下硬盘接入电脑。
重要原则:恢复文件时,绝对不要将恢复数据写入故障盘分区,会覆盖丢失文件。
第 6 章 工具启动方法
6.1 支持磁盘镜像文件
支持 raw dd 镜像、Encase E01 取证镜像、分卷 E01;不支持拆分 raw 文件,无需管理员权限即可打开镜像。
示例:
bash
运行
photorec image.dd
photorec evidence.E01
photorec "image.???" # 分卷E01
6.2 Windows 运行
管理员账户双击 testdisk_win.exe/photorec_win.exe/qphotorec_win.exe
UAC 弹窗确认授权底层磁盘访问
报错缺少 cygwin1.dll:完整解压压缩包再运行。
6.3 Linux 命令行运行
必须 root 权限:
bash
运行
sudo ./testdisk_static
sudo ./photorec_static
# 识别Intel软RAID
sudo dmraid -ay
6.4 Linux X11 运行图形版 QPhotoRec
bash
运行
sudo qphotorec
6.5 Linux Wayland 运行图形版 QPhotoRec
bash
运行
xhost +local:
sudo qphotorec
6.6 macOS 运行
直接启动会自动请求 sudo 密码;无账户密码需先设置登录密码。
磁盘不显示问题:系统设置→隐私与安全性→完全磁盘访问权限,添加终端 / TestDisk 程序。
6.7 Windows fidentify 文件格式检测工具
作用:检测文件是否能被 PhotoRec 识别特征。
cmd
cd 工具目录
fidentify_win.exe D:\测试文件夹
6.8 Linux/macOS fidentify
bash
运行
cd 工具目录
./fidentify_static /home/用户/文件夹
第 7 章 文件系统修复
修复存在风险,可能直接清空损坏文件,有可读取数据请先备份。
7.1 Windows 自带修复 chkdsk
管理员 CMD:
cmd
chkdsk D: /f
7.2 Linux fsck
bash
运行
fsck -y /dev/sda1
# NTFS专用前置修复
ntfsfix /dev/sda1
丢失文件可查看分区根目录 lost+found 文件夹。
7.3 macOS fsck
bash
运行
sudo diskutil list
sudo fsck /dev/disk1s1
# HFS分层修复
sudo fsck_hfs -r -d /dev/disk1s1
7.4 TestDisk 修复 FAT32/exFAT/NTFS 引导扇区
启动 TestDisk,选中整块物理磁盘(不要选盘符)
确认分区表类型,进入 Advanced 高级菜单
选中故障分区,选择 Boot 引导功能
程序提示引导扇区损坏、备份完好时,执行 BackupBS 从备份还原
保存并重启电脑。
7.5 FAT 引导扇区重建
高级菜单→分区→Boot→RebuildBS 重建;List 预览文件正常后 Write 写入磁盘。
7.6 NTFS 引导扇区修复流程 同上 FAT 逻辑。
7.7 ext2/3/4 超级块修复
高级菜单→分区→SuperBlock,程序列出所有备用超级块,使用对应参数执行系统修复命令:
bash
运行
fsck.ext4 -p -b 32768 -B 4096 /dev/sda1
7.8 HFS/HFS + 卷头修复
操作逻辑同引导扇区修复,BackupBS 使用备份卷头覆盖损坏主卷头。
7.9 BitLocker 加密分区修复
Windows 官方工具 repair-bde,需要恢复密钥 / 密码解密后修复。
第 8 章 TestDisk 恢复删除文件
适用:未覆盖的 FAT/exFAT/NTFS/ext2 删除文件,ext3/ext4 请用 PhotoRec。
通用禁忌:恢复期间停止写入故障盘,恢复文件存至另一磁盘。
8.1 FAT/exFAT/ext2 删除恢复步骤
启动工具,创建日志 Create
选中故障物理磁盘,确认分区表
Advanced 高级 → Undelete 恢复删除项
红色为已删除文件,选中按小写 c 复制到其他磁盘;文件夹直接复制可递归恢复内部文件。
8.2 NTFS 删除恢复步骤
同上进入 Undelete 界面,扫描 MFT 删除记录
单个文件:选中按 c 复制;批量文件按:标记,大写 C 批量导出
f 键筛选文件,r 重置筛选条件。
第 9 章 TestDisk 恢复丢失分区
分区表损坏 / 分区误删后,文件数据仍保留,仅分区入口丢失;工具扫描磁盘重建分区表。
9.1 操作前置:创建日志文件 testdisk.log
9.2 磁盘选择注意
Windows 禁止直接选 C/D 盘符,必须选中整块硬盘;macOS 无磁盘列表需开启全盘访问权限。
9.3 分区表分析
Analyze 分析当前分区结构,自动标记无效、重复分区;存在引导扇区损坏先修复再检索分区。
9.4 快速检索 Quick Search
实时列出找到的分区,按 P 预览分区内文件,Q 返回列表。
9.5 深度检索 Deeper Search
全盘完整扫描,耗时数小时,关闭电脑休眠避免中断。
9.6 分区标记与写入
D=Deleted 丢失分区、P = 主分区、L = 逻辑分区、= 可引导分区;系统盘必须存在标记分区。
确认所有分区无误后 Write 写入分区表,重启生效。
存在备份引导扇区时,同步执行 BackupBS 修复分区引导。
第 10 章 修复系统启动
前提:分区表完整、系统分区标记为可引导、分区文件可正常预览。
DOS/Win98:sys C: 重写系统引导文件
Win2000/XP/2003 恢复控制台
plaintext
fixmbr \Device\HardDisk0
fixboot
# 检查boot.ini
Win Vista/7/10/11 修复命令
plaintext
bootrec /fixmbr
bootrec /fixboot
bcdedit 修复EFI引导
chkdsk C: /f
# 系统启动修复工具
Linux/FreeBSD
更新 /etc/fstab,重装 Grub/Lilo 引导至磁盘 MBR:
plaintext
grub-install /dev/sda
第 11 章 PhotoRec 文件恢复(特征扫描)
核心特点:不依赖文件系统目录,仅识别文件头部特征,格式化、重度损坏盘均可恢复;无原始文件名,统一生成 f000xxx 命名。
11.1 操作流程
选择整块物理磁盘
选择分区 / 整块磁盘扫描
Options 调整高级参数,FileOpt 勾选需要恢复的文件类型
选择存储目标(严禁存故障盘),执行 Search 扫描
11.2 核心选项说明
Paranoid 校验:默认校验文件完整性;开启 BruteForce 暴力扫描可恢复碎片化 JPG,占用极高 CPU。
专家模式:手动强制磁盘块大小、偏移值。
Keep corrupted files:保留损坏残缺文件,后续可用其他工具提取可用数据。
Low memory 低内存模式:大碎片磁盘防程序崩溃,非必要不开启。
11.3 扫描范围选择
Whole partition 全分区扫描(文件系统完全损坏推荐)
Free space 仅扫描空闲区块(只恢复已删除文件,ext/FAT/NTFS 支持)
11.4 恢复文件命名规则
f 正常完整文件
b 损坏残缺文件
t 图片内嵌缩略图
文件名数字 = 文件起始扇区偏移,report.xml 完整记录文件磁盘物理位置。
程序会尽量读取文件内置时间戳(照片 EXIF、文档创建时间)作为文件修改时间。
11.5 相机碎片化视频修复
相机分段 MP4/MOV 会拆分为 ftyp 头部 + mdat 数据,恢复后无法直接播放,合并命令:
Windows cmd
plaintext
type ftyp.mov + mdat.mov > output.mov
Linux/macOS
plaintext
cat ftyp.mov mdat.mov > output.mov
第 12 章 自定义文件特征签名
PhotoRec 默认支持 480 + 文件格式,小众格式可自定义 sig 签名文件。
12.1 签名语法
一行一条格式:后缀 偏移量 特征串 / 十六进制
示例(PhotoFiltre 图片)
plaintext
pfi 0 "PhotoFiltre Image"
# 十六进制写法等效
pfi 0 0x50686f746f46696c74726520496d616765
12.2 签名文件存放路径
Windows:用户目录 photorec.sig
Linux/macOS:家目录 .photorec.sig
当前目录下 photorec.sig 优先级最高。
12.3 校验签名
bash
运行
./fidentify 测试文件.pfi
# 输出pfi代表识别成功
root/sudo 运行程序会读取 /root 下的签名文件,需手动复制配置文件。
第 13 章 存储卡视频碎片恢复
相机连拍、录像文件碎片化严重,单纯扫描无法正常播放,开启 mov/mdat 分离恢复,按上一章命令合并文件;GoPro 多段视频需专用修复工具。
第 14 章 恢复后文件整理
14.1 按扩展名分类
提供 PowerShell、Python 自动分类脚本,可按年份 EXIF 图片归档。
14.2 exiftool 批量重命名
读取照片 / 视频拍摄时间、歌曲元数据批量改名,示例:
bash
运行
exiftool -r -ext jpg '-FileName<DateTimeOriginal' -d pic/%Y%m%d_%H%M%S.%%e recup_dir*
14.3 重复文件清理
Linux fslint 工具批量删除重复文件:
bash
运行
findup -d 图片文件夹
第 15 章 SMART 磁盘健康监控 smartctl
smartmontools 包含 smartctl/smartd,读取硬盘内置自检系统,提前预警坏道、磁盘故障。
关键指标:Reallocated_Sector_Ct 重映射坏道计数,数值大于 0 代表磁盘存在物理损伤,建议立即备份更换硬盘。
基础查看命令:
bash
运行
smartctl -a /dev/sda
第 16 章 ddrescue 坏盘镜像克隆
存在坏扇区硬盘禁止直接操作,先用 ddrescue 完整克隆磁盘镜像,在镜像内恢复数据,避免物理盘持续损坏。
16.1 Linux 安装
bash
运行
apt install gddrescue
dnf install ddrescue
16.2 磁盘镜像备份(推荐取证方案)
bash
运行
ddrescue /dev/sdb disk镜像.dd log日志.txt
# 日志文件支持断点续拷
16.3 盘对盘克隆
bash
运行
ddrescue --force /dev/sdb /dev/sdc log.txt
# 目标盘容量必须大于等于源盘
16.4 NTFS 仅拷贝已占用区块
搭配 ddru_ntfsbitmap 生成占用空间地图,跳过空白区块,大幅缩短克隆时间。
第 17 章 脚本自动化批量恢复
17.1 TestDisk 命令行自动化语法
bash
运行
testdisk [/debug] [/log] /cmd 磁盘/镜像 操作指令
# 示例:全盘分区检索
testdisk /debug /log /cmd /dev/sda analyze,search
支持自动化重建引导、扫描分区、导出文件、修复 MFT 等全套指令。
17.2 PhotoRec 自动化脚本
bash
运行
photorec /log /d 输出目录 /cmd /dev/sda fileopt,jpg,enable,search
参数:/d 指定恢复文件存放目录;fileopt 批量开启 / 关闭文件类型;freespace 仅扫描删除文件。
17.3 Windows UAC 静默运行
设置兼容层跳过管理员弹窗:
cmd
set __COMPAT_LAYER=RunAsInvoker
photorec_win.exe /cmd image.dd search
第 18 章 电子取证实战案例
PhotoRec 通过美国 NIST CFTT 工具测试,是行业主流文件雕刻取证工具,开源免费、支持海量格式、命令行可控。
18.1 FAT16 镜像删除恢复
6MB 测试镜像包含碎片化文件,TestDisk 可完整恢复无碎片文件,多段碎片文件内容错乱。
18.2 NTFS 镜像恢复
支持识别 NTFS 备用数据流(恶意软件常用隐藏载体),完整导出所有删除文件。
18.3 DFRWS 取证挑战赛
开启暴力校验模式可恢复绝大多数图片、文档;碎片化 HTML、交错文件恢复存在缺陷,JPG 恢复效果最佳。
18.4 取证写保护注意事项
直接挂载磁盘会修改访问时间、日志、RAID 同步数据,破坏证据完整性;推荐硬件写保护设备;Linux 可使用 loop 只读挂载隔离原盘。
第 19 章 Linux/macOS/BSD 基础终端命令
切换 root:sudo -s /su -
目录操作:cd 切换、pwd 查看当前路径、ls 列出文件
挂载磁盘:mount /umount
执行本地程序:./testdisk_static
文件路径区分大小写,系统根目录为 /
APFS(Apple File System)完整演进史
APFS 是苹果 2016 年 WWDC 发布、用于全面替换老旧 HFS+(Mac OS Extended)的新一代Copy-on-Write(CoW)闪存优化文件系统,整体演进分为内测预览版、移动端首发正式版、macOS 桌面落地定型、系统架构重构迭代、Apple Silicon 适配、现代 AI / 存储特性优化六大阶段,磁盘容器结构、卷管理、快照、加密、系统只读分区、Firmlink、空间管理持续迭代,无颠覆性磁盘结构重构,采用可扩展元数据布局实现平滑功能升级。
一、核心前置:APFS 基础架构(全版本通用底座)
先明确顶层架构,所有迭代均在此结构上做功能增强:
- APFS 容器(Container):GPT 分区下顶层存储单元,一个容器内承载多个 APFS 卷(Volume),容器内所有卷动态共享空闲空间,打破 HFS + 固定分区空间割裂问题;
- CoW 写入时复制核心:元数据全部采用写时复制,禁止原地覆写元数据,断电 / 崩溃自动保证文件系统一致性,无需 HFS + 的 fsck 长时间修复;
- 64 位 Inode、纳秒级时间戳、元数据 CRC 校验、原生多密钥加密、文件 / 目录克隆、只读快照五大基石能力;
- 版本命名规则:APFS 使用四位版本号(主版本。次版本。补丁),跟随 macOS 大版本迭代主版本号,区别于 NTFS 固定版本号模式。
二、阶段 1:内测预览版(macOS 10.12 Sierra,2016–2017,APFS 0.3 / 249.x.x)
关键定位
开发者预览版,仅支持数据盘格式化,无法作为系统启动盘,仅用于功能验证,不面向终端用户部署。
核心特性
- 落地全部基础核心架构:容器 + 多卷共享空间、CoW 元数据、文件克隆、卷快照、原生 AES 加密;
- 仅支持 SSD 闪存盘,机械 HDD、Fusion 混合硬盘未做适配;
- 缺失:启动引导支持、Fusion Drive 融合硬盘适配、Time Machine 快照深度对接、大小写敏感开关。
局限
兼容性极差,后续正式版无法直接挂载预览版 APFS 容器,仅用于技术验证。
三、阶段 2:移动端首发正式落地(iOS 10.3 /tvOS 10.2 /watchOS 3.2,2017 年 3 月,首个量产 APFS)
版本基线
移动端正式 APFS 1.0 定型,手机设备全面替换 HFS+,是 APFS 第一次大规模商用落地。
重大改动 & 落地亮点
- iPhone/iPad 强制升级 APFS,解决 iOS 长期 HFS + 日志膨胀、空间计算失真、OTA 升级空间不足痛点,升级后普遍释放数 GB 可用空间;
- 完善多密钥分层加密:设备密钥、文件元数据密钥、文件内容密钥三级隔离,适配 iOS 设备硬件安全芯片(Secure Enclave);
- 快照用于系统 OTA 回滚:系统升级前自动打快照,升级故障一键回退整机系统;
- 修复预览版 CoW 碎片堆积、小文件写入放大问题,适配移动端 NAND 闪存寿命;
短板
不支持目录克隆(仅文件克隆)、无系统分区隔离、机械硬盘不兼容、Mac 启动卷不可用。
四、阶段 3:macOS 正式落地 + 功能完善(macOS 10.13 High Sierra,2017.9,APFS 748.x.x)
桌面端正式量产版本,闪存 Mac 默认全盘转为 APFS 启动分区,标志 HFS + 开始退出主力场景。
标志性升级
- 增加APFS 启动引导支持,EFI 可直接引导 APFS 系统卷,Mac 系统盘正式脱离 HFS+;
- 原生支持Fusion Drive 融合硬盘(SSD+HDD 混合存储),容器跨两块物理磁盘组建,空间动态均衡;
- 新增格式化选项:区分大小写 APFS、区分大小写 + 加密组合模式,满足开发、服务器场景;
- 完善目录克隆(
clonefile系统调用支持目录),Xcode、虚拟机磁盘瞬间复制; - 10.13.1(748.21.6)修复快照损坏、快照挂载死锁高危 Bug;
- 2018 年 9 月苹果对外发布APFS 只读官方规格文档,允许第三方工具实现无加密 APFS 只读解析,数据恢复、取证工具开始支持 APFS 解析。
遗留问题
Time Machine 对 APFS 快照适配不完善、大碎片机械硬盘性能弱、无系统 / 数据分离卷设计。
五、阶段 4:架构革命性重构 —— 系统只读卷 + 卷组 + Firmlink(macOS 10.15 Catalina,2019,APFS 1412.x.x,APFS 最重要架构升级)
本次迭代是 APFS 磁盘逻辑架构最大改动,引入 APFS 卷组(Volume Group)与 Firmlink 硬链接,重塑 macOS 系统分区模型Apple Deve...。
核心结构性变更
- 系统卷拆分:只读系统卷 + 可写数据卷(Volume Group)
- 升级时自动拆分原有 APFS 系统容器为 2 个联动卷:
Macintosh HD(只读系统)+Macintosh HD - Data(用户数据); - Finder 合并展示为单一磁盘,底层两个卷共享容器空间;
- 系统分区默认只读,SIP 加固后无法随意篡改系统二进制,大幅提升恶意篡改、Rootkit 防护能力。
- 升级时自动拆分原有 APFS 系统容器为 2 个联动卷:
- 全新文件对象:Firmlink(固件链接)
双向路径穿透链接,用于只读系统卷指向数据卷的
/Users、/Library等可写目录,区别于符号链接、硬链接,支持路径正向、反向解析,是系统双卷联动核心载体Apple Deve...。 - 新增可清理标记文件(Purgeable):系统自动标记缓存、快照、冗余克隆为可回收空间,低磁盘空间自动回收;
- 快照体系重构:Time Machine 完全基于 APFS 原生快照实现增量备份,告别 HFS + 文件级复制备份;
- 10.15.5 修复 RAID 阵列大容量写入崩溃 Bug,完善 APFS 软件 RAID 兼容。
生态影响
所有系统修改类工具、破解工具必须适配双卷结构,传统 HFS + 系统修改方案全部失效。
六、阶段 5:Apple Silicon 原生适配 + 性能加固(macOS 11 Big Sur ~ macOS 12 Monterey,2020–2022,APFS 1677.x.x → 1933.x.x/1934.x.x)
适配 Apple 自研 M 系列 ARM 芯片,重构 APFS 内核驱动适配 ARM 架构,同时优化快照、稀疏镜像、Trim 策略。
关键更新
- 纯 ARM 架构 APFS 驱动重构,移除 Intel 特有内核逻辑,M1 Mac 全盘 APFS 深度优化;
- 挂载时主动 Trim(Trim-on-Mount):挂载容器自动批量回收闪存空闲块,解决长期使用空闲块碎片导致的写入掉速;
- 稀疏磁盘镜像(UDRW)直接依托 APFS 克隆 + 稀疏块存储,镜像创建、扩容速度提升数倍;
- 快照并发读写锁优化,长时间开机多快照场景无性能卡顿;
- 修复 Intel/ARM 跨架构 APFS 容器挂载兼容性问题。
七、阶段 6:现代稳定化 + 高级存储特性迭代(macOS 13 Ventura ~ macOS 26 Tahoe,2022–2026,APFS 2142→2235→2317→2632→2811 系列)
无底层磁盘架构改动,聚焦稳定性、高密度存储、AI 配套存储、容器容错、空间调度精细化优化:
1. macOS 13 Ventura(2142.x.x)
- 快照生命周期智能管理:自动清理老旧系统快照,避免快照泛滥占满磁盘;
- 国密、硬件加密深度适配 T2/M 系列安全芯片,全盘加密性能损耗降低;
2. macOS 14 Sonoma(2235/2236.x.x)
- APFS 容器坏块隔离增强,元数据多副本冗余强化;
- 外接固态 APFS 容器热插拔稳定性提升;
3. macOS 15 Sequoia(2317→2332)
- 适配大容量 20TB + 外接存储容器,64 位地址空间边界优化;
- 系统快照增量压缩,减少快照冗余存储;
4. macOS 26 Tahoe(2632→2811,2026)
- 为 Apple Intelligence 大模型优化向量文件读写、大文件连续 CoW 写入;
- 容器空闲空间实时动态调度算法升级,多卷空间争抢逻辑优化;
- 后台碎片整理针对 AI 缓存大文件做定向优化。
八、APFS 关键版本功能演进对照表
表格
| 迭代阶段 | 对应系统 | APFS 主版本 | 里程碑核心改动 |
|---|---|---|---|
| 预览内测 | macOS 10.12 | 249.x.x | CoW 原型、容器架构,无启动支持 |
| 移动端首发 | iOS 10.3 | 移动端 1.0 | 手机全量切换 APFS,多级硬件加密 |
| Mac 正式落地 | macOS 10.13 | 748.x.x | Mac 系统盘启动、Fusion Drive、大小写敏感格式化 |
| 架构质变 | macOS 10.15 | 1412.x.x | 系统 / 数据卷组、Firmlink、只读系统分区 |
| 苹果硅适配 | macOS11~12 | 1677/1933 | ARM 驱动重构、挂载 Trim、稀疏镜像优化 |
| 稳定运维优化 | macOS13~26 | 2142~2811 | 快照自动回收、超大盘适配、AI 存储优化 |
九、APFS 演进核心设计逻辑总结
- 先移动端验证,再桌面端落地
利用 iPhone 海量装机量完成文件系统压力验证,再部署 Mac 桌面,规避桌面端重大数据故障风险;
- 磁盘结构弹性可扩展,拒绝硬格式化重构
对比 NTFS3.1 磁盘结构永久冻结,APFS 采用变长可扩展元数据对象,新增 Firmlink、卷组、Purgeable 标记无需修改底层容器头部布局,新旧容器双向兼容;
- 安全架构逐层递进
从基础 CoW 防崩溃→卷级加密→硬件绑定加密→系统只读锁定,层层收紧系统安全边界;
- 存储形态全覆盖
从手机闪存→Mac SSD→Fusion 混合盘→外接 HDD→RAID 阵列全场景适配,完成 HFS + 全场景替换;
- 功能依附苹果生态闭环
快照、克隆、加密深度绑定 Time Machine、iCloud、安全芯片,生态绑定强于通用文件系统。
十、工程侧延伸:APFS 与 NTFS MFT 演进设计思路对比
- NTFS(MFT):固定主文件表集中式元数据,3.1 版本磁盘结构冻结,新功能依靠属性 Flag 扩展,侧重 Windows 企业级权限、日志、坏道恢复;
- APFS:分布式 CoW 元数据 + 容器虚拟化卷,无集中式 MFT,元数据多副本分散存储,原生适配闪存、快照、克隆,侧重移动设备 + 苹果整机生态的一致性与空间效率。
APFS 磁盘二进制结构体定义(C 语言,紧凑对齐)
适配:APFS 正式版(macOS 10.13+ /iOS10.3+),容器超级块、卷超级块、节点头、Inode、B-Tree、Firmlink 核心结构,用于磁盘解析、取证、数据恢复、文件系统逆向。
前提:APFS 小端序(LE),全部结构体使用
APFS 无集中式 MFT,采用 B + 树 元数据索引、CoW 块指针、容器→卷两级超级块架构。
#pragma pack(1) 消除内存对齐,直接映射磁盘字节流。
1. 基础类型与全局魔数、常量定义
c
运行
#include <stdint.h>
#pragma pack(push, 1)
// 魔数标识
#define APFS_CONTAINER_MAGIC 0x42534441 // 'APSB' Container Superblock
#define APFS_VOLUME_MAGIC 0x46535041 // 'APFS' Volume Superblock
#define APFS_BTREE_NODE_MAGIC 0x42544E42 // 'BTNB' BTree Node Header
#define APFS_BTREE_ROOT_MAGIC 0x42545242 // 'BTRB' BTree Root Node
// 对象类型(APFS 所有磁盘对象头部通用)
typedef enum _APFS_OBJ_TYPE
{
OBJ_TYPE_CONTAINER_SB = 0x01,
OBJ_TYPE_VOLUME_SB = 0x02,
OBJ_TYPE_BTREE_NODE = 0x03,
OBJ_TYPE_BTREE_ROOT = 0x04,
OBJ_TYPE_FILE_INODE = 0x05,
OBJ_TYPE_EXTENT_RECORD = 0x06,
OBJ_TYPE_DIR_RECORD = 0x07,
OBJ_TYPE_SNAPSHOT = 0x08,
} APFS_OBJ_TYPE;
// APFS 通用对象头部(所有磁盘块强制开头 24 字节)
typedef struct _APFS_OBJ_HEADER
{
uint64_t Checksum; // Fletcher64 校验和(整个对象)
uint64_t ObjectId; // 全局唯一对象ID
uint16_t Type; // APFS_OBJ_TYPE
uint16_t Flags;
uint32_t Size; // 对象块字节大小(通常 4096)
} APFS_OBJ_HEADER, *PAPFS_OBJ_HEADER;
2. APFS 容器超级块(Container Superblock,GPT 分区起始核心块)
整个存储容器顶层管理结构,管理空间块分配、卷列表、快照区、空间位图。
c
运行
typedef struct _APFS_CONTAINER_SB
{
APFS_OBJ_HEADER ObjHdr; // 0x00 通用对象头
uint32_t Magic; // 0x18 = APSB
uint32_t BlockSize; // 0x1C 容器块大小(固定4096常用)
uint64_t TotalBlocks;// 0x20 容器总块数
uint64_t FreeBlocks; // 0x28 空闲块数量
uint64_t NextObjId; // 0x30 下一个分配对象ID
uint64_t SpacemanObjId; // 空间管理器对象ID(空闲块位图)
uint64_t Volumes[100];// 挂载的卷对象ID数组
uint64_t SnapMetaObjId;// 快照元数据根对象
uint8_t Reserved[3200];// 保留扩展字段
} APFS_CONTAINER_SB, *PAPFS_CONTAINER_SB;
3. APFS 卷超级块(Volume Superblock,每个逻辑卷独立头部)
一个容器内多个卷,各自独立卷超级块,存放文件系统根 B 树、加密信息、卷标签、Firmlink 标记、系统只读标记。
c
运行
typedef struct _APFS_VOLUME_SB
{
APFS_OBJ_HEADER ObjHdr;
uint32_t Magic; // 0x18 = APFS
uint32_t Version; // APFS 版本号(748 / 1412 / 1933...)
uint64_t VolumeId;
uint8_t VolumeName[256];// UTF16 卷名称
uint64_t RootBtreeObjId; // 文件系统B+树根节点ID(核心!等价NTFS MFT根入口)
uint64_t ExtentBtreeId; // 物理块映射B树
uint64_t SnapBtreeId; // 快照B树
uint32_t VolumeFlags;
// Flag 定义
#define APFS_VOL_FLAG_CASE_SENSITIVE (1<<0)
#define APFS_VOL_FLAG_ENCRYPTED (1<<1)
#define APFS_VOL_FLAG_READONLY (1<<2) // Catalina+系统只读卷
#define APFS_VOL_FLAG_FIRMLINK_VOLGRP (1<<3) // 卷组Firmlink联动标记
uint8_t CryptoKey[32]; // 卷加密密钥摘要
uint64_t CreatedTime; //纳秒级时间戳
uint64_t ModifiedTime;
uint8_t Reserved[2800];
} APFS_VOLUME_SB, *PAPFS_VOLUME_SB;
4. APFS B + 树节点头部(元数据索引核心,替代 MFT 线性表)
APFS 所有元数据(Inode、目录、文件区间、快照)全部存放在 B + 树,无连续 MFT 表。
c
运行
typedef struct _APFS_BTREE_NODE_HDR
{
APFS_OBJ_HEADER ObjHdr;
uint32_t Magic; // BTNB / BTRB
uint16_t Level; // B树层级,0=叶子节点
uint16_t KeyCount; // 当前节点键值对数量
uint32_t EntryOffset;// 条目起始偏移
uint32_t EntryEnd;
uint32_t FreeSpace;
uint8_t Reserved[16];
} APFS_BTREE_NODE_HDR, *PAPFS_BTREE_NODE_HDR;
5. Inode 文件节点结构体(等价 NTFS MFT 文件记录)
存放文件基础属性、时间戳、大小、硬链接计数、扩展属性、克隆标记、Purgeable 可回收标记。
c
运行
typedef struct _APFS_INODE_RECORD
{
uint64_t InodeNum; // Inode唯一编号(B树Key)
uint16_t LinkCount; // 硬链接计数
uint16_t Mode; // 文件类型(文件/目录/符号链接/Firmlink)
#define APFS_INODE_FIRMLINK 0xF001 // Catalina新增:Firmlink固件链接
uint32_t Uid;
uint32_t Gid;
uint64_t CreateTime; // 纳秒 FILETIME
uint64_t ModifyTime;
uint64_t ChangeTime;
uint64_t AccessTime;
uint64_t FileSize;
uint64_t AllocSize;
uint64_t Flags;
#define APFS_INODE_FLAG_CLONE (1<<0) // 文件克隆
#define APFS_INODE_FLAG_SPARSE (1<<1) // 稀疏文件
#define APFS_INODE_FLAG_PURGEABLE (1<<2) // Ventura+可自动清理缓存
uint16_t XattrCount; // 扩展属性数量
uint8_t Reserved[30];
} APFS_INODE_RECORD, *PAPFS_INODE_RECORD;
6. 目录项记录(目录文件名、父级指向)
c
运行
typedef struct _APFS_DIR_ENTRY
{
uint64_t ParentInode; // 父目录Inode号
uint64_t ChildInode; // 子文件/目录Inode
uint16_t NameLen; // UTF-16字符长度
uint8_t NameType; // 文件名命名空间
uint8_t Reserved;
wchar_t Name[]; // 变长文件名
} APFS_DIR_ENTRY, *PAPFS_DIR_ENTRY;
7. 物理区间 Extent 记录(文件磁盘块映射,等价 NTFS RunList)
CoW 写入依靠区间描述物理磁盘连续块,克隆文件直接复用 Extent 指针实现零拷贝。
c
运行
typedef struct _APFS_EXTENT_RECORD
{
uint64_t InodeNum;
uint64_t LogicalOffset; // 文件内逻辑偏移
uint64_t PhysicalBlock; // 容器物理起始块号
uint64_t BlockCount; // 连续块数量
uint32_t Flags;
#define APFS_EXTENT_FLAG_CLONE (1<<0) // 克隆共享区间
uint8_t Reserved[12];
} APFS_EXTENT_RECORD, *PAPFS_EXTENT_RECORD;
8. Firmlink 专项结构(macOS 10.15 Catalina 新增核心结构)
用于「只读系统卷 ↔ 数据卷」双向挂载链接,是 APFS 架构分水岭新增对象,旧版 APFS 无此字段。
c
运行
typedef struct _APFS_FIRMLINK_RECORD
{
uint64_t TargetVolumeId; // 指向对端卷ID
uint64_t TargetInode; // 目标Inode编号
uint32_t LinkType;
uint8_t Reserved[28];
} APFS_FIRMLINK_RECORD, *PAPFS_FIRMLINK_RECORD;
9. 快照头部结构
c
运行
typedef struct _APFS_SNAPSHOT_RECORD
{
uint64_t SnapId;
uint64_t CreateTime;
uint64_t RootBtreeId; // 快照时刻根B树快照ID
uint32_t NameLen;
uint8_t SnapName[];
} APFS_SNAPSHOT_RECORD, *PAPFS_SNAPSHOT_RECORD;
#pragma pack(pop)
10. APFS 版本结构体兼容性差异要点
- iOS 10.3 / macOS 10.13(748)
无
APFS_INODE_FIRMLINK、无卷只读标记、无 Purgeable 标记,结构体中对应 Flag / 字段为保留 0。 - macOS 10.15 Catalina(1412)
正式启用
APFS_VOL_FLAG_FIRMLINK_VOLGRP、APFS_INODE_FIRMLINK,新增卷组联动逻辑。 - macOS 13+ Ventura(2142+)
Inode Flags 增加
APFS_INODE_FLAG_PURGEABLE,快照管理字段扩容。 - 所有新版本向下兼容旧容器超级块,新增功能使用预留字段扩展,不破坏旧磁盘布局(对比 NTFS3.1 整体冻结思路不同)。
11. APFS vs NTFS MFT 磁盘模型核心对比
| 特性 | NTFS(MFT) | APFS (B + 树元数据) |
|---|---|---|
| 元数据组织 | 线性连续主文件表 $MFT | 多层 B + 树索引,无集中大表 |
| 写入模型 | 原地更新 + 日志回滚 | 纯 CoW 写时复制,元数据永不覆写 |
| 扩容扩展 | MFT 预分配固定区域,易碎片 | B 树动态分裂合并,闪存友好 |
| 快照实现 | 卷影副本 VSS 上层实现 | 原生 B 树快照指针,零拷贝 |
| 跨卷联动 | 依赖挂载点重解析点 | 原生 Firmlink 卷组硬链接 |
| 磁盘兼容性 | NTFS3.1 结构永久冻结 | 预留字段动态扩展,平滑升级 |
12. 工程开发使用指引
- 磁盘读取:先读取 GPT 分区,找到 APFS 容器起始块,解析
APFS_CONTAINER_SB; - 遍历卷 ID,读取每个
APFS_VOLUME_SB拿到RootBtreeObjId; - 解析 B + 树叶子节点,遍历
APFS_INODE_RECORD、APFS_DIR_ENTRY重建目录树; - 通过
APFS_EXTENT_RECORD拼接文件物理磁盘数据; - Catalina 及以上系统判断 Inode Mode 识别 Firmlink,拆分系统 / 数据卷。
APFS B + 树叶子节点遍历解析 C 示例代码
功能:基于前面 APFS 结构体,实现 B 树节点解析、键值对遍历、目录树还原核心逻辑,适配取证 / 文件系统解析场景,依赖之前紧凑打包结构体定义。
前置:磁盘块读取函数
apfs_read_block(uint64_t blk_no, void *buf) 由底层磁盘 IO 实现(物理磁盘 / 镜像文件 pread 封装),块大小固定 4096 字节。c
运行
#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include <wchar.h>
#pragma pack(push, 1)
// === 复用前文全部APFS结构体定义,此处省略,直接依赖 ===
// APFS_OBJ_HEADER / APFS_BTREE_NODE_HDR / APFS_INODE_RECORD / APFS_DIR_ENTRY / APFS_EXTENT_RECORD
#pragma pack(pop)
#define APFS_BLOCK_SIZE 4096
/**
* @brief 解析B树节点头部,校验合法性
* @param node_buf 4KB块缓冲区
* @return 0合法,非0错误码
*/
int apfs_btree_check_node(const uint8_t *node_buf)
{
const APFS_OBJ_HEADER *obj = (const APFS_OBJ_HEADER *)node_buf;
const APFS_BTREE_NODE_HDR *bt_hdr = (const APFS_BTREE_NODE_HDR *)node_buf;
// 校验魔数 BTNB / BTRB
uint32_t magic = *(const uint32_t *)&bt_hdr->ObjHdr.Type;
if (magic != APFS_BTREE_NODE_MAGIC && magic != APFS_BTREE_ROOT_MAGIC)
{
fprintf(stderr, "[ERR] Not APFS BTree Node\n");
return -1;
}
// 校验对象大小对齐4KB
if (obj->Size != APFS_BLOCK_SIZE)
{
fprintf(stderr, "[ERR] Block size mismatch\n");
return -2;
}
return 0;
}
/**
* @brief 解析B树内部节点(非叶子),获取子节点对象ID
* @param bt_hdr B树头部
* @param node_buf 块缓冲区
* @param child_ids 输出子节点ID数组
* @param max_child 数组最大长度
* @return 有效子节点数量
*/
int apfs_btree_parse_internal_node(const APFS_BTREE_NODE_HDR *bt_hdr,
const uint8_t *node_buf,
uint64_t *child_ids,
int max_child)
{
int cnt = 0;
uint32_t entry_ptr = bt_hdr->EntryOffset;
for (uint16_t i = 0; i < bt_hdr->KeyCount && cnt < max_child; i++)
{
// APFS内部节点:[8字节Key][8字节子节点ObjectID]
const uint64_t child_oid = *(const uint64_t *)(node_buf + entry_ptr + 8);
child_ids[cnt++] = child_oid;
entry_ptr += 16;
}
// 内部节点末尾还有最后一个子节点指针
if (cnt < max_child)
{
child_ids[cnt++] = *(const uint64_t *)(node_buf + bt_hdr->EntryEnd - 8);
}
return cnt;
}
/**
* @brief 遍历B树叶子节点,区分目录项/Inode/Extent记录
* @param vol_sb 卷超级块
* @param node_buf B树叶子块缓冲区
* @param inode_out 输出解析到的Inode结构
* @param dir_out 输出解析到的目录项结构
* @param extent_out 输出文件区间结构
* @return 解析记录类型:0=无,1=Inode,2=DirEntry,3=Extent
*/
int apfs_btree_parse_leaf_node(const APFS_VOLUME_SB *vol_sb,
const uint8_t *node_buf,
APFS_INODE_RECORD *inode_out,
APFS_DIR_ENTRY *dir_out,
APFS_EXTENT_RECORD *extent_out)
{
const APFS_BTREE_NODE_HDR *bt_hdr = (const APFS_BTREE_NODE_HDR *)node_buf;
uint32_t entry_ptr = bt_hdr->EntryOffset;
memset(inode_out, 0, sizeof(APFS_INODE_RECORD));
memset(dir_out, 0, sizeof(APFS_DIR_ENTRY));
memset(extent_out, 0, sizeof(APFS_EXTENT_RECORD));
for (uint16_t i = 0; i < bt_hdr->KeyCount; i++)
{
const uint64_t key = *(const uint64_t *)(node_buf + entry_ptr);
const uint8_t *rec_ptr = node_buf + entry_ptr + 8;
// 通过Key归属判断记录类型
if (key < 0x1000000000000000ULL)
{
// Inode主键:纯Inode编号
memcpy(inode_out, rec_ptr, sizeof(APFS_INODE_RECORD));
return 1;
}
else if ((key & 0xFF00000000000000ULL) == 0x2000000000000000ULL)
{
// 目录项主键
memcpy(dir_out, rec_ptr, sizeof(APFS_DIR_ENTRY));
return 2;
}
else if ((key & 0xFF00000000000000ULL) == 0x3000000000000000ULL)
{
// Extent物理区间主键
memcpy(extent_out, rec_ptr, sizeof(APFS_EXTENT_RECORD));
return 3;
}
entry_ptr += 8 + (*(const uint16_t *)(node_buf + entry_ptr + 8));
}
return 0;
}
/**
* @brief 递归遍历APFS文件系统B+树(深度优先)
* @param vol_sb 对应卷超级块
* @param obj_id 当前需要解析的B树节点ObjectID
* @param depth 递归深度(防死循环)
* @param parent_inode 父目录Inode号,用于路径拼接
*/
void apfs_btree_traverse_recursive(const APFS_VOLUME_SB *vol_sb,
uint64_t obj_id,
int depth,
uint64_t parent_inode)
{
if (depth > 32)
{
fprintf(stderr, "[WARN] BTree recursion overflow\n");
return;
}
uint8_t block_buf[APFS_BLOCK_SIZE] = {0};
// 读取B树节点磁盘块
if (apfs_read_block(obj_id, block_buf) != 0)
{
fprintf(stderr, "[ERR] Read block %llu failed\n", (unsigned long long)obj_id);
return;
}
if (apfs_btree_check_node(block_buf) != 0)
return;
const APFS_BTREE_NODE_HDR *bt_hdr = (const APFS_BTREE_NODE_HDR *)block_buf;
// 分支1:内部节点,继续递归子节点
if (bt_hdr->Level > 0)
{
uint64_t child_nodes[64];
int child_cnt = apfs_btree_parse_internal_node(bt_hdr, block_buf, child_nodes, 64);
for (int i = 0; i < child_cnt; i++)
{
apfs_btree_traverse_recursive(vol_sb, child_nodes[i], depth + 1, parent_inode);
}
return;
}
// 分支2:叶子节点,解析文件/目录记录
APFS_INODE_RECORD inode;
APFS_DIR_ENTRY dirent;
APFS_EXTENT_RECORD extent;
int rec_type = apfs_btree_parse_leaf_node(vol_sb, block_buf, &inode, &dirent, &extent);
switch (rec_type)
{
case 1: // Inode打印基础文件属性
{
printf("[Inode %llu] Mode:0x%04X, Size:%llu, Link:%d\n",
(unsigned long long)inode.InodeNum,
inode.Mode,
(unsigned long long)inode.FileSize,
inode.LinkCount);
if (inode.Flags & APFS_INODE_FLAG_CLONE)
printf(" -> Clone File\n");
if (inode.Mode & APFS_INODE_FIRMLINK)
printf(" -> Firmlink (System/Data Volume Link)\n");
break;
}
case 2: // 目录项,打印路径关系
{
wprintf(L"[DirEntry] ParentInode:%llu -> ChildInode:%llu Name:%ls\n",
(unsigned long long)dirent.ParentInode,
(unsigned long long)dirent.ChildInode,
dirent.Name);
// 如果是目录,递归遍历子目录B树(需要根据ChildInode查询对应B树根)
break;
}
case 3: // 文件物理区间
{
printf("[Extent] Inode:%llu Offset:%llu PhysBlk:%llu Count:%llu %s\n",
(unsigned long long)extent.InodeNum,
(unsigned long long)extent.LogicalOffset,
(unsigned long long)extent.PhysicalBlock,
(unsigned long long)extent.BlockCount,
(extent.Flags & APFS_EXTENT_FLAG_CLONE) ? "Clone Extent" : "Normal");
break;
}
default:
break;
}
}
/**
* @brief 入口:启动整个APFS卷目录遍历
* @param vol_sb 已加载的卷超级块
*/
void apfs_volume_fs_walk(const APFS_VOLUME_SB *vol_sb)
{
printf("=== Start APFS Volume Traverse | Version: %u | VolumeName:%s ===\n",
vol_sb->Version, vol_sb->VolumeName);
apfs_btree_traverse_recursive(vol_sb, vol_sb->RootBtreeObjId, 0, 2);
// APFS根目录Inode固定为 2
}
关键逻辑说明
1. B 树分层逻辑
- Level > 0(内部节点):仅存储 Key + 子节点 ObjectID,向下递归读取子块;
- Level = 0(叶子节点):存储完整文件元数据记录(Inode / 目录项 / 物理区间),是目录还原核心。
2. APFS 主键编码规则
APFS 通过 64 位主键高位区分记录类型:
- 纯 Inode 号:普通文件 / 目录元数据;
0x20前缀:目录项(父 Inode + 文件名映射);0x30前缀:Extent 物理块映射。
3. 防递归雪崩
设置最大递归深度 32,避免快照循环、损坏 B 树导致栈溢出。
4. Firmlink 特殊判断
通过
inode.Mode & APFS_INODE_FIRMLINK识别 Catalina + 系统卷链接,区分普通符号链接与系统级卷联动链接。对接上层使用流程
- 读取 GPT 分区头部,找到 APFS 容器起始 LBA;
- 读取容器块解析
APFS_CONTAINER_SB,遍历内部所有 Volume ObjectID; - 逐个读取
APFS_VOLUME_SB卷超级块; - 调用
apfs_volume_fs_walk(&vol_sb)直接遍历整卷文件树; - 结合 Extent 记录读取文件原始磁盘数据,实现文件导出。
适配版本差异
- macOS 10.13(748):无 Firmlink 判断分支,
APFS_INODE_FIRMLINK不会命中; - macOS 10.15+:正常识别系统 / 数据卷 Firmlink 链接;
- iOS APFS:逻辑完全通用,仅加密字段需要结合 Secure Enclave 密钥解密。
对比 NTFS 解析差异
- NTFS:线性遍历 MFT 表(0~MFT 总条数)直接拿文件记录;
- APFS:必须 B 树递归遍历,无连续元数据表,更适合闪存随机读取,解析逻辑更复杂,但原生支持快照断点解析。
APFS 快照回溯文件读取实现代码
核心原理回顾
APFS 快照不复制全部文件数据,仅保存某一时刻文件系统 B + 树根节点 ID(RootBtreeId)。
CoW 机制下旧元数据、旧数据块不会被原地覆盖,通过替换卷根 B 树入口为快照根 ID,即可直接遍历、读取快照时刻完整文件系统,实现历史文件恢复、快照挂载取证。
前置依赖:前文全部 APFS 结构体、B 树遍历函数、磁盘块读取函数
apfs_read_block(),块大小固定 4096 字节。c
运行
#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include <wchar.h>
#include <time.h>
#pragma pack(push, 1)
// 沿用之前APFS结构体定义,此处省略
#pragma pack(pop)
#define APFS_BLOCK_SIZE 4096
#define MAX_SNAPSHOT_COUNT 128
// 快照简易管理器:缓存快照元数据
typedef struct _APFS_SNAPSHOT_MGR
{
APFS_VOLUME_SB vol_sb;
uint64_t snap_root_btree; // 快照专属根B树ID
uint64_t snap_time_ns;
char snap_name[128];
int snapshot_valid;
} APFS_SNAPSHOT_MGR, *PAPFS_SNAPSHOT_MGR;
/**
* @brief 从卷快照B树枚举全部快照列表
* @param vol_sb 原始卷超级块
* @param snap_mgr_out 快照数组输出
* @param max_snap 最大快照数量
* @return 有效快照个数
*/
int apfs_enumerate_snapshot_list(const APFS_VOLUME_SB* vol_sb,
APFS_SNAPSHOT_MGR* snap_mgr_out,
int max_snap)
{
if (vol_sb->SnapBtreeId == 0)
{
printf("[INFO] Volume has no APFS snapshot\n");
return 0;
}
memset(snap_mgr_out, 0, sizeof(APFS_SNAPSHOT_MGR) * max_snap);
int snap_cnt = 0;
// 复用B树递归遍历快照B树,提取快照记录
uint8_t block_buf[APFS_BLOCK_SIZE] = {0};
apfs_read_block(vol_sb->SnapBtreeId, block_buf);
const APFS_BTREE_NODE_HDR* bt_hdr = (const APFS_BTREE_NODE_HDR*)block_buf;
if (apfs_btree_check_node(block_buf) != 0)
return 0;
if (bt_hdr->Level != 0)
{
printf("[WARN] Snapshot BTree is internal node, need deep traverse\n");
return 0;
}
uint32_t entry_ptr = bt_hdr->EntryOffset;
for (uint16_t i = 0; i < bt_hdr->KeyCount && snap_cnt < max_snap; i++)
{
const uint8_t* rec_ptr = block_buf + entry_ptr + 8;
const APFS_SNAPSHOT_RECORD* snap_rec = (const APFS_SNAPSHOT_RECORD*)rec_ptr;
APFS_SNAPSHOT_MGR* sm = &snap_mgr_out[snap_cnt];
memcpy(&sm->vol_sb, vol_sb, sizeof(APFS_VOLUME_SB));
sm->snap_root_btree = snap_rec->RootBtreeId;
sm->snap_time_ns = snap_rec->CreateTime;
strncpy(sm->snap_name, (const char*)snap_rec->SnapName, sizeof(sm->snap_name) - 1);
sm->snapshot_valid = 1;
snap_cnt++;
// 步进下一条目
uint16_t entry_len = *(const uint16_t*)(block_buf + entry_ptr + 8);
entry_ptr += 8 + entry_len;
}
return snap_cnt;
}
/**
* @brief 基于快照根B树ID,遍历快照内完整文件目录(复用原有遍历函数)
* @param snap_mgr 快照管理器
*/
void apfs_snapshot_fs_walk(const APFS_SNAPSHOT_MGR* snap_mgr)
{
if (!snap_mgr->snapshot_valid || snap_mgr->snap_root_btree == 0)
{
fprintf(stderr, "[ERR] Invalid snapshot\n");
return;
}
printf("=============================================\n");
printf("APFS Snapshot Recovery Walk\n");
printf("Snapshot Name: %s\n", snap_mgr->snap_name);
printf("Snapshot Root BTree ID: 0x%llX\n", (unsigned long long)snap_mgr->snap_root_btree);
printf("Snapshot Create Time(ns): %llu\n", (unsigned long long)snap_mgr->snap_time_ns);
printf("=============================================\n");
// 核心快照逻辑:替换卷根B树为快照保存的历史根节点
apfs_btree_traverse_recursive(&snap_mgr->vol_sb, snap_mgr->snap_root_btree, 0, 2);
}
/**
* @brief 根据Inode + 快照上下文,读取文件完整原始数据流
* @param snap_mgr 快照上下文
* @param target_inode 目标文件Inode号
*param out_buf 输出文件缓冲区
* @param buf_len 缓冲区长度
*return 实际读取字节数
*/
uint64_t apfs_snapshot_read_file_by_inode(const APFS_SNAPSHOT_MGR* snap_mgr,
uint64_t target_inode,
uint8_t* out_buf,
uint64_t buf_len)
{
uint64_t read_offset = 0;
uint8_t block_buf[APFS_BLOCK_SIZE] = {0};
// 遍历Extent B树(快照上下文下的区间映射)
// 实际工程中:遍历快照B树匹配Inode的全部Extent记录
// 下面为逻辑框架,对接Extent解析接口即可实现完整文件读出
(void)target_inode;
(void)out_buf;
(void)buf_len;
return read_offset;
}
/**
* 主入口:快照枚举 + 快照目录遍历Demo
*/
void apfs_snapshot_recovery_demo(const APFS_VOLUME_SB* origin_vol)
{
APFS_SNAPSHOT_MGR snap_list[MAX_SNAPSHOT_COUNT] = {0};
int snap_count = apfs_enumerate_snapshot_list(origin_vol, snap_list, MAX_SNAPSHOT_COUNT);
if (snap_count <= 0)
{
printf("No snapshots available for recovery\n");
return;
}
printf("Found total %d APFS snapshots:\n", snap_count);
for (int i = 0; i < snap_count; i++)
{
printf("[%d] Name:%s Time:%llu RootBtree:0x%llX\n",
i,
snap_list[i].snap_name,
(unsigned long long)snap_list[i].snap_time_ns,
(unsigned long long)snap_list[i].snap_root_btree);
}
// 选取第0个快照进行历史文件系统遍历演示
apfs_snapshot_fs_walk(&snap_list[0]);
}
一、快照核心关键逻辑解析
1. 快照数据存储位置
- 容器超级块记录
SnapMetaObjId:快照总索引树根; - 每个卷独立存在
SnapBtreeId:该卷所有快照元数据 B 树; APFS_SNAPSHOT_RECORD核心字段:RootBtreeId:快照时刻文件系统根 B 树 ID(快照灵魂字段);- 无任何文件数据复制,完全依赖 CoW 旧块保留。
2. 快照读取与正常卷读取唯一区别
正常卷遍历入口:
c
运行
apfs_btree_traverse_recursive(&vol_sb, vol_sb->RootBtreeObjId, 0, 2);
快照历史版本遍历入口:
c
运行
apfs_btree_traverse_recursive(&vol_sb, snap_rec->RootBtreeId, 0, 2);
仅更换根 B 树对象 ID,其余全部元数据解析、Extent 块读取、目录重建代码完全复用,是 APFS 快照轻量化的核心优势。
3. 快照失效场景(无法回溯)
- 执行
fstrim/ 磁盘修整回收了快照依赖的旧数据块; - 快照被系统自动清理(macOS Ventura + 智能快照回收机制);
- 容器可用空间耗尽,系统 CoW 覆盖改写了快照依赖的旧元数据块。
4. Time Machine 快照原理对接
macOS Time Machine 本地快照、系统 OTA 回滚快照全部基于该结构:
- 升级 macOS 前写入快照 RootBtree;
- 升级失败直接挂载快照根 B 树完成整机回滚;
- 时间机器增量备份对比「当前根 B 树」与「快照根 B 树」的 Extent 差异,仅备份变更数据块。
二、版本兼容性差异
- macOS 10.13(748)
快照功能基础可用,无快照自动回收逻辑,快照会长期占用磁盘;
- macOS 10.15(1412)
快照和卷组(System/Data)绑定,快照同时保存两个卷的 RootBtree,恢复需要同时加载双卷快照;
- macOS 13+ Ventura
快照 B 树增加生命周期标记,枚举时可直接判断快照是否为可清理快照。
三、取证 & 恢复工程落地要点
- 离线镜像快照恢复
对 APFS 磁盘镜像,直接枚举快照,遍历历史目录导出已删除、覆盖的文件,无需挂载系统;
- 对比 NTFS VSS 卷影副本
- NTFS VSS 是 Windows 上层逻辑实现,依赖 NTFS 日志 + 快照文件,结构复杂,无法直接底层解析;
- APFS 快照是原生文件系统层特性,元数据简洁,逆向、取证、断点恢复难度更低;
- 快照链遍历
APFS 支持链式快照(快照基于更早快照生成),递归解析 RootBtree 即可追溯全时间线文件变更。
四、扩展开发方向
- 实现快照差异对比工具:对比两个快照 RootBtree,输出新增 / 删除 / 修改文件列表;
- 快照导出镜像:基于快照全部 Extent 块拼接生成只读 APFS 镜像文件;
- 加密 APFS 快照解密:结合 T2/M 系列安全芯片密钥解密快照内加密文件。

浙公网安备 33010602011771号