稀疏文件主要由0构成:
为何会有这种奇怪的文件呢?有些软件在创建文件的时候,会先给文件提供空间,然后再逐渐把数据写入文件,这些文件包括:
数据库文件:
aOracle Temporary Tablespaces
MS SQL Backup files
虚拟机文件
VMDK NTFS Metadata 文件
$BadCluster
$ Usnjrnl:$j
举例说明:你创建了一个60GB的虚拟机磁盘文件,在正常情况下,该文件要占用60GB的物理磁盘空间。
但在大多数情况下虚拟磁盘并未写满,例如下图,虚拟机内的操作系统本身仅占用了9.29GB,还有50.7GB的剩余空间,如果在物理硬盘上创建完整60GB的虚拟磁盘文件未免太浪费了:
我们可以采取一种技巧减少物理磁盘的占用:将60GB的虚拟磁盘分为“真正写入”的部分和“不写入”的部分,前者把已用空间里的数据真正写入物理磁盘,未用空间的数据不写入磁盘但作声明:剩下的数据都是0。
这样一来,虚拟机用户虽然看到了完整的磁盘容量,但只有一部分数据真正写入了物理磁盘。随着写入数据量的增大,虚拟磁盘所占的物理空间也逐渐增大,直至声明的最大空间。
所以稀疏文件包含有大量的0,但文件系统并没有真正使用磁盘上的物理块来存储这些0,那么NTFS是怎样做到这点的呢?我们使用三个工具来分析:fsutil、FlexHex和Aactive@ Disk Editor,前者是Windows自带,需要以管理员身份进入命令提示符运行。
1、创建一个4MB的文件,以16进制给出文件大小:
fsutil file createnew silly.txt
该文件的数据是被完全填充的,用FlexHex可以看到0被真实的分配了,因为是黑色的字体:
但是保存大量的0是很不划算的,因为数据写入磁盘需要时间,还要占用成本高昂的存储空间。所以我们可用这样做:在文件的属性里设置某种标志,指明哪些部分是全0的(不用写入磁盘),哪些部分是真实写入的。这样做的好处是:
避免写入0到磁盘,提升了写入速度
只搜索真正写入数据的部分,不再搜索稀疏部分,提升了搜索速度。
2、将silly.txt转换为稀疏文件:
fsutil sparse setflag silly.txt
我们用Aactive@ Disk Editor证实这点,右键选择silly.txt文件:
之后点选左边的Attribute $10 -> $STANDARD_INFORMATION ->File Permissions,可见Sparase File位已经被置1:
再进入Attribute $80 -> Flags,Sparse位也被置1:
再往下进入DATA -> Data run:
可见本文件被分配了1024个“簇”(也叫分配单元),每个簇是4096字节(默认,在格式化时选择),那么总容量就是1024*4096=4194304,也就是4MB字节。
3、置该文件的前3MB为0(不写入磁盘),后1M是真实数据,写入磁盘:
fsutil sparse setrange silly.txt 0 0x300000
查询稀疏范围。可见稀疏部分是文件开头之后的0x300000字节,后面紧跟长度为0x100000字节的真实写入的数据:
fsutil sparse queryrange silly.txt
如何证明这点?再次运行Aactive@ Disk Editor,重新载入文件,发现有了2个Data Run(之前只有一个):
第一个Data Run告诉你该文件的稀疏部分被分配了768个簇(下方的Sparse位是Yes),768×4096=3145728,正好是3MB。
第二个Data Run告诉你真正写入的部分被分配了256个簇,256×4096=1048576,正好是1MB。
这与Windows资源管理器的磁盘占用统计是吻合的,该文件只占用了1M的物理空间:
FlexHex也能证明这点,显示的全0部分为灰色字体,表明为稀疏部分,真实部分为黑底白字,右下角的”SPARSE“被突出显示:
现在你知道了什么是稀疏文件,这就是。
NTFS在读写稀疏文件时是这样处理的:稀疏属性对用户/应用程序透明。当读稀疏文件,NTFS将返回0以替代文件中的稀疏部分;当写入稀疏文件,NTFS需要被告知该文件是稀疏的,并标记0区域。
有些应用程序能够识别稀疏文件,有些则不能。比如用记事本打开稀疏文件,修改几个字符再保存,该文件就变为普通文件了,复制或移动稀疏文件也是如此,因为稀疏文件需要特殊的处理方式。Windows提供了一些这方面的API:
- GetVolumeInformationA
-lpFileSystemFlags 输出参数包含一个标志列表
–FILE_SUPPORTS_SPARSE_FILES- DeviceloControl
–FSCTL_SET_SPARSE
–FSCTL_SET_ZERO_DATA
–FSCTL QUERY ALLOCATED
稀疏文件提升了大文件的写入速度和搜索速度,但也带来了风险,因为磁盘剩余空间的数值会误导你。比如下面这个批处理,它会创建500个大小为50MB的稀疏文件,总容量为24.4GB,但却一个字节也没有存入物理磁盘:
for /l %%x in (1,1,500) do (
echo %%x
fsutil file createnew %%x.txt 0x3200000
fsutil sparse setflag %%x.txt
fsutil sparse setrange %%x.txt 0 0x3200000
)
你可以把这些文件考入剩余空间为100MB的磁盘,但如果其中某几个文件逐渐增大,磁盘就会被写满。另外,如果你使用的空间被“磁盘配额”限制,那也是按照24.4GB计算的,哪怕没有写入任何数据。
最后,我们讨论一下稀疏文件的安全性问题。2016年的一篇文章提到一个有趣的反分析技巧。你可以将一个小巧的可执行文件转换为一个巨大的稀疏文件,且不会影响到可执行文件的加载。但如果有人试图复制或移动该文件进行分析,那么他将得到一个臃肿的文件。
// SparseMaker.cpp - taken from https://www.scriptjunkie.us/2016/08/defying-analysis-with-sparse-malware/
#include "pch.h"
#include <Windows.h>
#include <iostream>
using namespace std;
void main(int argc, char** argv) {
if (argc < 3) {
cerr << "Usage: " << argv[0] << " file size" << endl;
return;
}
HANDLE h = CreateFileA(argv[1], GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
if (h == INVALID_HANDLE_VALUE) {
cerr << "Cannot open file" << endl;
return;
}
DWORD outlen;
DeviceIoControl(h, FSCTL_SET_SPARSE, NULL, 0, NULL, 0, &outlen, NULL);
LARGE_INTEGER newlen;
newlen.QuadPart = atoll(argv[2]);
SetFilePointer(h, newlen.LowPart, &newlen.HighPart, FILE_BEGIN);
SetEndOfFile(h);
CloseHandle(h);
}
sparsemaker.exe helloworld.exe 1073741824
这样就把一个几十k的小文件转换为1GB大小的稀疏文件。