├── CMakeLists.txt ├── LICENSE ├── README.md └── include └── superfetch ├── nt.h └── superfetch.h /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.17) 2 | 3 | project( 4 | superfetch 5 | DESCRIPTION "Translate virtual addresses to physical addresses from usermode" 6 | HOMEPAGE_URL "https://github.com/jonomango/superfetch" 7 | LANGUAGES CXX C 8 | ) 9 | 10 | add_library( 11 | superfetch INTERFACE 12 | "include/superfetch/superfetch.h" 13 | "include/superfetch/nt.h" 14 | ) 15 | 16 | target_include_directories( 17 | superfetch INTERFACE 18 | "include" 19 | ) 20 | 21 | target_link_libraries( 22 | superfetch INTERFACE 23 | ntdll 24 | ) 25 | 26 | # C++23, C11. 27 | set_target_properties( 28 | superfetch PROPERTIES 29 | CXX_STANDARD 23 30 | CXX_STANDARD_REQUIRED ON 31 | C_STANDARD 11 32 | C_STANDARD_REQUIRED ON 33 | ) 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 jono 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # superfetch 2 | Simple library for translating virtual addresses to physical addresses from usermode. 3 | 4 | ## Example usage 5 | 6 | ```cpp 7 | #include 8 | 9 | int main() { 10 | auto const mm = spf::memory_map::current(); 11 | if (!mm) { 12 | // Do something with mm.error() 13 | } 14 | 15 | // Any kernel virtual address. 16 | void const* const virt = ...; 17 | 18 | std::uint64_t const phys = mm->translate(virt); 19 | if (!phys) { 20 | // Do something... 21 | } 22 | 23 | std::printf("%p -> %zX\n", virt, phys); 24 | } 25 | ``` 26 | 27 | ## Installation 28 | 29 | To use `superfetch`, simply add the `include` directory to your project and include 30 | `superfetch/superfetch.h`. 31 | 32 | ### CMake 33 | 34 | If you are using [CMake](https://cmake.org/), add this repository as a 35 | subdirectory and link to the `superfetch` target library. 36 | 37 | ```cmake 38 | add_subdirectory(path/to/superfetch/repo/dir) 39 | target_link_libraries(my_project_target superfetch) 40 | ``` 41 | 42 | -------------------------------------------------------------------------------- /include/superfetch/nt.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace spf { 7 | 8 | enum SUPERFETCH_INFORMATION_CLASS { 9 | SuperfetchRetrieveTrace = 1, // Query 10 | SuperfetchSystemParameters = 2, // Query 11 | SuperfetchLogEvent = 3, // Set 12 | SuperfetchGenerateTrace = 4, // Set 13 | SuperfetchPrefetch = 5, // Set 14 | SuperfetchPfnQuery = 6, // Query 15 | SuperfetchPfnSetPriority = 7, // Set 16 | SuperfetchPrivSourceQuery = 8, // Query 17 | SuperfetchSequenceNumberQuery = 9, // Query 18 | SuperfetchScenarioPhase = 10, // Set 19 | SuperfetchWorkerPriority = 11, // Set 20 | SuperfetchScenarioQuery = 12, // Query 21 | SuperfetchScenarioPrefetch = 13, // Set 22 | SuperfetchRobustnessControl = 14, // Set 23 | SuperfetchTimeControl = 15, // Set 24 | SuperfetchMemoryListQuery = 16, // Query 25 | SuperfetchMemoryRangesQuery = 17, // Query 26 | SuperfetchTracingControl = 18, // Set 27 | SuperfetchTrimWhileAgingControl = 19, 28 | SuperfetchInformationMax = 20 29 | }; 30 | 31 | struct SUPERFETCH_INFORMATION { 32 | ULONG Version = 45; 33 | ULONG Magic = 'kuhC'; 34 | SUPERFETCH_INFORMATION_CLASS InfoClass; 35 | PVOID Data; 36 | ULONG Length; 37 | }; 38 | 39 | struct MEMORY_FRAME_INFORMATION { 40 | ULONGLONG UseDescription : 4; 41 | ULONGLONG ListDescription : 3; 42 | ULONGLONG Reserved0 : 1; 43 | ULONGLONG Pinned : 1; 44 | ULONGLONG DontUse : 48; 45 | ULONGLONG Priority : 3; 46 | ULONGLONG Reserved : 4; 47 | }; 48 | 49 | struct FILEOFFSET_INFORMATION { 50 | ULONGLONG DontUse : 9; 51 | ULONGLONG Offset : 48; 52 | ULONGLONG Reserved : 7; 53 | }; 54 | 55 | struct PAGEDIR_INFORMATION { 56 | ULONGLONG DontUse : 9; 57 | ULONGLONG PageDirectoryBase : 48; 58 | ULONGLONG Reserved : 7; 59 | }; 60 | 61 | struct UNIQUE_PROCESS_INFORMATION { 62 | ULONGLONG DontUse : 9; 63 | ULONGLONG UniqueProcessKey : 48; 64 | ULONGLONG Reserved : 7; 65 | }; 66 | 67 | struct MMPFN_IDENTITY { 68 | union { 69 | MEMORY_FRAME_INFORMATION e1; 70 | FILEOFFSET_INFORMATION e2; 71 | PAGEDIR_INFORMATION e3; 72 | UNIQUE_PROCESS_INFORMATION e4; 73 | } u1; 74 | SIZE_T PageFrameIndex; 75 | union { 76 | struct { 77 | ULONG Image : 1; 78 | ULONG Mismatch : 1; 79 | } e1; 80 | PVOID FileObject; 81 | PVOID UniqueFileObjectKey; 82 | PVOID ProtoPteAddress; 83 | PVOID VirtualAddress; 84 | } u2; 85 | }; 86 | 87 | struct SYSTEM_MEMORY_LIST_INFORMATION { 88 | SIZE_T ZeroPageCount; 89 | SIZE_T FreePageCount; 90 | SIZE_T ModifiedPageCount; 91 | SIZE_T ModifiedNoWritePageCount; 92 | SIZE_T BadPageCount; 93 | SIZE_T PageCountByPriority[8]; 94 | SIZE_T RepurposedPagesByPriority[8]; 95 | ULONG_PTR ModifiedPageCountPageFile; 96 | }; 97 | 98 | struct PF_PFN_PRIO_REQUEST { 99 | ULONG Version; 100 | ULONG RequestFlags; 101 | SIZE_T PfnCount; 102 | SYSTEM_MEMORY_LIST_INFORMATION MemInfo; 103 | MMPFN_IDENTITY PageData[ANYSIZE_ARRAY]; 104 | }; 105 | 106 | struct PF_PHYSICAL_MEMORY_RANGE { 107 | ULONG_PTR BasePfn; 108 | ULONG_PTR PageCount; 109 | }; 110 | 111 | struct PF_MEMORY_RANGE_INFO_V1 { 112 | ULONG Version = 1; 113 | ULONG RangeCount; 114 | PF_PHYSICAL_MEMORY_RANGE Ranges[ANYSIZE_ARRAY]; 115 | }; 116 | 117 | struct PF_MEMORY_RANGE_INFO_V2 { 118 | ULONG Version = 2; 119 | ULONG Flags; 120 | ULONG RangeCount; 121 | PF_PHYSICAL_MEMORY_RANGE Ranges[ANYSIZE_ARRAY]; 122 | }; 123 | 124 | inline constexpr ULONG SE_PROF_SINGLE_PROCESS_PRIVILEGE = 13; 125 | inline constexpr ULONG SE_DEBUG_PRIVILEGE = 20; 126 | 127 | inline constexpr SYSTEM_INFORMATION_CLASS SystemSuperfetchInformation = SYSTEM_INFORMATION_CLASS(79); 128 | 129 | extern "C" NTSYSAPI NTSTATUS RtlAdjustPrivilege(ULONG, BOOLEAN, BOOLEAN, PBOOLEAN); 130 | 131 | } // namespace spf 132 | -------------------------------------------------------------------------------- /include/superfetch/superfetch.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "nt.h" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace spf { 11 | 12 | struct memory_range { 13 | std::uint64_t pfn = 0; 14 | std::size_t page_count = 0; 15 | }; 16 | 17 | enum class spf_error { 18 | raise_privilege, 19 | query_ranges, 20 | query_pfn 21 | }; 22 | 23 | using memory_ranges = std::vector; 24 | using memory_translations = std::unordered_map; 25 | 26 | class memory_map { 27 | public: 28 | // Create a snapshot of the current system memory map. 29 | static std::expected current(); 30 | 31 | // Translate a virtual address to a physical address. 32 | std::uint64_t translate(void const* address) const; 33 | 34 | // Get a vector of physical memory ranges. 35 | memory_ranges const& ranges() const; 36 | 37 | // Get a map of virtual to physical page translations. 38 | memory_translations const& translations() const; 39 | 40 | private: 41 | static bool raise_privilege(); 42 | 43 | static memory_ranges query_memory_ranges(); 44 | static memory_ranges query_memory_ranges_v1(); 45 | static memory_ranges query_memory_ranges_v2(); 46 | 47 | static NTSTATUS query_superfetch_info( 48 | SUPERFETCH_INFORMATION_CLASS info_class, 49 | PVOID buffer, 50 | ULONG length, 51 | PULONG return_length = nullptr 52 | ); 53 | 54 | private: 55 | // Contiguous physical memory ranges. 56 | memory_ranges ranges_ = {}; 57 | 58 | // Virtual to physical page translations. 59 | memory_translations translations_ = {}; 60 | }; 61 | 62 | // Take a snapshot of the current system memory map. 63 | inline std::expected memory_map::current() { 64 | if (!raise_privilege()) 65 | return std::unexpected(spf_error::raise_privilege); 66 | 67 | memory_map mm = {}; 68 | mm.ranges_ = query_memory_ranges(); 69 | 70 | if (mm.ranges_.empty()) 71 | return std::unexpected(spf_error::query_ranges); 72 | 73 | for (auto const& [base_pfn, page_count] : mm.ranges_) { 74 | // This is a bit too big, but its not a big deal. 75 | std::size_t const buffer_length = sizeof(PF_PFN_PRIO_REQUEST) + 76 | sizeof(MMPFN_IDENTITY) * page_count; 77 | 78 | auto const buffer = std::make_unique(buffer_length); 79 | auto const request = reinterpret_cast(buffer.get()); 80 | request->Version = 1; 81 | request->RequestFlags = 1; 82 | request->PfnCount = page_count; 83 | 84 | for (std::uint64_t i = 0; i < page_count; ++i) 85 | request->PageData[i].PageFrameIndex = base_pfn + i; 86 | 87 | if (!NT_SUCCESS(query_superfetch_info( 88 | SuperfetchPfnQuery, request, buffer_length))) 89 | return std::unexpected(spf_error::query_pfn); 90 | 91 | for (std::uint64_t i = 0; i < page_count; ++i) { 92 | // Cache the translation for this page. 93 | if (void const* const virt = request->PageData[i].u2.VirtualAddress) 94 | mm.translations_[virt] = (base_pfn + i) << 12; 95 | } 96 | } 97 | 98 | return mm; 99 | } 100 | 101 | // Translate a virtual address to a physical address. 102 | inline std::uint64_t memory_map::translate(void const* const address) const { 103 | // Align to the lowest page boundary. 104 | void const* const aligned = reinterpret_cast( 105 | reinterpret_cast(address) & ~0xFFFull); 106 | 107 | auto const it = translations_.find(aligned); 108 | if (it == end(translations_)) 109 | return 0; 110 | 111 | return it->second + (reinterpret_cast(address) & 0xFFF); 112 | } 113 | 114 | // Get a vector of physical memory ranges. 115 | inline memory_ranges const& memory_map::ranges() const { 116 | return ranges_; 117 | } 118 | 119 | // Get a map of virtual to physical page translations. 120 | inline memory_translations const& memory_map::translations() const { 121 | return translations_; 122 | } 123 | 124 | inline bool memory_map::raise_privilege() { 125 | BOOLEAN old = FALSE; 126 | 127 | if (!NT_SUCCESS(RtlAdjustPrivilege( 128 | SE_PROF_SINGLE_PROCESS_PRIVILEGE, TRUE, FALSE, &old))) 129 | return false; 130 | 131 | if (!NT_SUCCESS(RtlAdjustPrivilege( 132 | SE_DEBUG_PRIVILEGE, TRUE, FALSE, &old))) 133 | return false; 134 | 135 | return true; 136 | } 137 | 138 | inline memory_ranges memory_map::query_memory_ranges() { 139 | auto ranges = query_memory_ranges_v1(); 140 | if (ranges.empty()) 141 | return query_memory_ranges_v2(); 142 | return ranges; 143 | } 144 | 145 | inline memory_ranges memory_map::query_memory_ranges_v1() { 146 | ULONG buffer_length = 0; 147 | 148 | // STATUS_BUFFER_TOO_SMALL. 149 | if (PF_MEMORY_RANGE_INFO_V1 info = {}; 0xC0000023 != query_superfetch_info( 150 | SuperfetchMemoryRangesQuery, &info, sizeof(info), &buffer_length)) 151 | return {}; 152 | 153 | auto const buffer = std::make_unique(buffer_length); 154 | auto const info = reinterpret_cast(buffer.get()); 155 | info->Version = 1; 156 | 157 | if (!NT_SUCCESS(query_superfetch_info( 158 | SuperfetchMemoryRangesQuery, info, buffer_length))) 159 | return {}; 160 | 161 | memory_ranges ranges = {}; 162 | 163 | for (std::uint32_t i = 0; i < info->RangeCount; ++i) { 164 | ranges.push_back({ 165 | .pfn = info->Ranges[i].BasePfn, 166 | .page_count = info->Ranges[i].PageCount 167 | }); 168 | } 169 | 170 | return ranges; 171 | } 172 | 173 | inline memory_ranges memory_map::query_memory_ranges_v2() { 174 | ULONG buffer_length = 0; 175 | 176 | // STATUS_BUFFER_TOO_SMALL. 177 | if (PF_MEMORY_RANGE_INFO_V2 info = {}; 0xC0000023 != query_superfetch_info( 178 | SuperfetchMemoryRangesQuery, &info, sizeof(info), &buffer_length)) 179 | return {}; 180 | 181 | auto const buffer = std::make_unique(buffer_length); 182 | auto const info = reinterpret_cast(buffer.get()); 183 | info->Version = 2; 184 | 185 | if (!NT_SUCCESS(query_superfetch_info( 186 | SuperfetchMemoryRangesQuery, info, buffer_length))) 187 | return {}; 188 | 189 | memory_ranges ranges = {}; 190 | 191 | for (std::uint32_t i = 0; i < info->RangeCount; ++i) { 192 | ranges.push_back({ 193 | .pfn = info->Ranges[i].BasePfn, 194 | .page_count = info->Ranges[i].PageCount 195 | }); 196 | } 197 | 198 | return ranges; 199 | } 200 | 201 | inline NTSTATUS memory_map::query_superfetch_info( 202 | SUPERFETCH_INFORMATION_CLASS info_class, 203 | PVOID buffer, 204 | ULONG length, 205 | PULONG return_length 206 | ) { 207 | SUPERFETCH_INFORMATION superfetch_info = { 208 | .InfoClass = info_class, 209 | .Data = buffer, 210 | .Length = length 211 | }; 212 | 213 | return NtQuerySystemInformation(SystemSuperfetchInformation, 214 | &superfetch_info, sizeof(superfetch_info), return_length); 215 | } 216 | 217 | } // spf 218 | --------------------------------------------------------------------------------