如何在C#中使用CreateFile打开一个目录以查看已删除的条目?

6
我该如何使用C#中的CreateFile打开一个目录以查看已删除文件的条目?还是现在不可能了?我记得很久以前能够使用CreateFile或可能是CreateFileEx在旧操作系统下使用C++打开NTFS分区上的目录。到目前为止,我已经成功使用Windows API调用(kernel32.dll)读取现有文件,但无法打开目录:
using System;
using System.Collections.Generic;
using System.Text;

using System.IO;
using Microsoft.Win32.SafeHandles;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using System.Runtime.ConstrainedExecution;
using System.Security;

namespace Kernel_Test
{
    class Program
    {
        static void Main(string[] args)
        {
            Kernel_Tools cKT = new Kernel_Tools();

            cKT.DoTest("C:\\Temp");
            cKT.DoTest("C:\\Temp\\test.txt");
        }
    }

    [SecurityPermission(SecurityAction.InheritanceDemand, UnmanagedCode = true)]
    [SecurityPermission(SecurityAction.Demand, UnmanagedCode = true)]
    class Kernel_Tools
    {
        public void DoTest(string cTarget)
        {
            IntPtr cFile = NativeMethods.CreateFile(
                cTarget,
                NativeMethods.GENERIC_READ /* 0 or NativeMethods.GENERIC_READ */ ,
                FileShare.Read,
                IntPtr.Zero /* failed try: NativeMethods.OPEN_ALWAYS */,
                (FileMode) NativeMethods.OPEN_EXISTING,
                NativeMethods.FILE_FLAG_BACKUP_SEMANTICS /* 0 */ ,
                IntPtr.Zero);

            Console.WriteLine(cTarget);
            Console.WriteLine(cFile);

            if ((int)cFile != -1)
            {
                int length = 20;

                byte[] bytes = new byte[length];
                int numRead = 0;

                int ErrorCheck = NativeMethods.ReadFile(cFile, bytes, length, out numRead, IntPtr.Zero);
                // This sample code will not work for all files.
                //int r = NativeMethods.ReadFile(_handle, bytes, length, out numRead, IntPtr.Zero);
                // Since we removed MyFileReader's finalizer, we no longer need to
                // call GC.KeepAlive here.  Platform invoke will keep the SafeHandle
                // instance alive for the duration of the call.
                if (ErrorCheck == 0)
                {
                    Console.WriteLine("Read failed.");
                    NativeMethods.CloseHandle(cFile);
                    return;
                    //throw new Win32Exception(Marshal.GetLastWin32Error());
                }

                if (numRead < length)
                {
                    byte[] newBytes = new byte[numRead];
                    Array.Copy(bytes, newBytes, numRead);
                    bytes = newBytes;
                }

                for (int i = 0; i < bytes.Length; i++)
                    Console.Write((char)bytes[i]);

                Console.Write("\n\r");

                //    Console.WriteLine();
                NativeMethods.CloseHandle(cFile);
            }
        }
    }

    [SuppressUnmanagedCodeSecurity()]
    internal static class NativeMethods
    {
        // Win32 constants for accessing files.
        internal const int GENERIC_READ = unchecked((int)0x80000000);

        internal const int FILE_FLAG_BACKUP_SEMANTICS = unchecked((int)0x02000000);

        internal const int OPEN_EXISTING = unchecked((int)3);

        // Allocate a file object in the kernel, then return a handle to it.
        [DllImport("kernel32", CharSet = CharSet.Auto, SetLastError = true)]
        internal extern static IntPtr CreateFile(
           String fileName,
           int dwDesiredAccess,
           System.IO.FileShare dwShareMode,
           IntPtr securityAttrs_MustBeZero,
           System.IO.FileMode dwCreationDisposition,
           int dwFlagsAndAttributes,
           IntPtr hTemplateFile_MustBeZero);

        // Use the file handle.
        [DllImport("kernel32", SetLastError = true)]
        internal extern static int ReadFile(
           IntPtr handle,
           byte[] bytes,
           int numBytesToRead,
           out int numBytesRead,
           IntPtr overlapped_MustBeZero);

        // Free the kernel's file object (close the file).
        [DllImport("kernel32", SetLastError = true)]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        internal extern static bool CloseHandle(IntPtr handle);
    }
}

编辑1:修改为使用OPEN_EXISTING、FILE_FLAG_BACKUP_SEMANTICS和GENERIC_READ。

这将打开并显示指定文本文件的开头,就像原始程序在以Vista管理员用户身份运行时所做的那样,但它仍然无法打开目录。我猜我需要SE_BACKUP_NAME和SE_RESTORE_NAME权限,但不确定如何指定这些权限,除非编写一个作为本地机器运行的服务(我只有模糊的想法)。


请注意,这些不是内核调用本身,它们是普通的用户模式调用(您无法从用户模式调用内核)。 - arul
它们使用Microsoft的kernel32.dll进行调用,使用原始的Windows API功能。 - Fred
3个回答

7
据我所知,这是一个相当复杂的过程。你不能仅使用CreateFile并枚举“已删除的文件”。你需要加载驱动器的主文件表,并枚举标记为已删除的文件,然后尝试从MFT中列出的磁盘位置读取数据。这将需要大量平台调用代码,可能还需要在C#中重新定义一些本地数据结构。
简短回答你的问题如下:
CreateFile("\\\\.\\PhysicalDrive0",
            GENERIC_READ,
            FILE_SHARE_READ|FILE_SHARE_WRITE,
            0,
            OPEN_EXISTING,
            0,
            NULL)

您可以使用create file打开磁盘本身。这是一篇关于整个过程的非常好的文章。但是,它全部都是用C++写的。代码已经提供,而且看起来您知道如何p\invoke,所以移植应该不是问题。编辑:事实上,驱动器是外部的并不会使它更难,您仍然可以像我展示的那样打开磁盘(也许在驱动器连接后使用WMI工具查找路径)。然后,您可以使用FAT32的维基百科页面上的信息来定义数据结构,以便将MFT和其他文件系统的部分读入其中。一旦到达那里,您只需遍历目录表中的32字节文件定义,查看第一个字节即可:
0xE5    Entry has been previously erased and is available. File undelete utilities must replace this character with a regular character as part of the undeletion process.

我其实比较熟悉C++,所以我可能会使用Visual C++。我正在使用C#来学习这门语言。 - Fred
困难的部分是遗失的文件位于FAT32外置硬盘上。 - Fred
1
除非这是一个教育项目,如果你只是想从FAT文件系统中“恢复”一些文件,最好寻找一个现有的工具来为你处理。 - reuben

2
我不确定您所说的检查已删除目录的意思,但是您应该能够通过将FILE_FLAG_BACKUP_SEMANTICS标志传递到CreateFile中,并确保为创建处置指定OPEN_EXISTING来获取目录的句柄。从MSDN CreateFile文章中可以看出:

要使用CreateFile打开目录, 请在dwFlagsAndAttributes的一部分中指定FILE_FLAG_BACKUP_SEMANTICS 标志。即使在没有使用SE_BACKUP_NAMESE_RESTORE_NAME 特权的情况下使用此标志时仍会应用适当的安全检查。

看起来您以前尝试过这些操作但已注释掉?如果这对您没用,您可能需要确保正在运行的用户有权限访问相关目录。

我做了大约7或8个不同的测试,并在我不使用它们时注释掉了部分,这样如果我后来发现我之前部分正确,我就不必重新开始。 - Fred

1

我来给你提供另一种方法,看是否相关。你可以使用 FileSystemWatcher 监控目录并捕获 Deleted 事件。当然,在删除时需要实时监控,但这可能比尝试恢复文件要容易得多(如果有此选项的话)。


旧备份实际上已经被删除了几年。不过还是谢谢你的建议。 - Fred

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接