数据恢复(二)-linux下ext文件系统数据恢复思路深入分析

数据恢复(二)-linux下ext文件系统数据恢复思路深入分析

在上一篇文章《数据恢复(一)-linux下ext文件系统数据删除原理浅析》中分析了ext数据删除的原理,这篇文章中会结合实例分析数据恢复的原理。

ext 含义

全称Linux extended file system, extfs,即Linux扩展文件系统,Ext2就代表第二代文件扩展系统,Ext3/Ext4以此类推,它们都是Ext2的升级版,只不过为了快速恢复文件系统,减少一致性检查的时间,增加了日志功能,所以Ext2被称为索引式文件系统,而Ext3/Ext4被称为日志式文件系统。并且Linux还支持很多文件系统,包括XFS、NFS、Fat等文件系统。

ext3/4 数据恢复思路

由于Ext3/Ext4被称为日志式文件系统,所以文件的删除会有日志记录。就意味着被删除的inode的block指针还可以从journal日志的inode副本中找到,也就有可能从磁盘中恢复出被删除的文件。

经过extundelete、ext3grep、ext4magic等几款linux下ext数据恢复软件的测试,总结出ext数据恢复的原理。

journal日志恢复

基于journal日志从inode副本中找到block指针从而确定被删除文件在磁盘上的物理位置进行恢复。

文件特征恢复

扫描整个磁盘,基于文件魔数(Magic Number)特征直接从磁盘中恢复文件。
很多类型的文件,其起始的几个字节的内容是固定的。这几个字节的内容也被称为魔数,因为根据这几个字节的内容就可以确定文件类型。

实验环境

  • 腾讯云轻量服务器1核1g
  • 操作系统 centos7、ext4文件系统

模拟ext3下文件删除后进行恢复

因为实验环境中centos7默认用的是ext4的文件系统,但是要测试ext3的文件删除,通常需要挂载一个新的硬盘并格式化为ext3。这里提供一个更简单的解决办法。

dd生成一个2G大小的空文件

[root@VM-8-8-centos newpart]# pwd 
/newpart
[root@VM-8-8-centos newpart]# dd if=/dev/zero of=test.ext3 bs=1024M count=2
dd: memory exhausted by input buffer of size 1073741824 bytes (1.0 GiB)
[root@VM-8-8-centos newpart]# dd if=/dev/zero of=test.ext3 bs=128M count=16
16+0 records in
16+0 records out
2147483648 bytes (2.1 GB, 2.0 GiB) copied, 13.2437 s, 162 MB/s
[root@VM-8-8-centos newpart]# ll -h
total 2.1G
-rw-r--r-- 1 root root 2.0G Mar 11 23:28 test.ext3

可以看到第一次使用dd命令生成空文件时报错了,dd: memory exhausted by input buffer of size。申请的内存的过大,超过了内存的限制。这里要生成一个2G的文件,一次写入内存的大小为1024M即1G,而机器的内存只有1G,超出内存限制导致dd命令报错。所以可以将bs的值改小,并分为多次写入文件,对最后生成的文件大小不会有影响。

mkfs.ext3将空文件格式化为ext3文件系统

[root@VM-8-8-centos newpart]# mkfs.ext3 -I 128 test.ext3 
mke2fs 1.45.6 (20-Mar-2020)
Discarding device blocks: done                            
Creating filesystem with 524288 4k blocks and 131072 inodes
Filesystem UUID: 8f008c7a-dc08-4efe-86ca-d0fcd6c61dab
Superblock backups stored on blocks: 
        32768, 98304, 163840, 229376, 294912
Allocating group tables: done                            
Writing inode tables: done                            
Creating journal (16384 blocks): done
Writing superblocks and filesystem accounting information: done 

[root@VM-8-8-centos newpart]# file test.ext3 
test.ext3: Linux rev 1.0 ext3 filesystem data, UUID=8f008c7a-dc08-4efe-86ca-d0fcd6c61dab (large files)

这里指定生成inode-size大小为128的ext文件系统,默认为256(后面会解释)。

挂载ext3文件到系统目录

[root@VM-8-8-centos newpart]# df -Th
Filesystem     Type      Size  Used Avail Use% Mounted on
/dev/vda1      ext4       25G  8.2G   16G  35% /
/dev/vdb1      ext4       40G  180M   37G   1% /newpart
[root@VM-8-8-centos newpart]# mount -o loop test.ext3 /ext3
[root@VM-8-8-centos newpart]# df -Th
Filesystem     Type      Size  Used Avail Use% Mounted on
/dev/vda1      ext4       25G  8.2G   16G  35% /
/dev/vdb1      ext4       40G  180M   37G   1% /newpart
/dev/loop1     ext3      2.0G  3.1M  1.9G   1% /ext3
[root@VM-8-8-centos newpart]# cd /ext3/
[root@VM-8-8-centos ext3]# mkdir test && cd test
[root@VM-8-8-centos test]# cp /etc/passwd ./
[root@VM-8-8-centos test]# ls -ilh 
total 4.0K
98306 -rw-r--r-- 1 root root 1.8K Mar 11 23:59 passwd

此时已经将我们的ext3文件作为文件系统挂载到系统目录中。进入该分区中,拷贝passwd作为测试文件。
补充:mount-o loop 文件 目录,使用 loop 模式用来将一个档案当成硬盘分割挂上系统。

模拟文件删除并记录block指针

[root@VM-8-8-centos test]# debugfs /dev/loop1 -R "ls -d /test"
debugfs 1.45.6 (20-Mar-2020)
 98305  (12) .    2  (12) ..    98306  (4072) passwd   
[root@VM-8-8-centos test]# debugfs /dev/loop1 -R "imap <98306>"
debugfs 1.45.6 (20-Mar-2020)
Inode 98306 is part of block group 12
        located at block 393218, offset 0x0080
[root@VM-8-8-centos test]# debugfs /dev/loop1 -R "blocks <98306>"
debugfs 1.45.6 (20-Mar-2020)
16994 
[root@VM-8-8-centos test]# rm -f passwd         //模拟文件删除
[root@VM-8-8-centos test]# debugfs /dev/loop1 -R "ls -d /test"
debugfs 1.45.6 (20-Mar-2020)  //删除后的文件inode带有尖括号显示
 98305  (12) .    2  (4084) ..   <98306> (4072) passwd 
[root@VM-8-8-centos test]# debugfs /dev/loop1 -R "blocks <98306>"
debugfs 1.45.6 (20-Mar-2020)
                         //文件被删除后,原inode的block指针被填0,获取为空

这里需要强度的是虽然将ext3文件系统挂载到了系统的/ext3目录下,但是文件系统本身也有根目录(区别于系统根目录),因此这里使用ls -d /test,而非ls -d /ext3/test

在删除文件之前特地记录了inode中block的值,也就是磁盘中该文件的真实位置。使用dd命令读取该block位置的文件。

删除文件后根据删除前的block仍能在磁盘中找到该文件。因此要想成功恢复被删除的文件,关键在于如何从journal日志中找到inode副本中的block值。
该例中文件的block为16994,只要能在日志中找到16994这个值就能恢复文件。

使用debugfs的logdump获取日志中inode副本

[root@VM-8-8-centos test]# debugfs /dev/loop1 -R "logdump -i <98306>"
debugfs 1.45.6 (20-Mar-2020)
Inode 98306 is at group 12, block 393218, offset 128
Journal starts at block 1, transaction 2
  FS block 393218 logged at sequence 47, journal block 369 (flags 0x2)
    (inode block for inode 98306):
    Inode: 98306   Type: bad type        Mode:  0000   Flags: 0x0
    Generation: 0    Version: 0x00000000
    User:     0   Group:     0   Size: 0
    File ACL: 0
    Links: 0   Blockcount: 0
    Fragment:  Address: 0    Number: 0    Size: 0
    ctime: 0x00000000 -- Thu Jan  1 08:00:00 1970
    atime: 0x00000000 -- Thu Jan  1 08:00:00 1970
    mtime: 0x00000000 -- Thu Jan  1 08:00:00 1970
    Blocks:      //该副本中为空
  FS block 393218 logged at sequence 50, journal block 397 (flags 0x2)
    (inode block for inode 98306):
    Inode: 98306   Type: regular        Mode:  0644   Flags: 0x0
    Generation: 4264657513    Version: 0x00000001
    User:     0   Group:     0   Size: 1825
    File ACL: 0
    Links: 1   Blockcount: 8
    Fragment:  Address: 0    Number: 0    Size: 0
    ctime: 0x622b71c8 -- Fri Mar 11 23:59:04 2022
    atime: 0x622b71c8 -- Fri Mar 11 23:59:04 2022
    mtime: 0x622b71c8 -- Fri Mar 11 23:59:04 2022
    Blocks:  (0+1): 16994 //被删除文件的inode副本中的block指针
  .....
No magic number at block 1620: end of journal.

注意上面的 Blocks: (0+1): 16994 为block指针。

使用logdump 命令在journal日志中找到了多个indoe为98306的副本,这里只展示了两个。第一个block记录为空,在第二个副本中找到了原始block的指针,且与之前记录的文件block指针值一致,证明该方法有效,可成功恢复文件。

使用dd从磁盘中dump恢复文件

[root@VM-8-8-centos test]# dd if=/dev/loop1 bs=4096 count=1 skip=16994 2>/dev/null 
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
...// 确定该block的文件就是被删除的文件内容
[root@VM-8-8-centos test]# dd if=/dev/loop1 bs=4096 count=1 skip=16994 of=passwd
1+0 records in
1+0 records out
4096 bytes (4.1 kB, 4.0 KiB) copied, 6.0939e-05 s, 67.2 MB/s
[root@VM-8-8-centos test]# ll -h
total 4   //文件恢复成功!
-rw-r--r-- 1 root root 4K Mar 12 00:50 passwd

文件恢复成功!没有使用任何数据恢复软件,仅使用linux自带的命令恢复了被删除的文件。

补充:为什么使用dd命令dump恢复的文件MD5值不一样?

[root@VM-8-8-centos test]# md5sum /etc/passwd
5e39a7f4aa25cd0de397c1685597460b  /etc/passwd
[root@VM-8-8-centos test]# md5sum passwd 
118c4d51a0425cf6d028076a54ba7fa9  passwd
[root@VM-8-8-centos test]# diff -Naur  /etc/passwd passwd 
--- /etc/passwd 2022-03-10 11:04:45.106866879 +0800
+++ passwd      2022-03-12 00:50:51.000000000 +0800
@@ -34,3 +34,4 @@
 lighthouse:x:1000:1000::/home/lighthouse:/bin/bash
 zgao:x:0:1001::/home/zgao:/bin/bash
 redis:x:991:984:Redis Database Server:/var/lib/redis:/sbin/nologin
+
\ No newline at end of file

很明显两者的md5并不相同,但用cat看到的内容却一样。因为dd命令是按照bs的大小来dump文件的,因为文件系统block-size为4096,整块一起dump包括了文件结尾填0部分,所以两者的md5值会不同。

小结

这里通过ext3演示了文件删除后的通过日志的方式恢复文件过程,由于ext4和ext3都是日志文件系统,数据恢复思路是一样的,就不再赘述了。

debugfs的logdump命令存在bug?

在大量ext3/4的数据恢复实验中发现,logdump命令并不一定成功!网上有大量误导人的文章,根本没有弄清楚原理!在此花费了大量时间验证!

结论:无论ext3、4,只有在文件系统的inode-size值为128时,logdump命令计算得到的journal日志才是正确的。而ext3默认indoe-size为128,ext4默认为256,因此通常ext3下使用logdump的结果是正确的,ext4则是错误的!

关于在ext4中如何从journal日志中正确获取indoe副本会在后续的系列文章中分析。

基于文件特征进行文件恢复

这种恢复思路最典型的实现工具就是 foremost

foremost是一款根据文件头,尾和内部结构来尝试从镜像文件(或者磁盘)中恢复文件的工具。foremost默认可以扫描出 jpggifpngbmpaviexempgmp4wavriffwmv 等文件。但是通过配置它的配置文件(默认为 /etc/foremost.conf),你还可以为它增加新的支持类型。

工具的使用网上有很多教程,这里只分析其实现原理。

文件类型文件头文件尾
JPGFF D8 FF E0FFD9
GIF47 49 46 38
PSD38 42 50 53
PNG89 50 4E 47AE 42 60 82
GIF47 49 46 3800 3B
BMP42 4D
TIFF49 49 2A 00
ZIP50 4B 03 0450 4B
MS Word/Excel (xls.or.doc)D0 CF 11 E0
PDF25 50 44 46 2D 31 2E
RAR52 61 72 21
常见文件类型的文件头尾

分析png图片文件格式特征

以下图为例,这是一张png格式的图片。

将其放在上面创建的ext3分区中,并模拟删除。

[root@VM-8-8-centos test]# ll -h
total 152K
-rw-r--r-- 1 root root 143K Mar 12 01:45 logo.png
-rw-r--r-- 1 root root 4.0K Mar 12 00:50 passwd
[root@VM-8-8-centos test]# xxd logo.png 
00000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452  .PNG........IHDR
00000010: 0000 1a7e 0000 059b 0806 0000 00ba cb9c  ...~............
00000020: 5100 0000 0970 4859 7300 002e 2300 002e  Q....pHYs...#...
......  //省略文件中间部分,只看开头和结尾
000239d0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000239e0: 0030 058e 25e5 446a 69a6 1500 0000 0049  .0..%.Dji......I
000239f0: 454e 44ae 4260 82                        END.B`.

模拟png图片文件被删除

对比上表的png文件头尾,头部89 50 4E 47,尾部AE 42 60 82。

[root@VM-8-8-centos test]# debugfs /dev/loop1 -R "ls -d /test"
debugfs 1.45.6 (20-Mar-2020)
 98305  (12) .    2  (12) ..    98306  (56) passwd   
 98307  (4016) logo.png   
[root@VM-8-8-centos test]# debugfs /dev/loop1 -R "blocks <98307>"
debugfs 1.45.6 (20-Mar-2020)
17302 17303 17304 17305 17306 17307 17308 17309 17310 17311 17312 17313 16794 17314 17315 17316 17317 16816 16817 16818 16819 16820 16821 16822 16823 16824 16825 16826 16827 16828 16829 16830 16831 17632 17633 17634 17635 //该图片文件包含多个连续block
[root@VM-8-8-centos test]# rm -f logo.png 
[root@VM-8-8-centos test]# debugfs /dev/loop1 -R "ls -d /test"
debugfs 1.45.6 (20-Mar-2020)
 98305  (12) .    2  (12) ..    98306  (4072) passwd   
<98307> (4016) logo.png   
[root@VM-8-8-centos test]# debugfs /dev/loop1 -R "blocks <98307>"
debugfs 1.45.6 (20-Mar-2020)
// 文件被删除后获取block指针为空

暴力搜索匹配文件特征

[root@VM-8-8-centos test]# xxd /newpart/test.ext3 | grep "8950 4e47"
04396000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452  .PNG........IHDR
[root@VM-8-8-centos test]# xxd /newpart/test.ext3 | grep  "ae 4260 82"
044e39f0: 454e 44ae 4260 8200 0000 0000 0000 0000  END.B`..........

将ext3分区看做成一个大文件,进行暴力搜索匹配文件特征(头部和尾部特征)。

如果我们使用dd命令进行dump,这里需要用到16进制来计算,4096的十六进制为1000。由于文件头部肯定在任意一个block的起始位置,所以相除必为整数倍。

这里算出来的值和我们之前用debugfs看到的block值相等。此时只要再算出偏移位置即可从磁盘中恢复文件。

使用dd将文件从磁盘中dump出来

[root@VM-8-8-centos test]# dd if=/newpart/test.ext3 bs=4096 count=1 skip=17302 | xxd
00000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452  .PNG........IHDR
00000010: 0000 1a7e 0000 059b 0806 0000 00ba cb9c  ...~............
00000020: 5100 0000 0970 4859 7300 002e 2300 002e  Q....pHYs...#...
00000030: 2301 78a5 3f76 0000 0019 7445 5874 536f  #.x.?v....tEXtSo
00000040: 6674 7761 7265 0041 646f 6265 2049 6d61  ftware.Adobe Ima
00000050: 6765 5265 6164 7971 c965 3c00 0239 8449  geReadyq.e<..9.I
00000060: 4441 5478 daec dd01 0100 200c 8030 b57f  DATx...... ..0..

对于大文件,由于文件block不一定连续,还需要从起始block中获取到下一个block所在位置进行索引,再将文件“拼起来”即可恢复被删除的文件。

对于文件不连续的情况,会在后面的数据恢复系列文章中深入分析。

总结

本文从ext两种数据恢复的思路深入分析,未使用任何数据恢复工具的前提下,逐步展示了数据恢复的原始过程,希望大家对数据恢复有更深刻的理解。

写完本文已是凌晨三点,洗洗睡了肝不动了。

赞赏

微信赞赏支付宝赞赏

Zgao

愿有一日,安全圈的师傅们都能用上Zgao写的工具。

3条评论

匿名 发布于5:20 下午 - 10月 9, 2023

师傅,ext3默认indoe-size为128,ext4默认为256,因此通常ext3下使用logdump的结果是正确的,ext4则是错误的! 这个问题你这边是如何解决的?方便分享一下吗?

    Zgao 发布于5:36 下午 - 10月 9, 2023

    看debugfs的源码,找的inode_size的值把128改成从变量中获取,然后重新编译

      匿名 发布于5:40 下午 - 10月 9, 2023

      明白了,谢谢。

发表评论