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

下载 TestDisk - CGSecurity

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

Download.png 下载适用于 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恢复增强:多对象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年(中性)                             │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

文档

要从数码相机或硬盘恢复丢失的图片或文件,请运行 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 基础架构(全版本通用底座)

先明确顶层架构,所有迭代均在此结构上做功能增强:
  1. APFS 容器(Container):GPT 分区下顶层存储单元,一个容器内承载多个 APFS 卷(Volume),容器内所有卷动态共享空闲空间,打破 HFS + 固定分区空间割裂问题;
  2. CoW 写入时复制核心:元数据全部采用写时复制,禁止原地覆写元数据,断电 / 崩溃自动保证文件系统一致性,无需 HFS + 的 fsck 长时间修复;
  3. 64 位 Inode、纳秒级时间戳、元数据 CRC 校验、原生多密钥加密、文件 / 目录克隆、只读快照五大基石能力;
  4. 版本命名规则:APFS 使用四位版本号(主版本。次版本。补丁),跟随 macOS 大版本迭代主版本号,区别于 NTFS 固定版本号模式。

二、阶段 1:内测预览版(macOS 10.12 Sierra,2016–2017,APFS 0.3 / 249.x.x)

关键定位

开发者预览版,仅支持数据盘格式化,无法作为系统启动盘,仅用于功能验证,不面向终端用户部署。

核心特性

  1. 落地全部基础核心架构:容器 + 多卷共享空间、CoW 元数据、文件克隆、卷快照、原生 AES 加密;
  2. 仅支持 SSD 闪存盘,机械 HDD、Fusion 混合硬盘未做适配;
  3. 缺失:启动引导支持、Fusion Drive 融合硬盘适配、Time Machine 快照深度对接、大小写敏感开关。

局限

兼容性极差,后续正式版无法直接挂载预览版 APFS 容器,仅用于技术验证。

三、阶段 2:移动端首发正式落地(iOS 10.3 /tvOS 10.2 /watchOS 3.2,2017 年 3 月,首个量产 APFS)

版本基线

移动端正式 APFS 1.0 定型,手机设备全面替换 HFS+,是 APFS 第一次大规模商用落地。

重大改动 & 落地亮点

  1. iPhone/iPad 强制升级 APFS,解决 iOS 长期 HFS + 日志膨胀、空间计算失真、OTA 升级空间不足痛点,升级后普遍释放数 GB 可用空间;
  2. 完善多密钥分层加密:设备密钥、文件元数据密钥、文件内容密钥三级隔离,适配 iOS 设备硬件安全芯片(Secure Enclave);
  3. 快照用于系统 OTA 回滚:系统升级前自动打快照,升级故障一键回退整机系统;
  4. 修复预览版 CoW 碎片堆积、小文件写入放大问题,适配移动端 NAND 闪存寿命;

短板

不支持目录克隆(仅文件克隆)、无系统分区隔离、机械硬盘不兼容、Mac 启动卷不可用。

四、阶段 3:macOS 正式落地 + 功能完善(macOS 10.13 High Sierra,2017.9,APFS 748.x.x)

桌面端正式量产版本,闪存 Mac 默认全盘转为 APFS 启动分区,标志 HFS + 开始退出主力场景。

标志性升级

  1. 增加APFS 启动引导支持,EFI 可直接引导 APFS 系统卷,Mac 系统盘正式脱离 HFS+;
  2. 原生支持Fusion Drive 融合硬盘(SSD+HDD 混合存储),容器跨两块物理磁盘组建,空间动态均衡;
  3. 新增格式化选项:区分大小写 APFS、区分大小写 + 加密组合模式,满足开发、服务器场景;
  4. 完善目录克隆(clonefile系统调用支持目录),Xcode、虚拟机磁盘瞬间复制;
  5. 10.13.1(748.21.6)修复快照损坏、快照挂载死锁高危 Bug;
  6. 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...。

核心结构性变更

  1. 系统卷拆分:只读系统卷 + 可写数据卷(Volume Group)
    • 升级时自动拆分原有 APFS 系统容器为 2 个联动卷:Macintosh HD(只读系统)+Macintosh HD - Data(用户数据)
    • Finder 合并展示为单一磁盘,底层两个卷共享容器空间;
    • 系统分区默认只读,SIP 加固后无法随意篡改系统二进制,大幅提升恶意篡改、Rootkit 防护能力。
  2. 全新文件对象:Firmlink(固件链接)
     
    双向路径穿透链接,用于只读系统卷指向数据卷的/Users/Library等可写目录,区别于符号链接、硬链接,支持路径正向、反向解析,是系统双卷联动核心载体Apple Deve...。
  3. 新增可清理标记文件(Purgeable):系统自动标记缓存、快照、冗余克隆为可回收空间,低磁盘空间自动回收;
  4. 快照体系重构:Time Machine 完全基于 APFS 原生快照实现增量备份,告别 HFS + 文件级复制备份;
  5. 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 策略。

关键更新

  1. 纯 ARM 架构 APFS 驱动重构,移除 Intel 特有内核逻辑,M1 Mac 全盘 APFS 深度优化;
  2. 挂载时主动 Trim(Trim-on-Mount):挂载容器自动批量回收闪存空闲块,解决长期使用空闲块碎片导致的写入掉速;
  3. 稀疏磁盘镜像(UDRW)直接依托 APFS 克隆 + 稀疏块存储,镜像创建、扩容速度提升数倍;
  4. 快照并发读写锁优化,长时间开机多快照场景无性能卡顿;
  5. 修复 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 演进核心设计逻辑总结

  1. 先移动端验证,再桌面端落地
     
    利用 iPhone 海量装机量完成文件系统压力验证,再部署 Mac 桌面,规避桌面端重大数据故障风险;
  2. 磁盘结构弹性可扩展,拒绝硬格式化重构
     
    对比 NTFS3.1 磁盘结构永久冻结,APFS 采用变长可扩展元数据对象,新增 Firmlink、卷组、Purgeable 标记无需修改底层容器头部布局,新旧容器双向兼容;
  3. 安全架构逐层递进
     
    从基础 CoW 防崩溃→卷级加密→硬件绑定加密→系统只读锁定,层层收紧系统安全边界;
  4. 存储形态全覆盖
     
    从手机闪存→Mac SSD→Fusion 混合盘→外接 HDD→RAID 阵列全场景适配,完成 HFS + 全场景替换;
  5. 功能依附苹果生态闭环
     
    快照、克隆、加密深度绑定 Time Machine、iCloud、安全芯片,生态绑定强于通用文件系统。

十、工程侧延伸:APFS 与 NTFS MFT 演进设计思路对比

  1. NTFS(MFT):固定主文件表集中式元数据,3.1 版本磁盘结构冻结,新功能依靠属性 Flag 扩展,侧重 Windows 企业级权限、日志、坏道恢复;
  2. APFS:分布式 CoW 元数据 + 容器虚拟化卷,无集中式 MFT,元数据多副本分散存储,原生适配闪存、快照、克隆,侧重移动设备 + 苹果整机生态的一致性与空间效率。

APFS 磁盘二进制结构体定义(C 语言,紧凑对齐)

适配:APFS 正式版(macOS 10.13+ /iOS10.3+),容器超级块、卷超级块、节点头、Inode、B-Tree、Firmlink 核心结构,用于磁盘解析、取证、数据恢复、文件系统逆向。
 
前提:APFS 小端序(LE),全部结构体使用 #pragma pack(1) 消除内存对齐,直接映射磁盘字节流。
 
APFS 无集中式 MFT,采用 B + 树 元数据索引、CoW 块指针、容器→卷两级超级块架构。

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 版本结构体兼容性差异要点

  1. iOS 10.3 / macOS 10.13(748)
     
    APFS_INODE_FIRMLINK、无卷只读标记、无 Purgeable 标记,结构体中对应 Flag / 字段为保留 0。
  2. macOS 10.15 Catalina(1412)
     
    正式启用 APFS_VOL_FLAG_FIRMLINK_VOLGRPAPFS_INODE_FIRMLINK,新增卷组联动逻辑。
  3. macOS 13+ Ventura(2142+)
     
    Inode Flags 增加 APFS_INODE_FLAG_PURGEABLE,快照管理字段扩容。
  4. 所有新版本向下兼容旧容器超级块,新增功能使用预留字段扩展,不破坏旧磁盘布局(对比 NTFS3.1 整体冻结思路不同)。

11. APFS vs NTFS MFT 磁盘模型核心对比

特性 NTFS(MFT) APFS (B + 树元数据)
元数据组织 线性连续主文件表 $MFT 多层 B + 树索引,无集中大表
写入模型 原地更新 + 日志回滚 纯 CoW 写时复制,元数据永不覆写
扩容扩展 MFT 预分配固定区域,易碎片 B 树动态分裂合并,闪存友好
快照实现 卷影副本 VSS 上层实现 原生 B 树快照指针,零拷贝
跨卷联动 依赖挂载点重解析点 原生 Firmlink 卷组硬链接
磁盘兼容性 NTFS3.1 结构永久冻结 预留字段动态扩展,平滑升级

12. 工程开发使用指引

  1. 磁盘读取:先读取 GPT 分区,找到 APFS 容器起始块,解析APFS_CONTAINER_SB
  2. 遍历卷 ID,读取每个APFS_VOLUME_SB拿到RootBtreeObjId
  3. 解析 B + 树叶子节点,遍历APFS_INODE_RECORDAPFS_DIR_ENTRY重建目录树;
  4. 通过APFS_EXTENT_RECORD拼接文件物理磁盘数据;
  5. 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 + 系统卷链接,区分普通符号链接与系统级卷联动链接。

对接上层使用流程

  1. 读取 GPT 分区头部,找到 APFS 容器起始 LBA;
  2. 读取容器块解析APFS_CONTAINER_SB,遍历内部所有 Volume ObjectID;
  3. 逐个读取APFS_VOLUME_SB卷超级块;
  4. 调用apfs_volume_fs_walk(&vol_sb)直接遍历整卷文件树;
  5. 结合 Extent 记录读取文件原始磁盘数据,实现文件导出。

适配版本差异

  1. macOS 10.13(748):无 Firmlink 判断分支,APFS_INODE_FIRMLINK不会命中;
  2. macOS 10.15+:正常识别系统 / 数据卷 Firmlink 链接;
  3. 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. 快照数据存储位置

  1. 容器超级块记录 SnapMetaObjId:快照总索引树根;
  2. 每个卷独立存在 SnapBtreeId:该卷所有快照元数据 B 树;
  3. 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. 快照失效场景(无法回溯)

  1. 执行 fstrim / 磁盘修整回收了快照依赖的旧数据块;
  2. 快照被系统自动清理(macOS Ventura + 智能快照回收机制);
  3. 容器可用空间耗尽,系统 CoW 覆盖改写了快照依赖的旧元数据块。

4. Time Machine 快照原理对接

macOS Time Machine 本地快照、系统 OTA 回滚快照全部基于该结构:
  • 升级 macOS 前写入快照 RootBtree;
  • 升级失败直接挂载快照根 B 树完成整机回滚;
  • 时间机器增量备份对比「当前根 B 树」与「快照根 B 树」的 Extent 差异,仅备份变更数据块。

二、版本兼容性差异

  1. macOS 10.13(748)
     
    快照功能基础可用,无快照自动回收逻辑,快照会长期占用磁盘;
  2. macOS 10.15(1412)
     
    快照和卷组(System/Data)绑定,快照同时保存两个卷的 RootBtree,恢复需要同时加载双卷快照;
  3. macOS 13+ Ventura
     
    快照 B 树增加生命周期标记,枚举时可直接判断快照是否为可清理快照。

三、取证 & 恢复工程落地要点

  1. 离线镜像快照恢复
     
    对 APFS 磁盘镜像,直接枚举快照,遍历历史目录导出已删除、覆盖的文件,无需挂载系统;
  2. 对比 NTFS VSS 卷影副本
  • NTFS VSS 是 Windows 上层逻辑实现,依赖 NTFS 日志 + 快照文件,结构复杂,无法直接底层解析;
  • APFS 快照是原生文件系统层特性,元数据简洁,逆向、取证、断点恢复难度更低;
  1. 快照链遍历
     
    APFS 支持链式快照(快照基于更早快照生成),递归解析 RootBtree 即可追溯全时间线文件变更。

四、扩展开发方向

  1. 实现快照差异对比工具:对比两个快照 RootBtree,输出新增 / 删除 / 修改文件列表;
  2. 快照导出镜像:基于快照全部 Extent 块拼接生成只读 APFS 镜像文件;
  3. 加密 APFS 快照解密:结合 T2/M 系列安全芯片密钥解密快照内加密文件。

posted @ 2025-01-24 03:25  suv789  阅读(2557)  评论(0)    收藏  举报