Как вызвать 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.)