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

本文关键字:FILE LINK INFORMATION NtSetInformationFile 调用 | 更新日期: 2023-09-27 18:15:47

下面是复制这里描述的CreateHardLink功能的尝试。

我需要这样做的原因是因为这是我知道我将拥有必要权限的唯一方法(该代码运行在。net中,在WinPE中,并且已经断言了恢复所需的特权)。特别是,我使用了BackupSemantics标志和SE_RESTORE_NAME特权。CreateHardLink的正常pInvoke机制没有规定恢复程序使用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得到一个结果,说我已经指定了一个无效的参数到系统函数。(结果= 0 xc000000d)。我不确定我是如何声明这些结构的——因为其中一个结构有一个文件名的长度,后面跟着名称的"第一个字符"。

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

如果你能就我所做的蠢事提供任何启发,我将不胜感激。

编辑:

冒着引来更多反对的风险,我将解释上下文,否则可能会有人认为我在寻找黑客。它是一种选择性的备份/恢复程序,存在于状态管理软件(主要是kiosks、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说明了一个漏洞,其中调用者不需要任何特定的权限来添加链接。游手好闲的人!我怀疑当微软关闭漏洞时,他们也引入了CreateHardLink的问题。当我知道更多的时候,我会重新审视这篇文章。

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

虽然我不建议使用内核API,除非作为最后的手段,但我认为您的直接问题是您错误地包装了FILE_LINK_INFO结构。

您已经指定了4字节的包装,根据文档将把directoryHandle置于4的偏移量。但是,您可能运行在64位系统上,在这种情况下,正确的偏移量是8。

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