Алгоритм получения списка потоков NTFS

Получить список потоков NTFS на C++




Алгоритм, позволяющий получить список потоков NTFS, прикреплённых к какому-либо файлу, каталогу или диску, можно реализовать несколькими способами. Первый способ заключается в использовании WinAPI-функции BackupRead (функция для резервного копирования файлов) и структуры WIN32_STREAM_ID. Второй способ основан на использовании стандартных функций FindFirstStreamW и FindNextStreamW, которые предназначены непосредственно для получения списка потоков NTFS, правда появились эти функции только в Windows Server 2003 и Windows Vista, то есть приложение не сможет использовать их в более ранних версиях Windows. И, наконец, третий способ это применение Native API функции NtQueryInformationFile, запроса с помощью неё информации FileStreamInformation (используется структура FILE_STREAM_INFORMATION).

Для поиска и редактирования потоков NTFS на компьютере можно воспользоваться программой NTFS Stream Explorer.

Получение списка потоков NTFS с помощью BackupRead

Особенности данного способа таковы. Во-первых этот способ самый универсальный, так как здесь используется стандартная функция WinAPI, доступная на максимальном количестве Windows-систем. Во-вторых, этот способ показывает не одни лишь альтернативные файловые потоки, но и остальные системные типы потоков. В третьих, с использованием этого способа связан баг в функции BackupSeek. Нижеприведённый код уже содержит обход этого бага для корректной работы.

Макрос BackupClose — это корректный способ завершить получение списка потоков. Описание полей структуры WIN32_STREAM_ID смотрите в MSDN.

#define BackupClose(x,y) BackupRead(x, NULL, 0, NULL, TRUE, FALSE, y)
  
WIN32_STREAM_ID sid;
LPVOID lpContext = NULL;

while (TRUE)
{
  BackupRead(hFile, (LPBYTE)&sid, dwStreamHeaderSize,
                  &dwRead, FALSE, TRUE, &lpContext);

  // Условие выхода из цикла
  if (0 == dwRead)
  {
    break;
  }

  // Тут можно вывести какую-либо информацию о потоке, взятую
  // из WIN32_STREAM_ID

  // Если поток именованный
  if (sid.dwStreamNameSize > 0)
  {
    // с помощью BackupRead можно прочитать имя потока
  }

  // Если поток имеет не нулевой размер
  if (sid.Size.QuadPart > 0)
  {
    if (BACKUP_SPARSE_BLOCK == sid.dwStreamId)
    {
      // Баг в BackupSeek не позволяет пропустить разреженный блок.
      // Читаем его, не обрабатывая.

      sparse_buf = (LPBYTE) GlobalAlloc(GPTR, BUF_SIZE);
      BackupRead(hFile, sparse_buf, sid.Size.LowPart, &dwRead,
                    FALSE, TRUE, &lpContext);
      GlobalFree(sparse_buf);
    } else 
    {
      //Перемещаемся к следующему потоку
      BackupSeek(hFile, sid.Size.LowPart, sid.Size.HighPart,
                  &dw1, &dw2, &lpContext);
    }
  }

  //Очищаем структуру перед следующим проходом цикла
  ZeroMemory(&sid, sizeof(WIN32_STREAM_ID));
}

BackupClose(hFile, &lpContext);  

Получение списка потоков NTFS с помощью FindFirstStreamW и FindNextStreamW

Получение потоков с помощью функций FindFirstStreamW и FindNextStreamW здорово напоминает поиск файлов с помощью функций FindFirstFile и FindNextFile. Я не буду пока приводить здесь код, иллюстрирующий этот способ, так как он должен быть достаточно простым. Код примера на C# можно посмотреть на этой странице MSDN. Следует помнить, что эти функции не доступны в операционных системах, вышедших до Win2k3/Vista.

Получение списка потоков NTFS с помощью NtQueryInformationFile

К особенностям этого метода относится то, что здесь используется функция из Native APINtQueryInformationFile. Список потоков получается через запрос FileStreamInformation. Информация о потоках оказывается в буфере в виде структур FILE_STREAM_INFORMATION. Эта структура имеет следующий формат:

typedef struct _FILE_STREAM_INFORMATION
{
  ULONG NextEntryOffset;
  ULONG StreamNameLength;
  LARGE_INTEGER StreamSize;
  LARGE_INTEGER StreamAllocationSize;
  WCHAR StreamName[1];
} FILE_STREAM_INFORMATION, *PFILE_STREAM_INFORMATION;

Сам код получения потоков представлен ниже:

  IO_STATUS_BLOCK  IoStatusBlock;
  CHAR buf[STREAM_BUF_LEN];
  PFILE_STREAM_INFORMATION fsi = (PFILE_STREAM_INFORMATION) buf;
  ULONG cur_pos;
  WCHAR str[MAX_PATH];

  NTSTATUS ntst =
    NtQueryInformationFile(
      hFile,
      &IoStatusBlock,
      fsi,
      STREAM_BUF_LEN,
      FileStreamInformation
    );

  if (NT_SUCCESS(ntst) && IoStatusBlock.Information > 0)
  {
    cur_pos = 0;
    while (TRUE)
    {
      memcpy(str, fsi->StreamName, fsi->StreamNameLength);
      str[fsi->StreamNameLength/sizeof(WCHAR)]=L'\0';

      // Основное содержимое файла видится как поток ::$DATA
      if (0 != wcsncmp(str, L"::", 2))
      {
        // Обработка имени потока
        PWCHAR pTempStreamName = (PWCHAR) GlobalAlloc(GPTR, fsi->StreamNameLength);
        wcscpy(pTempStreamName, &str[1]);
        ZeroMemory(str, MAX_PATH);
        wcsncpy(str, pTempStreamName, wcslen(pTempStreamName) - wcslen(L":$DATA"));
        GlobalFree(pTempStreamName);
        
        // Здесь доступно имя найденного альтернативного потока
      }
      
      // Условие выхода из цикла
      if (0 == fsi->NextEntryOffset)
      {
        break;
      }
      
      // Переход к следующему потоку
      cur_pos += fsi->NextEntryOffset;
      fsi = (PFILE_STREAM_INFORMATION)&buf[cur_pos];
    }
  }
  CloseHandle(hFile);

По теме файловых потоков также есть следующее:


При копировании материалов хорошим тоном будет указание авторства и ссылка на сайт.