个人网站 | 思想驻留地

0%

读取给定FAT12文件系统磁盘文件中的特定文件

通过解析FAT12文件系统的文件逻辑存储,使用C语言模拟读取fat12.img磁盘镜像中的FAT.pdf文件,代码实现运行在Debian 10.0 32bit系统中

下载:文章中使用的镜像文件fat12.img、待提取的文件FAT.pdf、源代码read.c,以及本文的PDF版本,此处下载

完整解析以及读取文件流程

1. 解析磁盘前的准备

  1. 首先查看文件的十六进制表示形式,打开vim,键入:

    1
    :%!xxd

    即可查看文件,即fat12.img的十六进制表示形式,行数、偏置数以及文件内容和ASCII码解析均可看到

  2. 如果明确了想要查看内容的字符,直接使用

    1
    /keyword

    即可找到直接跳转到对应内容,如果该字符出现多次,则使用np进行跳转

2. FAT12格式文件系统基本分区(例)

FAT12文件系统的磁盘组织结构(并非本实验文件的分区)

3. 解析保留扇区(Boot Sector)

行号 偏移:二进制内容 解析(注意小端存储)
1 00000000 00 02: 每个扇区0x0200(512)个字节
eb3c 906d 6b66 732e 6661 7400 0204 0100 04: 每个簇有4个扇区
0100: 一共有1个保留扇区
2 00000010 02: FAT表的数量一共有2个
0200 0200 10f8 0300 2000 4000 0000 0000 00 02: 最大根目录地址的数量为0x0200(512)个
00 10: 总共有0x1000(4096)个扇区(所有4部分)
0300: 每个FAT有3个扇区
2000: 每磁轨扇区数为32个
4000: 一共有64个头(heads)
3 00000020 0000 0000: FAT32的扇区数量,但是咱们是12
0000 0000 8000 2931 37f4 004e 4f20 4e41 29: boot signature 表示还有三个有效部分
32 37f4: volume id
4 00000030 从上面的00一直到这行的第三个20是volume label
4d45 2020 2020 4641 5431 3220 2020 0e1f 20 4641 5431 3220 20: ASCII表示的FAT12
后面的就没啥意义了不分析了

根据以上信息,我们经过计算可以得到该文件的实际分区信息:

  • $保留扇区数 = 1$
  • $FAT区的扇区数 = FAT数FAT数占用的扇区 = 23 = 6$
  • $根目录区 = 最大根目录地址数32 / 512 = 512 32512 = 32$
  • $数据区 = 总扇区数 - 前三部分扇区数 = 4096 -(1+6+32)= 4057$
扇区(Disk Sectors) 区块名称 大小
0 保留扇区(Boot Sector) 512字节
1 - 3 FAT1区(FAT Table 1) 3512字节
4 - 6 FAT2区(FAT Table 2) 3512字节
7 - 38 根目录区(Root Directory) 32512字节
39 - 4095 数据区(Data Area) 4057512字节

4. FAT区(File Allocation Table)

根据FAT文件格式分区信息,以及保留扇区的解析信息可知,每个扇区一共有 $512$ 个字节,那么FAT区的起始为第513个字节,即磁盘第二个扇区的起始处。

那么对应于文件中的二进制信息,从第33行的第一个字节开始为FAT表的内容:
$$
33\ 00000200: f8ff\ ff00\ 0000\ 00f0\ ff07\ 8000\ 09a0\ 000b
$$
再根据分区信息,FAT表所展示的地址前两个是保留地址,从第三个地址开始才描述数据区的簇,所以FAT表中的地址序数与实际描述的数据簇的起始分区地址的关系如下:

$$
物理起始分区地址= 39 + 4(FAT分区号 - 2) \\
举例: 39 = 39+2-2 (2是FAT表的第三个地址信息)
$$

5. 解析根目录区中表示的 FAT.pdf 文件信息(entry)

根据分区信息,从第8个扇区开始为根目录区,每一个扇区共保存了16个文件信息(directory entrys),其中每一个都有32字节的信息(entry)来表示这个文件,为了了解 FAT.pdf 文件的存放,我们需要首先找到 FAT.pdf 文件信息(entry)存放哪里

在寻找 FAT.pdf 的过程中,我们需要明确一下文件名规则

  • 文件名存放在每一个32字节信息的首8个字节处
  • 文件扩展名存放在之后的3个字节里
  • 文件名和文件扩展名都被存放为大写字母

我们目前已经明确了文件名,即 FAT.pdf ,并且我们已经得知了entry的前11字节表示,即:

位置(字节) 0 1 2 3 4 5 6 7 8 9 10
内容 F A T <空格> <空格> <空格> <空格> <空格> P D F

在vim中直接键入\FAT PDF进行搜索,我们找到了该文件的信息(entry)如下,并且可以计算推得该信息处于磁盘的根目录区(Root Directory):

行数 偏置 信息 字符解释
227 00000e20 4641 5420 2020 2020 5044 4620 0064 276e FAT PDF .d'n
行数 偏置 信息 字符解释
228 00000e30 474f 474f 0000 276e 474f 0600 f391 1b00 GOGO..'nGO......

$$
22616512>7处于FAT区的第一个扇区内
$$

我们需要解析的关键信息其实就只有两个,存放文件的起始簇以及文件大小

  • 起始簇(First Logical Cluster):0600:数字6
  • 文件大小(File Size):f391 1b00:十六进制:0x1b91f3:十进制:1806835字节=1.723M字节

预计存放该文件的簇数:
$$
[1806835(5124)]\approx883(个)
$$

6. 计算FAT区与数据区对应关系

根据第4部分以及第5部分,我们已经了解了足够的文件信息,接下来就是根据首个逻辑簇以及FAT区的链表关系,迭代查找出所有簇读取并放入文件中,读取文件的大致算法流程如下

  1. 得到首个逻辑簇编号,即6
  2. 计算出实际簇(及扇区)编号
  3. 根据实际扇区编号跳转到数据区(Data Area)
  4. 读取该簇的 $5124$ 字节存入到文件中
  5. 根据逻辑簇编号找到FAT区的对应位置并读取该位置的逻辑簇编号
  6. 如果逻辑簇编号表示达到文件终点,则结束读取,否则跳转到2

在实现上述的各种跳转时,需要注意以下要点

  • 得到的逻辑簇号表示的是指数(Index),如果逻辑簇号是6,那么指的是FAT表中的第7个信息(entry)

  • 如果需要找FAT表Index为n的信息(即n+1个地址信息),则12bit的entry从高位到低位依次:

    • 如果n是奇数:
      • FAT表起始位置 + (3 * n)/2的高四位
      • FAT表起始位置 + (3 * n)/2 + 1 的八位
    • 如果n是偶数:
      • FAT表起始位置 + ((3 * (n+1)/2 )的低四位
      • FAT表起始位置 + ((3 * (n+1)/2 - 1)的八位
  • 本次查找中只涉及到达文件末尾这一特殊情况,即12bit逻辑地址为0xFF8-0xFFF时读取结束

  • 逻辑簇编号对应于簇的第一个区的指数(Index):4(逻辑簇编号 - 2)+ 39
    比如,如果编号是6,即所指簇的第一个扇区是编号为55的扇区,即整个文件系统的第56个扇区

  • FAT区中的簇指数(FAT Entry)所对应的表示信息如下:

    数值 含义
    0x000 未使用的簇(Unused)
    0xFF0-0xFF6 保留簇(Reserved)
    0xFF7 坏簇(Bad Cluster)
    0xFF8 - 0xFFF 文件最后一个簇(Last Cluster in a File)
    其他 下一个簇指数(Next Cluster Index)

    那么,判断是否读到末尾,只需判断簇指数是否为0XFF8~9xFFF即可

7. 读取流程设计

根据上述的大致算法,再将我们需要的读取文件等操作加入,即可得到完整的流程:

  1. 读取fat12.img文件,扫描文件后读入一个足够大的字符数组内
  2. 得到首个逻辑簇编号,即6
  3. 计算出实际簇(及初始扇区)编号
  4. 根据实际扇区编号跳转到数据区(Data Area)
  5. 读取该扇区起始的四个扇区(即一个簇)的 $5124$ 字节存入到足够大的字符数组
  6. 根据逻辑簇编号找到FAT区的对应位置并读取该位置的逻辑簇编号如果逻辑簇编号表示达到文件终点,则结束读取,否则跳转到2
  7. 将文件写入新建的文件FAT.pdf内即完成了读取

8. 代码编写

主要函数和数据结构树如下:

1
2
3
4
5
6
7
8
9
10
11
12
struct FAT12_info{...}   //info for structure of FAT12          
struct FAT_PDF_info{...} //info for extracting FAT.pdf

char fat_img_content[] //char array for fat12.img
char fat_pdf_content[] //char array for FAT.pdf
void main() -- void read_disk()//fat12.img to fat_img_content[]
-- void extract_content()//take to fat_pdf_content[]
-- void examine_disk()
-- void examine_fat_pdf()
-- void reach_end(int index)
-- void next_cluster_index(int index)
-- void save_file()//fat_pdf_content to FAT.pdf

首先,设计两个结构数据类型分别保存文件结构和文件FAT.pdf的信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//必要的FAT12结构信息
struct FAT12_info{
//necessary info for understanding structure of img file
int bytes_per_sector;
int sectors_per_cluster;
int number_reserved_cluster;
int number_fat;
int number_directory_entry;
int number_sector;
int sectors_per_fat;
}fat12_info;

//必要的FAT.pdf文件信息
struct FAT_PDF_info{
//necessary info for extracting FAT.pdf
int start_cluster;
unsigned file_size;
}fat_pdf_info;

设计了三个主函数,void read_disk()void extract_content()void save_file()分别负责读取磁盘到数组中,取出文件内容,保存文件到数组中:

1
2
3
4
5
6
7
int main()
{
read_disk(); //read the original disk
extract_content(); //extract the content of FAT.pdf
save_file(); //save the content to FAT.pdf
return 0;
}

读取文件函数read_disk()负责打开文件后将文件的每个字节一个一个拿出来存到字符数组static char fat_img_content[MAX_DISK_SIZE]中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void read_disk()
{
FILE *fat_img_file;
fat_img_file = fopen("fat12.img", "r");

//read to array
int i;
for(i = 0; !feof(fat_img_file); i++)
{
fread(&fat_img_content[i], 1, 1, fat_img_file);
}

printf("finish reading fat12.img\n");
fclose(fat_img_file);
}

取文件内容函数负责将我们需要的FAT.pdf文件按照FAT区规定的链表顺序依次取出来存入字符数组static char fat_pdf_content[]中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
void extract_content()
{
examine_disk(); //examine the disk content to understant the structure
examine_fat_pdf(); //exmine the disk to get infor about FAT.pdf

int logic_cluster_index = 6; //first logical sector
int pdf_pointer = 0;

printf("start to extract FAT.pdf, start from cluster %d:\n", fat_pdf_info.start_cluster);
while(1)
{
//take 1 cluster out of the disk
int sector_start_index = (39 + 4*(logic_cluster_index - 2)&0xfff) * fat12_info.bytes_per_sector;
int i;
for(i = 1; i <= fat12_info.bytes_per_sector * fat12_info.sectors_per_cluster; ++i)
{
fat_pdf_content[pdf_pointer++] = fat_img_content[sector_start_index++];
}

//if reach the end of the file
if(reach_end(logic_cluster_index))
{
printf("end\n");
printf("finish extracting FAT.pdf\n");
break;
}

//calculate next cluster index number
printf("%03x->", logic_cluster_index);
logic_cluster_index = next_cluster_index(logic_cluster_index);
}
}

在提取之前,首先需要对文件的结构进行分析,在上述函数中,我们调用了函数void examine_disk()获取必要的文件信息以方便提取,并存到结构fat12_info中:

1
2
3
4
5
6
7
8
9
10
11
12
void examine_disk()
{
//examine the disk and get necessary info for structure of img file
fat12_info.bytes_per_sector = (((unsigned)fat_img_content[12]) << 8) + fat_img_content[11];
fat12_info.sectors_per_cluster = fat_img_content[13];
fat12_info.number_reserved_cluster = (((unsigned)fat_img_content[15]) << 8) + fat_img_content[14];
fat12_info.number_fat = fat_img_content[16];
fat12_info.number_directory_entry = (((unsigned)fat_img_content[18]) << 8) + fat_img_content[17];
fat12_info.number_sector = (((unsigned)fat_img_content[20]) << 8) + fat_img_content[19];
fat12_info.sectors_per_fat = (((unsigned)fat_img_content[23]) << 8) + fat_img_content[22];
printf("finish examining the disk and get info\n");
}

接着调用void examine_fat_pdf()函数,依次扫过每一个entry,找到FAT.pdf的内容后,提取必要信息到fat_pdf_info结构中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
void examine_fat_pdf()
{
//scan through Root Directory Section for FAT.pdf
int sectors_before_directory_section = fat12_info.number_reserved_cluster + (fat12_info.number_fat * fat12_info.sectors_per_fat);
int head_of_directory_section = sectors_before_directory_section * fat12_info.bytes_per_sector;
int end_of_directory_section = head_of_directory_section + fat12_info.number_directory_entry * BYTES_FILE_ENTRY;
char file_entry[BYTES_FILE_ENTRY];

for(int i = head_of_directory_section; i < end_of_directory_section; i += BYTES_FILE_ENTRY)
{
for(int j = i; j < i+BYTES_FILE_ENTRY; ++j)
//get the file entry
file_entry[j - i] = fat_img_content[j];

//judge whether it's FAT.pdf
int file_name = file_entry[0] == 'F' && file_entry[1] == 'A' && file_entry[2] == 'T';
int file_type = file_entry[8] == 'P' && file_entry[9] == 'D' && file_entry[10] == 'F';
if(file_name && file_type)
{
//take out info
fat_pdf_info.start_cluster = (((unsigned)file_entry[27]) << 8) + file_entry[26];
fat_pdf_info.file_size = (((unsigned)file_entry[31]) << 24) + (((unsigned)file_entry[30] + 1) << 16)
+ (((unsigned)file_entry[29] + 1) << 8) + (unsigned)file_entry[28];
break;
}
}
printf("starting cluster:%d, file size:%u bytes\n", fat_pdf_info.start_cluster, fat_pdf_info.file_size);
printf("finish examining info of FAT.pdf\n");
}

提取文件内容用到的函数包括两个函数int reach_end(int index)int next_cluster_index(int index)分别用于判断簇指数是否为文件末尾以及根据FAT区计算下一个簇指数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
int reach_end(int index)
{
//examine whether index marks the end of file
if( ((index&0x00000fff) >= 0xff8) && ((index&0x00000fff) <= 0xfff))
return 1;
//not reach end
return 0;
}

int next_cluster_index(int index)
{
int next_index = 0;
unsigned DATA_SECTOR_SIZE = fat12_info.bytes_per_sector;

if(index%2 == 1)
{
//index is odd
//low 4 bits
next_index = ((unsigned)fat_img_content[DATA_SECTOR_SIZE*1 + (3*index)/2] &0xf0);
next_index >>= 4;
//high 8 bits
next_index |= ((unsigned)fat_img_content[DATA_SECTOR_SIZE*1 + (3*index)/2 + 1] << 4);
return next_index & 0x00000fff;
}
else
{
//index is even
//high 4 bits
next_index = (unsigned)fat_img_content[DATA_SECTOR_SIZE*1 + (3*(index + 1))/2] & 0x0f;
next_index <<= 8;
//low 8 bits
next_index |= (unsigned)fat_img_content[DATA_SECTOR_SIZE*1 + (3*(index + 1))/2 -1] & 0xff;
return next_index & 0x00000fff;
}
}

最后写入内容到文件的函数为void save_file(),需要注意的是,写入文件的字节数需要与我们之前根目录区解析的文件大小相同,在这里为1806835字节,同时,每次写入一个簇来加快文件写入的速率:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void save_file()
{
FILE* fat_pdf;
int bytes_per_cluster = fat12_info.bytes_per_sector * fat12_info.sectors_per_cluster;
fat_pdf = fopen("FAT.pdf", "w");
int i;
//write except for last cluster
for(i = 0; i < fat_pdf_info.file_size - bytes_per_cluster; i += bytes_per_cluster)
{
fwrite(&fat_pdf_content[i], 1, bytes_per_cluster, fat_pdf); }
//write the last cluster
fwrite(&fat_pdf_content[i], 1, fat_pdf_info.file_size - i, fat_pdf);

printf("finish writing in FAT.pdf\n");
fclose(fat_pdf);
}

代码存储在read.c,编译执行后,使用xpdf FAT.pdf打开已读取的文件即可:

参考资料