昨天追踪EXT4文件系统的过程中出了点问题,就是找不到文件,于是试了一下追踪FAT32文件系统的,成功之后有了点信心,今天继续嗑EXT4文件系统,终于找到啦,记录一下。
- 操作系统:linux(centos 6.5)
- 文件系统:EXT4
- 工具:hexdump,windows自带计算器
- 参考资源:《数据重现-文件系统原理精解与数据恢复最佳实践》(马林 著)
《基于EXT4文件系统的数据恢复方法研究》(徐国天)
题为《Ext4文件系统架构分析》的系列博客
题为《 深入理解ext4(一)----extent区段》的博客
题为《ext4的Extent解析》的博客
题为《ext4_ext_find_extent解析》的博客
EXT4文件系统架构(非原创):
补充说明:EXT4文件系统中只有0号块组的超级块和块组描述符表的位置是固定的,其他都不固定。其中,超级块总是开始于偏移位置1024(字节),占据1024个字节,块组描述符表紧随超级块后面,占用的大小是不定。
步骤:
1、查看文件系统基本情况,新建子目录和文件
可以看到挂载在/boot目录下的文件系统类型是EXT4,因此在改目录下新建子目录及文件:
文件内容为:“This test is belong to Boot folder!”文件基本信息如下:
2、查看超级块,找到0号块组起始块号、块大小、每块组所含块数、每块组i节点数、第一个非保留i节点、每个i节点大小。
命令:hexdump -s 1024 -n 1024 -C /dev/sda1
查看结果:
首先可以看到0x38-0x39是EXT系列文件系统的签名标志:“53 ef”
0x14-0x17是0号块组起始块号:0x01,说明超级块前面有一个块为保留块,用来存储引导程序。
0x18-0x1b是块大小:0x00,这里的值指的是将1024字节左移的位数,移动0位也就是1024字节,移动一位相当于乘以2,就是2048字节。
0x20-0x23是每块组所含块数:0x2000(十进制8192)
0x28-0x2b是每块组所含i节点数:0x07f0(十进制2032)
0x54-0x57是第一个非保留i节点号:0x0b(11),一般为lost+found目录
0x58-0x59是每个i节点结构的大小:0x80(十进制128),也就是每个i节点表项占用128个字节。
3、查看块组描述符表,找到块位图块、i节点位图块、i节点表起始块号、块组目录数。
命令:hexdump -s 2048 -n 1024 -C /dev/sda1
查看结果:
块组描述符表中每个块组使用32个字节来描述,因此第一个32字节描述的就是0号块组。
0x00-0x04是块位图块起始块号:0x0104
0x05-0x07是i节点位图块起始块号:0x0114
0x08-0x0b是i节点表起始块号:0x0124
0x10-0x11是该块组的目录数:0x02
这里获取的起始块号是逻辑块号(将文件系统所有的块从0开始递增编号),因此在计算偏移量时可以直接乘以每块字节数(0x400,也就是十进制的1024)
这里补充说明一下:EXT3文件系统的块位图块号、i节点位图块和i节点表起始块号是递增的,而这里他们三个之间却是相差一个常量:16。
因为这里使用了一个EXT4新引进的结构:Flexible 块组(flex_bg)
Flexible 块组 Flexible 块组的设计目的是组成更大的逻辑块组,尽量让大文件连续,将元数据聚集加快元数据载入。因此它的做法是将几个块组的块位图块,i节点位图块,i节点表块放在这个逻辑块组的第一个块组中,这样剩下的块组中就只存储了该块组的超级块和块组描述表。 上面的块组描述表中可以看出这个Flexible 块组是将16个块组合成了一个逻辑块组。 说起Flexible 块组就要提起元块组(Media Block Group),因为他和Flexible 块组是“有你没我”的关系。Flexible 块组是移动了块位图块、i节点位图块、i节点表块,元块组是减少了块组描述符表的备份,原本块组描述符表和超级块一起备份在块组号为0或者3、5、7的幂的块组,元块组是只在一个元块组的第一、二个块组和最后一个块组中备份块组描述符表,增加元块组存储数据的空间。 |
3、从根目录中找到子目录
第一步我们提到了第一个非保留i节点号为11,那么前面的10个保留i节点的作用是什么呢(i节点号从1开始编号),这里只说明2号节点是存储的是根目录i节点号,因此我们读取i节点表的2号表项值就可以找到根目录所在块号了。
计算i节点表项的偏移量涉及到了块组描述符表中的i节点表起始块号:0x0124。
某i节点表项起始字节=i节点起始块号*每块所占字节数+(该i节点号-1)*每个i节点表项所占字节数
0x0124*0x400+(0x02-0x01)*0x80=0x49080
下面就可以读取根目录i节点表项值了。
命令:hexdump -s 0x49080 -n 128 -C /dev/sda1
查看结果:
0xa8-0xd7是12个直接块指针,其中四个字节为一个单位,表示一个块号。
(这里要解释的是:EXt4文件系统将12个直接块指针、1个一级间接块指针、1个二级间接块指针、1个三级间接块指针,一共60个字节用extent结构来替换,但前提是偏移0xa0-0xa3处的标志位置为“00 00 08 00”,而这里全为0,则证明没有使用extent结构,因此依旧按照块指针形式查找根目录)
图中可以看出根目录只占用了一个块,块号为:0x1104
根目录的起始偏移字节为=根目录所在块号*每块所占字节数
则根目录的起始偏移字节:0x1104*0x400=0x441000
使用命令:hexdump -s 0x441000 -n 1024 -C /dev/sda1 查看根目录内容:
查看/boot目录下的文件:
可以看到两者的内容是相符的,说明我们找的没有错。根目录中BOOTDIR的目录项用黑色底纹标注。
0x6c-0x6f是该文件内容所在i节点号:0x7f01
0x70-0x71是本目录项长度:0x10(16字节)
0x72是本目录项名字长度0x07(7个字节)
0x73是本文件类型:0x02(表示目录)
0x74开始是文件名的ASCCI码:“42 4f 4f 54 44 49 52 00”
在这一步中与FAT32文件系统的区别有两个:
一是怎么寻找根目录。FAT32中根目录在数据区的开头,因此我们可以直接去数据区读取;而EXT4文件系统中,我们需要通过2号i节点表找到根目录所在的块号,才能看到根目录内容,这里就可以看出EXT4文件系统将目录也看作文件了,因为他的读取方式和普通文件是一样的,只不过普通文件需要从目录中得到i节点号,而根目录是一开始就定好了i节点号。
二是目录的大小。FAT32中目录的大小是固定的(短文件名目录占32字节,长文件名目录占多个32字节),所以当文件名过长时,使用了长文件名机制来解决,而EXT4文件系统的目录项大小是在目录项中灵活定的。比如这一步中我们查看到的根目录结果中,开始的12个字节是本目录项,紧接着12个字节是根目录项,而我们要找的目标目录项的长度是16个字节,其中说明部分(i节点号,本目录项长度字节数,名字长度,文件类型)占用都是一样的,差就差在文件名部分。但我们也看到文件名后面总有“00”补齐,这是因为目录项的长度总要是4的倍数,因此不够时会用0补齐。
4、从i节点表中找到子目录所在块号
第三步中我们找到指向子目录的i节点号为0x7f01,也是逻辑i节点号,因此我们要先找到0x7f01在哪个块组中:
某i节点所在块组=该i节点号/每块组i节点个数
0x7f01/0x7f0=0x10(十进制16)
某i节点所在i节点表号=该i节点号%每块组i节点个数
0x7f01%0x7f0=0x01
因此0x7f01在16号块组的i节点表中,在改i节点表的1号表项中。
接下来我们要从块组描述符表中找到16号块组的i节点表起始块号,以便找到子目录所在块号。
某块组在块组描述符表中偏移字节=块组描述符表起始字节+块组号*每块组描述符表项字节数
2048+16*32=2560字节
我们读取2560偏移字节开始的32字节:
可以看到:0x08-0x0b为i节点表起始块号:0x020021
读取16号块组i节点表的1号i节点表项值:
这里需要重点注意:0x20-0x23处的标志内容为“00 00 08 00”也就是0x080000,表示使用了extent结构,因此这里文件子目录的搜索就需要按照extent结构来读取0x28-0x63这60个字节的内容。
有个疑问:EXT4文件系统什么时候使用extent结构,查看0号块组的10个保留i节点时,看到只有8号也就是日志节点启用了extent结构,其他都没有使用,而其他块组中似乎是都默认启用extent结构,但也发现了例外,因此不能确定EXT4关于启用extent结构的规定,后续需要注意!
下面介绍一下extent的结构,内容有参考。 每个extent结构占用12个字节,所以每个i节点表项中可以有60/12=5个extent结构,这其中第一个extent作为extent头(extent header是一个B+树的描述头),剩下的4个是extent体(extent body,由于extent树中的节点有两种:索引节点和叶子节点,因此当节点为索引节点时(可以从extent header中区分索引节点和叶子节点)extent body存储的是下一级extent树节点信息,当节点为叶子节点时extent body 中存储的是数据块块号信息) extent树结构 extent数据结构 extent header
这里要说明的是:魔数是一个校验值,只有当校验结果是0xf30a时B+树的块才正确 节点在extent树中的深度是从叶子节点算起,因此根节点的深度是最深的(看到有人说根节点的最大深度不超过5),当深度为0时表明是叶子节点,那么这个节点就是数据节点,他后面的extent body就是指向的数据块,存储数据块号;当深度大于0,后面的extent body表示索引节点。 extent body 当为索引节点时 当为叶子节点时
补充说明:当extent body表示索引节点时最后最后两个节点冗余是为了迁就叶子节点。 |
从0x28-0x63是extent结构。
extent头中说明的信息有:本区段有一个extent body,最大区段个数为4,本段在extent树中深度为0,是叶子节点。
后面紧着的12个字节是extent body说明的信息有:本区段的第一个块号是0,本区段含有一个块,本区段指向的数据块块号为0x021002。
该块的偏移字节:0x8400800
5、从子目录对应的i节点号得到目标文件块号
查看结果第4步中子目录块内容:
加黑色底纹的是本目录(“.”)和根目录(“..”)接下来就是目标文件:BOOTTEXT.txt,他的i节点号为:0x7f04
查看i节点表,找到目标文件的块号:
该i节点表项的偏移字节为:0x020021*0x400+(0x7f04%0x7f0-1)*0x80=0x8008580
读取该偏移字节处开始的128字节内容:
同样目标文件的i节点表项也启用了extent结构,按照与第4步同样的方法分析,得带目标文件所在块号0x024005(偏移字节为0x9001400)。
接下来读取该块内容:
找到啦!和第一步中使用cat命令查看的文件内容一致。