昼過ぎに起きて、何気なく NTFS の Alternative Data Stream について気になった。
そういえばどうやったら列挙できるんだろう?知らないな!知りたいな!と思ったら…いつの間にか日付が変わってた orz
というわけで、休日を一日つぶした結果をここで発表。
#include <windows.h> #include <stdio.h> void EnumerateNTFSStream(HANDLE hTargetFile, bool& bFailed) { bFailed = true; void* pContext = NULL; const int STREAMID_SIZE = 20; //sizeof(streamID) - (sizeof(WCHAR) * ANYSIZE_ARRAY); bool bContinue = true; while (bContinue) { // ---------------------------------------------------------------------------------------------------------------- // Step.1 - Read a stream header. // ---------------------------------------------------------------------------------------------------------------- WIN32_STREAM_ID streamID; memset(&streamID, 0x00, sizeof(streamID)); DWORD numberOfBytesRead = 0; HANDLE hFile = hTargetFile; LPBYTE lpBuffer = (LPBYTE)&streamID; DWORD nNumberOfBytesToRead = STREAMID_SIZE; LPDWORD lpNumberOfBytesRead = &numberOfBytesRead; BOOL bAbort = FALSE; BOOL bProcessSecurity = FALSE; LPVOID* lpContext = &pContext; BOOL retBackupRead = BackupRead(hFile, lpBuffer, nNumberOfBytesToRead, lpNumberOfBytesRead, bAbort, bProcessSecurity, lpContext); if (retBackupRead == (BOOL)0) { fprintf(stderr, "BackupRead() Failed\n"); bFailed = true; break; } // ---------------------------------------------------------------------------------------------------------------- // Break loop if the stream ended. // ---------------------------------------------------------------------------------------------------------------- // printf("numberOfBytesRead:%d\n", numberOfBytesRead); if (numberOfBytesRead == 0) { // Success bFailed = false; break; } // ---------------------------------------------------------------------------------------------------------------- // Step.2 - Read and show stream name. // ---------------------------------------------------------------------------------------------------------------- if (streamID.dwStreamNameSize == 0) { printf(" StreamName: None\n"); } else { size_t bufferSize = streamID.dwStreamNameSize + (sizeof(WCHAR) * 1); WCHAR* pStreamName = (WCHAR*)malloc(bufferSize); memset(pStreamName, 0x00, bufferSize); BackupRead(hFile, (BYTE*)pStreamName, streamID.dwStreamNameSize, lpNumberOfBytesRead, bAbort, bProcessSecurity, lpContext); printf(" StreamName: \"%S\"\n", pStreamName); free(pStreamName); } printf(" dwStreamId: %d\n", streamID.dwStreamId); printf("dwStreamAttributes: %d\n", streamID.dwStreamAttributes); printf(" Size: %lld\n", streamID.Size.QuadPart); printf(" dwStreamNameSize: %d\n", streamID.dwStreamNameSize); printf("\n"); // ---------------------------------------------------------------------------------------------------------------- // Step.3 - Skip main content of this stream if needed. // ---------------------------------------------------------------------------------------------------------------- if (streamID.Size.QuadPart > 0) { DWORD dwLowByteSeeked = 0; DWORD dwHighByteSeeked = 0; DWORD dwLowBytesToSeek = streamID.Size.LowPart; DWORD dwHighBytesToSeek = streamID.Size.HighPart; LPDWORD lpdwLowByteSeeked = &dwLowByteSeeked; LPDWORD lpdwHighByteSeeked = &dwHighByteSeeked; BOOL retBackupSeek = BackupSeek(hFile, dwLowBytesToSeek, dwHighBytesToSeek, lpdwLowByteSeeked, lpdwHighByteSeeked, lpContext); if (retBackupSeek == (BOOL)0) { fprintf(stderr, "BackupSeek() Failed\n"); fprintf(stderr, " required:(%d %d) skipped:(%d %d)\n", dwLowBytesToSeek, dwHighBytesToSeek, dwLowByteSeeked, dwHighByteSeeked); bFailed = true; break; } if ((dwLowByteSeeked == 0) && (dwHighByteSeeked == 0)) { fprintf(stderr, "BackupSeek() Can't skipped\n"); bFailed = true; break; } } } bFailed = false; } int main(int argc, char** argv) { for (int i = 1; i < argc; ++i) { printf("==================================================\n"); printf(" %s\n", argv[i]); printf("==================================================\n"); LPCTSTR lpFileName = argv[i]; DWORD dwDesiredAccess = GENERIC_READ; DWORD dwShareMode = FILE_SHARE_READ; LPSECURITY_ATTRIBUTES lpSecurityAttributes = NULL; DWORD dwCreationDisposition = OPEN_EXISTING; DWORD dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL; HANDLE hTemplateFile = NULL; HANDLE hTargetFile = CreateFile(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); if (hTargetFile == INVALID_HANDLE_VALUE) { fprintf(stderr, "CreateFile() Failed\n"); return 1; } bool bFailed = false; EnumerateNTFSStream(hTargetFile, bFailed); CloseHandle(hTargetFile); if (bFailed) { fprintf(stderr, "Enumeration failed\n"); return 1; } } return 0; }
こんな感じで BackupRead() と BackupSeek() を使えば列挙できるようだ。
なんでこんな名前の関数なのかを推測すると…
バックアップをとるための関数以外に NTFS-Stream の情報にアクセスする手段がないんだろうなw
例外的に、バックアップをとる関数だけは、ちゃんと丸ごと保存できなきゃいけないからアクセスできる、と。
この NTFS-Stream 名の列挙テストを書いていてハマった点をいくつか。
g++ をつかった為なのか、それとも Platform SDK のヘッダでもなるのかは確かめてないけど、
バックアップのストリームのヘッダ部分を読み込むとき、
WIN32_STREAM_ID 構造体から末尾の cStreamName[ANYSIZE_ARRAY] の分を取り除いたサイズを指定したけど 2 バイトくらいずれた(パディングが入ってた)。
もしかしたら Platform SDK のヘッダだと WIN32_STREAM_ID 構造体をちゃんと pack してあるのかもしれない。
回避策としては決め打ちで 20 バイト読むことにした。本当は unsigned char の配列に読んでから WIN32_STREAM_ID にコピーした方がよさそうだけど、
とりあえず今は直接 WIN32_STREAM_ID を指定してサイズだけ 20 バイト指定で上手く行ったようだ。
あと、BackupRead() と BackupSeek() に指定するコンテキストなのだけど、
MSDN 見ても開放する関数が書いてない。
このコンテキストを NULL にして呼び出すと今まで読み込み/シークした結果が無視されるので、
普通に考えれば開放しないとリークするような気がするんだけど?
ファイルハンドルを閉じるときに一緒に閉じてくれるんだろうか?
そして、テストに使ったファイルなのだけど、Explorer でプロパティから概要を適当に埋め尽くした結果、
メインのストリーム以外に無名のストリームが出来てしまったように見えるファイルがある…。
これってどういうことだろう? dwStreamID は 7 でサイズは 64 となってるんだけど…。