Как вызвать NtSetInformationFile (w/ FILE_LINK_INFORMATION) в C#

Ниже приведена попытка воспроизвести функциональность CreateHardLink, как описано здесь.

Причина, по которой мне даже нужно это сделать, заключается в том, что только так я знаю, что у меня будут необходимые разрешения (этот код работает в.Net, в WinPE и обладает необходимыми привилегиями для восстановления). В частности, я использую флаг BackupSemantics и привилегию SE_RESTORE_NAME. Обычный механизм pInvoke для CreateHardLink не содержит положений, позволяющих программе восстановления использовать BackupSemantics... и существует множество файлов, к которым моя учетная запись не имеет "нормального" доступа - следовательно, этот беспорядок.

 unsafe bool CreateLink( string linkName, string existingFileName )
 {
   var access = 
     NativeMethods.EFileAccess.AccessSystemSecurity |
     NativeMethods.EFileAccess.WriteAttributes |
     NativeMethods.EFileAccess.Synchronize;

   var disp = NativeMethods.ECreationDisposition.OpenExisting;

   var flags = 
     NativeMethods.EFileAttributes.BackupSemantics |
     NativeMethods.EFileAttributes.OpenReparsePoint;

   var share = 
     FileShare.ReadWrite | 
     FileShare.Delete;

   var handle = NativeMethods.CreateFile
   ( 
     existingFileName, 
     access, 
     ( uint ) share, 
     IntPtr.Zero, 
     ( uint ) disp, 
     ( uint ) flags, 
     IntPtr.Zero 
   );

   if ( !handle.IsInvalid )
   {
     var mem = Marshal.AllocHGlobal( 1024 );
     try
     {
       var linkInfo = new NativeMethods.FILE_LINK_INFORMATION( );
       var ioStatus = new NativeMethods.IO_STATUS_BLOCK( );
       linkInfo.replaceIfExisting = false;
       linkInfo.directoryHandle = IntPtr.Zero;
       linkInfo.fileName = linkName;
       linkInfo.fileNameLength = ( uint )
         Encoding
         .Unicode
         .GetByteCount( linkInfo.fileName );

       Marshal.StructureToPtr( linkInfo, mem, true );
       var result = NativeMethods.NtSetInformationFile
       ( 
         handle.DangerousGetHandle( ), 
         ref ioStatus, 
         mem.ToPointer( ), 
         1024,
         NativeMethods.FILE_INFORMATION_CLASS.FileLinkInformation 
       );

       return result == 0;
     }
     finally
     {
       Marshal.FreeHGlobal( mem );
     }
   }
   return false;
 }

Я продолжаю получать результат от NtSetInformationFile, который говорит, что я указал неверный параметр для системной функции. (Результат =0xC000000D). Я не уверен в том, как я объявил структуры - поскольку у одного из них есть длина имени файла, сопровождаемая "первым символом" имени. Это задокументировано здесь.

Вот как я объявил структуры - и импорт. Это всего лишь предположение, так как я не нашел никого, кто объявил бы это на C# (pinvoke.net и в других местах), я перепутал с некоторыми перестановками... все с точно такой же ошибкой:

[StructLayout( LayoutKind.Sequential, Pack = 4 )]
internal struct FILE_LINK_INFORMATION
{
  [MarshalAs( UnmanagedType.Bool )]
  public bool replaceIfExisting;
  public IntPtr directoryHandle;
  public uint fileNameLength;
  [MarshalAs( UnmanagedType.ByValTStr, SizeConst = MAX_PATH )]
  public string fileName;
}

internal struct IO_STATUS_BLOCK
{
  uint status;
  ulong information;
}

[DllImport( "ntdll.dll", CharSet = CharSet.Unicode )]
unsafe internal static extern uint NtSetInformationFile
( 
  IntPtr fileHandle, 
  ref IO_STATUS_BLOCK IoStatusBlock, 
  void* infoBlock, 
  uint length, 
  FILE_INFORMATION_CLASS fileInformation 
);

Любой свет, который ты можешь пролить на глупость, которую я совершил, был бы очень признателен.

РЕДАКТИРОВАТЬ:

Рискуя получить больше отрицательных голосов, я объясню контекст, без которого, возможно, было бы некоторое убеждение, что я искал взлом. Это программа выборочного резервного копирования / восстановления, которая работает в среде программного обеспечения для управления состоянием - в основном это киоски, POS-терминалы и библиотечные компьютеры. Операции резервного копирования и восстановления выполняются в предзагрузочной среде (WinPE).

То, что в конечном итоге сработало в отношении использования функции, было необходимость изменить структуру FILE_LINK_INFORMATION и поворот в именовании файлов. Во-первых, рабочая FILE_LINK_INFORMATION нужно идти так:

[StructLayout( LayoutKind.Sequential, CharSet = CharSet.Unicode )]
internal struct FILE_LINK_INFORMATION
{
  [MarshalAs( UnmanagedType.U1 )]
  public bool ReplaceIfExists;
  public IntPtr RootDirectory;
  public uint FileNameLength;
  [MarshalAs( UnmanagedType.ByValTStr, SizeConst = MAX_PATH )]
  public string FileName;
}

Как отметил Гарри Джонстон, Pack=4 был неправильным - и сортировка bool должна была быть немного другой. MAX_PATH 260

Потом при звонке NtSetInformationFile в контексте файла, который открывается с правами чтения, записи и удаления и совместного использования:

unsafe bool CreateLink( DirectoryEntry linkEntry, DirectoryEntry existingEntry, SafeFileHandle existingFileHandle )
{
  var statusBlock = new NativeMethods.IO_STATUS_BLOCK( );
  var linkInfo = new NativeMethods.FILE_LINK_INFORMATION( );
  linkInfo.ReplaceIfExists = true;
  linkInfo.FileName = @"\??\" + storage.VolumeQualifiedName( streamCatalog.FullName( linkEntry ) );
  linkInfo.FileNameLength = ( uint ) linkInfo.FileName.Length * 2;
  var size = Marshal.SizeOf( linkInfo );
  var buffer = Marshal.AllocHGlobal( size );
  try
  {
    Marshal.StructureToPtr( linkInfo, buffer, false );
    var result = NativeMethods.NtSetInformationFile
    (
      existingFileHandle.DangerousGetHandle( ),
      statusBlock,
      buffer,
      ( uint ) size,
      NativeMethods.FILE_INFORMATION_CLASS.FileLinkInformation
    );
    if ( result != 0 )
    {
      Session.Emit( "{0:x8}: {1}\n{2}", result, linkInfo.FileName, streamCatalog.FullName( existingEntry ) );
    }
    return ( result == 0 );
  }
  finally
  {
    Marshal.FreeHGlobal( buffer );
  }
}

Обратите внимание, в частности, префикс пространства имен - не работал, пока я не добавил это.

Кстати, DirectoryEntry описывает файл, который должен был находиться на диске на момент последнего резервного копирования.

Что касается неиспользования CreateHardLinkКак описывает оригинальная статья, уязвимость была проиллюстрирована с помощью NtSetInformationFile там, где вызывающий абонент не нуждался в каких-либо особых разрешениях для добавления ссылки. Вот досада! Я подозреваю, что когда Microsoft закрыла дыру, они также представили проблему с CreateHardLink, Я вернусь к этой публикации, когда узнаю больше.

1 ответ

Решение

Хотя я бы не рекомендовал использовать API ядра, кроме как в крайнем случае, я считаю, что вашей непосредственной проблемой является то, что вы упаковываете FILE_LINK_INFO структура неправильно.

Вы указали упаковку в 4 байта, которую согласно документации положите directoryHandle со смещением 4. Однако вы, вероятно, работаете в 64-битной системе, и в этом случае правильное смещение равно 8.

Я не уверен, как это исправить, но я думаю, что вам нужно использовать правила упаковки по умолчанию, т.е. не указывать Pack совсем. (Обратите внимание, что если вы укажете упаковку из 8 байтов, FileName предположительно будет смещено на 24, когда должно быть на смещение 20.)

Другие вопросы по тегам