VisualBasic.Net 2003实现NTFS文件附加数据流的读写类

    技术2022-05-11  71

    VisualBasic.Net 2003实现NTFS文件

    附加数据流的读写类

    中国人民解放军91515部队 贾文博(海南三亚 572016

    摘要:NTFSMicrosoft公司开发的一种有着良好安全性和稳定性的高性能文件系统,NTFS的文件或文件夹中附加多个额外的数据流,但是其访问一直没有很好的解决办法,本文使用VB2003实现NTFS文件附加数据流的读写类,提供.Net框架下NTFS文件附加数据流的完整解决方案。

    关键词VB.Net NTFS 数据流 类

    1        导言

    NTFSNew Technology File System)是Microsoft公司开发的一种有着良好安全性和稳定性的高性能文件系统,广泛用于WindowsNTWindows2000WindowsXPWindows2003等操作系统中。为了使NTFS能访问Macintosh文件服务器的HFSHierarchical File System),MicrosoftNTFS中引入了交换数据流ADSAlternate Data Streams),并提供了访问ADS的命令方式和编程APIApplication Programming Interface)。这种机制允许在一个基于NTFS的文件或文件夹中附加多个额外的数据流,但其中只有一个是主数据流(Main Data Stream),其余为附加数据流,每个数据流都可以看作一个独立的文件,其内容可以是任何数据或程序代码,并能通过相应的接口访问,这种访问包括读写和执行。(图1.1

    1.1

    MSDN中已经演示了一种使用C++MFC访问NTFS文件附加数据流的方法,由于对数据流的访问需要借助API函数,因此到目前为止,尚未有VB.Net程序能够完整地解决操作数据流的问题,为了跨越APIVB.Net的鸿沟,特写了此类访问NTFS文件附加数据流,提供.Net框架下文件附加数据流的完整解决方案。

    2        设计方案

    NTFS文件附加数据流的访问类,应具有如下功能:

    2.1 读取/写入文件的特定数据流、获取其文件流FileStream(流文件名已知)

    对流文件访问需要已知流文件的完整文件名,其完整文件名为:

    主文件名:流文件名

    声明如下:

    Dim sFileStreamName As String = FileName & “:” & sStreamName

    相对于.Net,获取文件名后我们需要借助于API函数CreateFile()创建流文件的操作句柄,然后串接到FileStream类进行数据的读取,其关键代码如下:

    声明CreateFile函数:

    Private Declare Function CreateFile Lib "kernel32" Alias "CreateFileA"

    (ByVal lpFileName As String, ByVal dwDesiredAccess As Integer, ByVal dwShareMode As Integer,     

    ByVal lpSecurityAttributes As Integer, ByVal dwCreationDisposition As Integer,

    ByVal dwFlagsAndAttributes As Integer, ByVal hTemplateFile As Integer) As Integer

    获得文件操作句柄:

        Dim hfile As Integer 定义文件操作句柄

        Dim tmpfilename As String = sFileStreamName ‘传递完整流文件名

        hfile = CreateFile(tmpfilename, GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0)

    由文件操作句柄串接至FileStream类:

        Dim sFile As New FileStream(IntPtr.op_Explicit(hfile), FileAccess.ReadWrite)

    其中IntPtr.op_Explicit()函数功能是把API文件操作句柄Integer类型转换为.Net文件操作句柄IntPtr类型。串接之后按照.Net下二进制数据的读取方法操作即可。

    对文件写入特定数据流方法与读取特定数据流大体相同,把操作属性改为Write即可。(本文后附完整类代码,可参照实现)

    2.2 枚举文件所有附加数据流名称

    对所有附加数据流名称的枚举需要借助API函数BackupRead()BackupSeek(),具体功能请查阅《Windows API参考手册完全版》,该函数主要用来在磁带机上读取备份。

    Function ReadNTFSStreamsName() As String()

         Dim returnNames() As String

          '获取文件操作句柄

         Dim tmpfilename As String = _str_sfilename ‘待枚举流文件名的NTFS文件

         Dim hfile As Integer = CreateFile(tmpfilename, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0)

         Try

             Dim Sid As New WIN32_STREAM_ID ‘定义流文件头部的格式,此结构后面介绍

             Dim dwStreamHeaderSize As Integer = Marshal.SizeOf(Sid) ‘获得占用字节大小

             Dim lpContext = 0

             Dim bContinue As Boolean = True 执行成功标志

             Dim num As Integer = -1 ‘附加数据流总数

             Dim sum As Integer

    循环读取文件数据流名称,直到全部读取完毕

             While (bContinue)

                 Dim lRead As Integer = 0

                 bContinue = BackupRead(hfile, Sid, dwStreamHeaderSize, lRead, False, False, lpContext)

                 If (bContinue AndAlso lRead = dwStreamHeaderSize) Then

                     sum += 1

                     If (Sid.dwStreamNameSize > 0) Then

                         num += 1

                         lRead = 0

    获得数据流名称地址

                         Dim pName As Int32 = Marshal.AllocHGlobal(Sid.dwStreamNameSize).ToInt32

                         Try

                             bContinue = BackupRead(hfile, pName, Sid.dwStreamNameSize, lRead, False, False, lpContext)

                             Dim bName(Sid.dwStreamNameSize - 1) As Char

    传递地址数据到字符数组bName

                             Marshal.Copy(IntPtr.op_Explicit(pName), bName, 0, Sid.dwStreamNameSize)

                         Dim sName As String = bName

    分析数据流名称

                             Dim i As Integer = sName.IndexOf(STREAM_SEP, 1)

                             If (i > -1) Then

                                 sName = sName.Substring(1, i - 1)

                             Else

                                 i = sName.IndexOf("/0")

                                 If (i > -1) Then

                                     sName = sName.Substring(1, i - 1)

                                 End If

                             End If

                             ReDim Preserve returnNames(num)

                             returnNames(num) = sName

                         Catch ex As Exception

                             Marshal.FreeHGlobal(IntPtr.op_Explicit(pName)) ‘释放内存

                         End Try

                     End If

                     Dim l As Integer = 0

                     Dim h As Integer = 0

    移动读取指针

                     BackupSeek(hfile, Sid.Size.Low, Sid.Size.High, l, h, lpContext)

                 Else

                     Exit While

                 End If

             End While

         Catch ex As Exception

             MsgBox(Hex(GetLastError()))

         End Try

         CloseHandle(hfile)

         Return returnNames

    End Function

    2.3 获取指定数据流大小

        Function GetNTFSStreamSize(ByVal sStreamName As String) As Long

            Try

                Dim returnSize As Long

    获取文件操作句柄同上

                Dim sFileStreamName As String = FileName & STREAM_SEP & sStreamName

                Dim hfile As Integer

                Dim tmpfilename As String = sFileStreamName

                Try

                    hfile = CreateFile(tmpfilename, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0)

                Catch ex As Exception

                    MsgBox(Hex(GetLastError()))

                End Try

                If hfile = INVALID_HANDLE_VALUE Then

                    Return Nothing

                End If

                Dim returnStream As FileStream = New FileStream(IntPtr.op_Explicit(hfile), FileAccess.Read)

                returnSize = returnStream.Length

                CloseHandle(hfile)

                Return returnSize

            Catch ex As Exception

                Return -1

            End Try

        End Function

    2.4 删除指定数据流

        Function DeleteNTFSStream(ByVal sStreamName As String) As Boolean

            Try

                Dim sFileStreamName As String = FileName & STREAM_SEP & sStreamName

                Dim hfile, fsuccess As Integer

                fsuccess = DeleteFile(sFileStreamName)

           返回0值时删除失败

                If fsuccess <> 0 Then

                    Return True

                Else

                    MsgBox(Hex(GetLastError()))

                End If

            Catch ex As Exception

                Return False

            End Try

    End Function

    3        主要困难及解决方法

    3.1 API函数和相关数据结构在VB.Net下的声明

    3.1.1 文中主要用到的结构声明如下:

        Private Structure WIN32_STREAM_ID ‘流文件头的格式

            Dim dwStreamId As Integer ‘数据流类型

            Dim dwStreamAttributes As Integer ‘数据流属性

            Dim Size As LARGE_INTEGER

            Dim dwStreamNameSize As Integer ‘数据流名称大小

        End Structure

     

        Private Structure LARGE_INTEGER

            Dim Low As Integer

            Dim High As Integer

        End Structure

    3.1.2 用到的常数声明如下:

        Private Const STREAM_SEP = ":" ‘数据流名分隔符

        Private Const INVALID_HANDLE_VALUE = -1 ‘文件句柄获取失败

    ‘CreateFile()函数参数

        Private Const CREATE_NEW = 1

        Private Const CREATE_ALWAYS = 2

        Private Const OPEN_EXISTING = 3

        Private Const OPEN_ALWAYS = 4

        Private Const TRUNCATE_EXISTING = 5

    数据流类型,dwStreamId的值(后面的英文说明请参考文献[4]

        Private Const BACKUP_DATA = 1  'Standard data

        Private Const BACKUP_EA_DATA = 2  'Extended attribute data

        Private Const BACKUP_SECURITY_DATA = 3  'Security descriptor data

        Private Const BACKUP_ALTERNATE_DATA = 4  'Alternative data streams

        Private Const BACKUP_LINK = 5  'Hard link information

        Private Const BACKUP_PROPERTY_DATA = 6  'Property data

        Private Const BACKUP_OBJECT_ID = 7  'Objects identifiers

        Private Const BACKUP_REPARSE_DATA = 8  'Reparse points

        Private Const BACKUP_SPARSE_BLOCK = 9  'Sparse file.

    文件属性常数

        Private Const FILE_ATTRIBUTE_READONLY = 1

        Private Const FILE_ATTRIBUTE_HIDDEN = 2

        Private Const FILE_ATTRIBUTE_SYSTEM = 4

        Private Const FILE_ATTRIBUTE_ARCHIVE = 32

        Private Const FILE_ATTRIBUTE_NORMAL = 128

        Private Const FILE_ATTRIBUTE_TEMPORARY = 256

    文件读写共享常数

        Private Const FILE_NONE_SHARE = 0

        Private Const FILE_SHARE_READ = 1

        Private Const FILE_SHARE_WRITE = 2

    其他

        Private Const FILE_FLAG_NO_BUFFERING = &H20000000

        Private Const FILE_FLAG_WRITE_THROUGH = &H80000000

        Private Const GENERIC_READ = &H80000000

    Private Const GENERIC_WRITE = &H40000000

    3.1.3 A PI函数声明如下:

    Private Declare Function CreateFile Lib "kernel32" Alias "CreateFileA" _

         (ByVal lpFileName As String, ByVal dwDesiredAccess As Integer, ByVal dwShareMode As Integer,      ByVal lpSecurityAttributes As Integer, ByVal dwCreationDisposition As Integer, ByVal dwFlagsAndAttributes As Integer, ByVal hTemplateFile As Integer) As Integer

     

    Private Declare Function BackupRead Lib "kernel32" Alias "BackupRead" _

         (ByVal hFile As Integer, ByRef lpBuffer As WIN32_STREAM_ID, ByVal nNumberOfBytesToRead As Integer, ByRef lpNumberOfBytesRead As Integer, ByVal bAbort As Boolean, ByVal bProcessSecurity As Boolean, ByRef lpContext As Integer) As Boolean

     

        Private Declare Function BackupRead Lib "kernel32" Alias "BackupRead" _

     (ByVal hFile As Integer, ByVal hName As Integer, ByVal nNumberOfBytesToRead As Integer,          ByRef lpNumberOfBytesRead As Integer, ByVal bAbort As Boolean, ByVal bProcessSecurity As Boolean, ByRef lpContext As Integer) As Boolean

     

        Private Declare Function BackupSeek Lib "kernel32" Alias "BackupSeek" _

         (ByVal hFile As Integer, ByVal dwLowBytesToSeek As Integer, ByVal dwHighBytesToSeek As Integer, ByRef lpdwLowByteSeeked As Integer, ByRef lpdwHighByteSeeked As Integer, ByRef lpContext As Integer) As Boolean

     

    Private Declare Function DeleteFile Lib "kernel32" Alias "DeleteFileA" (ByVal lpFileName As String) As Integer

    获取API执行失败代码

    Private Declare Function GetLastError Lib "kernel32" Alias "GetLastError" () As Integer

        关闭打开的文件

    Private Declare Function CloseHandle Lib "kernel32" Alias "CloseHandle" (ByVal hObject As Integer) As Integer

    3.2 .Net框架下不支持对流文件名“FullFilenameStreamname”的直接访问

    .Net框架下不支持对含有“:”字符的流文件名进行直接访问,因此必须使用CreateFile()函数取得文件操作句柄hfileinteger)然后通过IntPtr.op_Explicit()函数转化为.Net可操作的文件句柄IntPtr类型,用FileStream Public Sub New(ByVal handle As System.IntPtr, ByVal access As System.IO.FileAccess)重载函数就可以像正常文件一样操作了。

    3.3 附加流文件头的处理

    定义头格式WIN32_STREAM_ID(参考文献[4])integer类型占位4字节,long类型占位8字节,WIN32_STREAM_ID总共占位20字节,使用BackupRead()函数的两个变形函数读取数据,再结合Marshal类对其中的数据类型进行处理,得到流名称,之后用BackupSeek()移动指针,循环读取文件头,直到枚举出所有附加数据流的名称。

    WIN32_STREAM_ID结构中,dwStreamAttributesinteger)说明了该数据流的类型,具体值所代表的数据类型请参考文献[4],本文不再深入探讨。

    3.4 读取附加流文件指针的移动

    在每次读取流文件名称成功后,必须BackupSeek()移动读取指针,虽然该函数返回执行结果(布尔值),但是实际使用中发现其返回值为False时仍有数据流名称没有被枚举,因此可忽略它的返回值,根据BackupRead()返回值退出循环即可。

    4        类的使用方法

    在项目中选择添加引用->浏览->选择“JWBStreamOP.dll”文件->确定,即可成功引用。

    4.1 类的声明:

    Dim myStreamOP As New ClassJWBStreamOP(“NTFS文件完整路径”)

    4.2 属性:

    该类共有3个只读属性

    属性名

    返回值类型

    备注

    FileName

    String

    只读,在成功声明后使用

    Ready

    Boolean

    只读,该类可操作时为True

    Ver

    String

    只读,类版本、版权信息

    4.3 方法

    该类共有6个方法:

    4.3.1 OpenNTFSStream(ByVal sStreamName As String) As System.IO.FileStream 打开指定文件(声明时指定)的指定数据流,返回值为指定数据流的FileStream接口。

    参数列表

    类型

    传递方式

    参数说明

    sStreamName

    String

    Byval

    流文件名

    4.3.2 GetNTFSStreamSize(ByVal sStreamName As String) As Long 获取指定数据流的大小,返回实际大小,执行失败返回-1

    参数列表

    类型

    传递方式

    参数说明

    sStreamName

    String

    Byval

    流文件名

    4.3.3  A ddNTFSStream(ByVal toHidName As String, ByRef percentDone As Double) As Boolean 添加附加数据流,返回执行结果。

    参数列表

    类型

    传递方式

    参数说明

    toHidName

    String

    ByVal

    待添加的文件路径

    percentDone

    Double

    ByRef

    传递一个完成百分比的参数

    4.3.4  SaveNTFSStream(ByVal sStreamName As String, ByVal outFileName As String, ByRef percentDone As Double) As Boolean将指定的数据流保存为文件,返回执行结果。

    参数列表

    类型

    传递方式

    参数说明

    sStreamName

    String

    ByVal

    流文件名

    outFileName

    String

    ByVal

    保存文件路径

    percentDone

    Double

    ByRef

    传递一个完成百分比的参数

    4.3.5 ReadNTFSStreamsName() As String() 获取文件的所有附加数据流名称,返回名称数组。

    4.3.6 DeleteNTFSStream(ByVal sStreamName As String) As Boolean 删除指定数据流,返回执行结果。

    参数列表

    类型

    传递方式

    参数说明

    sStreamName

    String

    Byval

    流文件名

    4.1

    5        结语

    Microsoft.Net中努力构建一个只能访问上层文件系统的框架,但是NTFS文件系统的附加数据流却不能依赖.Net的内置函数访问,不能不说是.Net设计的一大遗憾。附加数据流中的数据通过Windows自带的程序无法浏览和写入,但是又切切实实地占用着可用磁盘空间,如一件文件的“隐身衣”,完全不尊重计算机用户的“知情权”。随着计算机病毒技术的发展,NTFS文件附加数据流也必会成为病毒代码的栖息之地;在涉及敏感信息的领域,使用附加数据流隐藏机密数据轻而易举地就可以躲过过滤系统的侦查,因此对NTFS文件附加数据流的清理已应提上日程,清理其中的“污垢”已迫在眉睫!(如图5.1

    5.1

    本文演示了使用VB.Net操作NTFS文件附加数据流的方法类的关键代码,该类可以直接使用,出于安全考虑,该类屏蔽了对扩展名为.EXE.COM.VBS文件的导入,该类对于NTFS文件夹附加数据流的操作暂时没有找到方法。限于本人水平有限,不足之处望广大同仁批评指正!

    6        参考文献

    [1]程序员对 NTFS 2000 的看法第一部分:流与硬链接, Dino Esposito,2000 3 , ms-help://MS.VSCC.2003/MS.MSDNQTR.2003FEB.2052/dntaloc/html/ntfs5.htm

    [2] Lester Yu 的思考空间,C#实现对 NTFS 命名流访问的程序,http://www.cnblogs.com/Threading/archive/2004/06/04/13476.html

    [3]NTFS数据流存在安全缺陷的原因与对策,西华师范大学,戚淮兵 刁永锋 闫正洲,《计算机安全》 2006.12

    [4]Windows API参考手册完全版》(英文帮助文件),Microsoft 公司

    [5]MSDN 2003

    [6] Windows API 函数 for Visual BasicCHM版),陈国强, 1999-12-05

     _______________________________________

    提供例程

    资源名称:文件隐身衣(NTFS文件数据流读写)附VB源代码!下载地址:http://download.csdn.net/source/161510

    屏蔽了.EXE,.COM,.VBS文件作为附加数据流的导入,防止用于非法目的。限于本人水平有限,关于文件夹的附加数据流本人没有找到很好的解决办法(不能枚举流名称),请各位同仁不吝赐教!本人QQ:84686E-mail:Jamesjia@tom.com


    最新回复(0)