如何在 C# 中调用 NtSetInformationFile (w/ FILE_LINK_INFORMATION)

How to invoke NtSetInformationFile (w/ FILE_LINK_INFORMATION) in c#

以下尝试重现 here 中描述的 CreateHardLink 功能。

我什至需要这样做的原因是因为这是我知道我将拥有必要权限的唯一方法(这段代码在 .Net 和 WinPE 中是 运行 并且断言了必要的权限恢复权限)。特别是,我使用了 BackupSemantics 标志和 SE_RESTORE_NAME 特权。 CreateHardLink 的正常 pInvoke 机制没有规定恢复程序使用 BackupSemantics...并且有大量文件我的帐户没有 "normal" 访问权限 - 因此,这个混乱。

 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)。我不确定我是如何声明这些结构的——因为其中一个有一个文件名的长度,后面跟着名称的 "first character"。已记录 here.

这是我声明结构和导入的方式。这只是最好的猜测,因为我没有找到任何人在 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 
);

如果你能对我所做的愚蠢的事情有所了解,我们将不胜感激。

编辑:

冒着引起更多反对票的风险,我将解释上下文,否则,有些人可能会认为我在寻找 hack。这是一个选择性的 backup/restore 程序,存在于状态管理软件中——主要是信息亭、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;
}

正如 Harry Johnston 所提到的,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 说明的漏洞,其中调用者不需要任何特定权限即可添加 link.无赖!我怀疑当微软关闭这个漏洞时,他们也引入了 CreateHardLink 的问题。当我知道更多时,我会重新访问此帖子。

虽然我不建议使用内核 API 除非万不得已,但我相信您的直接问题是您未正确打包 FILE_LINK_INFO 结构。

您指定了 4 个字节的打包,根据文档,这会将 directoryHandle 放在偏移量 4 处。但是,您可能 运行 在 64 位系统上,其中如果正确的偏移量是 8.

我不确定如何解决这个问题,但我最好的猜测是您需要使用默认的打包规则,即根本不指定 Pack。 (请注意,如果您指定 8 个字节的打包,FileName 可能会被放置在偏移量 24 处,而它应该位于偏移量 20 处。)