Work with alternative data streams through WinAPI

Original author: FlexHex
  • Transfer
In a previous article, I described what alternative streams are and how they can be used. There were examples of working with them through the command line, but you can do the same with software using standard WinAPI tools.
The following is a free translation of the part of the article relating to this issue.

Type check FS


The first step is to check whether the file system supports alternative streams, otherwise it makes no sense to try to work with them.
char szVolName[MAX_PATH], szFSName[MAX_PATH];
DWORD dwSN, dwMaxLen, dwVolFlags;
::GetVolumeInformation("C:\\", szVolName, MAX_PATH, &dwSN,
&dwMaxLen, &dwVolFlags, szFSName, MAX_PATH);

if (dwVolFlags & FILE_NAMED_STREAMS)
{
// File system supports named streams
}
else
{
// Named streams are not supported
}


* This source code was highlighted with Source Code Highlighter .

Creating an Alternate Stream


Like a regular file, an alternative stream can be created using the CreateFile function:
HANDLE hFile = ::CreateFile( "file.dat:alt" , ...

If the file did not exist, a zero-length file with the specified name of the main stream will be created.

Removing Alternate Stream


Removing an alternate stream is no more difficult than deleting a regular file. The DeleteFile function, which fully supports streams, is suitable for this:
::DeleteFile( "file.dat:alt" );

When deleting a standard stream, all its alternate ones will also be deleted.

Copy Alternate Stream


To copy alternative streams, you can use the Win32 API CopyFile / CopyFileEx functions. However, these functions may produce unexpected results. There may be two options for copying.

Standard stream to standard stream: normal file operation, with all named streams being copied along with the file. If the file existed then it is replaced.

Named stream to standard stream: in this case, only the selected stream is copied. If the destination file exists, it will be deleted with all streams (if any), and a new file will be created only with the standard stream, into which copying will occur.

Copying in a simple read / write cycle gives the expected result:
HANDLE hInFile = ::CreateFile(szFromStream, GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);
HANDLE hOutFile = ::CreateFile(szToStream, GENERIC_WRITE, FILE_SHARE_READ, NULL,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL);

BYTE buf[64*1024];
DWORD dwBytesRead, dwBytesWritten;

do
{
::ReadFile(hInFile, buf, sizeof (buf), &dwBytesRead, NULL);
if (dwBytesRead) ::WriteFile(hOutFile, buf, dwBytesRead, &dwBytesWritten, NULL);
}
while (dwBytesRead == sizeof (buf));

::CloseHandle(hInFile);
::CloseHandle(hOutFile);


* This source code was highlighted with Source Code Highlighter .

List of threads


You can get a list of threads using some functions of the Native API. Below is the code for obtaining a list of streams from a file:
// Open a file and obtain stream information

BYTE InfoBlock[64 * 1024]; // Buffer must be large enough
PFILE_STREAM_INFORMATION pStreamInfo = (PFILE_STREAM_INFORMATION)InfoBlock;
IO_STATUS_BLOCK ioStatus;

HANDLE hFile = ::CreateFile(szPath, 0, FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, 0, NULL);
NtQueryInformationFile(hFile, &ioStatus, InfoBlock,
sizeof (InfoBlock), FileStreamInformation);
::CloseHandle(hFile);


* This source code was highlighted with Source Code Highlighter .

The situation with obtaining a list of directory flows is more complicated:
// Open a directory and obtain stream information

// Obtain backup privilege in case we don't have it
HANDLE hToken;
TOKEN_PRIVILEGES tp;
::OpenProcessToken(::GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken);
::LookupPrivilegeValue(NULL, SE_BACKUP_NAME, &tp.Privileges[0].Luid);
tp.PrivilegeCount = 1;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
::AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof (TOKEN_PRIVILEGES), NULL, NULL);
::CloseHandle(hToken);

HANDLE hFile = ::CreateFile(szPath, 0, FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);

BYTE InfoBlock[64 * 1024]; // Buffer must be large enough
PFILE_STREAM_INFORMATION pStreamInfo = (PFILE_STREAM_INFORMATION)InfoBlock;
IO_STATUS_BLOCK ioStatus;

pStreamInfo->StreamNameLength = 0; // Zero in this field means empty info block
NtQueryInformationFile(hFile, &ioStatus, InfoBlock,
sizeof (InfoBlock), FileStreamInformation);
::CloseHandle(hFile);


* This source code was highlighted with Source Code Highlighter .

Now, having received information about the file / directory, we can display the list itself:
WCHAR wszStreamName[MAX_PATH];

for (;;)
{
// Check if stream info block is empty (directory may have no stream)
if (pStreamInfo->StreamNameLength == 0) break ; // No stream found

// Get null-terminated stream name
memcpy(wszStreamName, pStreamInfo->StreamName, pStreamInfo->StreamNameLength);
wszStreamName[pStreamInfo->StreamNameLength / sizeof (WCHAR)] = L '\0' ;

print( "%S" , wszStreamName);

if (pStreamInfo->NextEntryOffset == 0) break ; // No more stream records
pStreamInfo = (PFILE_STREAM_INFORMATION)
((LPBYTE)pStreamInfo + pStreamInfo->NextEntryOffset); // Next stream record
}


* This source code was highlighted with Source Code Highlighter .


VC ++ Console Examples