大家把ntfs分区上的文件拷贝到非ntfs分区上时, 可能偶尔遇到过下面的情况, 系统提示会有数据丢失, 这是怎么回事呢? 实际上ntfs文件系统引入了"流"这个概念, 每个文件都可以有多个流, 而我们一般只使用了一个, 通过给文件分配更多的流, 可以实现某种意义上的"文件隐藏". 例如可以控制台中使用下面的命令建立一个文本文件: dir d:>abc.txt 它列出d:根目录的所有文件, 然后将其重定向到文件abc.txt, 现在你可以检查一下abc.txt的大小和内容, 并记录下来. 然后再执行下面这条命令 dir c:>abc.txt:stream.txt 执行完毕后, 检查abc.txt, 大小和内容都没有变化, 但其实abc.txt已经多了一个流stream.txt, 而重定向的内容就输出到了它里面, 不信使用下面的命令看一下(注意流的名字也要以.txt结尾, 否则notepad就找不到了): notepad abc.txt:stream.txt 这样我们就把一个文件隐藏了, dir命令看不见, 文件属性看不到, 资源管理器也看不到, 如果不知道流的名字, notepad也是无法访问的. 实际上, 流还可以不依赖于文件, 下面的命令也是合法的(先不要试, 否则可能会有点麻烦): dir e:>:stream.txt 这是把流绑到了文件夹上, 这种流就更隐蔽了. 一般情况下要想删除流只有将其宿主删除, 如果你执行了刚才的命令, 并且是在根文件夹上执行的, 如果你想删除它, 那就恭喜你要格盘了:). 不过通过写程序还是不难删除流的, 只要调用DeleteFile, 并提供流的名字就行了. 要想枚举一个文件中的所有流, 目前只能通过BackupRead来完成. 我写了一个小程序, 通过它可以枚举、删除、导入导出流中的数据, 下面的是它的代码(写的比较仓促, 可能还有一些bug, 不过主要功能都实现了, 它的名字叫nsvw, 即Ntfs Stream Viewer). #include <windows.h> #include <stdio.h> #include <locale.h> #include <wchar.h> #include <malloc.h> #include <stddef.h>
enum RUN_MODE { SHOW_USAGE = 0, SHOW_STREAMS, DELETE_STREAMS, IMPORT_STREAM, EXPORT_STREAM, };
LPCWSTR g_szObj = NULL; LPCWSTR g_szStrm = NULL; LPCWSTR g_szFile = NULL;
int ParseArgs( int argc, LPWSTR* argv ) { if( argc == 1 || argc == 3 ) return SHOW_USAGE;
g_szObj = *(argv + 1); if( argc == 2 ) return SHOW_STREAMS;
LPCWSTR act = *(argv + 2); if( act[0] != L'-' && act[0] != L'/' ) return SHOW_USAGE;
if( act[1] == L'd' ) return DELETE_STREAMS;
if( argc == 4 || argc > 5 ) return SHOW_USAGE;
g_szStrm = *(argv + 3); g_szFile = *(argv + 4); if( act[1] == L'i' ) return IMPORT_STREAM;
if( act[1] == L'e' ) return EXPORT_STREAM;
return SHOW_USAGE; }
int ShowUsage() { wprintf( L"USAGE: " L"nsvw file.a : view streams in file.a " L"nsvw file.a -d s1 s2 : delete stream s1, s2 and from file.a " L"nsvw file.a -i s1 file.b : copy the content of file.b to stream s1 in file.a " L"nsvw file.a -e s1 file.c : copy the content of stream s1 in file.a to file.c " ); return 0; }
int ShowStreams() { HANDLE hFile = CreateFile( g_szObj, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); if( hFile == INVALID_HANDLE_VALUE ) { wprintf( L"Unable to open object "%s" ", g_szObj ); return static_cast<int>( GetLastError() ); }
WIN32_STREAM_ID wsi = {0}; WCHAR szStrmName[MAX_PATH]; LPVOID pContext = NULL;
BOOL bOk = TRUE; int nCount = 0; while( bOk ) { DWORD dwBytes = 0; LPBYTE buf = reinterpret_cast<LPBYTE>( &wsi ); DWORD dwSize = static_cast<DWORD>(offsetof(WIN32_STREAM_ID, cStreamName)); bOk = BackupRead( hFile, buf, dwSize, &dwBytes, FALSE, FALSE, &pContext ); if( !bOk || dwBytes == 0 ) break; if( wsi.dwStreamNameSize > 0 ) { buf = reinterpret_cast<LPBYTE>( szStrmName ); dwSize = wsi.dwStreamNameSize; BackupRead( hFile, buf, dwSize, &dwBytes, FALSE, FALSE, &pContext ); szStrmName[dwSize / sizeof(WCHAR)] = 0; wprintf( L"NAME: "%s" SIZE: %I64d ", szStrmName, wsi.Size.QuadPart ); ++nCount; } DWORD dw1, dw2; BackupSeek( hFile, wsi.Size.LowPart, wsi.Size.HighPart, &dw1, &dw2, &pContext ); }
DWORD dwError = GetLastError(); ::BackupRead( hFile, NULL, 0, NULL, TRUE, FALSE, &pContext ); ::CloseHandle( hFile );
wprintf( L"Total %d stream(s). ", nCount );
return static_cast<int>( dwError ); }
void BuildStreamName( LPCWSTR szStrm, LPWSTR buf, size_t size ) { _snwprintf( buf, size, L"%s:%s", g_szObj, szStrm ); buf[size - 1] = 0; }
int DeleteStreams( int count, LPWSTR* streams ) { const int nSize = MAX_PATH * 2; WCHAR szStrmName[nSize]; size_t size = sizeof(szStrmName) / sizeof(WCHAR);
for( int i = 0; i < count; ++i ) { BuildStreamName( *(streams + i), szStrmName, nSize ); if( ::DeleteFileW( szStrmName ) ) wprintf( L"stream %s was deleted. ", *(streams + i) ); else wprintf( L"unable to delete stream %s. ", *(streams + i) ); }
return 0; }
int CopyStream( LPCWSTR szSrc, LPCWSTR szDst ) { int nRet = 0; HANDLE hSrc = INVALID_HANDLE_VALUE, hDst = INVALID_HANDLE_VALUE; HANDLE hSrcFm = NULL, hDstFm = NULL; PVOID pSrc = NULL, pDst = NULL;
__try { hSrc = ::CreateFile( szSrc, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); if( hSrc == INVALID_HANDLE_VALUE ) __leave;
DWORD dwSize = ::GetFileSize( hSrc, NULL ); if( dwSize > 0 ) { hSrcFm = ::CreateFileMapping( hSrc, NULL, PAGE_READONLY, 0, 0, NULL ); if( hSrcFm == NULL ) __leave;
pSrc = ::MapViewOfFile( hSrcFm, FILE_MAP_READ, 0, 0, dwSize ); if( pSrc == NULL ) __leave; }
hDst = ::CreateFile( szDst, FILE_ALL_ACCESS, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); if( hDst == INVALID_HANDLE_VALUE ) __leave;
if( dwSize > 0 ) { hDstFm = ::CreateFileMapping( hDst, NULL, PAGE_READWRITE, 0, dwSize, NULL ); if( hDstFm == NULL ) __leave;
pDst = ::MapViewOfFile( hDstFm, FILE_MAP_WRITE, 0, 0, dwSize ); if( pDst == NULL ) __leave;
memcpy( pDst, pSrc, dwSize ); } else { ::SetFilePointer( hDst, 0, 0, FILE_BEGIN ); ::SetEndOfFile( hDst ); } } __finally { nRet = static_cast<int>( ::GetLastError() ); }
if( pDst != NULL ) ::UnmapViewOfFile( pDst ); if( pSrc != NULL ) ::UnmapViewOfFile( pSrc ); if( hDstFm != NULL ) ::CloseHandle( hDstFm ); if( hSrcFm != NULL ) ::CloseHandle( hSrcFm ); if( hDst != INVALID_HANDLE_VALUE ) ::CloseHandle( hDst ); if( hSrc != INVALID_HANDLE_VALUE ) ::CloseHandle( hSrc );
return nRet; }
int ImportStream() { const int nSize = MAX_PATH * 2; WCHAR szStrmName[nSize]; size_t size = sizeof(szStrmName) / sizeof(WCHAR); BuildStreamName( g_szStrm, szStrmName, nSize ); int nRes = CopyStream( g_szFile, szStrmName ); if( nRes != 0 ) wprintf( L"Import failed. " ); else wprintf( L"Import completed. " ); return nRes; }
int ExportStream() { const int nSize = MAX_PATH * 2; WCHAR szStrmName[nSize]; size_t size = sizeof(szStrmName) / sizeof(WCHAR); BuildStreamName( g_szStrm, szStrmName, nSize ); int nRes = CopyStream( szStrmName, g_szFile ); if( nRes != 0 ) wprintf( L"Export failed. " ); else wprintf( L"Export completed. " ); return nRes;
}
int __cdecl wmain( int argc, LPWSTR* argv ) { int nRetCode = 0;
_wsetlocale( LC_ALL, L".OCP" ); wprintf( L"NTFS Stream Viewer VERSION 1.0 " );
switch( ParseArgs( argc, argv ) ) { case SHOW_USAGE: nRetCode = ShowUsage(); break;
case SHOW_STREAMS: nRetCode = ShowStreams(); break;
case DELETE_STREAMS: nRetCode = DeleteStreams( argc - 3, argv + 3 ); break;
case IMPORT_STREAM: nRetCode = ImportStream(); break;
case EXPORT_STREAM: nRetCode = ExportStream(); break;
default: wprintf( L"internel error! " ); nRetCode = -1; break; }
return nRetCode; }
|