이 글을 읽고 있다면, 소프트웨어를 위해 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}결과:
참고:
상대 경로를 사용해도 함수는 정상적으로 성공합니다. 하지만 출력에는 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}결과:
제가 게시한 코드 예제가 혼란스럽지만, 상황을 정리해보겠습니다. 이 함수는 제공된 구조체에서 NT 경로를 교체합니다.
함수 실행 전에 브레이크포인트를 설정했습니다:
그리고 함수 실행 후 변경사항을 확인할 수 있습니다:
이것이 NT에서 DOS 경로 변환을 수행하는 독특한 함수입니다.
이 함수들에 대해 더 자세한 정보를 제공하지 못해 죄송합니다. 추가 정보가 있으시면 아래 댓글을 남겨주세요.
다음에 뵙겠습니다.
댓글