comparison dep/animia/src/win32.cpp @ 62:4c6dd5999b39

*: update 1. updated animia 2. use widestrings for filesystem on Windows
author Paper <mrpapersonic@gmail.com>
date Sun, 01 Oct 2023 06:16:06 -0400
parents 6ff7aabeb9d7
children eab9e623eb84
comparison
equal deleted inserted replaced
61:327568ad9be9 62:4c6dd5999b39
1 /**
2 * win32.cpp
3 * - provides support for Windows clients
4 *
5 **/
1 #include "win32.h" 6 #include "win32.h"
2 #include <windows.h> 7 #include <fileapi.h>
3 #include <winternl.h> 8 #include <handleapi.h>
9 #include <iostream>
4 #include <libloaderapi.h> 10 #include <libloaderapi.h>
5 #include <ntdef.h> 11 #include <ntdef.h>
6 #include <psapi.h> 12 #include <psapi.h>
13 #include <shlobj.h>
14 #include <stdexcept>
15 #include <string>
16 #include <stringapiset.h>
7 #include <tlhelp32.h> 17 #include <tlhelp32.h>
8 #include <fileapi.h> 18 #include <unordered_map>
9 #include <handleapi.h>
10 #include <vector> 19 #include <vector>
11 #include <iostream> 20 #include <windows.h>
12 #include <string> 21 #include <winternl.h>
13 #include <unordered_map> 22
14 #include <stdexcept>
15 #include <locale>
16 #include <codecvt>
17 /* This file is noticably more complex than Unix and Linux, and that's because 23 /* This file is noticably more complex than Unix and Linux, and that's because
18 there is no "simple" way to get the paths of a file. */ 24 there is no "simple" way to get the paths of a file. In fact, this thing requires
25 you to use *internal functions* that can't even be linked to, hence why we have to
26 use GetProcAddress and such. What a mess. */
19 27
20 #define SystemExtendedHandleInformation ((SYSTEM_INFORMATION_CLASS)0x40) 28 #define SystemExtendedHandleInformation ((SYSTEM_INFORMATION_CLASS)0x40)
21 constexpr NTSTATUS STATUS_INFO_LENGTH_MISMATCH = 0xC0000004UL; 29 constexpr NTSTATUS STATUS_INFO_LENGTH_MISMATCH = 0xC0000004UL;
22 constexpr NTSTATUS STATUS_SUCCESS = 0x00000000UL; 30 constexpr NTSTATUS STATUS_SUCCESS = 0x00000000UL;
23 31
24 static unsigned short file_type_index = 0; 32 static unsigned short file_type_index = 0;
25 33
26 struct SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX { 34 struct SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX {
27 PVOID Object; 35 PVOID Object;
28 ULONG_PTR UniqueProcessId; 36 ULONG_PTR UniqueProcessId;
29 HANDLE HandleValue; 37 HANDLE HandleValue;
30 ACCESS_MASK GrantedAccess; 38 ACCESS_MASK GrantedAccess;
31 USHORT CreatorBackTraceIndex; 39 USHORT CreatorBackTraceIndex;
32 USHORT ObjectTypeIndex; 40 USHORT ObjectTypeIndex;
33 ULONG HandleAttributes; 41 ULONG HandleAttributes;
34 ULONG Reserved; 42 ULONG Reserved;
35 }; 43 };
36 44
37 struct SYSTEM_HANDLE_INFORMATION_EX { 45 struct SYSTEM_HANDLE_INFORMATION_EX {
38 ULONG_PTR NumberOfHandles; 46 ULONG_PTR NumberOfHandles;
39 ULONG_PTR Reserved; 47 ULONG_PTR Reserved;
40 SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX Handles[1]; 48 SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX Handles[1];
41 }; 49 };
42 50
43 namespace Animia::Windows { 51 namespace Animia::Windows {
44
45 std::vector<int> get_all_pids() {
46 std::vector<int> ret;
47 HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
48 PROCESSENTRY32 pe32;
49 pe32.dwSize = sizeof(PROCESSENTRY32);
50
51 if (hProcessSnap == INVALID_HANDLE_VALUE)
52 return std::vector<int>();
53
54 if (!Process32First(hProcessSnap, &pe32))
55 return std::vector<int>();
56
57 ret.push_back(pe32.th32ProcessID);
58 while (Process32Next(hProcessSnap, &pe32)) {
59 ret.push_back(pe32.th32ProcessID);
60 }
61 // clean the snapshot object
62 CloseHandle(hProcessSnap);
63
64 return ret;
65 }
66
67 std::string get_process_name(int pid) {
68 HANDLE handle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
69 if (!handle)
70 return "";
71
72 std::string ret(MAX_PATH, '\0');
73 if (!GetModuleBaseNameA(handle, 0, &ret.front(), ret.size()))
74 throw std::runtime_error("GetModuleBaseNameA failed: " + std::to_string(GetLastError()));
75 CloseHandle(handle);
76
77 return ret;
78 }
79 52
80 /* All of this BS is required on Windows. Why? */ 53 /* All of this BS is required on Windows. Why? */
81 54
82 HANDLE DuplicateHandle(HANDLE process_handle, HANDLE handle) { 55 HANDLE DuplicateHandle(HANDLE process_handle, HANDLE handle) {
83 HANDLE dup_handle = nullptr; 56 HANDLE dup_handle = nullptr;
84 const bool result = ::DuplicateHandle(process_handle, handle, 57 const bool result =
85 ::GetCurrentProcess(), &dup_handle, 0, false, DUPLICATE_SAME_ACCESS); 58 ::DuplicateHandle(process_handle, handle, ::GetCurrentProcess(), &dup_handle, 0, false, DUPLICATE_SAME_ACCESS);
86 return result ? dup_handle : nullptr; 59 return result ? dup_handle : nullptr;
87 } 60 }
88 61
89 PVOID GetNTDLLAddress(LPCSTR proc_name) { 62 PVOID GetNTDLLAddress(LPCSTR proc_name) {
90 return reinterpret_cast<PVOID>(GetProcAddress(GetModuleHandleA("ntdll.dll"), proc_name)); 63 return reinterpret_cast<PVOID>(::GetProcAddress(::GetModuleHandleA("ntdll.dll"), proc_name));
91 } 64 }
92 65
93 NTSTATUS QuerySystemInformation(SYSTEM_INFORMATION_CLASS cls, PVOID sysinfo, ULONG len, PULONG retlen) { 66 NTSTATUS QuerySystemInformation(SYSTEM_INFORMATION_CLASS cls, PVOID sysinfo, ULONG len, PULONG retlen) {
94 static const auto func = reinterpret_cast<decltype(::NtQuerySystemInformation)*>(GetNTDLLAddress("NtQuerySystemInformation")); 67 static const auto func =
68 reinterpret_cast<decltype(::NtQuerySystemInformation)*>(GetNTDLLAddress("NtQuerySystemInformation"));
95 return func(cls, sysinfo, len, retlen); 69 return func(cls, sysinfo, len, retlen);
96 } 70 }
97 71
98 NTSTATUS QueryObject(HANDLE handle, OBJECT_INFORMATION_CLASS cls, PVOID objinf, ULONG objinflen, PULONG retlen) { 72 NTSTATUS QueryObject(HANDLE handle, OBJECT_INFORMATION_CLASS cls, PVOID objinf, ULONG objinflen, PULONG retlen) {
99 static const auto func = reinterpret_cast<decltype(::NtQueryObject)*>(GetNTDLLAddress("NtQueryObject")); 73 static const auto func = reinterpret_cast<decltype(::NtQueryObject)*>(GetNTDLLAddress("NtQueryObject"));
110 status = STATUS_NO_MEMORY; 84 status = STATUS_NO_MEMORY;
111 85
112 if (!(info = (SYSTEM_HANDLE_INFORMATION_EX*)malloc(cb *= 2))) 86 if (!(info = (SYSTEM_HANDLE_INFORMATION_EX*)malloc(cb *= 2)))
113 continue; 87 continue;
114 88
89 res.reserve(cb);
90
115 if (0 <= (status = QuerySystemInformation(SystemExtendedHandleInformation, info, cb, &cb))) { 91 if (0 <= (status = QuerySystemInformation(SystemExtendedHandleInformation, info, cb, &cb))) {
116 if (ULONG_PTR handles = info->NumberOfHandles) { 92 if (ULONG_PTR handles = info->NumberOfHandles) {
117 SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX* entry = info->Handles; 93 SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX* entry = info->Handles;
118 do { 94 do {
119 if (entry) res.push_back(*entry); 95 if (entry)
96 res.push_back(*entry);
120 } while (entry++, --handles); 97 } while (entry++, --handles);
121 } 98 }
122 } 99 }
123 free(info); 100 free(info);
124 } while (status == STATUS_INFO_LENGTH_MISMATCH); 101 } while (status == STATUS_INFO_LENGTH_MISMATCH);
130 OBJECT_TYPE_INFORMATION info; 107 OBJECT_TYPE_INFORMATION info;
131 QueryObject(handle, ObjectTypeInformation, &info, sizeof(info), NULL); 108 QueryObject(handle, ObjectTypeInformation, &info, sizeof(info), NULL);
132 return info; 109 return info;
133 } 110 }
134 111
135 std::string UnicodeStringToStdString(UNICODE_STRING string) { 112 /* we're using UTF-8. originally, I had used just the ANSI versions of functions, but that
136 ANSI_STRING result; 113 sucks massive dick. this way we get unicode in the way every single other OS does it */
137 static const auto uc_to_ansi = reinterpret_cast<decltype(::RtlUnicodeStringToAnsiString)*>(GetNTDLLAddress("RtlUnicodeStringToAnsiString")); 114 std::string UnicodeStringToUtf8(std::wstring string) {
138 uc_to_ansi(&result, &string, TRUE); 115 unsigned long size = ::WideCharToMultiByte(CP_UTF8, 0, string.c_str(), string.length(), NULL, 0, NULL, NULL);
139 std::string ret = std::string(result.Buffer, result.Length); 116 std::string ret = std::string(size, '\0');
140 static const auto free_ansi = reinterpret_cast<decltype(::RtlFreeAnsiString)*>(GetNTDLLAddress("RtlFreeAnsiString")); 117 ::WideCharToMultiByte(CP_UTF8, 0, string.c_str(), string.length(), &ret.front(), ret.length(), NULL, NULL);
141 free_ansi(&result); 118 return ret;
119 }
120
121 std::string UnicodeStringToUtf8(UNICODE_STRING string) {
122 unsigned long size = ::WideCharToMultiByte(CP_UTF8, 0, string.Buffer, string.Length, NULL, 0, NULL, NULL);
123 std::string ret = std::string(size, '\0');
124 ::WideCharToMultiByte(CP_UTF8, 0, string.Buffer, string.Length, &ret.front(), ret.length(), NULL, NULL);
125 return ret;
126 }
127
128 std::wstring Utf8StringToUnicode(std::string string) {
129 unsigned long size = ::MultiByteToWideChar(CP_UTF8, 0, string.c_str(), string.length(), NULL, 0);
130 std::wstring ret = std::wstring(size, L'\0');
131 ::MultiByteToWideChar(CP_UTF8, 0, string.c_str(), string.length(), &ret.front(), ret.length());
142 return ret; 132 return ret;
143 } 133 }
144 134
145 std::string GetHandleType(HANDLE handle) { 135 std::string GetHandleType(HANDLE handle) {
146 OBJECT_TYPE_INFORMATION info = QueryObjectTypeInfo(handle); 136 OBJECT_TYPE_INFORMATION info = QueryObjectTypeInfo(handle);
147 return UnicodeStringToStdString(info.TypeName); 137 return UnicodeStringToUtf8(info.TypeName);
148 } 138 }
149 139
150 /* GetFinalPathNameByHandleA literally just doesn't work */
151 std::string GetFinalPathNameByHandle(HANDLE handle) { 140 std::string GetFinalPathNameByHandle(HANDLE handle) {
152 std::wstring buffer; 141 std::wstring buffer;
153 142
154 int result = ::GetFinalPathNameByHandleW(handle, NULL, 0, FILE_NAME_NORMALIZED | VOLUME_NAME_DOS); 143 int result = ::GetFinalPathNameByHandleW(handle, NULL, 0, FILE_NAME_NORMALIZED | VOLUME_NAME_DOS);
155 buffer.resize(result); 144 buffer.resize(result);
156 ::GetFinalPathNameByHandleW(handle, &buffer.front(), buffer.size(), FILE_NAME_NORMALIZED | VOLUME_NAME_DOS); 145 ::GetFinalPathNameByHandleW(handle, &buffer.front(), buffer.size(), FILE_NAME_NORMALIZED | VOLUME_NAME_DOS);
157 146 buffer.resize(buffer.find('\0'));
158 std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t> converter; 147
159 148 return UnicodeStringToUtf8(buffer);
160 return converter.to_bytes(buffer);
161 }
162
163 std::string GetSystemDirectory() {
164 std::string windir = std::string(MAX_PATH, '\0');
165 ::GetWindowsDirectoryA(&windir.front(), windir.length());
166 return "\\\\?\\" + windir;
167 }
168
169 /* This function is useless. I'm not exactly sure why, but whenever I try to compare the two
170 values, they both come up as different. I'm assuming it's just some Unicode BS I can't be bothered
171 to deal with. */
172 bool IsSystemDirectory(const std::string& path) {
173 std::string path_l = path;
174 CharUpperBuffA(&path_l.front(), path_l.length());
175
176 std::string windir = GetSystemDirectory();
177 CharUpperBuffA(&windir.front(), windir.length());
178
179 return path_l.rfind(windir, 0) != std::string::npos;
180 } 149 }
181 150
182 bool IsFileHandle(HANDLE handle, unsigned short object_type_index) { 151 bool IsFileHandle(HANDLE handle, unsigned short object_type_index) {
183 if (file_type_index) 152 if (file_type_index)
184 return object_type_index == file_type_index; 153 return object_type_index == file_type_index;
190 } 159 }
191 return false; 160 return false;
192 } 161 }
193 162
194 bool IsFileMaskOk(ACCESS_MASK access_mask) { 163 bool IsFileMaskOk(ACCESS_MASK access_mask) {
195 /* this filters out any file handles that, legitimately,
196 do not make sense (for what we're using it for)
197
198 shoutout to erengy for having these in Anisthesia */
199
200 if (!(access_mask & FILE_READ_DATA)) 164 if (!(access_mask & FILE_READ_DATA))
201 return false; 165 return false;
202 166
203 if ((access_mask & FILE_APPEND_DATA) || 167 if ((access_mask & FILE_APPEND_DATA) || (access_mask & FILE_WRITE_EA) || (access_mask & FILE_WRITE_ATTRIBUTES))
204 (access_mask & FILE_WRITE_EA) ||
205 (access_mask & FILE_WRITE_ATTRIBUTES))
206 return false; 168 return false;
207 169
208 return true; 170 return true;
209 } 171 }
210 172
211 bool IsFilePathOk(const std::string& path) { 173 bool IsFilePathOk(const std::string& path) {
212 if (path.empty() || IsSystemDirectory(path)) 174 if (path.empty())
213 return false; 175 return false;
214 176
215 const auto file_attributes = GetFileAttributesA(path.c_str()); 177 const auto file_attributes = GetFileAttributesA(path.c_str());
216 if ((file_attributes == INVALID_FILE_ATTRIBUTES) || 178 if ((file_attributes == INVALID_FILE_ATTRIBUTES) || (file_attributes & FILE_ATTRIBUTE_DIRECTORY))
217 (file_attributes & FILE_ATTRIBUTE_DIRECTORY))
218 return false; 179 return false;
219 180
220 return true; 181 return true;
221 } 182 }
222 183
184 std::string GetSystemDirectory() {
185 PWSTR path_wch;
186 SHGetKnownFolderPath(FOLDERID_Windows, 0, NULL, &path_wch);
187 std::wstring path_wstr(path_wch);
188 CoTaskMemFree(path_wch);
189 return UnicodeStringToUtf8(path_wstr);
190 }
191
192 bool IsSystemFile(const std::string& path) {
193 std::wstring path_w = Utf8StringToUnicode(path);
194 CharUpperBuffW(&path_w.front(), path_w.length());
195 std::wstring windir_w = Utf8StringToUnicode(GetSystemDirectory());
196 CharUpperBuffW(&windir_w.front(), windir_w.length());
197 return path_w.find(windir_w) == 4;
198 }
199
200 std::vector<int> get_all_pids() {
201 std::vector<int> ret;
202 HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
203 PROCESSENTRY32 pe32;
204 pe32.dwSize = sizeof(PROCESSENTRY32);
205
206 if (hProcessSnap == INVALID_HANDLE_VALUE)
207 return std::vector<int>();
208
209 if (!Process32First(hProcessSnap, &pe32))
210 return std::vector<int>();
211
212 ret.push_back(pe32.th32ProcessID);
213 while (Process32Next(hProcessSnap, &pe32)) {
214 ret.push_back(pe32.th32ProcessID);
215 }
216 // clean the snapshot object
217 CloseHandle(hProcessSnap);
218
219 return ret;
220 }
221
222 std::string get_process_name(int pid) {
223 unsigned long size = 256, ret_size = 0;
224 HANDLE handle = ::OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
225 if (!handle)
226 return "";
227
228 std::wstring ret(size, L'\0');
229 while (size < 32768) {
230 ret.resize(size, L'\0');
231
232 if (!(ret_size = ::GetModuleBaseNameW(handle, 0, &ret.front(), ret.length())))
233 ret = L"";
234 else if (size > ret_size)
235 ret.resize(ret.find('\0'));
236
237 size *= 2;
238 }
239
240 CloseHandle(handle);
241
242 return UnicodeStringToUtf8(ret);
243 }
244
223 std::vector<std::string> get_open_files(int pid) { 245 std::vector<std::string> get_open_files(int pid) {
224 std::unordered_map<int, std::vector<std::string>> map = get_all_open_files(); 246 std::vector<std::string> ret;
225 return map[pid]; 247 std::vector<SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX> info = GetSystemHandleInformation();
248 for (auto& h : info) {
249 if (h.UniqueProcessId != pid)
250 continue;
251
252 if (!IsFileHandle(nullptr, h.ObjectTypeIndex))
253 continue;
254 if (!IsFileMaskOk(h.GrantedAccess))
255 continue;
256
257 const HANDLE proc = ::OpenProcess(PROCESS_DUP_HANDLE, false, pid);
258 HANDLE handle = DuplicateHandle(proc, h.HandleValue);
259 if (!handle)
260 continue;
261
262 if (GetFileType(handle) != FILE_TYPE_DISK)
263 continue;
264
265 std::string path = GetFinalPathNameByHandle(handle);
266 if (!IsFilePathOk(path))
267 continue;
268
269 ret.push_back(path);
270 }
271 return ret;
272 }
273
274 std::vector<std::string> filter_system_files(const std::vector<std::string>& source) {
275 std::vector<std::string> ret;
276 for (const std::string& s : source) {
277 if (!IsSystemFile(s))
278 ret.push_back(s);
279 }
280 return ret;
226 } 281 }
227 282
228 std::unordered_map<int, std::vector<std::string>> get_all_open_files() { 283 std::unordered_map<int, std::vector<std::string>> get_all_open_files() {
229 std::unordered_map<int, std::vector<std::string>> map; 284 std::unordered_map<int, std::vector<std::string>> map;
230 std::vector<SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX> info = GetSystemHandleInformation(); 285 std::vector<SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX> info = GetSystemHandleInformation();
253 map[pid].push_back(path); 308 map[pid].push_back(path);
254 } 309 }
255 return map; 310 return map;
256 } 311 }
257 312
258 } 313 } // namespace Animia::Windows