使用hexdump工具追踪EXT4文件系统中的一个文件

时间:2023-02-05 09:48:51

  昨天追踪EXT4文件系统的过程中出了点问题,就是找不到文件,于是试了一下追踪FAT32文件系统的,成功之后有了点信心,今天继续嗑EXT4文件系统,终于找到啦,记录一下。

  • 操作系统:linux(centos 6.5)
  • 文件系统:EXT4
  • 工具:hexdump,windows自带计算器
  • 参考资源:《数据重现-文件系统原理精解与数据恢复最佳实践》(马林 著)

         《基于EXT4文件系统的数据恢复方法研究》(徐国天)

         题为《Ext4文件系统架构分析》的系列博客

         题为《 深入理解ext4(一)----extent区段》的博客

         题为《ext4的Extent解析》的博客

         题为《ext4_ext_find_extent解析》的博客

        

EXT4文件系统架构(非原创):

  使用hexdump工具追踪EXT4文件系统中的一个文件

补充说明:EXT4文件系统中只有0号块组的超级块和块组描述符表的位置是固定的,其他都不固定。其中,超级块总是开始于偏移位置1024(字节),占据1024个字节,块组描述符表紧随超级块后面,占用的大小是不定。

步骤:

1、查看文件系统基本情况,新建子目录和文件

  使用hexdump工具追踪EXT4文件系统中的一个文件

可以看到挂载在/boot目录下的文件系统类型是EXT4,因此在改目录下新建子目录及文件:

使用hexdump工具追踪EXT4文件系统中的一个文件

文件内容为:“This test is belong to Boot folder!”文件基本信息如下:

使用hexdump工具追踪EXT4文件系统中的一个文件

2、查看超级块,找到0号块组起始块号、块大小、每块组所含块数、每块组i节点数、第一个非保留i节点、每个i节点大小。

命令:hexdump -s 1024 -n 1024 -C /dev/sda1

查看结果:

使用hexdump工具追踪EXT4文件系统中的一个文件

首先可以看到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

查看结果:

使用hexdump工具追踪EXT4文件系统中的一个文件

块组描述符表中每个块组使用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

查看结果:

使用hexdump工具追踪EXT4文件系统中的一个文件

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 查看根目录内容:

使用hexdump工具追踪EXT4文件系统中的一个文件

查看/boot目录下的文件:

使用hexdump工具追踪EXT4文件系统中的一个文件

可以看到两者的内容是相符的,说明我们找的没有错。根目录中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字节:

使用hexdump工具追踪EXT4文件系统中的一个文件

可以看到:0x08-0x0b为i节点表起始块号:0x020021

读取16号块组i节点表的1号i节点表项值:

使用hexdump工具追踪EXT4文件系统中的一个文件

这里需要重点注意: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树结构

使用hexdump工具追踪EXT4文件系统中的一个文件

extent数据结构

extent header

使用hexdump工具追踪EXT4文件系统中的一个文件

这里要说明的是:魔数是一个校验值,只有当校验结果是0xf30a时B+树的块才正确

        节点在extent树中的深度是从叶子节点算起,因此根节点的深度是最深的(看到有人说根节点的最大深度不超过5),当深度为0时表明是叶子节点,那么这个节点就是数据节点,他后面的extent body就是指向的数据块,存储数据块号;当深度大于0,后面的extent body表示索引节点。

extent body

当为索引节点时

使用hexdump工具追踪EXT4文件系统中的一个文件

当为叶子节点时

使用hexdump工具追踪EXT4文件系统中的一个文件

补充说明:当extent body表示索引节点时最后最后两个节点冗余是为了迁就叶子节点。

从0x28-0x63是extent结构。

extent头中说明的信息有:本区段有一个extent body,最大区段个数为4,本段在extent树中深度为0,是叶子节点。

后面紧着的12个字节是extent body说明的信息有:本区段的第一个块号是0,本区段含有一个块,本区段指向的数据块块号为0x021002。

该块的偏移字节:0x8400800

5、从子目录对应的i节点号得到目标文件块号

查看结果第4步中子目录块内容:

使用hexdump工具追踪EXT4文件系统中的一个文件

加黑色底纹的是本目录(“.”)和根目录(“..”)接下来就是目标文件:BOOTTEXT.txt,他的i节点号为:0x7f04

 查看i节点表,找到目标文件的块号:

  该i节点表项的偏移字节为:0x020021*0x400+(0x7f04%0x7f0-1)*0x80=0x8008580

读取该偏移字节处开始的128字节内容:

使用hexdump工具追踪EXT4文件系统中的一个文件

同样目标文件的i节点表项也启用了extent结构,按照与第4步同样的方法分析,得带目标文件所在块号0x024005(偏移字节为0x9001400)。

接下来读取该块内容:

使用hexdump工具追踪EXT4文件系统中的一个文件

找到啦!和第一步中使用cat命令查看的文件内容一致。

使用hexdump工具追踪EXT4文件系统中的一个文件的更多相关文章

  1. 使用hexdump追踪FAT32文件系统中的一个文件

    最近在看文件系统基础结构等知识,本来重点是想看EXT4文件系统,但是目前没有找到比较详细说明EXT4文件系统详细结构的,用EXT3的对应着找结果有点出入,在想是不是我用hexdump的参数有问题,于是 ...

  2. 在/proc文件系统中增加一个目录hello,并在这个目录中增加一个文件world,文件的内容为hello world

    一.题目 编写一个内核模块,在/proc文件系统中增加一个目录hello,并在这个目录中增加一个文件world,文件的内容为hello world.内核版本要求2.6.18 二.实验环境 物理主机:w ...

  3. 创建一个目录info,并在目录中创建一个文件test.txt,把该文件的信息读取出来,并显示出来

    /*4.创建一个目录info,并在目录中创建一个文件test.txt,把该文件的信息读取出来,并显示出来*/ #import <Foundation/Foundation.h>#defin ...

  4. 分别应用include指令和include动作标识在一个jsp页面中包含一个文件。

    分别应用include指令和include动作标识在一个jsp页面中包含一个文件. hello.jsp <%@ page language="java" import=&qu ...

  5. 在windows中把一个文件夹打成war包

    转: 在windows中把一个文件夹打成war包 一般开发打war包时都是用MyEclipse或IntelliJ IDEA等直接导出war文件,这里介绍一种如何把一个文件夹打成war包的方式,如下   ...

  6. Linux中显示一个文件最后几行的命令

    tail -n 20 filename说明:显示filename最后20行. Linux下tail命令的使用方法.linux tail命令用途是依照要求将指定的文件的最后部分输出到标准设备,通常是终端 ...

  7. C&num;获取路径中最后一个文件夹的名字

    using System; using System.IO; namespace ConsoleApplication1 { class Program { static void Main(stri ...

  8. Linux 获取目录中最后一个文件的名字

    find /application/docker_hub/logs/fof1private/amount_dev -type l | xargs basename

  9. 替换Jar包中的一个文件 Replace a file in a JAR

    例如: jar uf myJarFile.jar com\vsoft\servlet\myServlet.class This will replace the class myServlet.cla ...

随机推荐

  1. c&plus;&plus;面试常用知识&lpar;sizeof计算类的大小,虚拟继承,重载,隐藏,覆盖&rpar;

    一. sizeof计算结构体 注:本机机器字长为64位 1.最普通的类和普通的继承 #include<iostream> using namespace std; class Parent ...

  2. sqlmap&period;config 配置

    <?xml version="1.0" encoding="utf-8"?> <sqlMapConfig xmlns="http:/ ...

  3. 设计模式之Composite(组合)模式

    1.出现原因 1.在面向对象系统中,我们常会遇到一类具有“容器”特征的对象——即它们在充当对象的同时,又是其他对象的容器. 如何将“客户代码与复杂的对象容器结构”解耦(将这种组合容器对象设计成树形结构 ...

  4. 使用PUT方法上传文件无法工作原因分析

    现象 在Spring Framework中,使用HTTP的PUT方法上传文件时,在服务器端发现Multipart参数为空. 原因 Spring中的StandardServletMultipartRes ...

  5. JDBC:四步完成MySQL数据库的连接

    ->首先,将MySQL的jar包引入 ->然后创建一个.properties的文件(例:connection.properties),在该文件中写入如下代码: jdbc.driver.cl ...

  6. Linux工具之bc计算器进制的转换

    bc是Linux下的命令行式的计算器. 题目虽然叫任意进制,但是因为bc的限制,输入进制是2~16范围:输出进制是2~999范围.这与常见计算器的进制范围是一致的,比如windows计算器最高也只能处 ...

  7. 【并发编程】【JDK源码】J&period;U&period;C--组件FutureTask、ForkJoin、BlockingQueue

    原文:慕课网实战·高并发探索(十三):并发容器J.U.C -- 组件FutureTask.ForkJoin.BlockingQueue FutureTask FutureTask是J.U.C中的类,是 ...

  8. LaTeX 一个段落加边框

    \usepackage{framed} \begin{framed} 对这里加边框啊 \end{framed}

  9. form表单获取多选的值

    flask 中 form 表单直接获取多选框的值时 language = request.values.getlist('values')或 language=request.from.getlist ...

  10. golang channle close&lpar;&rpar; x,ok &colon;&equals; &lt&semi;- c

    close为内置函数 close内置函数关闭一个通道channle,其效果为:在最后的值从已关闭的信道中被接收后,任何对其的接收操作都会无阻塞的成功.对于已关闭的信道使用v,ok := <-   ...