9

I'm working on reporting some information gleaned from native system APIs. (I know this is bad.... but I'm getting information that I can't get otherwise, and I have little issue with having to update my app if/when that time comes around.)

The native API returns native pathnames, as seen by ob, i.e. \SystemRoot\System32\Ntoskrnl.exe, or \??\C:\Program Files\VMWare Workstation\vstor-ws60.sys.

I can replace common prefixes, i.e.

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"));
}

but I'd be strongly surprised if there's not some API, native or otherwise, which will convert these into Win32 path names. Does such an API exist?

Billy ONeal
  • 104,103
  • 58
  • 317
  • 552
  • Does the shell API function `PathCanonicalize` do the trick? http://msdn.microsoft.com/en-us/library/bb773569%28v=vs.85%29.aspx – Praetorian Dec 14 '10 at 23:01
  • @Praetorian: No, PathCanonicalize accepts Win32 paths. I'm trying to get a win32 path. – Billy ONeal Dec 14 '10 at 23:02
  • 1
    I don't know of any such function, and it's not always possible: NT can use paths that Win32 can't at all. Good luck anyhow… – ephemient Dec 14 '10 at 23:10
  • 1
    @Billy: Here's something you could try. First use `NtCreateFile` (http://msdn.microsoft.com/en-us/library/bb432380%28v=vs.85%29.aspx) to open the file, volume etc. for reading. Then use the returned `HANDLE` to get the full path as described here http://msdn.microsoft.com/en-us/library/aa366789%28v=vs.85%29.aspx – Praetorian Dec 14 '10 at 23:27
  • @ephemient: True, but NT's got to do it somewhere. After all, NtQueryDirectoryFile returns native paths, and FindFirstFile returns Win32 paths... – Billy ONeal Dec 14 '10 at 23:47
  • Doesn't seem definitive to me. Of course `FindFirstFile` can easily construct Win32 paths; you have to give it one to start from, unlike `ZwQueryDirectoryFile` which takes a HANDLE instead. Of course, your problem would be solved if there were an API to return a Win32 path from a HANDLE. – ephemient Dec 15 '10 at 00:51
  • If you search, you can find [Obtaining a File Name From a File Handle](http://msdn.microsoft.com/en-us/library/aa366789.aspx) on MSDN, but it still returns an NT path and *only* works for objects which can be memory mapped. – ephemient Dec 15 '10 at 00:52
  • @Ephemient: If I can open the actual handle, then I could just call GetFileInformationByHandleEx... I'd rather avoid having to actually avoid opening the handle to the actual file if at all possible though. – Billy ONeal Dec 15 '10 at 01:01
  • @Billy: Not all open file handles are created equal. If you open for `QUERY_INFORMATION` access level (and not `GENERIC_READ` or `GENERIC_WRITE`) then you shouldn't interfere in any way with other programs using the file. – Ben Voigt Dec 15 '10 at 04:20
  • @Ben: While true, that still requires me to have access to the file, which I may not have. – Billy ONeal Dec 15 '10 at 04:21
  • Rather interesting. The last time I programmed for Windows I'm pretty sure GetFileInformationByHandleEx didn't exist yet. I agree that open+query is not a pleasant way to do this (though open+mmap+query is worse), but it does seem like there's no publicly exposed method for this. – ephemient Dec 15 '10 at 05:31
  • 1
    @ephemient: Ah -- looks like it was added in Vista. No matter. One can call `NtQueryFileInformation` and ask it for `FILE_NAMES_INFORMATION` from a handle. – Billy ONeal Dec 15 '10 at 13:06
  • @Praetorian: Put that in an answer so that we can upvote it. – Billy ONeal Dec 15 '10 at 16:10
  • @Billy: Gladly, you might even have the honor bumping my rep up over 1K :-) – Praetorian Dec 15 '10 at 16:13
  • Where is GetWindowsPath() coming from ? Why not use RtlDosPathNameToNtPathName_U_WithStatus ? – Mecanik Jun 21 '18 at 20:49
  • @NorbertBoros I can't seem to find any documentation on a function of that name. – Billy ONeal Jun 22 '18 at 22:28
  • @BillyONeal I know... there are so many undocumented, but exist and used since XP to 10 :) I will post a gist or something with some of these functions and explanations. – Mecanik Jun 22 '18 at 22:39

6 Answers6

8

We do this in production code. As far as I know there is no API (public or private) that handles this. We just do some string comparisons with a few prefixes and it works for us.

Apparently there is a function named RtlNtPathNameToDosPathName() in ntdll.dll (introduced with XP?), but I have no idea what it does; I would guess it has more to do with stuff like \Device\Harddisk0, though.

I'm not sure there is really a need for such a function, though. Win32 passes paths (in the sense of CreateFile, etc) to NT; NT doesn't pass paths to Win32. So ntdll.dll doesn't really have a need to go from NT paths to Win32 paths. In the rare case where some NT query function returns a full path, any conversion function could be internal to the Win32 dll (e.g. not exported). I don't even know if they bother, as stuff like GetModuleFileName() will just return whatever path was used to load the image. I guess this is just a leaky abstraction.

Luke
  • 11,211
  • 2
  • 27
  • 38
  • Pretty much. The only ones I've seen on any regular basis are \\??\\ and \\SystemRoot, and even then that is pretty rare (typically with low-level processes launched early in the boot sequence. Also sometimes in the registry, usually related to said processes. – Luke Dec 15 '10 at 16:00
  • I'm mucking around with undocumented stuff and all those prefixes seem to be common here. I guess there's a reason they're undocumented :P – Billy ONeal Dec 16 '10 at 03:46
  • 2
    It is a shame that Microsoft does not provide a documented API for this. Internally Windows converts NT paths to DOS path all the day long, but we programmers cannot. If you are interested in how to use RtlNtPathNameToDosPathName() have a look here: http://forum.sysinternals.com/rtlntpathnametodospathname_topic27697.html – Elmue Aug 11 '15 at 03:13
5

Here's something you could try. First use NtCreateFile to open the file, volume etc. for reading. Then use the returned HANDLE to get the full path as described here.

Praetorian
  • 106,671
  • 19
  • 240
  • 328
  • 1
    +1 :) Note that one can probably skip the file mapping step by calling `NtQueryFileInformation` and asking for the information class `FileNameInformation`. (Though I haven't tested this yet, and of course that's going into the undocumented stuff...) – Billy ONeal Dec 15 '10 at 17:03
  • GetFinalPathNameByHandle might help clean this up! – Gbps Feb 19 '18 at 00:01
4

This is a bit late, but I will still post my answer since even today this is a very good question!

I will share one of my functions tested and used for converting NT to DOS path. In my case, I also had to convert from ANSI to UNICODE so this is a small bonus for you to see and understand how this can be done.

All this code can be used in User Mode, so we need to first prepare some things.

Definitions & Structures:

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);

Importing functions:

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");

Helper function:

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);
}

Function usage:

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);

As mentioned, in my case I needed to convert from ANSI to UNICODE and this might not apply for your case, thus you can remove it.

Same as above can be used to create custom functions and convert paths as needed.

Mecanik
  • 1,539
  • 1
  • 20
  • 50
2

Check this out for getting the canonical pathname in Win32. It may be helpful for you:

http://pdh11.blogspot.com/2009/05/pathcanonicalize-versus-what-it-says-on.html

miked
  • 3,458
  • 1
  • 22
  • 25
  • 1
    +1 -- this is interesting, but it's about turning a Win32 path into another kind of Win32 path. I don't have a win32 path in the first place. The PathXxx functions aren't going to work on what I have. – Billy ONeal Dec 14 '10 at 23:00
2

See my answer to this question.

You'd need to first get a handle to the file at that path, and then get the Win32 path for the handle.

Community
  • 1
  • 1
user541686
  • 205,094
  • 128
  • 528
  • 886
  • This works -- but the problem is that you need access to the file's location in order to execute it (because you need to be able to open a handle) -- I often don't have that. – Billy ONeal Jun 04 '11 at 23:37
  • @Billy: All you need is `FILE_READ_ATTRIBUTES` access, which you can almost always get. If you can't get it, try it on the parent directory, and then concatenate the file name at the end. (You can't convert without any handle, because it's not always a one-to-one mapping.) – user541686 Jun 04 '11 at 23:44
  • @Mehrdad: It's got to be possible to convert without a handle. FindFirstFile/FindNextFile/FindClose do not open handles. You can use them to list a directory structure even if you can't read any attributes on the file. This solution works, so +1, but it really doesn't help me :( – Billy ONeal Jun 04 '11 at 23:58
  • @Billy: Actually, `FindFirstFile` *does* open a handle to the directory! What makes you think it doesn't? :) – user541686 Jun 04 '11 at 23:59
  • @Mehrdad: The directory, yes. Not the files inside the directory. – Billy ONeal Jun 05 '11 at 00:11
  • @Billy: But like I mentioned in my comment, can't you just try my method on the parent directory, and concatenate the file names at the end? Seems like that would work. – user541686 Jun 05 '11 at 00:12
  • @Mehrdad: That just turns into another maze of string manipulations. That won't work out of the box, for example, with `\??\C:\Windows\..`. Stripping the last file leads to C:\Windows, tacking the .. back on leads to c:\Windows\.. . I guess you could re-canonicalize afterward... either way, the current series of string manipulations hasn't failed me yet. – Billy ONeal Jun 05 '11 at 00:24
  • @Billy: Huh, okay... if you run into problems with your current method then I guess give this a try. :) – user541686 Jun 05 '11 at 00:28
  • `GetFinalPathNameByHandle` is the best way to go, by far. You don't need to know whether the target is a file or directory, just use `CreateFileW`to get a handle for your path, passing `0` (zero) for **dwDesiredAccess** (`FILE_ACCESS`) and `FILE_FLAG_BACKUP_SEMANTICS` (only). These tell `CreateFileW` that you won't actually be opening the file, which bypasses the security problems some people mentioned. You will still be able to use `GetFinalPathNameByHandle` to resolve a full, minimal path to the target. I do this heavily and successfully to resolve cross-volume reparse points all day long. – Glenn Slayden Sep 29 '19 at 07:47
0

I wrote a function that converts different types of NT device names (filenames, COM ports, network paths, etc.) into a DOS path.

There are two functions. One converts a handle into an NT path and the other one converts this NT path into a DOS path.

Have a look here: How to get name associated with open HANDLE

// "\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)
Community
  • 1
  • 1
Elmue
  • 7,602
  • 3
  • 47
  • 57