FileLocksmith.cpp
1 #include "pch.h" 2 3 #include "FileLocksmith.h" 4 #include "NtdllExtensions.h" 5 6 static bool is_directory(const std::wstring path) 7 { 8 DWORD attributes = GetFileAttributesW(path.c_str()); 9 return attributes != INVALID_FILE_ATTRIBUTES && attributes & FILE_ATTRIBUTE_DIRECTORY; 10 } 11 12 // C++20 method 13 static constexpr bool starts_with(std::wstring_view whole, std::wstring_view part) 14 { 15 return whole.size() >= part.size() && whole.substr(0, part.size()) == part; 16 } 17 18 std::vector<ProcessResult> find_processes_recursive(const std::vector<std::wstring>& paths) 19 { 20 NtdllExtensions nt_ext; 21 22 // TODO use a trie! 23 24 // This maps kernel names of files within `paths` to their normal paths. 25 std::map<std::wstring, std::wstring> kernel_names_files; 26 27 // This maps kernel names of directories within `paths` to their normal paths. 28 std::map<std::wstring, std::wstring> kernel_names_dirs; 29 30 for (const auto& path : paths) 31 { 32 auto kernel_path = nt_ext.path_to_kernel_name(path.c_str()); 33 if (!kernel_path.empty()) 34 { 35 (is_directory(path) ? kernel_names_dirs : kernel_names_files)[kernel_path] = path; 36 } 37 } 38 39 std::map<ULONG_PTR, std::set<std::wstring>> pid_files; 40 41 // Returns a normal path of the file specified by kernel_name, if it matches 42 // the search criteria. Otherwise, return an empty string. 43 auto kernel_paths_contain = [&](const std::wstring& kernel_name) -> std::wstring 44 { 45 // Normal equivalence 46 if (auto it = kernel_names_files.find(kernel_name); it != kernel_names_files.end()) 47 { 48 return it->second; 49 } 50 51 if (auto it = kernel_names_dirs.find(kernel_name); it != kernel_names_dirs.end()) 52 { 53 return it->second; 54 } 55 56 for (const auto& [dir_kernel_name, dir_path] : kernel_names_dirs) 57 { 58 if (starts_with(kernel_name, dir_kernel_name + (dir_kernel_name.length()>0&&dir_kernel_name[dir_kernel_name.length()-1]!=L'\\' ? L"\\" : L""))) 59 { 60 return dir_path + kernel_name.substr(dir_kernel_name.size()); 61 } 62 } 63 64 return {}; 65 }; 66 67 for (const auto& handle_info : nt_ext.handles()) 68 { 69 if (handle_info.type_name == L"File") 70 { 71 auto path = kernel_paths_contain(handle_info.kernel_file_name); 72 if (!path.empty()) 73 { 74 pid_files[handle_info.pid].insert(std::move(path)); 75 } 76 } 77 } 78 79 // Check all modules used by processes 80 auto processes = nt_ext.processes(); 81 82 for (const auto& process : processes) 83 { 84 for (const auto& path : process.modules) 85 { 86 auto kernel_name = nt_ext.path_to_kernel_name(path.c_str()); 87 88 auto found_path = kernel_paths_contain(kernel_name); 89 if (!found_path.empty()) 90 { 91 pid_files[process.pid].insert(std::move(found_path)); 92 } 93 } 94 } 95 96 std::vector<ProcessResult> result; 97 98 for (const auto& process_info : processes) 99 { 100 if (auto it = pid_files.find(process_info.pid); it != pid_files.end()) 101 { 102 result.push_back(ProcessResult 103 { 104 process_info.name, 105 process_info.pid, 106 process_info.user, 107 std::vector(it->second.begin(), it->second.end()) 108 }); 109 } 110 } 111 112 return result; 113 } 114 115 constexpr size_t LongMaxPathSize = 65536; 116 117 std::wstring pid_to_full_path(DWORD pid) 118 { 119 HANDLE process = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid); 120 121 std::wstring result(LongMaxPathSize, L'\0'); 122 123 // Returns zero on failure, so it's okay to resize to zero. 124 auto length = GetModuleFileNameExW(process, NULL, result.data(), static_cast<DWORD>(result.size())); 125 result.resize(length); 126 127 CloseHandle(process); 128 return result; 129 }