NTFSのストリームを利用する


インターネットからダウンロードしたファイルを実行しようとすると「これ、ネットからダウンロードしたファイルだけど、ホントに実行しちゃっていいの?ねぇ?」という確認メッセージが表示されます。いつも「うっせぇな、んなこたぁ知ってるよ!いいから実行しろよ!」みたいな感じで[OK]ボタンをクリックするのですが、はて、Windowsはどうしてそのファイルがダウンロードされたものと知っているのでしょうか。

そのうち、Windows系のMVPさんにでも聞いてみようかと思っていましたが、思わぬことからその答がわかりました。秘密はNTFSのストリームだったんですね。実に興味深い技術です。いろいろな使い道が考えられますね。とりあえず、今わかっている事だけ書いておきましょう。以下はVBAのコードですが、たぶんVB6でも動くでしょう。

ストリームに文字列を保存する

Sub Sample1()
    Dim FSO
    Set FSO = CreateObject("Scripting.FileSystemObject")
    FSO.CreateTextFile "E:\Sample.txt:myData"
    With FSO.GetFile("E:\Sample.txt:myData").OpenAsTextStream(8)
        .Write "このファイルは田中が作りました" & vbCrLf
        .Write Now()
        .Close
    End With
    Set FSO = Nothing
End Sub

ストリームの文字列を取得する

Sub Sample2()
    Dim FSO, buf As String
    Set FSO = CreateObject("Scripting.FileSystemObject")
    With FSO.GetFile("E:\Sample.txt:myData").OpenAsTextStream
        buf = .ReadAll
        MsgBox buf
        .Close
    End With
    Set FSO = Nothing
End Sub

テキストファイルに任意の情報を保存できるのですから驚きです。もちろん、このテキストファイルをエディタで開いてもストリームの文字列は表示されません。ブック(xls)のストリームに何らかの情報を書き込んでおく…という使い方もありそうです。

ストリームを列挙する

Public Const MAX_PATH = 260
Public Type WIN32_STREAM_ID
        dwStreamID As Long
        dwStreamAttributes As Long
        dwStreamSizeLow As Long
        dwStreamSizeHigh As Long
        dwStreamNameSize As Long
End Type
Public Const GENERIC_READ = &H80000000
Public Const INVALID_HANDLE_VALUE = -1
Public Const FILE_SHARE_READ = &H1
Public Const OPEN_EXISTING = 3
Public Const FILE_FLAG_BACKUP_SEMANTICS = &H2000000
Public Declare Function CreateFile Lib "kernel32" Alias _
                                     "CreateFileA" (ByVal lpFileName As String, _
                                                    ByVal dwDesiredAccess As Long, _
                                                    ByVal dwShareMode As Long, _
                                                    ByVal lpSecurityAttributes As Long, _
                                                    ByVal dwCreationDisposition As Long, _
                                                    ByVal dwFlagsAndAttributes As Long, _
                                                    ByVal hTemplateFile As Long) As Long
Public Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
Public Declare Function BackupRead Lib "kernel32" (ByVal hFile As Long, _
                                                   lpBuffer As Any, _
                                                   ByVal nNumberOfBytesToRead As Long, _
                                                   lpNumberOfBytesRead As Long, _
                                                   ByVal bAbort As Long, _
                                                   ByVal bProcessSecurity As Long, _
                                                   lpContext As Any) As Long
Public Declare Function BackupSeek Lib "kernel32" (ByVal hFile As Long, _
                                                   ByVal dwLowBytesToSeek As Long, _
                                                   ByVal dwHighBytesToSeek As Long, _
                                                   lpdwLowByteSeeked As Long, _
                                                   lpdwHighByteSeeked As Long, _
                                                   lpContext As Long) As Long
Public Declare Function DeleteFile Lib "kernel32" Alias _
                                        "DeleteFileA" (ByVal lpFileName As String) As Long
Sub Sample3()
    MsgBox EnumStreams("E:\Sample.txt")
End Sub
Public Function EnumStreams(szFile) As String
    Dim arr() As String
    Dim ind As Long: ind = 0
    Dim hFile As Long
    
    hFile = CreateFile(szFile, GENERIC_READ, FILE_SHARE_READ, 0, _
                                             OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0)
    If hFile = INVALID_HANDLE_VALUE Then
        Exit Function
    End If
    Dim lpContext As Long: lpContext = 0
    Dim sid As WIN32_STREAM_ID
    Dim dwRead As Long
    Dim wszStreamName(1 To MAX_PATH * 2) As Byte
    Dim bContinue As Boolean: bContinue = True
    Do While (bContinue)
        sid.dwStreamID = 0
        sid.dwStreamAttributes = 0
        sid.dwStreamSizeLow = 0
        sid.dwStreamSizeHigh = 0
        sid.dwStreamNameSize = 0
        
        Dim dwStreamHeaderSize As Long
        
        dwStreamHeaderSize = LenB(sid) + sid.dwStreamNameSize
        Dim k
        For k = 1 To MAX_PATH * 2
            wszStreamName(k) = 0
        Next
        bContinue = BackupRead(hFile, sid, dwStreamHeaderSize, dwRead, _
                                                                 False, False, lpContext)
        If dwRead = 0 Then Exit Do
        If dwRead <> dwStreamHeaderSize Then Exit Do
        
        Call BackupRead(hFile, wszStreamName(1), sid.dwStreamNameSize, _
                                                         dwRead, False, False, lpContext)
        
        If dwRead <> sid.dwStreamNameSize Then Exit Do
        
        If dwRead > 0 Then
            Dim StreamName As String
            StreamName = Left(wszStreamName, InStr(wszStreamName, vbNullChar) - 1)
            ReDim Preserve arr(ind)
            arr(ind) = StreamName
            ind = ind + 1
        End If
        Dim dw1 As Long, dw2 As Long
        Call BackupSeek(hFile, sid.dwStreamSizeLow, sid.dwStreamSizeHigh, _
                                                                     dw1, dw2, lpContext)
    Loop
    Call CloseHandle(hFile)
    EnumStreams = Join(arr, vbCrLf)
End Function

上記のコードは、吉岡 照雄さんの「EnumStreams.XLS」(EXCEL VBA経由でNTFSストリームを列挙/削除するVBScript)を参考(というか流用)させていただきました。素晴らしいです。ありがとうございます。勝手に使ってすいません。

吉岡 照雄さんの作品ページはこちら→http://www.vector.co.jp/vpack/browse/person/an010222.html