从文件句柄获取文件名?

5
我已经钩取了ntdll.dll的NtCreateFile()函数,以允许/拒绝对某些文件的访问。与kernel32.dll的CreateFile()函数不同,后者可以轻松地为您提供有关所讨论的文件的完整路径,而ntdll.dll的NtCreateFile()函数仅提供文件的句柄。我需要从文件句柄获取文件的完整路径,以便随后允许/拒绝访问。我搜索过了,似乎没有一个可行的C#解决方案。 解决方案是用C++编写的,并由Microsoft进行了文档化。我尝试将其移植到C#,但并没有太大的成功。这是我尝试使用C#等效于“从文件句柄获取文件名”的C++版本:
    public string GetFileNameFromHandle(IntPtr FileHandle)
    {
        string fileName = String.Empty;
        IntPtr fileMap = IntPtr.Zero, fileSizeHi = IntPtr.Zero;
        UInt32 fileSizeLo = 0;

        fileSizeLo = GetFileSize(FileHandle, fileSizeHi);

        if (fileSizeLo == 0 && fileSizeHi == IntPtr.Zero)
        {
            // cannot map an 0 byte file
            return String.Empty;
        }

        fileMap = CreateFileMapping(FileHandle, IntPtr.Zero, FileMapProtection.PageReadonly, 0, 1, null);

        if (fileMap != IntPtr.Zero)
        {
            IntPtr pMem = MapViewOfFile(fileMap, FileMapAccess.FileMapRead, 0, 0, 1);
            if (pMem != IntPtr.Zero)
            {
                StringBuilder fn = new StringBuilder(250);
                GetMappedFileName(System.Diagnostics.Process.GetCurrentProcess().MainWindowHandle, pMem, fn, 250);
                if (fileName.Length > 0)
                {
                    UnmapViewOfFile(pMem);
                    CloseHandle(FileHandle);
                    return fn.ToString();
                }
                else
                {
                    UnmapViewOfFile(pMem);
                    CloseHandle(FileHandle);
                    return String.Empty;
                }
            }
        }

        return String.Empty;
    }

我当然拥有所有必要的DLLImports和用户定义类型。当我在句柄上使用此函数时,返回一个空字符串。这也很难调试,因为该方法位于注入到目标进程中的DLL中,不像您可以设置断点并享受Visual Studio的调试系统那样。我想我可以编写日志文件或一些跟踪系统,但我还没有那么绝望。我只需要一个成功的C#版本的“从文件句柄获取文件名”。 有什么见解、代码修复、链接吗?

2
我会问一个显而易见的问题:你为什么要这样做?用托管负载挂钩本地API函数调用已经够糟糕的了(我希望你已经获得了Detours的许可,因为没有其他方法支持),但是目的是为了应用一层安全性?为什么不使用文件系统中内置的安全性呢? - Stephen Cleary
1
看起来你需要考虑编写 minifilter 驱动程序,这将允许你在更合适的上下文中做出访问决策。请参见:http://msdn.microsoft.com/en-us/library/ff540402(v=VS.85).aspx。请不要使用 hooking...尝试在网络共享上打开文件或在 Vista/7 x64 上运行它。minifilter 将会工作,而你的解决方案将会崩溃。 - Karl Strings
3个回答

6

我自己解决了这个问题。下面是代码和参考资料。

[DllImport("kernel32.dll")]
static extern uint GetFileSize(IntPtr hFile, IntPtr lpFileSizeHigh);

[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern IntPtr CreateFileMapping(
    IntPtr hFile,
    IntPtr lpFileMappingAttributes,
    FileMapProtection flProtect,
    uint dwMaximumSizeHigh,
    uint dwMaximumSizeLow,
    [MarshalAs(UnmanagedType.LPTStr)]string lpName);

[Flags]
public enum FileMapProtection : uint
{
    PageReadonly = 0x02,
    PageReadWrite = 0x04,
    PageWriteCopy = 0x08,
    PageExecuteRead = 0x20,
    PageExecuteReadWrite = 0x40,
    SectionCommit = 0x8000000,
    SectionImage = 0x1000000,
    SectionNoCache = 0x10000000,
    SectionReserve = 0x4000000,
}

[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr MapViewOfFile(
    IntPtr hFileMappingObject,
    FileMapAccess dwDesiredAccess,
    uint dwFileOffsetHigh,
    uint dwFileOffsetLow,
    uint dwNumberOfBytesToMap);

[Flags]
public enum FileMapAccess : uint
{
    FileMapCopy = 0x0001,
    FileMapWrite = 0x0002,
    FileMapRead = 0x0004,
    FileMapAllAccess = 0x001f,
    fileMapExecute = 0x0020,
}

[DllImport("psapi.dll", SetLastError = true)]
public static extern uint GetMappedFileName(IntPtr m_hProcess, IntPtr lpv, StringBuilder 
        lpFilename, uint nSize);

[DllImport("kernel32.dll", SetLastError = true)]
static extern bool UnmapViewOfFile(IntPtr lpBaseAddress);

[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool CloseHandle(IntPtr hObject);

public static string GetFileNameFromHandle(IntPtr FileHandle)
{
    string fileName = String.Empty;
    IntPtr fileMap = IntPtr.Zero, fileSizeHi = IntPtr.Zero;
    UInt32 fileSizeLo = 0;

    fileSizeLo = GetFileSize(FileHandle, fileSizeHi);

    if (fileSizeLo == 0)
    {
        // cannot map an 0 byte file
        return "Empty file.";
    }

    fileMap = CreateFileMapping(FileHandle, IntPtr.Zero, FileMapProtection.PageReadonly, 0, 1, null);

    if (fileMap != IntPtr.Zero)
    {
        IntPtr pMem = MapViewOfFile(fileMap, FileMapAccess.FileMapRead, 0, 0, 1);
        if (pMem != IntPtr.Zero)
        {
            StringBuilder fn = new StringBuilder(250);
            GetMappedFileName(System.Diagnostics.Process.GetCurrentProcess().Handle, pMem, fn, 250);
            if (fn.Length > 0)
            {
                UnmapViewOfFile(pMem);
                CloseHandle(FileHandle);
                return fn.ToString();
            }
            else
            {
                UnmapViewOfFile(pMem);
                CloseHandle(FileHandle);
                return "Empty filename.";
            }
        }
    }

    return "Empty filemap handle.";
}

0

来自http://msdn.microsoft.com/en-us/library/aa366789.aspx

“以下示例使用文件映射对象从文件对象句柄获取文件名。它使用CreateFileMapping和MapViewOfFile函数创建映射。接下来,它使用GetMappedFileName函数获取文件名。”

代码看起来很正常,希望能有所帮助。


1
嗯,我不确定你是否看了我的帖子,但那就是我说的 :P - Rudi
我只是确认你的代码看起来正确。根据 OP、答案和最新的 MSDN 页面,代码仍然看起来正确。那是什么原因呢? - Shaun Wilson

0
你在这里发布的代码是从MSDN复制的。 它有几个缺点:它需要一个真实的文件大于0字节才能工作。它不能用于0字节的文件,也不能用于目录。(我甚至没有谈论网络驱动器) 我已经发布了一个完美运行的代码: 如何获取与打开HANDLE相关联的名称

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