文/圖 彭毅
===================================
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交換數據流的能力