用Delphi实现NTFS交换数据流的枚举
文/图 彭毅
===================================
NTFS交换数据流(Alternate Data Stream)是NTFS文件系统的一个特性,它允许文件存在多个数据流。一般来说,文件有很多属性,如文件名、文件所有者、文件创建时间等,文件每个属性都包含一个单一的数据流。默认创建的文件都只有默认数据流。文件默认数据流没有名字,而且在非NTFS文件系统中,默认数据流也是唯一被识别的数据流。而在NTFS中,可以通过程序实现附加的数据流来存储更多的内容,这些附加的数据流就叫做NTFS交换数据流。
流这种东西从Windows NT 3.1 开始就得到了支持,到了Windows 2000,NTFS交换数据流ADS作为新特性添加到了Windows系统中。由于微软对于ADS本身API支持不是很好,所以在程序中一直没有被推广;加上ADS作为文件的附加数据支持,大部分的人都认为是食之无味的鸡肋,可有可无。
ADS在实际程序中的运用,特别是在安全方面的应用,一直到了2000年才得以改观。2000年29A病毒研究组织发布了第一个利用NTFS交换数据流进行病毒感染的病毒原型,让全世界对ADS刮目相看。对此,各个研究组织纷纷开始研究ADS的检测技术,如Norton、卡巴斯基都在自己的杀毒软件中加入了查杀NTFS交换数据流病毒的能力。
由于Win32 API 对于ADS支持不是很好,所以让ADS的枚举技术一度称为一些公司的秘密技术。就API本身来说,官方推荐使用BackupRead、BackupSeek和BackupWrite这组函数进行ADS的枚举和读取。现在在网上能找到很多相关的程序和代码,这些代码大部分是用C/C++实现,而用Delphi实现的很少。这里我们就一起探讨一下用Delphi来实现ADS的枚举。
首先我们来看一下Backup*系列函数的原型,代码如下。
BOOL BackupRead(HANDLE hFile,LPBYTE lpBuffer,DWORD nNumberOfBytesToRead,LPDWORD lpNumberOfBytesRead,BOOL bAbort,BOOL bProcessSecurity,LPVOID* lpContext);
BackupRead函数被用于备份文件或文件夹,包括备份它们相关的安全信息。其中hFile参数代表备份文件的句柄;lpBuffer指定数据读入的缓冲区地址,我们得到的ADS信息就在这里;nNumberOfBytesToRead参数指定程序开设的缓冲区大小。注意,这个参数大小一定要大于WIN32_STREAM_ID结构的大小。WIN32_STREAM_ID结构我们会在后面介绍。lpNumberOfBytesRead参数代表实际调用后返回的大小。lpContext用于BackupRead函数内部处理的信息。其它参数可以选择默认,具体大家可以参考MSDN描述。函数成功执行返回非零值,以此来判断调用成功与否。
在BackupRead成功读取的文件信息中,各类流信息是一个整体。为了方便读取各个流信息,API函数BackupSeek可以实现流到流的跳转功能,其函数原型如下。
BOOL BackupSeek(HANDLE hFile,DWORD dwLowBytesToSeek,DWORD dwHighBytesToSeek,LPDWORD lpdwLowByteSeeked,LPDWORD lpdwHighByteSeeked,LPVOID* lpContext);
hFile参数为待操作的文件句柄;dwLowBytesToSeek是预搜索的偏移地址的低字节部分,dwHighBytesToSeek为高字节部分。lpdwLowByteSeeked代表已搜索到的偏移地址低字节部分指定的变量的地址,lpdwHighByteSeeked为相应的高字节。函数成功执行则返回非零值。
下面我们再来看一下刚才提到的WIN32_STREAM_ID结构,其原型如下。
typedef struct _WIN32_STREAM_ID {DWORD dwStreamId;DWORD dwStreamAttributes;LARGE_INTEGER Size;DWORD dwStreamNameSize;WCHAR cStreamName[ANYSIZE_ARRAY];}WIN32_STREAM_ID, *LPWIN32_STREAM_ID;
WIN32_STREAM_ID结构中存贮的就是详细的数据流的信息。dwStreamId字段为数据流的类型。
NTFS交换数据流的标志为BACKUP_ALTERNATE_DATA。其它的属性,如BACKUP_SECURITY_DATA代表文件的安全属性等,大家可以查看MSDN得到详细的属性列表。
万事俱备,我们现在开始用Backup*系列API函数来进行NTFS交换数据流的枚举。一般来说,用Backup*系列函数都需要以下几个步骤。
1)用CreateFile打开文件,得到文件句柄hFile,代码如下。
hFile := CreateFile(FilePath,GENERIC_READ, 0, nil, OPEN_EXISTING,FILE_FLAG_BACKUP_SEMANTICS or FILE_FLAG_POSIX_SEMANTICS, 0);
其中hFile为返回的文件句柄,FilePath为待处理文件的文件名(用绝对路径方式)。
2)第一次调用BackupRead看文件是否存在数据流信息。
BackupRead (hFile, @ pBuffer, BytesToRead, BytesRead, FALSE, FALSE, pContext);
这里pBuffer和WIN32_STREAM_ID结构进行关联,在C/C++中通常是定义为:
WIN32_STREAM_ID & wsi = *( (WIN32_STREAM_ID *) buf );
而在Delphi里则需要一个特殊的技巧,就是用关键字absolute来进行连接,代码如下。
StreamId :WIN32_STREAM_ID absolute pBuffer;
这样,在后面的程序中,我们就可以直接使用StreamId来进行数据处理了。
3)通过读取StreamId.dwStreamNameSize属性,如果大于0,则进行第二次BackupRead调用来读取数据流信息。
4)调用BackupSeek来循环读取数据流信息。
5)再次调用BackupRead,将nNumberOfBytesToRead设为0,将bAbort参数设为TRUE来释放BackupRead函数内部使用的lpContext结构。
我们可以看到,通过上面的方法能够完成ADS的枚举工作,但程序编写会显得有些凌乱,并且模块化程度不高,不利于进行后期的维护。幸运的是,我们可以通过Delphi的开源程序包JCL来实现以上功能。JEDI Code Library(JCL)是一个开放源代码的Delphi程序包,它封装了绝大部分记录于MSDN文档的函数声明和调用,能够方便地应用于程序开发中,其下载地址为http://sourceforge.net/projects/jcl/。
下载JCL后,我们找到JclNTFS.pas文件,其中NtfsFindFirstStream、NtfsFindNextStream和NtfsFindStreamClose完成了数据流打开、枚举及文件关闭的操作。我们可以写一个测试程序,由于文件流有很多,所以我们在Delphi窗体上设置一个选择按钮,并且定义一个全局变量,来让用户选择是否显示所有数据流,还是只显示NTFS交换数据流。代码如下。
var
bShowAll : Boolean; // 是否显示所有数据流
接下来我们在Delphi窗体上添加TreeView控件来显示枚举信息,如图1所示,然后开始枚举ADS。
图1
procedure TformMain.btnEnumClick(Sender: TObject);
var
fsd: TFindStreamData; // 定义Jcl数据流结构
root,sub,sub2,sub3:TTreeNode; // 定义TreeView结点
i: integer;
begin
i :=1;
tv.Items.Clear; // TreeView结点清空
root := tv.Items.Add(tv.TopItem,NTFS交换数据流);
// 添加根结点
oot.ImageIndex :=0;
if NtfsFindFirstStream(edtFile.Text, [], fsd) then
// 开始枚举
begin
sub := tv.Items.AddChild(root,edtFile.Text);
// 添加存在数据流的文件
sub.ImageIndex :=1;
repeat
if bShowAll then // 显示全部数据流信息
begin
sub2 := tv.Items.AddChild(sub,数据流 +IntToStr(i));
sub2.ImageIndex :=2;
sub3 := tv.Items.AddChild(sub2,属性: +IntToStr(fsd.Attributes));
sub3 := tv.Items.AddChild(sub2,数据流ID: +sid2str(fsd.StreamID));
sub3 := tv.Items.AddChild(sub2,名称: +fsd.Name);
sub3 := tv.Items.AddChild(sub2,大小: +IntToStr(fsd.Size)+字节);
Inc(i);
end
else if fsd.StreamID = siAlternate then
// 只显示ADS信息
begin
sub2 := tv.Items.AddChild(sub,数据流 +IntToStr(i));
sub2.ImageIndex :=2;
sub3 := tv.Items.AddChild(sub2,属性: +IntToStr(fsd.Attributes));
sub3 := tv.Items.AddChild(sub2,数据流ID: +sid2str(fsd.StreamID));
sub3 := tv.Items.AddChild(sub2,名称: +fsd.Name);
sub3 := tv.Items.AddChild(sub2,大小: +IntToStr(fsd.Size));
Inc(i);
end;
until not NtfsFindNextStream(fsd); // 循环枚举
NtfsFindStreamClose(fsd); // 做清理工作
end;
end;
为了测试效果,我们现在自己构建一个存在ADS的文件。我的C盘是NTFS结构用命令行方式先看一下,如图2所示。现在用我们的枚举工具试一下,如图3所示,成功了!再选上“显示所有流属性”试一下,如图4所示。
图2
图3
图4
总的来说,NTFS交换数据流必须存在NTFS文件系统下。如果你的分区格式为FAT32,那么这个程序不能读取文件流信息。有了上面的程序,我们还可以为这个枚举程序添加ADS添加、删除的功能,这样我们的程序也就有了读取NTFS交换数据流的能力
补充:软件开发 , Delphi ,