Convert DOS and NT paths using RTL functions

If you are here reading this post, it means you are looking for a way to convert your DOS and/or NT paths for your software. Rest assured that this is what you will learn here today 🙂

The problem with Windows paths are clear; it’s so confusing. Let me repeat that, so confusing. This becomes a problem when you are working on your amazing piece of software and you need to convert DOS and NT paths.

In this article I will try to keep things short and sweet, so let’s begin.

There are functions that are undocumented inside ntdll that can be used for different conversions. The list of these functions begin with Rtl* and are as follows:

  • RtlDosPathNameToNtPathName_U (all windows)
  • RtlDosPathNameToNtPathName_U_WithStatus (all windows)
  • RtlDosLongPathNameToNtPathName_U_WithStatus (windows 10 Redstone 3)
  • RtlDosPathNameToRelativeNtPathName_U (all windows)
  • RtlDosPathNameToRelativeNtPathName_U_WithStatus (all windows)
  • RtlDosLongPathNameToRelativeNtPathName_U_WithStatus (windows 10 Redstone 3)

The above functions have the same input/output parameters.

  • DosFileName (in – required): A constant string containing the DOS name of the target file or directory.
  • NtFileName(out – required): Return the NT path to a file or directory. Must use a UNICODE_STRING which does not need to be initialised.
  • FilePart (out – optional): At the output, the file name address in the generated path will be entered into the pointer. If this parameter is filled with NULL in the output, then only the directory name is specified.
  • RelativeName (out – optional): Pointer to a RTL_RELATIVE_NAME_U structure containing information about the current directory.
  • RtlNtPathNameToDosPathName (all windows)

This particular function is quite a mystery because you cannot find definitions or information about it anywhere. However we will test all these functions accordingly and see their output.

For our play with these functions I have defined 3 paths:

static const wchar_t NormalNamespace[MAX_PATH]  = L"C:\\Windows\\System32\\csrss.exe"; // Normal Win32 namespace
static const wchar_t DeviceNamespace[MAX_PATH]  = L"\\\\.\\C:\\Windows\\System32\\csrss.exe"; // Win32 Device Namespace
static const wchar_t FileNamespace[MAX_PATH]    = L"\\\\?\\C:\\Windows\\System32\\csrss.exe"; // Win32 Device Namespace
static const wchar_t DevicePath[MAX_PATH]       = L"\\Device\\HarddiskVolume1\\Windows\\System32\\csrss.exe"; // Win32 Device Path
static const wchar_t ExtraTest[MAX_PATH]        = L"directory.name\\file.name"; // Relative Path
Using RtlDosPathNameToNtPathName_U

This function will return a BOOLEAN.

  • TRUE – The path transform operation completed successfully.
  • FALSE – The path conversion operation failed.

Code:

{
    UNICODE_STRING Converted{};
    RTL_RELATIVE_NAME_U RelativeName{};
    PWSTR FilePart{};

    static const wchar_t NormalNamespace[MAX_PATH]  = L"C:\\Windows\\System32\\csrss.exe"; // Normal Win32 namespace
    static const wchar_t DeviceNamespace[MAX_PATH]  = L"\\\\.\\C:\\Windows\\System32\\csrss.exe"; // Win32 Device Namespace
    static const wchar_t FileNamespace[MAX_PATH]    = L"\\\\?\\C:\\Windows\\System32\\csrss.exe"; // Win32 Device Namespace
    static const wchar_t DevicePath[MAX_PATH]       = L"\\Device\\HarddiskVolume1\\Windows\\System32\\csrss.exe"; // Win32 Device Path
    static const wchar_t ExtraTest[MAX_PATH]        = L"directory.name\\file.name"; // Relative Path

    pRtlDosPathNameToNtPathName_U _RtlDosPathNameToNtPathName_U = reinterpret_cast<pRtlDosPathNameToNtPathName_U>(GetProcAddress(GetModuleHandle(L"ntdll.dll"), "RtlDosPathNameToNtPathName_U"));

    if (_RtlDosPathNameToNtPathName_U(NormalNamespace, &Converted, &FilePart, &RelativeName))
    {
        std::wcout << L"[NormalNamespace] (previous): " << NormalNamespace << std::endl;
        std::wcout << L"[NormalNamespace] (converted): " << Converted.Buffer << std::endl;
        std::wcout << L"[NormalNamespace] FilePart: " << FilePart << std::endl;
        std::wcout << L"[NormalNamespace] RelativeName.Buffer: " << ((RelativeName.RelativeName.Buffer != NULL) ? RelativeName.RelativeName.Buffer : L"NULL") << std::endl;
        std::wcout << L"[NormalNamespace] ContainingDirectory: " << (void*)RelativeName.ContainingDirectory << std::endl;
        std::wcout << L"[NormalNamespace] CurDirRef->DirectoryHandle: " << ((RelativeName.CurDirRef != nullptr) ? (void*)RelativeName.CurDirRef->DirectoryHandle : L"NULL") << std::endl;
        std::wcout << L"[NormalNamespace] CurDirRef->ReferenceCount: " << ((RelativeName.CurDirRef != nullptr) ? RelativeName.CurDirRef->ReferenceCount : 0UL) << std::endl;
    }

    std::wcout << L"------------------------------------------------------------------------" << std::endl;

    Converted = { 0 };
    RelativeName = { 0 };
    FilePart = { 0 };

    if (_RtlDosPathNameToNtPathName_U(DeviceNamespace, &Converted, &FilePart, &RelativeName))
    {
        std::wcout << L"[DeviceNamespace] (previous): " << DeviceNamespace << std::endl;
        std::wcout << L"[DeviceNamespace] (converted): " << Converted.Buffer << std::endl;
        std::wcout << L"[DeviceNamespace] FilePart: " << FilePart << std::endl;
        std::wcout << L"[DeviceNamespace] RelativeName.Buffer: " << ((RelativeName.RelativeName.Buffer != NULL) ? RelativeName.RelativeName.Buffer : L"NULL") << std::endl;
        std::wcout << L"[DeviceNamespace] ContainingDirectory: " << (void*)RelativeName.ContainingDirectory << std::endl;
        std::wcout << L"[DeviceNamespace] CurDirRef->DirectoryHandle: " << ((RelativeName.CurDirRef != nullptr) ? (void*)RelativeName.CurDirRef->DirectoryHandle : L"NULL") << std::endl;
        std::wcout << L"[DeviceNamespace] CurDirRef->ReferenceCount: " << ((RelativeName.CurDirRef != nullptr) ? RelativeName.CurDirRef->ReferenceCount : 0UL) << std::endl;
    }

    std::wcout << L"------------------------------------------------------------------------" << std::endl;

    Converted = { 0 };
    RelativeName = { 0 };
    FilePart = { 0 };

    if (_RtlDosPathNameToNtPathName_U(FileNamespace, &Converted, &FilePart, &RelativeName))
    {
        std::wcout << L"[FileNamespace] (previous): " << FileNamespace << std::endl;
        std::wcout << L"[FileNamespace] (converted): " << Converted.Buffer << std::endl;
        std::wcout << L"[FileNamespace] FilePart: " << FilePart << std::endl;
        std::wcout << L"[FileNamespace] RelativeName.Buffer: " << ((RelativeName.RelativeName.Buffer != NULL) ? RelativeName.RelativeName.Buffer : L"NULL") << std::endl;
        std::wcout << L"[FileNamespace] ContainingDirectory: " << (void*)RelativeName.ContainingDirectory << std::endl;
        std::wcout << L"[FileNamespace] CurDirRef->DirectoryHandle: " << ((RelativeName.CurDirRef != nullptr) ? (void*)RelativeName.CurDirRef->DirectoryHandle : L"NULL") << std::endl;
        std::wcout << L"[FileNamespace] CurDirRef->ReferenceCount: " << ((RelativeName.CurDirRef != nullptr) ? RelativeName.CurDirRef->ReferenceCount : 0UL) << std::endl;
    }

    std::wcout << L"------------------------------------------------------------------------" << std::endl;

    Converted = { 0 };
    RelativeName = { 0 };
    FilePart = { 0 };

    if (_RtlDosPathNameToNtPathName_U(DevicePath, &Converted, &FilePart, &RelativeName))
    {
        std::wcout << L"[DevicePath] (previous): " << DevicePath << std::endl;
        std::wcout << L"[DevicePath] (converted): " << Converted.Buffer << std::endl;
        std::wcout << L"[DevicePath] FilePart: " << FilePart << std::endl;
        std::wcout << L"[DevicePath] RelativeName.Buffer: " << ((RelativeName.RelativeName.Buffer != NULL) ? RelativeName.RelativeName.Buffer : L"NULL") << std::endl;
        std::wcout << L"[DevicePath] ContainingDirectory: " << (void*)RelativeName.ContainingDirectory << std::endl;
        std::wcout << L"[DevicePath] CurDirRef->DirectoryHandle: " << ((RelativeName.CurDirRef != nullptr) ? (void*)RelativeName.CurDirRef->DirectoryHandle : L"NULL") << std::endl;
        std::wcout << L"[DevicePath] CurDirRef->ReferenceCount: " << ((RelativeName.CurDirRef != nullptr) ? RelativeName.CurDirRef->ReferenceCount : 0UL) << std::endl;
    }

    std::wcout << L"------------------------------------------------------------------------" << std::endl;

    Converted = { 0 };
    RelativeName = { 0 };
    FilePart = { 0 };

    if (_RtlDosPathNameToNtPathName_U(ExtraTest, &Converted, &FilePart, &RelativeName))
    {
        std::wcout << L"[ExtraTest] (previous): " << ExtraTest << std::endl;
        std::wcout << L"[ExtraTest] (converted): " << Converted.Buffer << std::endl;
        std::wcout << L"[ExtraTest] FilePart: " << FilePart << std::endl;
        std::wcout << L"[ExtraTest] RelativeName.Buffer: " << ((RelativeName.RelativeName.Buffer != NULL) ? RelativeName.RelativeName.Buffer : L"NULL") << std::endl;
        std::wcout << L"[ExtraTest] ContainingDirectory: " << (void*)RelativeName.ContainingDirectory << std::endl;
        std::wcout << L"[ExtraTest] CurDirRef->DirectoryHandle: " << ((RelativeName.CurDirRef != nullptr) ? (void*)RelativeName.CurDirRef->DirectoryHandle : L"NULL") << std::endl;
        std::wcout << L"[ExtraTest] CurDirRef->ReferenceCount: " << ((RelativeName.CurDirRef != nullptr) ? RelativeName.CurDirRef->ReferenceCount : 0UL) << std::endl;
    }
}

Result:

RtlDosPathNameToNtPathName_U Output

See the results

Notes:

If you relative path, the function will still succeed. However the output will contain not only the NT path but it will also prepend the current working directory path. Check the last test result “ExtraTest”.

Using RtlDosPathNameToNtPathName_U_WithStatus

This function will return NTSTATUS instead of BOOLEAN.

It is exactly the same as the above one RtlDosPathNameToNtPathName_U, however with this version you can retrieve the last NTSTATUS if the function has failed.

Using RtlDosLongPathNameToNtPathName_U_WithStatus

This function will return NTSTATUS.

From my tests this function returned the exact same result as above, however with this version you can retrieve the last NTSTATUS if the function has failed.

This function is only available starting Windows 10 Redstone 3. I cannot find anything special about it.

From my guess it can convert longer paths, hence the “long” in the function name.

Using RtlDosPathNameToRelativeNtPathName_U

This function will return a BOOLEAN.

From my tests this function returned the exact same result as above, so I`m not sure what’s the deal with it. (feel free to comment)

Using RtlDosPathNameToRelativeNtPathName_U_WithStatus

This function will return NTSTATUS instead of BOOLEAN.

From my tests this function returned the exact same result as above, so I`m not sure what’s the deal with it. (feel free to comment)

Using RtlDosLongPathNameToRelativeNtPathName_U_WithStatus

This function will return NTSTATUS instead of BOOLEAN.

Once again this function returned the same results, maybe I am calling it wrong. (feel free to comment)

This function is only available starting Windows 10 Redstone 3.

Using RtlNtPathNameToDosPathName

This function will return a NTSTATUS.

The function itself is interesting, as it’s purpose is to reverse what we achieved above. To be honest I had a hard time finding some documentation, like anything at all just to test it.

  • Flags (in – optional): I have no idea what it’s for.
  • Path (in/out – required): Pointer to RTL_UNICODE_STRING_BUFFER initialised structure. It will also contain the result after execution.
  • Disposition (in – optional): I have no idea what it’s for.
  • FilePart (out- optional): I have no idea what it’s for.

From my tests you can only convert paths and not full paths (eg. to a file) with this function. If you try a full path it will not convert it, but it will still execute normally o.O

Code:

{

    RTL_UNICODE_STRING_BUFFER DosPath = { 0 };
    PWSTR FilePart{};

    UNICODE_STRING NtPath;
    RtlInitUnicodeString(&NtPath, L"\\??\\C:\\WINDOWS\\system32\\drivers");

    wchar_t* DosPathBuffer = NULL;
    wchar_t* NtPathBuffer = NULL;

    DosPathBuffer = (wchar_t*)new char[NtPath.Length + sizeof(wchar_t)];
    NtPathBuffer  = (wchar_t*)new char[NtPath.Length + sizeof(wchar_t)];

    RtlZeroMemory(DosPathBuffer, NtPath.Length + sizeof(wchar_t));
    RtlZeroMemory(NtPathBuffer, NtPath.Length + sizeof(wchar_t));
    RtlCopyMemory(DosPathBuffer, NtPath.Buffer, NtPath.Length);
    RtlCopyMemory(NtPathBuffer, NtPath.Buffer, NtPath.Length);

    DosPath.ByteBuffer.Buffer       = DosPathBuffer;
    DosPath.ByteBuffer.StaticBuffer = NtPathBuffer;
    DosPath.String.Buffer           = NtPath.Buffer;
    DosPath.String.Length           = NtPath.Length;
    DosPath.String.MaximumLength    = NtPath.Length;
    DosPath.ByteBuffer.Size         = NtPath.Length;
    DosPath.ByteBuffer.StaticSize   = NtPath.Length;

    pRtlNtPathNameToDosPathName _RtlNtPathNameToDosPathName = reinterpret_cast<pRtlNtPathNameToDosPathName>(GetProcAddress(GetModuleHandle(L"ntdll.dll"), "RtlNtPathNameToDosPathName"));

    if (NT_SUCCESS(_RtlNtPathNameToDosPathName(0, &DosPath, NULL, &FilePart)))
    {
        //RtlCopyMemory(pszDosPath, ByteDosPathBuffer, wcslen(ByteDosPathBuffer) * sizeof(wchar_t));

        std::wcout << L"[NtPath] (previous): " << NtPath.Buffer << std::endl;
        std::wcout << L"[DosPathBuffer] (converted): " << DosPathBuffer << std::endl;
        std::wcout << L"[NtPathBuffer] (untouched): " << NtPathBuffer << std::endl;
        std::wcout << L"[NormalNamespace] FilePart: " << ((FilePart != nullptr) ? FilePart : L"NULL") << std::endl;
    }

    std::wcout << L"------------------------------------------------------------------------" << std::endl;

}

Result:

RtlNtPathNameToDosPathName Output

See the result

The code example I posted is confusing, however let me try to clear the air. What happens is that this function will replace the NT paths in the structure provided.

I have set a breakpoint before the function executes:

RtlNtPathNameToDosPathName Debug

See the values before execution

And then after the function executes you can see the changes:

RtlNtPathNameToDosPathName Debug

See the values after execution

So there you have it, a weird ass function that does NT to DOS path conversion.

I`m sorry that I cannot give more details on these functions, however please feel free to comment below with any additions.

Until next time.

Liked it? Support me on Patreon with a coffee 😀

Leave a comment

Your email address will not be published. Required fields are marked *