一、由于android的底层是Linux,所以我们可以把问题换一种问法:在linux中,被rm的文件还能恢复不?
其实,不管是在linux系统还是其他系统上,删除的文件实际上并没有从硬盘上抹去,只是information node索引的相关信息被删除了,就像C/C++链表结构,我们保存了数据在这个链表中,但是呢,我们找不到这个链表的head了,当我们要查询数据的时候就没有入口了,就相当于把数据删除了。因此,只要我们能够找到我们存储数据的起始block,总长度,就可以恢复已经删除的文件。前提是,这块数据区没有被其他数据覆盖掉。
在linux中主要借助debugfs命令
假设删除的文件在dir下面,位于/dev/sda5上。
1 运行debugfs,进入调度模式
2 执行open /dev/sda5
3 执行ls -d dir 会列出此目录最近的操作,其中可以看到<num>的日志删除记录
4 执行logdump -i <num> 显示此日志内容
5 在输出中寻找删除文件对应的block,记录下来blockid
6 退出debugfs,运行dd if=/dev/sda5 of=/tmp/saved bs=1024 count=1 skip=blockid
此时就把删除的文件恢复了,不过这个方法有个问题,如果删除的是大文件,则占用多个block,操作起来比较麻烦。
二、另外如果不使用写程序的方式恢复数据的话,可以借助专业的磁盘恢复工具
① Testdisk工具
② Extundelete工具
三、当真正了解了存储的文件系统,也可以自己编写程序恢复rm的文件。
附 ext2文件系统简介
我们知道,在操作系统中,文件系统是采用一种层次化的形式表示的,通常可以表示成一棵倒置的树。所有的文件和子目录都是通过查找其父目录项来定位的,目录项中通过匹配文件名可以找到对应的索引节点号(inode),通过查找索引节点表(inode table)就可以找到文件在磁盘上的位置,整个过程如图1所示。
图 1. 文件数据定位过程
对于 ext2 类型的文件系统来说,目录项是使用一个名为 ext2_dir_entry_2 的结构来表示的,该结构定义如下所示:
清单 1. ext2_dir_entry_2 结构定义
struct ext2_dir_entry_2 {
__le32 inode;/* 索引节点号 */
__le16 rec_len;/* 目录项的长度 */
__u8 name_len; /* 文件名长度 */
__u8 file_type;/* 文件类型 */
charname[EXT2_NAME_LEN];/* 文件名 */
};
在 Unix/Linux 系统中,目录只是一种特殊的文件。目录和文件是通过 file_type 域来区分的,该值为 1 则表示是普通文件,该值为 2 则表示是目录。
对于每个 ext2 分区来说,其在物理磁盘上的布局如图 2 所示:
图 2. ext2 分区的布局
从图 2 中可以看到,对于 ext2 文件系统来说,磁盘被划分成一个个大小相同的数据块,每个块的大小可以是1024、2048 或 4096 个字节。其中,第一个块称为引导块,一般保留做引导扇区使用,因此 ext2 文件系统一般都是从第二个块开始的。剩余的块被划分为一个个的块组,ext2 文件系统会试图尽量将相同文件的数据块都保存在同一个块组中,并且尽量保证文件在磁盘上的连续性,从而提高文件读写时的性能。
至于一个分区中到底有多少个块组,这取决于两个因素:分区大小,块大小。
最终的计算公式如下:
分区中的块组数=分区大小/(块大小*8)
这是由于在每个块组中使用了一个数据块位图来标识数据块是否空闲,因此每个块组中最多可以有(块大小*8)个块;该值除上分区大小就是分区中总的块组数。
每个块组都包含以下内容:
超级块。存放文件系统超级块的一个拷贝。
组描述符。该块组的组描述符。
数据块位图。标识相应的数据块是否空闲。
索引节点位图。标识相应的索引节点是否空闲。
索引节点表。存放所有索引节点的数据。
数据块。该块组中用来保存实际数据的数据块。
在每个块组中都保存了超级块的一个拷贝,默认情况下,只有第一个块组中的超级块结构才会被系统内核使用;其他块组中的超级块可以在 e2fsck 之类的程序对磁盘上的文件系统进行一致性检查使用。在 ext2 文件系统中,超级块的结构会通过一个名为 ext2_super_block 的结构进行引用。该结构的一些重要域如下所示:
清单 2. ext2_super_block 结构定义
struct ext2_super_block {
__le32 s_inodes_count; /* 索引节点总数 */
__le32 s_blocks_count; /* 块数,即文件系统以块为单位的大小 */
__le32 s_r_blocks_count; /* 系统预留的块数 */
__le32 s_free_blocks_count;/* 空闲块数 */
__le32 s_free_inodes_count;/* 空闲索引节点数 */
__le32 s_first_data_block; /* 第一个可用数据块的块号 */
__le32 s_log_block_size; /* 块大小 */
__le32 s_blocks_per_group; /* 每个块组中的块数 */
__le32 s_inodes_per_group; /* 每个块组中的索引节点个数 */
...
}
每个块组都有自己的组描述符,在 ext2 文件系统中是通过一个名为 ext2_group_desc的结构进行引用的。该结构的定义如下:
清单 3. ext2_group_desc 结构定义
/*
* Structure of a blocks group descriptor
*/
struct ext2_group_desc
{
__le32 bg_block_bitmap;/* 数据块位图的块号 */
__le32 bg_inode_bitmap;/* 索引节点位图的块号 */
__le32 bg_inode_table; /* 第一个索引节点表的块号 */
__le16 bg_free_blocks_count; /* 该组中空闲块数 */
__le16 bg_free_inodes_count; /* 该组中空闲索引节点数 */
__le16 bg_used_dirs_count; /* 该组中的目录项 */
__le16 bg_pad;
__le32 bg_reserved[3];
};
数据块位图和索引节点位图分别占用一个块的大小,其每一位描述了对应数据块或索引节点是否空闲,如果该位为0,则表示空闲;如果该位为1,则表示已经使用。
索引节点表存放在一系列连续的数据块中,每个数据块中可以包括若干个索引节点。每个索引节点在 ext2 文件系统中都通过一个名为 ext2_inode 的结构进行引用,该结构大小固定为 128 个字节,其中一些重要的域如下所示:
清单 4. ext2_inode 结构定义
/*
* Structure of an inode on the disk
*/
struct ext2_inode {
__le16 i_mode; /* 文件模式 */
__le16 i_uid;/* 文件所有者的 uid */
__le32 i_size; /* 以字节为单位的文件长度 */
__le32 i_atime;/* 最后一次访问该文件的时间 */
__le32 i_ctime;/* 索引节点最后改变的时间 */
__le32 i_mtime;/* 文件内容最后改变的时间 */
__le32 i_dtime;/* 文件删除的时间 */
__le16 i_gid;/* 文件所有者的 gid */
__le16 i_links_count;/* 硬链接数 */
__le32 i_blocks; /* 文件的数据块数 */
...
__le32 i_block[EXT2_N_BLOCKS];/* 指向数据块的指针 */
...
};
第一个索引节点所在的块号保存在该块组描述符的 bg_inode_table 域中。请注意 i_block 域,其中就包含了保存数据的数据块的位置。有关如何对数据块进行寻址,请参看后文“数据块寻址方式”一节的内容。
需要知道的是,在普通的删除文件操作中,操作系统并不会逐一清空保存该文件的数据块的内容,而只会释放该文件所占用的索引节点和数据块,方法是将索引节点位图和数据块位图中的相应标识位设置为空闲状态。因此,如果我们可以找到文件对应的索引节点,由此查到相应的数据块,就可能从磁盘上将已经删除的文件恢复出来。
幸运的是,这一切都是可能的!本文将通过几个实验来了解一下如何从磁盘上恢复删除的文件。
数据块寻址方式
回想一下,ext2_inode 结构的 i_block 域是一个大小为 EXT2_N_BLOCKS 的数组,其中保存的就是真正存放文件数据的数据块的位置。通常来说,EXT2_N_BLOCKS 大小为 15。在 ext2 文件系统,采用了直接寻址和间接寻址两种方式来对数据块进行寻址,原理如图3 所示:
图 3. 数据块寻址方式
对于 i_block 的前 12 个元素(i_block[0]到i_block[11])来说,其中存放的就是实际的数据块号,即对应于文件的 0 到 11 块。这种方式称为直接寻址。
对于第13个元素(i_block[12])来说,其中存放的是另外一个数据块的逻辑块号;这个块中并不存放真正的数据,而是存放真正保存数据的数据块的块号。即 i_block[12] 指向一个二级数组,其每个元素都是对应数据块的逻辑块号。由于每个块号需要使用 4 个字节表示,因此这种寻址方式可以访问的对应文件的块号范围为 12 到 (块大小/4)+11。这种寻址方式称为间接寻址。
对于第14个元素(i_block[13])来说,其中存放也是另外一个数据块的逻辑块号。与间接寻址方式不同的是,i_block[13] 所指向的是一个数据块的逻辑块号的二级数组,而这个二级数组的每个元素又都指向一个三级数组,三级数组的每个元素都是对应数据块的逻辑块号。这种寻址方式称为二次间接寻址,对应文件块号的寻址范围为 (块大小/4)+12 到 (块大小/4)2+(块大小/4)+11。
对于第15个元素(i_block[14])来说,则利用了三级间接索引,其第四级数组中存放的才是逻辑块号对应的文件块号,其寻址范围从 (块大小/4)2+(块大小/4)+12 到 (块大小/4)3+ (块大小/4)2+(块大小/4)+11。
ext2 文件系统可以支持1024、2048和4096字节三种大小的块,对应的寻址能力如下表所示:
表 1. 各种数据块对应的文件寻址范围
块大小 |
直接寻址 |
间接寻址 |
二次间接寻址 |
三次间接寻址 |
1024 |
12KB |
268KB |
64.26MB |
16.06GB |
2048 |
24KB |
1.02MB |
513.02MB |
265.5GB |
4096 |
48KB |
4.04MB |
4GB |
~ 4TB |