이 글을 읽고 있다면, 소프트웨어를 위해 DOS 및/또는 NT 경로를 변환하는 방법을 찾고 있다는 뜻입니다. 오늘 여기서 정확히 그것을 배울 수 있으니 안심하세요!

Windows 경로 의 문제점은 분명합니다. 매우 혼란스럽습니다. 다시 말하겠습니다, 매우 혼란스럽습니다. 훌륭한 소프트웨어를 개발하면서 DOS와 NT 경로를 변환해야 할 때 이것이 문제가 됩니다.

이 글에서는 간결하게 설명하겠습니다. 시작해봅시다.

ntdll 내에 다양한 변환에 사용할 수 있는 비공개 함수들이 있습니다. 이 함수 목록은 Rtl*로 시작하며 다음과 같습니다:

  • RtlDosPathNameToNtPathName_U (모든 Windows)
  • RtlDosPathNameToNtPathName_U_WithStatus (모든 Windows)
  • RtlDosLongPathNameToNtPathName_U_WithStatus (Windows 10 Redstone 3)
  • RtlDosPathNameToRelativeNtPathName_U (모든 Windows)
  • RtlDosPathNameToRelativeNtPathName_U_WithStatus (모든 Windows)
  • RtlDosLongPathNameToRelativeNtPathName_U_WithStatus (Windows 10 Redstone 3)
  • RtlNtPathNameToDosPathName (모든 Windows; 이 함수는 상당히 신비로운데, 어디서도 정의나 정보를 찾을 수 없기 때문입니다. 하지만 모든 함수를 적절히 테스트하고 출력을 확인하겠습니다.)

위 함수들은 동일한 입출력 매개변수를 가집니다:

  • DosFileName (입력 - 필수): 대상 파일 또는 디렉토리의 DOS 이름을 포함하는 상수 문자열.
  • NtFileName (출력 - 필수): 파일 또는 디렉토리의 NT 경로를 반환합니다. 초기화할 필요가 없는 UNICODE_STRING을 사용해야 합니다.
  • FilePart (출력 - 선택): 출력 시 생성된 경로에서 파일 이름 주소가 포인터에 입력됩니다. 이 매개변수가 출력에서 NULL로 채워지면 디렉토리 이름만 지정됩니다.
  • RelativeName (출력 - 선택): 현재 디렉토리에 대한 정보를 포함하는 RTL_RELATIVE_NAME_U 구조체에 대한 포인터.

이 함수들을 테스트하기 위해 3개의 경로를 정의했습니다:

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

RtlDosPathNameToNtPathName_U 사용

이 함수는 BOOLEAN을 반환합니다.

  • TRUE - 경로 변환 작업이 성공적으로 완료되었습니다.
  • FALSE - 경로 변환 작업이 실패했습니다.

코드:

 1{
 2UNICODE_STRING Converted{};
 3RTL_RELATIVE_NAME_U RelativeName{};
 4PWSTR FilePart{};
 5
 6    static const wchar_t NormalNamespace[MAX_PATH]  = L"C:\\Windows\\System32\\csrss.exe"; // Normal Win32 namespace
 7    static const wchar_t DeviceNamespace[MAX_PATH]  = L"\\\\.\\C:\\Windows\\System32\\csrss.exe"; // Win32 Device Namespace
 8    static const wchar_t FileNamespace[MAX_PATH]    = L"\\\\?\\C:\\Windows\\System32\\csrss.exe"; // Win32 Device Namespace
 9    static const wchar_t DevicePath[MAX_PATH]       = L"\\Device\\HarddiskVolume1\\Windows\\System32\\csrss.exe"; // Win32 Device Path
10    static const wchar_t ExtraTest[MAX_PATH]        = L"directory.name\\file.name"; // Relative Path
11
12    pRtlDosPathNameToNtPathName_U _RtlDosPathNameToNtPathName_U = reinterpret_cast<pRtlDosPathNameToNtPathName_U>(GetProcAddress(GetModuleHandle(L"ntdll.dll"), "RtlDosPathNameToNtPathName_U"));
13
14    if (_RtlDosPathNameToNtPathName_U(NormalNamespace, &Converted, &FilePart, &RelativeName))
15    {
16        std::wcout << L"[NormalNamespace] (previous): " << NormalNamespace << std::endl;
17        std::wcout << L"[NormalNamespace] (converted): " << Converted.Buffer << std::endl;
18        std::wcout << L"[NormalNamespace] FilePart: " << FilePart << std::endl;
19        std::wcout << L"[NormalNamespace] RelativeName.Buffer: " << ((RelativeName.RelativeName.Buffer != NULL) ? RelativeName.RelativeName.Buffer : L"NULL") << std::endl;
20        std::wcout << L"[NormalNamespace] ContainingDirectory: " << (void*)RelativeName.ContainingDirectory << std::endl;
21        std::wcout << L"[NormalNamespace] CurDirRef->DirectoryHandle: " << ((RelativeName.CurDirRef != nullptr) ? (void*)RelativeName.CurDirRef->DirectoryHandle : L"NULL") << std::endl;
22        std::wcout << L"[NormalNamespace] CurDirRef->ReferenceCount: " << ((RelativeName.CurDirRef != nullptr) ? RelativeName.CurDirRef->ReferenceCount : 0UL) << std::endl;
23    }
24
25    std::wcout << L"------------------------------------------------------------------------" << std::endl;
26
27    Converted = { 0 };
28    RelativeName = { 0 };
29    FilePart = { 0 };
30
31    if (_RtlDosPathNameToNtPathName_U(DeviceNamespace, &Converted, &FilePart, &RelativeName))
32    {
33        std::wcout << L"[DeviceNamespace] (previous): " << DeviceNamespace << std::endl;
34        std::wcout << L"[DeviceNamespace] (converted): " << Converted.Buffer << std::endl;
35        std::wcout << L"[DeviceNamespace] FilePart: " << FilePart << std::endl;
36        std::wcout << L"[DeviceNamespace] RelativeName.Buffer: " << ((RelativeName.RelativeName.Buffer != NULL) ? RelativeName.RelativeName.Buffer : L"NULL") << std::endl;
37        std::wcout << L"[DeviceNamespace] ContainingDirectory: " << (void*)RelativeName.ContainingDirectory << std::endl;
38        std::wcout << L"[DeviceNamespace] CurDirRef->DirectoryHandle: " << ((RelativeName.CurDirRef != nullptr) ? (void*)RelativeName.CurDirRef->DirectoryHandle : L"NULL") << std::endl;
39        std::wcout << L"[DeviceNamespace] CurDirRef->ReferenceCount: " << ((RelativeName.CurDirRef != nullptr) ? RelativeName.CurDirRef->ReferenceCount : 0UL) << std::endl;
40    }
41
42    std::wcout << L"------------------------------------------------------------------------" << std::endl;
43
44    Converted = { 0 };
45    RelativeName = { 0 };
46    FilePart = { 0 };
47
48    if (_RtlDosPathNameToNtPathName_U(FileNamespace, &Converted, &FilePart, &RelativeName))
49    {
50        std::wcout << L"[FileNamespace] (previous): " << FileNamespace << std::endl;
51        std::wcout << L"[FileNamespace] (converted): " << Converted.Buffer << std::endl;
52        std::wcout << L"[FileNamespace] FilePart: " << FilePart << std::endl;
53        std::wcout << L"[FileNamespace] RelativeName.Buffer: " << ((RelativeName.RelativeName.Buffer != NULL) ? RelativeName.RelativeName.Buffer : L"NULL") << std::endl;
54        std::wcout << L"[FileNamespace] ContainingDirectory: " << (void*)RelativeName.ContainingDirectory << std::endl;
55        std::wcout << L"[FileNamespace] CurDirRef->DirectoryHandle: " << ((RelativeName.CurDirRef != nullptr) ? (void*)RelativeName.CurDirRef->DirectoryHandle : L"NULL") << std::endl;
56        std::wcout << L"[FileNamespace] CurDirRef->ReferenceCount: " << ((RelativeName.CurDirRef != nullptr) ? RelativeName.CurDirRef->ReferenceCount : 0UL) << std::endl;
57    }
58
59    std::wcout << L"------------------------------------------------------------------------" << std::endl;
60
61    Converted = { 0 };
62    RelativeName = { 0 };
63    FilePart = { 0 };
64
65    if (_RtlDosPathNameToNtPathName_U(DevicePath, &Converted, &FilePart, &RelativeName))
66    {
67        std::wcout << L"[DevicePath] (previous): " << DevicePath << std::endl;
68        std::wcout << L"[DevicePath] (converted): " << Converted.Buffer << std::endl;
69        std::wcout << L"[DevicePath] FilePart: " << FilePart << std::endl;
70        std::wcout << L"[DevicePath] RelativeName.Buffer: " << ((RelativeName.RelativeName.Buffer != NULL) ? RelativeName.RelativeName.Buffer : L"NULL") << std::endl;
71        std::wcout << L"[DevicePath] ContainingDirectory: " << (void*)RelativeName.ContainingDirectory << std::endl;
72        std::wcout << L"[DevicePath] CurDirRef->DirectoryHandle: " << ((RelativeName.CurDirRef != nullptr) ? (void*)RelativeName.CurDirRef->DirectoryHandle : L"NULL") << std::endl;
73        std::wcout << L"[DevicePath] CurDirRef->ReferenceCount: " << ((RelativeName.CurDirRef != nullptr) ? RelativeName.CurDirRef->ReferenceCount : 0UL) << std::endl;
74    }
75
76    std::wcout << L"------------------------------------------------------------------------" << std::endl;
77
78    Converted = { 0 };
79    RelativeName = { 0 };
80    FilePart = { 0 };
81
82    if (_RtlDosPathNameToNtPathName_U(ExtraTest, &Converted, &FilePart, &RelativeName))
83    {
84        std::wcout << L"[ExtraTest] (previous): " << ExtraTest << std::endl;
85        std::wcout << L"[ExtraTest] (converted): " << Converted.Buffer << std::endl;
86        std::wcout << L"[ExtraTest] FilePart: " << FilePart << std::endl;
87        std::wcout << L"[ExtraTest] RelativeName.Buffer: " << ((RelativeName.RelativeName.Buffer != NULL) ? RelativeName.RelativeName.Buffer : L"NULL") << std::endl;
88        std::wcout << L"[ExtraTest] ContainingDirectory: " << (void*)RelativeName.ContainingDirectory << std::endl;
89        std::wcout << L"[ExtraTest] CurDirRef->DirectoryHandle: " << ((RelativeName.CurDirRef != nullptr) ? (void*)RelativeName.CurDirRef->DirectoryHandle : L"NULL") << std::endl;
90        std::wcout << L"[ExtraTest] CurDirRef->ReferenceCount: " << ((RelativeName.CurDirRef != nullptr) ? RelativeName.CurDirRef->ReferenceCount : 0UL) << std::endl;
91    }
92
93}

결과:

RtlDosPathNameToNtPathName_U Output

참고:

상대 경로를 사용해도 함수는 정상적으로 성공합니다. 하지만 출력에는 NT 경로뿐만 아니라 현재 작업 디렉토리 경로도 앞에 추가됩니다. 마지막 테스트 결과를 확인하세요.

RtlDosPathNameToNtPathName_U_WithStatus 사용

이 함수는 BOOLEAN 대신 NTSTATUS를 반환합니다.

위의 RtlDosPathNameToNtPathName_U와 정확히 동일하지만, 이 버전에서는 함수가 실패한 경우 마지막 NTSTATUS를 가져올 수 있습니다.

RtlDosLongPathNameToNtPathName_U_WithStatus 사용

이 함수는 NTSTATUS를 반환합니다.

테스트에서 이 함수는 위와 정확히 동일한 결과를 반환했지만, 이 버전에서는 함수가 실패한 경우 마지막 NTSTATUS를 가져올 수 있습니다.

이 함수는 Windows 10 Redstone 3부터만 사용 가능합니다. 특별한 점은 찾을 수 없었습니다.

추측하건대 더 긴 경로를 변환할 수 있을 것입니다. 함수 이름에 “long"이 포함된 이유가 그것일 겁니다.

RtlDosPathNameToRelativeNtPathName_U 사용

이 함수는 BOOLEAN을 반환합니다.

테스트에서 이 함수는 위와 정확히 동일한 결과를 반환했으므로, 무엇이 다른지 잘 모르겠습니다. (댓글을 남겨주세요)

RtlDosPathNameToRelativeNtPathName_U_WithStatus 사용

이 함수는 BOOLEAN 대신 NTSTATUS를 반환합니다.

테스트에서 이 함수는 위와 정확히 동일한 결과를 반환했으므로, 무엇이 다른지 잘 모르겠습니다. (댓글을 남겨주세요)

RtlDosLongPathNameToRelativeNtPathName_U_WithStatus 사용

이 함수는 BOOLEAN 대신 NTSTATUS를 반환합니다.

역시 이 함수도 동일한 결과를 반환했습니다. 아마 제가 잘못 호출하고 있는 것일 수도 있습니다. (댓글을 남겨주세요)

이 함수는 Windows 10 Redstone 3부터만 사용 가능합니다.

RtlNtPathNameToDosPathName 사용

이 함수는 NTSTATUS를 반환합니다.

이 함수 자체는 흥미로운데, 위에서 달성한 것을 역으로 수행하는 것이 목적입니다. 솔직히 테스트하기 위한 문서를 찾는 데 상당히 어려움을 겪었습니다.

  • Flags (입력 - 선택): 무엇을 위한 것인지 모르겠습니다.
  • Path (입출력 - 필수): 초기화된 RTL_UNICODE_STRING_BUFFER 구조체에 대한 포인터. 실행 후 결과도 포함됩니다.
  • Disposition (입력 - 선택): 무엇을 위한 것인지 모르겠습니다.
  • FilePart (출력 - 선택): 무엇을 위한 것인지 모르겠습니다.

테스트에서 이 함수로는 경로만 변환할 수 있고, 전체 경로(예: 파일까지의 경로)는 변환할 수 없었습니다. 전체 경로를 시도하면 변환하지 않지만, 정상적으로 실행됩니다 o.O

코드:

 1{
 2
 3    RTL_UNICODE_STRING_BUFFER DosPath = { 0 };
 4    PWSTR FilePart{};
 5
 6    UNICODE_STRING NtPath;
 7    RtlInitUnicodeString(&NtPath, L"\\??\\C:\\WINDOWS\\system32\\drivers");
 8
 9    wchar_t* DosPathBuffer = NULL;
10    wchar_t* NtPathBuffer = NULL;
11
12    DosPathBuffer = (wchar_t*)new char[NtPath.Length + sizeof(wchar_t)];
13    NtPathBuffer  = (wchar_t*)new char[NtPath.Length + sizeof(wchar_t)];
14
15    RtlZeroMemory(DosPathBuffer, NtPath.Length + sizeof(wchar_t));
16    RtlZeroMemory(NtPathBuffer, NtPath.Length + sizeof(wchar_t));
17    RtlCopyMemory(DosPathBuffer, NtPath.Buffer, NtPath.Length);
18    RtlCopyMemory(NtPathBuffer, NtPath.Buffer, NtPath.Length);
19
20    DosPath.ByteBuffer.Buffer       = DosPathBuffer;
21    DosPath.ByteBuffer.StaticBuffer = NtPathBuffer;
22    DosPath.String.Buffer           = NtPath.Buffer;
23    DosPath.String.Length           = NtPath.Length;
24    DosPath.String.MaximumLength    = NtPath.Length;
25    DosPath.ByteBuffer.Size         = NtPath.Length;
26    DosPath.ByteBuffer.StaticSize   = NtPath.Length;
27
28    pRtlNtPathNameToDosPathName _RtlNtPathNameToDosPathName = reinterpret_cast<pRtlNtPathNameToDosPathName>(GetProcAddress(GetModuleHandle(L"ntdll.dll"), "RtlNtPathNameToDosPathName"));
29
30    if (NT_SUCCESS(_RtlNtPathNameToDosPathName(0, &DosPath, NULL, &FilePart)))
31    {
32        //RtlCopyMemory(pszDosPath, ByteDosPathBuffer, wcslen(ByteDosPathBuffer) * sizeof(wchar_t));
33
34        std::wcout << L"[NtPath] (previous): " << NtPath.Buffer << std::endl;
35        std::wcout << L"[DosPathBuffer] (converted): " << DosPathBuffer << std::endl;
36        std::wcout << L"[NtPathBuffer] (untouched): " << NtPathBuffer << std::endl;
37        std::wcout << L"[NormalNamespace] FilePart: " << ((FilePart != nullptr) ? FilePart : L"NULL") << std::endl;
38    }
39
40    std::wcout << L"------------------------------------------------------------------------" << std::endl;
41
42}

결과:

RtlNtPathNameToDosPathName Output

제가 게시한 코드 예제가 혼란스럽지만, 상황을 정리해보겠습니다. 이 함수는 제공된 구조체에서 NT 경로를 교체합니다.

함수 실행 전에 브레이크포인트를 설정했습니다:

RtlNtPathNameToDosPathName Debug 1

그리고 함수 실행 후 변경사항을 확인할 수 있습니다:

RtlNtPathNameToDosPathName Debug 2

이것이 NT에서 DOS 경로 변환을 수행하는 독특한 함수입니다.

이 함수들에 대해 더 자세한 정보를 제공하지 못해 죄송합니다. 추가 정보가 있으시면 아래 댓글을 남겨주세요.

다음에 뵙겠습니다.