Skip to content

Instantly share code, notes, and snippets.

@hugsy
Created June 9, 2022 23:18
Show Gist options
  • Save hugsy/6e5a6766e81928d3e647844283db6039 to your computer and use it in GitHub Desktop.
Save hugsy/6e5a6766e81928d3e647844283db6039 to your computer and use it in GitHub Desktop.
NTFS streams
//
// http://web.archive.org/web/20150617011338/http://win32.mvps.org/ntfs/dump_ntfs_streams.cpp
//
#include <windows.h>
#include <stdio.h>
#pragma hdrstop
#define err doerr( __FILE__, __LINE__ )
void doerr( const char *file, int line )
{
DWORD e;
e = GetLastError();
if ( e == 0 )
return;
printf( "%s(%d): gle = %lu\n", file, line, e );
exit( 2 );
}
void enableprivs()
{
HANDLE hToken;
byte buf[sizeof TOKEN_PRIVILEGES * 2];
TOKEN_PRIVILEGES & tkp = *( (TOKEN_PRIVILEGES *) buf );
if ( ! OpenProcessToken( GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken ) )
err;
// enable SeBackupPrivilege, SeRestorePrivilege
if ( !LookupPrivilegeValue( NULL, SE_BACKUP_NAME, &tkp.Privileges[0].Luid ) )
err;
if ( !LookupPrivilegeValue( NULL, SE_RESTORE_NAME, &tkp.Privileges[1].Luid ) )
err;
tkp.PrivilegeCount = 2;
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
tkp.Privileges[1].Attributes = SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges( hToken, FALSE, &tkp, sizeof tkp,
NULL, NULL );
}
void dumphdr( WIN32_STREAM_ID & wsi )
{
const char *p;
printf( "\nstream [%lu] \"%S\":\n", wsi.dwStreamNameSize,
wsi.dwStreamNameSize? wsi.cStreamName: L"" );
switch ( wsi.dwStreamId )
{
case BACKUP_DATA:
p = "data";
break;
case BACKUP_EA_DATA:
p = "extended attributes";
break;
case BACKUP_SECURITY_DATA:
p = "security";
break;
case BACKUP_ALTERNATE_DATA:
p = "other streams";
break;
case BACKUP_LINK:
p = "link";
break;
default:
p = "unknown";
break;
}
printf( " type: %s\n", p );
printf( " size: %I64d\n", wsi.Size.QuadPart );
}
int main( int argc, char *argv[] )
{
HANDLE fh;
if ( argc != 2 )
{
printf( "usage: dump_ntfs_streams {file}\n" );
return 1;
}
// SeBackupPrivilege is not necessary to enumerate streams --
// but it helps if you are an admin/backup-operator and need
// to scan files to which you have no permissions
enableprivs();
fh = CreateFile( argv[1], GENERIC_READ, 0, NULL, OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_POSIX_SEMANTICS, NULL );
if ( fh == INVALID_HANDLE_VALUE || fh == NULL )
err;
byte buf[4096];
DWORD numread, numtoskip;
void *ctx = NULL;
WIN32_STREAM_ID & wsi = *( (WIN32_STREAM_ID *) buf );
numtoskip = 0;
while ( 1 )
{
// we are at the start of a stream header. read it.
if ( ! BackupRead( fh, buf, 20, &numread, FALSE, TRUE, &ctx ) )
err;
if ( numread == 0 )
break;
if ( wsi.dwStreamNameSize > 0 )
{
if ( ! BackupRead( fh, buf + 20, wsi.dwStreamNameSize, &numread, FALSE, TRUE, &ctx ) )
err;
if ( numread != wsi.dwStreamNameSize )
break;
}
dumphdr( wsi );
// skip stream data
if ( wsi.Size.QuadPart > 0 )
{
DWORD lo, hi;
BackupSeek( fh, 0xffffffffL, 0x7fffffffL, &lo, &hi, &ctx );
}
}
// make NT release the context
BackupRead( fh, buf, 0, &numread, TRUE, FALSE, &ctx );
CloseHandle( fh );
return 0;
}
//
// http://web.archive.org/web/20150616195443/http://win32.mvps.org/ntfs/streams.cpp
//
#ifndef UNICODE
#define UNICODE
#endif
#ifndef UNICODE
#define _UNICODE
#endif
#include <stdio.h>
#include <windows.h>
#include <wchar.h>
#include <string.h>
#include <ntsecapi.h> // for LsaNtStatusToWinError()
#pragma hdrstop
#define IN
#define OUT
//
// Define the file information class values
//
// WARNING: The order of the following values are assumed by the I/O system.
// Any changes made here should be reflected there as well.
//
typedef enum _FILE_INFORMATION_CLASS {
FileDirectoryInformation = 1,
FileFullDirectoryInformation,
FileBothDirectoryInformation,
FileBasicInformation,
FileStandardInformation,
FileInternalInformation,
FileEaInformation,
FileAccessInformation,
FileNameInformation,
FileRenameInformation,
FileLinkInformation,
FileNamesInformation,
FileDispositionInformation,
FilePositionInformation,
FileFullEaInformation,
FileModeInformation,
FileAlignmentInformation,
FileAllInformation,
FileAllocationInformation,
FileEndOfFileInformation,
FileAlternateNameInformation,
FileStreamInformation,
FilePipeInformation,
FilePipeLocalInformation,
FilePipeRemoteInformation,
FileMailslotQueryInformation,
FileMailslotSetInformation,
FileCompressionInformation,
FileCopyOnWriteInformation,
FileCompletionInformation,
FileMoveClusterInformation,
FileOleClassIdInformation,
FileOleStateBitsInformation,
FileNetworkOpenInformation,
FileObjectIdInformation,
FileOleAllInformation,
FileOleDirectoryInformation,
FileContentIndexInformation,
FileInheritContentIndexInformation,
FileOleInformation,
FileMaximumInformation
} FILE_INFORMATION_CLASS, *PFILE_INFORMATION_CLASS;
//
// Define the various structures which are returned on query operations
//
typedef struct _FILE_BASIC_INFORMATION {
LARGE_INTEGER CreationTime;
LARGE_INTEGER LastAccessTime;
LARGE_INTEGER LastWriteTime;
LARGE_INTEGER ChangeTime;
ULONG FileAttributes;
} FILE_BASIC_INFORMATION, *PFILE_BASIC_INFORMATION;
typedef struct _FILE_STANDARD_INFORMATION {
LARGE_INTEGER AllocationSize;
LARGE_INTEGER EndOfFile;
ULONG NumberOfLinks;
BOOLEAN DeletePending;
BOOLEAN Directory;
} FILE_STANDARD_INFORMATION, *PFILE_STANDARD_INFORMATION;
typedef struct _FILE_POSITION_INFORMATION {
LARGE_INTEGER CurrentByteOffset;
} FILE_POSITION_INFORMATION, *PFILE_POSITION_INFORMATION;
typedef struct _FILE_ALIGNMENT_INFORMATION {
ULONG AlignmentRequirement;
} FILE_ALIGNMENT_INFORMATION, *PFILE_ALIGNMENT_INFORMATION;
typedef struct _FILE_NETWORK_OPEN_INFORMATION {
LARGE_INTEGER CreationTime;
LARGE_INTEGER LastAccessTime;
LARGE_INTEGER LastWriteTime;
LARGE_INTEGER ChangeTime;
LARGE_INTEGER AllocationSize;
LARGE_INTEGER EndOfFile;
ULONG FileAttributes;
} FILE_NETWORK_OPEN_INFORMATION, *PFILE_NETWORK_OPEN_INFORMATION;
typedef struct _FILE_DISPOSITION_INFORMATION {
BOOLEAN DeleteFile;
} FILE_DISPOSITION_INFORMATION, *PFILE_DISPOSITION_INFORMATION;
typedef struct _FILE_END_OF_FILE_INFORMATION {
LARGE_INTEGER EndOfFile;
} FILE_END_OF_FILE_INFORMATION, *PFILE_END_OF_FILE_INFORMATION;
typedef struct _FILE_FULL_EA_INFORMATION {
ULONG NextEntryOffset;
UCHAR Flags;
UCHAR EaNameLength;
USHORT EaValueLength;
CHAR EaName[1];
} FILE_FULL_EA_INFORMATION, *PFILE_FULL_EA_INFORMATION;
struct FILE_STREAM_INFORMATION
{
ULONG NextEntryOffset;
ULONG StreamNameLength;
LARGE_INTEGER StreamSize;
LARGE_INTEGER StreamAllocationSize;
WCHAR StreamName[1];
};
//
// Define the base asynchronous I/O argument types
//
typedef struct _IO_STATUS_BLOCK {
NTSTATUS Status;
ULONG Information;
} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;
DWORD __stdcall NtQueryInformationFile(
IN HANDLE FileHandle,
OUT PIO_STATUS_BLOCK IoStatusBlock,
OUT PVOID FileInformation,
IN ULONG Length,
IN FILE_INFORMATION_CLASS FileInformationClass
);
typedef DWORD (__stdcall *NQIF)(
IN HANDLE FileHandle,
OUT PIO_STATUS_BLOCK IoStatusBlock,
OUT PVOID FileInformation,
IN ULONG Length,
IN FILE_INFORMATION_CLASS FileInformationClass
);
NQIF nqif = NULL;
const int MAXLEN = 2048; // arbitrary filename length limit
void checkOneFile( const wchar_t *file, int& numFilesWithStreams, int& numStreams )
{
HANDLE h;
DWORD rc;
IO_STATUS_BLOCK iosb;
static byte fsibuf[32768];
FILE_STREAM_INFORMATION *fsi;
bool mustPrintName = true;
static const wchar_t * const DefaultStreamName = L"::$DATA";
static const ULONG DefaultStreamNameLength = 7;
// note that while we do have to open the file, not even read access is needed
h = CreateFile( file, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
OPEN_EXISTING, 0, NULL );
if ( h == INVALID_HANDLE_VALUE )
{
wprintf( L"Error %d opening \"%s\".\n", GetLastError(), file );
return;
}
rc = nqif( h, &iosb, &fsibuf, sizeof( fsibuf ), FileStreamInformation );
if ( rc != 0 )
{
wprintf( L"Error %d [NTSTATUS %ld/%08lxh] on NQIF() of \"%s\".\n",
LsaNtStatusToWinError( rc ), rc, rc, file );
CloseHandle( h );
return;
}
CloseHandle( h );
fsi = (FILE_STREAM_INFORMATION *) fsibuf;
while ( 1 )
{
if ( fsi->StreamNameLength != 2 * DefaultStreamNameLength ||
wcsncmp( fsi->StreamName, DefaultStreamName, DefaultStreamNameLength ) != 0 )
{
// non-default stream found!
if ( mustPrintName )
{
// first stream for this file, bump file count and show file name
++ numFilesWithStreams;
wprintf( L"\n%s\n", file );
mustPrintName = false;
}
// bump stream count, show stream info
++ numStreams;
wprintf( L" %8I64u %-*.*s\n", fsi->StreamSize, fsi->StreamNameLength / 2,
fsi->StreamNameLength / 2, fsi->StreamName );
}
if ( fsi->NextEntryOffset == 0 )
break;
fsi = (FILE_STREAM_INFORMATION *) ( fsi->NextEntryOffset + (byte *) fsi );
}
return;
}
void doFilespec( const wchar_t *srchPath, int& numFilesWithStreams, int& numStreams )
{
wchar_t path[MAXLEN] = L"";
wchar_t *p, *endOfPath;
WIN32_FIND_DATA wfd;
HANDLE ffh;
if ( srchPath == NULL || *srchPath == L'\0' || wcslen( srchPath ) >= MAXLEN )
return;
wcscpy( path, srchPath );
for ( p = path; *p != L'\0'; ++ p )
{
if ( *p == L'/' )
*p = L'\\';
}
p = wcsrchr( path, L'\\' ); // isolate last backslash
if ( p == NULL ) // no backslash, only a filespec
endOfPath = path;
else
{
endOfPath = p + 1;
if ( *endOfPath == L'\0' ) // no filespec?
wcscpy( endOfPath, L"*" ); // invent one
}
// loop over files
ffh = FindFirstFile( path, &wfd );
if ( ffh != INVALID_HANDLE_VALUE )
{
BOOL ok;
do
{
if ( ( wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) != 0 )
{
// it's a directory -- may want to recurse
}
else // it's a file
{
wcscpy( endOfPath, wfd.cFileName ); // tack file onto path
checkOneFile( path, numFilesWithStreams, numStreams);
}
ok = FindNextFile( ffh, &wfd );
} while ( ok );
FindClose( ffh );
}
else
wprintf( L"Error %d when searching for \"%s\".\n", GetLastError(), path );
return;
}
extern "C"
int __cdecl wmain( int argc, wchar_t *argv[], wchar_t *envp[] )
{
int numFilesWithStreams = 0, numStreams = 0, i;
HINSTANCE hNtdll;
if ( argc == 1 )
{
_putws( L"usage: streams filespec [...]" );
_putws( L"To specify all files in a directory, use \"dir\\\" or \"dir\\*\"." );
_putws( L"streams _only_ reports on files having non-default streams" );
return 1;
}
hNtdll = LoadLibrary( L"ntdll.dll" );
if ( hNtdll < (HINSTANCE) 33 )
{
wprintf( L"Error %d [%d] loading NTDLL.DLL. Huh?\n", (int) hNtdll, GetLastError() );
return -2;
}
nqif = (NQIF) GetProcAddress( hNtdll, "NtQueryInformationFile" );
if ( nqif == NULL )
{
wprintf( L"NQIF() missing from NTDLL.DLL. Huh?\n" );
FreeLibrary( hNtdll );
return -2;
}
for ( i = 1; i < argc; ++ i )
doFilespec( argv[i], numFilesWithStreams, numStreams );
FreeLibrary( hNtdll );
return numStreams;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment