1

以下是重现此处描述的 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 得到一个结果,说我已经为系统函数指定了一个无效参数。(结果=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;
}

正如 Harry Johnston 所提到的,Pack=4 是错误的——并且 bool 的编组需要有所不同。是MAX_PATH260。

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. 当我知道更多时,我会重新访问这个帖子。

4

1 回答 1

1

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

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

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

于 2016-11-01T00:16:46.820 回答