如何将本地(NT)路径名转换为Win32路径名?

10
我正在报告从本地系统API中获取的一些信息。(我知道这是不好的...但我在得到无法通过其他方式获得的信息,如果必要,我更新我的应用程序也没什么问题。)
本机API返回本机路径名,如ob所见,即\SystemRoot\System32\Ntoskrnl.exe\??\C:\Program Files\VMWare Workstation\vstor-ws60.sys
我可以替换常见的前缀,例如:
std::wstring NtPathToWin32Path( std::wstring ntPath )
{
    if (boost::starts_with(ntPath, L"\\\\?\\"))
    {
        ntPath.erase(ntPath.begin(), ntPath.begin() + 4);
        return ntPath;
    }
    if (boost::starts_with(ntPath, L"\\??\\"))
    {
        ntPath.erase(ntPath.begin(), ntPath.begin() + 4);
    }
    if (boost::starts_with(ntPath, L"\\"))
    {
        ntPath.erase(ntPath.begin(), ntPath.begin() + 1);
    }
    if (boost::istarts_with(ntPath, L"globalroot\\"))
    {
        ntPath.erase(ntPath.begin(), ntPath.begin() + 11);
    }
    if (boost::istarts_with(ntPath, L"systemroot"))
    {
        ntPath.replace(ntPath.begin(), ntPath.begin() + 10, GetWindowsPath());
    }
    if (boost::istarts_with(ntPath, L"windows"))
    {
        ntPath.replace(ntPath.begin(), ntPath.begin() + 7, GetWindowsPath());
    }
    return ntPath;
}

TEST(Win32Path, NtPathDoubleQuestions)
{
    ASSERT_EQ(L"C:\\Example", NtPathToWin32Path(L"\\??\\C:\\Example"));
}

TEST(Win32Path, NtPathUncBegin)
{
    ASSERT_EQ(L"C:\\Example", NtPathToWin32Path(L"\\\\?\\C:\\Example"));
}

TEST(Win32Path, NtPathWindowsStart)
{
    ASSERT_EQ(GetCombinedPath(GetWindowsPath(), L"Hello\\World"), NtPathToWin32Path(L"\\Windows\\Hello\\World"));
}

TEST(Win32Path, NtPathSystemrootStart)
{
    ASSERT_EQ(GetCombinedPath(GetWindowsPath(), L"Hello\\World"), NtPathToWin32Path(L"\\SystemRoot\\Hello\\World"));
}

TEST(Win32Path, NtPathGlobalRootSystemRoot)
{
    ASSERT_EQ(GetCombinedPath(GetWindowsPath(), L"Hello\\World"), NtPathToWin32Path(L"\\globalroot\\SystemRoot\\Hello\\World"));
}

但是如果没有一些API(本地或其他方式)可以将它们转换为Win32路径名,我会非常惊讶。这样的API存在吗?


@Praetorian:不,PathCanonicalize接受Win32路径。我正在尝试获取一个Win32路径。 - Billy ONeal
1
我不知道有这样的功能,而且这并不总是可能的:NT可以使用Win32根本无法使用的路径。无论如何,祝你好运... - ephemient
1
@Billy:这里有个你可以尝试的方法。首先使用NtCreateFile(http://msdn.microsoft.com/en-us/library/bb432380%28v=vs.85%29.aspx)打开文件、卷等以进行读取。然后使用返回的`HANDLE`按照这里所述的方式获取完整路径http://msdn.microsoft.com/en-us/library/aa366789%28v=vs.85%29.aspx。 - Praetorian
对我来说似乎不是最终答案。当然,FindFirstFile可以轻松构建Win32路径;您需要提供一个起始路径,而ZwQueryDirectoryFile则使用一个句柄。当然,如果有一个API可以从句柄返回Win32路径,那么您的问题将得到解决。 - ephemient
1
@ephemient:啊——看起来这是在Vista中添加的。无论如何,可以调用NtQueryFileInformation并从句柄请求FILE_NAMES_INFORMATION - Billy ONeal
显示剩余12条评论
7个回答

8
我们在生产代码中进行这项工作。据我所知,没有处理这个的 API(公开或私有)。我们只是做一些字符串比较,并使用一些前缀,这对我们有效。
显然,在 ntdll.dll 中有一个名为 RtlNtPathNameToDosPathName() 的函数(从 XP 开始使用?),但我不知道它的作用; 我猜它更多地涉及到像 \Device\Harddisk0 这样的东西。
虽然我不确定是否真的需要这样的函数。Win32将路径(如CreateFile等)传递给NT;NT不将路径传递给Win32。因此,ntdll.dll 实际上没有必要从 NT 路径转换为 Win32 路径。在某些情况下,当某些 NT 查询函数返回完整路径时,任何转换函数都可以在 Win32 dll 内部执行(例如不导出)。我甚至不知道他们是否会费心去这样做,因为诸如 GetModuleFileName() 的东西只会返回用于加载图像的任何路径。我想这只是一个泄漏的抽象。

基本上来说,我只在 \??\ 和 \SystemRoot 上有过常见的看法,即使如此这也相当少见(通常出现在引导序列中早期启动的低级进程中)。有时它们也会出现在注册表中,通常与这些进程有关。 - Luke
我正在研究一些未记录的东西,这里似乎所有的前缀都很常见。我想它们之所以未被记录,肯定有原因 :P - Billy ONeal
2
微软没有提供这个API的文档是一件遗憾的事情。在Windows内部,NT路径一直被转换为DOS路径,但我们程序员却不能这样做。如果你想知道如何使用RtlNtPathNameToDosPathName(),可以看看这里:http://forum.sysinternals.com/rtlntpathnametodospathname_topic27697.html - Elmue

6
这是您可以尝试的方法。首先使用NtCreateFile打开要读取的文件、卷等。然后使用返回的HANDLE按此处所述获取完整路径。

1
注意,可以通过调用NtQueryFileInformation并请求信息类FileNameInformation来跳过文件映射步骤。(虽然我还没有测试过,当然这是进入未记录的内容...) - Billy ONeal
GetFinalPathNameByHandle 可能有助于清理这个问题! - Gbps
这种方法对我很有效。对于Windows Vista及更高版本,有一种更简单的方法可以通过使用此函数来获取句柄的路径:https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfinalpathnamebyhandlew - undefined

4
这有点晚,但我仍然会发表我的答案,因为即使今天这仍然是一个非常好的问题!
我将分享一个已经测试和使用过的函数,用于将NT转换为DOS路径。在我的情况下,我还必须从ANSI转换为UNICODE,所以这是一个小的奖励,让你看到和了解如何完成这个操作。
所有这些代码都可以在用户模式下使用,因此我们需要先准备一些东西。
定义和结构:
typedef NTSTATUS(WINAPI* pRtlAnsiStringToUnicodeString)(PUNICODE_STRING, PANSI_STRING, BOOL);

typedef struct _RTL_BUFFER {
    PUCHAR    Buffer;
    PUCHAR    StaticBuffer;
    SIZE_T    Size;
    SIZE_T    StaticSize;
    SIZE_T    ReservedForAllocatedSize; // for future doubling
    PVOID     ReservedForIMalloc; // for future pluggable growth
} RTL_BUFFER, * PRTL_BUFFER;

typedef struct _RTL_UNICODE_STRING_BUFFER {
    UNICODE_STRING String;
    RTL_BUFFER     ByteBuffer;
    UCHAR          MinimumStaticBufferForTerminalNul[sizeof(WCHAR)];
} RTL_UNICODE_STRING_BUFFER, * PRTL_UNICODE_STRING_BUFFER;

#define RTL_NT_PATH_NAME_TO_DOS_PATH_NAME_AMBIGUOUS   (0x00000001)
#define RTL_NT_PATH_NAME_TO_DOS_PATH_NAME_UNC         (0x00000002)
#define RTL_NT_PATH_NAME_TO_DOS_PATH_NAME_DRIVE       (0x00000003)
#define RTL_NT_PATH_NAME_TO_DOS_PATH_NAME_ALREADY_DOS (0x00000004)

typedef NTSTATUS(WINAPI* pRtlNtPathNameToDosPathName)(__in ULONG Flags, __inout PRTL_UNICODE_STRING_BUFFER Path, __out_opt PULONG Disposition, __inout_opt PWSTR* FilePart);

#define RTL_DUPLICATE_UNICODE_STRING_NULL_TERMINATE (0x00000001)
#define RTL_DUPLICATE_UNICODE_STRING_ALLOCATE_NULL_STRING (0x00000002)
#define RTL_DUPSTR_ADD_NULL                          RTL_DUPLICATE_UNICODE_STRING_NULL_TERMINATE
#define RTL_DUPSTR_ALLOC_NULL                        RTL_DUPLICATE_UNICODE_STRING_ALLOCATE_NULL_STRING

typedef NTSTATUS(WINAPI* pRtlDuplicateUnicodeString)(_In_ ULONG Flags, _In_ PUNICODE_STRING StringIn, _Out_ PUNICODE_STRING StringOut);

导入函数:

pRtlAnsiStringToUnicodeString MyRtlAnsiStringToUnicodeString;
pRtlNtPathNameToDosPathName MyRtlNtPathNameToDosPathName;
pRtlDuplicateUnicodeString MyRtlDuplicateUnicodeString;

MyRtlAnsiStringToUnicodeString = (pRtlAnsiStringToUnicodeString)GetProcAddress(GetModuleHandle("ntdll.dll"), "RtlAnsiStringToUnicodeString");
MyRtlNtPathNameToDosPathName = (pRtlNtPathNameToDosPathName)GetProcAddress(GetModuleHandle("ntdll.dll"), "RtlNtPathNameToDosPathName");
MyRtlDuplicateUnicodeString = (pRtlDuplicateUnicodeString)GetProcAddress(GetModuleHandle("ntdll.dll"), "RtlDuplicateUnicodeString");

辅助函数:

NTSTATUS NtPathNameToDosPathName(PUNICODE_STRING DosPath, PUNICODE_STRING NtPath)
{
    NTSTATUS                    Status;
    ULONG_PTR                   BufferSize;
    PWSTR                       Buffer;
    RTL_UNICODE_STRING_BUFFER   UnicodeBuffer;

    BufferSize = NtPath->MaximumLength + MAX_PATH * sizeof(WCHAR);

    Buffer = (PWSTR)_alloca(BufferSize);

    ZeroMemory(&UnicodeBuffer, sizeof(UnicodeBuffer));

    UnicodeBuffer.String = *NtPath;
    UnicodeBuffer.String.Buffer = Buffer;
    UnicodeBuffer.String.MaximumLength = (USHORT)BufferSize;
    UnicodeBuffer.ByteBuffer.Buffer = (PUCHAR)Buffer;
    UnicodeBuffer.ByteBuffer.Size = BufferSize;

    CopyMemory(Buffer, NtPath->Buffer, NtPath->Length);

    MyRtlNtPathNameToDosPathName(0, &UnicodeBuffer, NULL, NULL);

    return MyRtlDuplicateUnicodeString(RTL_DUPSTR_ADD_NULL, &UnicodeBuffer.String, DosPath);
}

函数使用:

UNICODE_STRING us;
UNICODE_STRING DosPath;
ANSI_STRING as;

as.Buffer = (char*)malloc(strlen(NT_PATH_FILE_OR_DIR) + 1);
strcpy(as.Buffer, NT_PATH_FILE_OR_DIR);
as.Length = as.MaximumLength = us.MaximumLength = us.Length = strlen(NT_PATH_FILE_OR_DIR);

MyRtlAnsiStringToUnicodeString(&us, &as, TRUE);

NtPathNameToDosPathName(&DosPath, &us);

如上所述,在我的情况下,我需要将ANSI转换为UNICODE,这可能不适用于您的情况,因此您可以将其删除。

与上面相同,可以使用它来创建自定义函数并根据需要转换路径。


2

这个可以运行 -- 但问题在于你需要访问文件的位置才能执行它(因为你需要打开一个句柄)-- 我经常没有那个权限。 - Billy ONeal
@Billy:你所需的是 FILE_READ_ATTRIBUTES 访问权限,几乎总是可以获得的。如果无法获取,请尝试在父目录上运行它,然后在末尾连接文件名。(因为它并不总是一对一映射,所以不能没有句柄进行转换。) - user541686
@Billy:实际上,FindFirstFile确实会打开一个目录句柄!你为什么认为它不会呢? :) - user541686
@Billy:但是就像我在评论中提到的那样,你不能只尝试在父目录上使用我的方法,然后在末尾连接文件名吗?看起来应该可以。 - user541686
@Billy:嗯,好的...如果你在使用当前方法时遇到问题,那么我猜可以试试这个。 :) - user541686
显示剩余4条评论

2

1
+1 -- 这很有趣,但它是关于将Win32路径转换为另一种Win32路径。我一开始就没有Win32路径。PathXxx函数对我所拥有的东西不起作用。 - Billy ONeal

0
在我需要同样的东西时,我偶然看到了这个帖子。我正在使用EnumDeviceDriversGetDeviceDriverFileName函数,得到的文件名是NT格式的。然后我想要在结果上调用GetFileVersionInfo,但它不接受该路径格式。
我首先尝试使用未记录的RtlNtPathNameToDosPathName函数,但对我来说没有起作用 - 路径有一个\\SystemRoot前缀,而该函数似乎对此没有做任何处理。最终起作用的是https://dev59.com/hlLTa4cB1Zd3GeqPcaqd#4452159中建议的NtCreateFile方法。从句柄获取路径的更简单方法是使用GetFinalPathNameByHandle,该方法从Windows Vista开始可用。
以下是我的完整代码:
#include <windows.h>
#include <winternl.h>


std::wstring NtPathToDosPath(const std::wstring& Path)
{
  UNICODE_STRING PathString;
  RtlInitUnicodeString(&PathString, Path.data());

  OBJECT_ATTRIBUTES ObjectAttributes;
  InitializeObjectAttributes(&ObjectAttributes, &PathString, OBJ_CASE_INSENSITIVE, nullptr, nullptr);

  HANDLE FileHandle;
  IO_STATUS_BLOCK   IoStatus;

  const auto Status = NtCreateFile(&FileHandle,
                             GENERIC_READ | SYNCHRONIZE,
                             &ObjectAttributes,
                             &IoStatus,
                             nullptr,
                             FILE_ATTRIBUTE_NORMAL,
                             FILE_SHARE_READ,
                             FILE_OPEN,
                             FILE_SYNCHRONOUS_IO_NONALERT | FILE_COMPLETE_IF_OPLOCKED,
                             nullptr,
                             0);

  if (!NT_SUCCESS(Status))
  {
    // Report error
    return {};
  }

  const auto NeededSize = GetFinalPathNameByHandleW(FileHandle, nullptr, 0, 0);

  std::wstring OutPath;
  OutPath.resize(NeededSize);

  if (!GetFinalPathNameByHandleW(FileHandle, &(OutPath[0]), OutPath.size(), 0))
  {
    // Report error
    return {};
  }

  return OutPath;
}

0
我编写了一个函数,可以将不同类型的NT设备名称(文件名、COM端口、网络路径等)转换为DOS路径。
这里有两个函数。一个将句柄转换为NT路径,而另一个将此NT路径转换为DOS路径。
请参考这里: 如何获取与打开句柄关联的名称
// "\Device\HarddiskVolume3"                                (Harddisk Drive)
// "\Device\HarddiskVolume3\Temp"                           (Harddisk Directory)
// "\Device\HarddiskVolume3\Temp\transparent.jpeg"          (Harddisk File)
// "\Device\Harddisk1\DP(1)0-0+6\foto.jpg"                  (USB stick)
// "\Device\TrueCryptVolumeP\Data\Passwords.txt"            (Truecrypt Volume)
// "\Device\Floppy0\Autoexec.bat"                           (Floppy disk)
// "\Device\CdRom1\VIDEO_TS\VTS_01_0.VOB"                   (DVD drive)
// "\Device\Serial1"                                        (real COM port)
// "\Device\USBSER000"                                      (virtual COM port)
// "\Device\Mup\ComputerName\C$\Boot.ini"                   (network drive share,  Windows 7)
// "\Device\LanmanRedirector\ComputerName\C$\Boot.ini"      (network drive share,  Windwos XP)
// "\Device\LanmanRedirector\ComputerName\Shares\Dance.m3u" (network folder share, Windwos XP)
// "\Device\Afd"                                            (internet socket)
// "\Device\Console000F"                                    (unique name for any Console handle)
// "\Device\NamedPipe\Pipename"                             (named pipe)
// "\BaseNamedObjects\Objectname"                           (named mutex, named event, named semaphore)
// "\REGISTRY\MACHINE\SOFTWARE\Classes\.txt"                (HKEY_CLASSES_ROOT\.txt)

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