├── LICENSE
├── README.md
├── chdr.cpp
└── chdr.h
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 ch4ncellor
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 |
2 | chdr (Cheddar 🧀)
3 |
4 |
5 | ---
6 |
7 |
8 | Cheddar 🧀 is a lightweight library geared towards windows process hacking/manipulation, but with much more use case.
9 |
10 |
11 | ---
12 |
13 |
14 | This is currently an early WIP, contributions&issues are welcomed.
15 |
16 |
17 |
--------------------------------------------------------------------------------
/chdr.cpp:
--------------------------------------------------------------------------------
1 | #include "chdr.h"
2 |
3 | // Process_t definitions and functions.
4 | namespace chdr
5 | {
6 | // Get target proces by name.
7 | Process_t::Process_t(const wchar_t* m_wszProcessName, std::int32_t m_ParseType, DWORD m_dDesiredAccess)
8 | {
9 | HANDLE m_hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
10 |
11 | PROCESSENTRY32 entry = { 0 };
12 | entry.dwSize = sizeof(entry);
13 |
14 | while (Process32Next(m_hSnapShot, &entry))
15 | {
16 | if (std::wcscmp(entry.szExeFile, m_wszProcessName) != 0)
17 | continue;
18 |
19 | this->m_nTargetProcessID = entry.th32ProcessID;
20 | this->m_hTargetProcessHandle = OpenProcess(m_dDesiredAccess, false, this->m_nTargetProcessID);
21 | break;
22 | }
23 |
24 | CloseHandle(m_hSnapShot);
25 |
26 | CH_ASSERT(true, this->m_hTargetProcessHandle && this->m_hTargetProcessHandle != INVALID_HANDLE_VALUE,
27 | "Couldn't obtain valid HANDLE for process %ws", m_wszProcessName);
28 |
29 | this->m_bShouldFreeHandleAtDestructor = this->m_hTargetProcessHandle && this->m_hTargetProcessHandle != INVALID_HANDLE_VALUE;
30 | this->m_eProcessArchitecture = this->GetProcessArchitecture_Internal();
31 |
32 | this->m_szProcessPath = this->GetProcessPath_Internal();
33 | this->m_szProcessName = this->GetProcessName_Internal();
34 |
35 | this->m_PEHeaderData = PEHeaderData_t(*this, m_ParseType);
36 | }
37 |
38 | // Get target proces by PID.
39 | Process_t::Process_t(std::uint32_t m_nProcessID, std::int32_t m_ParseType, DWORD m_dDesiredAccess)
40 | {
41 | this->m_nTargetProcessID = m_nProcessID;
42 | this->m_hTargetProcessHandle = OpenProcess(m_dDesiredAccess, false, this->m_nTargetProcessID);
43 |
44 | CH_ASSERT(true, this->m_hTargetProcessHandle && this->m_hTargetProcessHandle != INVALID_HANDLE_VALUE,
45 | "Couldn't obtain valid HANDLE for PID %i", m_nProcessID);
46 |
47 | this->m_bShouldFreeHandleAtDestructor = this->m_hTargetProcessHandle && this->m_hTargetProcessHandle != INVALID_HANDLE_VALUE;
48 | this->m_eProcessArchitecture = this->GetProcessArchitecture_Internal();
49 |
50 | this->m_szProcessPath = this->GetProcessPath_Internal();
51 | this->m_szProcessName = this->GetProcessName_Internal();
52 |
53 | this->m_PEHeaderData = PEHeaderData_t(*this, m_ParseType);
54 | }
55 |
56 | // Get target proces by HANDLE.
57 | Process_t::Process_t(HANDLE m_hProcessHandle, std::int32_t m_ParseType)
58 | {
59 | this->m_hTargetProcessHandle = m_hProcessHandle;
60 | this->m_nTargetProcessID = GetProcessId(this->m_hTargetProcessHandle);
61 |
62 | this->m_bShouldFreeHandleAtDestructor = this->m_hTargetProcessHandle && this->m_hTargetProcessHandle != INVALID_HANDLE_VALUE;
63 | this->m_eProcessArchitecture = this->GetProcessArchitecture_Internal();
64 |
65 | this->m_szProcessPath = this->GetProcessPath_Internal();
66 | this->m_szProcessName = this->GetProcessName_Internal();
67 |
68 | this->m_PEHeaderData = PEHeaderData_t(*this, m_ParseType);
69 | }
70 |
71 | // Default dtor
72 | Process_t::~Process_t()
73 | {
74 | // Just to be safe, free all of our allocated memory from this process.
75 | if (!this->m_AllocatedMemoryTracker.empty())
76 | {
77 | for (const auto& ForgottenMemory : this->m_AllocatedMemoryTracker)
78 | if (this->Free(ForgottenMemory.first)) // Has to be this ugly since I've included string enc in the log macros... :(
79 | CH_LOG("Successful free of memory at 0x%X with size 0x%X", ForgottenMemory.first, ForgottenMemory.second)
80 | else
81 | CH_LOG("Couldn't free memory at 0x%X with size 0x%X", ForgottenMemory.first, ForgottenMemory.second);
82 | }
83 |
84 | // Not allowed to release this HANDLE, or was already released.
85 | CH_ASSERT(true,
86 | this->m_bShouldFreeHandleAtDestructor &&
87 | this->m_hTargetProcessHandle &&
88 | this->m_hTargetProcessHandle != INVALID_HANDLE_VALUE,
89 | "adawdasd");
90 |
91 | CloseHandle(m_hTargetProcessHandle);
92 | }
93 |
94 | // The process ID of the target process. (lol)
95 | std::uint32_t Process_t::GetProcessID()
96 | {
97 | return this->m_nTargetProcessID;
98 | }
99 |
100 | // Ensure we found a HANDLE to the target process.
101 | bool Process_t::IsValid()
102 | {
103 | return this->m_hTargetProcessHandle && this->m_hTargetProcessHandle != INVALID_HANDLE_VALUE;
104 | }
105 |
106 | // Is this process 32-bit running on 64-bit OS?
107 | bool Process_t::IsWow64()
108 | {
109 | BOOL m_bIsWow64 = FALSE;
110 | IsWow64Process(this->m_hTargetProcessHandle, &m_bIsWow64);
111 |
112 | return m_bIsWow64;
113 | }
114 |
115 | // Get name of target process.
116 | std::string Process_t::GetProcessName_Internal()
117 | {
118 | TCHAR m_szProcessNameBuffer[MAX_PATH];
119 | if (!GetModuleBaseName(this->m_hTargetProcessHandle, NULL, m_szProcessNameBuffer, MAX_PATH))
120 | return "";
121 |
122 | m_szProcessNameBuffer[MAX_PATH - 1] = '\0';
123 |
124 | // TCHAR->string
125 | _bstr_t m_szPreProcessName(m_szProcessNameBuffer);
126 | return std::string(m_szPreProcessName);
127 | }
128 |
129 | // The base address of the target process.
130 | std::uintptr_t Process_t::GetBaseAddress()
131 | {
132 | for (auto& CurrentModule : this->EnumerateModules(true))
133 | {
134 | if (std::strcmp(CurrentModule.m_szName.c_str(), this->m_szProcessName.c_str()) != 0)
135 | continue;
136 |
137 | return CurrentModule.m_BaseAddress;
138 | }
139 | return NULL;
140 | }
141 |
142 | // Helper function to get name of target process.
143 | std::string Process_t::GetName()
144 | {
145 | return this->m_szProcessName;
146 | }
147 |
148 | // Get filesystem path of target process.
149 | std::string Process_t::GetProcessPath_Internal()
150 | {
151 | TCHAR m_szProcessPathBuffer[MAX_PATH];
152 | if (!GetModuleFileNameEx(this->m_hTargetProcessHandle, NULL, m_szProcessPathBuffer, MAX_PATH))
153 | return "";
154 |
155 | m_szProcessPathBuffer[MAX_PATH - 1] = '\0';
156 |
157 | // TCHAR->string
158 | _bstr_t m_szPreProcessPath(m_szProcessPathBuffer);
159 | return std::string(m_szPreProcessPath);
160 | }
161 |
162 | // Helper function to get filesystem path of target process.
163 | std::string Process_t::GetPath()
164 | {
165 | return this->m_szProcessPath;
166 | }
167 |
168 | // Get architecture of target process.
169 | Process_t::eProcessArchitecture Process_t::GetProcessArchitecture_Internal()
170 | {
171 | SYSTEM_INFO m_SystemInformation = { 0 };
172 | GetNativeSystemInfo(&m_SystemInformation);
173 |
174 | // Native x86, or WOW64 process.
175 | if (m_SystemInformation.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_INTEL || this->IsWow64())
176 | return eProcessArchitecture::ARCHITECTURE_x86;
177 |
178 | // Everything else should be native x64.
179 | return eProcessArchitecture::ARCHITECTURE_x64;
180 | }
181 |
182 | // Helper function to get architecture of target process.
183 | Process_t::eProcessArchitecture Process_t::GetProcessArchitecture()
184 | {
185 | return this->m_eProcessArchitecture;
186 | }
187 |
188 | // Helper function to get PE header data of target process.
189 | PEHeaderData_t Process_t::GetPEHeaderData()
190 | {
191 | return this->m_PEHeaderData;
192 | }
193 |
194 | // Did we suspend the target process ourselves?
195 | bool Process_t::IsManuallySuspended()
196 | {
197 | return this->m_bIsProcessManuallySuspended;
198 | }
199 |
200 | // Is the target process suspended?
201 | bool Process_t::IsSuspended()
202 | {
203 | // Traverse all threads, and ensure each is in a suspended state.
204 | for (const auto& CurrentThread : this->EnumerateThreads())
205 | {
206 | if (CurrentThread.m_bIsThreadSuspended)
207 | continue;
208 |
209 | return false;
210 | }
211 | return true;
212 | }
213 |
214 | // Is the target process running under a debugger?
215 | bool Process_t::IsBeingDebugged()
216 | {
217 | BOOL m_bHasRemoteDebugger = FALSE;
218 | CheckRemoteDebuggerPresent(this->m_hTargetProcessHandle, &m_bHasRemoteDebugger);
219 | return m_bHasRemoteDebugger;
220 | }
221 |
222 | // The PEB of the target process.
223 | PEB Process_t::GetPEB()
224 | {
225 | NtQueryInformationProcess_fn NtQueryInformationProcess =
226 | CH_R_CAST(GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtQueryInformationProcess"));
227 |
228 | PROCESS_BASIC_INFORMATION m_ProcessBasicInformation;
229 |
230 | // Get address where PEB resides in this target process.
231 | if (NtQueryInformationProcess(this->m_hTargetProcessHandle, PROCESSINFOCLASS::ProcessBasicInformation,
232 | &m_ProcessBasicInformation, sizeof(PROCESS_BASIC_INFORMATION), nullptr) != 0x00000000/*STATUS_SUCCESS*/)
233 | {
234 | CH_LOG("NtQueryInformationProcess failure!");
235 | return {};
236 | }
237 |
238 | // Read PEB from found base address.
239 | const PEB m_PEB = this->Read(CH_R_CAST(m_ProcessBasicInformation.PebBaseAddress));
240 | return m_PEB;
241 | }
242 |
243 | // Custom struct to pass through as lpReserved to communicate extra data to module.
244 | struct TransmittedData_t {
245 | char szKey[256];
246 | };
247 |
248 | // Data to pass through our shellcode.
249 | struct LoaderData_t {
250 | std::uintptr_t m_ModuleBase = 0u, m_ImageBase = 0u, m_EntryPoint = 0u, m_LoadLibrary = 0u, m_GetProcAddress = 0u, m_Memset = 0u;
251 | std::uint32_t m_RelocDirVA = 0u, m_RelocDirSize = 0u, m_ImportDirVA = 0u, m_PEHeaderSize = 0u, m_eInjectionFlags = 0u, m_Reason = 0u;
252 | TransmittedData_t m_CustomTransmitted = {};
253 | } LoaderData;
254 |
255 | // Code to fix up needed data, then execute DllMain in target process.
256 | void __stdcall Shellcode(LoaderData_t* m_LoaderData)
257 | {
258 | const std::uintptr_t m_TargetBase = m_LoaderData->m_ModuleBase;
259 | const std::uintptr_t m_ImageBase = m_LoaderData->m_ImageBase;
260 |
261 | // Calculate delta to relocate.
262 | const std::uintptr_t m_Delta = m_TargetBase - m_ImageBase;
263 |
264 | if (m_Delta && m_LoaderData->m_RelocDirVA)
265 | // Relocate image.
266 | {
267 | PIMAGE_BASE_RELOCATION m_pRelocation = CH_R_CAST(m_TargetBase + m_LoaderData->m_RelocDirVA);
268 | PIMAGE_BASE_RELOCATION m_pRelocationEnd = CH_R_CAST(CH_R_CAST(m_pRelocation) + m_LoaderData->m_RelocDirSize - sizeof(IMAGE_BASE_RELOCATION)/*?*/);
269 |
270 | for (; m_pRelocation < m_pRelocationEnd;
271 | m_pRelocation = CH_R_CAST(CH_R_CAST(m_pRelocation) + m_pRelocation->SizeOfBlock))
272 | {
273 | std::uint16_t* m_pRelocationType = CH_R_CAST(m_pRelocation + 0x1);
274 |
275 | const std::size_t m_nRelocationAmount = CH_S_CAST((m_pRelocation->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(std::uint16_t));
276 | for (std::size_t i = 0u; i < m_nRelocationAmount; ++i, ++m_pRelocationType)
277 | {
278 | switch (*m_pRelocationType >> 0xC)
279 | {
280 | #if defined (_WIN64)
281 | case IMAGE_REL_BASED_DIR64:
282 | #else
283 | case IMAGE_REL_BASED_HIGHLOW:
284 | #endif
285 | *CH_R_CAST(m_TargetBase + m_pRelocation->VirtualAddress + ((*m_pRelocationType) & 0xFFF)) += m_Delta;
286 | break;
287 | }
288 | }
289 | }
290 | }
291 |
292 | typedef int(__stdcall* DllMain_fn)(std::uintptr_t, std::uint32_t, void*);
293 | DllMain_fn DllMain = CH_R_CAST(m_TargetBase + m_LoaderData->m_EntryPoint);
294 |
295 | if (!m_LoaderData->m_ImportDirVA)
296 | {
297 | // Call EP of our module.
298 | DllMain(m_TargetBase, m_LoaderData->m_Reason, CH_R_CAST(&m_LoaderData->m_CustomTransmitted));
299 | return;
300 | }
301 |
302 | typedef HMODULE(__stdcall* LoadLibraryA_fn)(LPCSTR);
303 | LoadLibraryA_fn _LoadLibraryA = CH_R_CAST(m_LoaderData->m_LoadLibrary);
304 |
305 | typedef FARPROC(__stdcall* GetProcAddress_fn)(HMODULE, LPCSTR);
306 | GetProcAddress_fn _GetProcAddress = CH_R_CAST(m_LoaderData->m_GetProcAddress);
307 |
308 | // Fix up imports.
309 | PIMAGE_IMPORT_DESCRIPTOR m_pImports = CH_R_CAST(m_TargetBase + m_LoaderData->m_ImportDirVA);
310 | for (; m_pImports->Name; ++m_pImports)
311 | {
312 | const HMODULE m_hImportModule = _LoadLibraryA(CH_R_CAST(m_TargetBase + m_pImports->Name));
313 |
314 | ULONG_PTR* m_pNameReference = CH_R_CAST(m_TargetBase + m_pImports->OriginalFirstThunk);
315 | ULONG_PTR* m_pThunk = CH_R_CAST(m_TargetBase + m_pImports->FirstThunk);
316 |
317 | for (; *m_pNameReference; ++m_pNameReference, ++m_pThunk)
318 | {
319 | if (IMAGE_SNAP_BY_ORDINAL(*m_pNameReference))
320 | *CH_R_CAST(m_pThunk) = _GetProcAddress(m_hImportModule, CH_R_CAST(*m_pNameReference & 0xFFFF));
321 | else
322 | {
323 | PIMAGE_IMPORT_BY_NAME m_pThunkData = CH_R_CAST(m_TargetBase + *m_pNameReference);
324 | *CH_R_CAST(m_pThunk) = _GetProcAddress(m_hImportModule, m_pThunkData->Name);
325 | }
326 | }
327 | }
328 |
329 | // Call EP of our module.
330 | DllMain(m_TargetBase, m_LoaderData->m_Reason, CH_R_CAST(&m_LoaderData->m_CustomTransmitted));
331 |
332 | typedef void*(__stdcall* memset_fn)(std::uintptr_t, std::int32_t, std::size_t);
333 | memset_fn _memset = CH_R_CAST(m_LoaderData->m_Memset);
334 |
335 | if (m_LoaderData->m_eInjectionFlags & Process_t::eManualMapInjectionFlags::INJECTION_EXTRA_WIPEPEHEADERS)
336 | _memset(m_TargetBase, NULL, m_LoaderData->m_PEHeaderSize);
337 |
338 | if (m_LoaderData->m_eInjectionFlags & Process_t::eManualMapInjectionFlags::INJECTION_EXTRA_WIPEENTRYPOINT)
339 | _memset(m_TargetBase + m_LoaderData->m_EntryPoint, NULL, 0x20u);
340 | };
341 |
342 | // Internal manual map function.
343 | bool Process_t::ManualMapInject_Internal(std::uint8_t* m_ImageBuffer, std::int32_t m_eInjectionFlags)
344 | {
345 | // Grab DOS header.
346 | const PIMAGE_DOS_HEADER m_pDosHeaders = CH_R_CAST(m_ImageBuffer);
347 | if (m_pDosHeaders->e_magic != IMAGE_DOS_SIGNATURE)
348 | {
349 | CH_LOG("Couldn't find IMAGE_DOS_SIGNATURE for m_ImageBuffer");
350 | return false;
351 | }
352 |
353 | // Grab NT header.
354 | const PIMAGE_NT_HEADERS m_pNTHeaders = CH_R_CAST(m_ImageBuffer + m_pDosHeaders->e_lfanew);
355 | if (m_pNTHeaders->Signature != IMAGE_NT_SIGNATURE)
356 | {
357 | CH_LOG("Couldn't find IMAGE_NT_SIGNATURE for m_ImageBuffer");
358 | return false;
359 | }
360 |
361 | // Address our module will be in context of target process.
362 | const std::uintptr_t m_TargetBaseAddress = this->Allocate(m_pNTHeaders->OptionalHeader.SizeOfImage, PAGE_EXECUTE_READWRITE, false);
363 |
364 | // Copy over PE Header to target process.
365 | if (!this->Write(m_TargetBaseAddress, m_ImageBuffer, m_pNTHeaders->OptionalHeader.SizeOfImage))
366 | {
367 | CH_LOG("Couldn't copy over PE header data to target process.");
368 | return false;
369 | }
370 |
371 | // Copy over needed sections to target process.
372 | PIMAGE_SECTION_HEADER m_pSectionHeaders = IMAGE_FIRST_SECTION(m_pNTHeaders);
373 | for (std::size_t i = 0u; i < m_pNTHeaders->FileHeader.NumberOfSections; ++i, ++m_pSectionHeaders)
374 | {
375 | const std::uintptr_t m_Address = m_TargetBaseAddress + m_pSectionHeaders->VirtualAddress;
376 | if (!this->Write(m_Address, &m_ImageBuffer[m_pSectionHeaders->PointerToRawData], m_pSectionHeaders->SizeOfRawData))
377 | {
378 | CH_LOG("Couldn't copy over %s section's data to target process.", CH_R_CAST(m_pSectionHeaders->Name));
379 | return false;
380 | }
381 | }
382 |
383 | // Can use this to communicate some data to the target process.
384 | // lpReserved parameter in DllMain is unused, so popping our custom struct here is all good.
385 | TransmittedData_t m_CustomTransmittedData = {};
386 | strcpy_s<256>(m_CustomTransmittedData.szKey, "Hello from the other side!");
387 |
388 | // Populate loader data structure.
389 | LoaderData = {
390 | m_TargetBaseAddress,
391 | CH_S_CAST(m_pNTHeaders->OptionalHeader.ImageBase),
392 | CH_S_CAST(m_pNTHeaders->OptionalHeader.AddressOfEntryPoint),
393 | CH_R_CAST(LoadLibraryA),
394 | CH_R_CAST(GetProcAddress),
395 | CH_R_CAST(std::memset),
396 | CH_S_CAST(m_pNTHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress),
397 | CH_S_CAST(m_pNTHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size),
398 | CH_S_CAST(m_pNTHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress),
399 | CH_S_CAST(m_pNTHeaders->OptionalHeader.SizeOfHeaders),
400 | CH_S_CAST(m_eInjectionFlags),
401 | DLL_PROCESS_ATTACH,
402 | NULL
403 | };
404 |
405 | if (m_eInjectionFlags & eManualMapInjectionFlags::INJECTION_EXTRA_CUSTOMARGUMENTS)
406 | LoaderData.m_CustomTransmitted = m_CustomTransmittedData;
407 |
408 | // Fix up data if we've built with mismatched architecture.
409 | if (m_pNTHeaders->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC)
410 | {
411 | #if defined (_WIN64) // Didn't already have NT_HEADERS32 data.
412 | const PIMAGE_NT_HEADERS32 m_NT32Temporary = CH_R_CAST(m_pNTHeaders);
413 | LoaderData.m_EntryPoint = CH_S_CAST(m_NT32Temporary->OptionalHeader.AddressOfEntryPoint);
414 | LoaderData.m_RelocDirVA = CH_S_CAST(m_NT32Temporary->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
415 | LoaderData.m_RelocDirSize = CH_S_CAST(m_NT32Temporary->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size);
416 | LoaderData.m_ImportDirVA = CH_S_CAST(m_NT32Temporary->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
417 | #endif
418 | }
419 | else if (m_pNTHeaders->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC)
420 | {
421 | #if defined (_WIN32) // Didn't already have NT_HEADERS64 data.
422 | const PIMAGE_NT_HEADERS64 m_NT64Temporary = CH_R_CAST(m_pNTHeaders);
423 | LoaderData.m_EntryPoint = CH_S_CAST(m_NT64Temporary->OptionalHeader.AddressOfEntryPoint);
424 | LoaderData.m_RelocDirVA = CH_S_CAST(m_NT64Temporary->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
425 | LoaderData.m_RelocDirSize = CH_S_CAST(m_NT64Temporary->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size);
426 | LoaderData.m_ImportDirVA = CH_S_CAST(m_NT64Temporary->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
427 | #endif
428 | }
429 |
430 | // Address our loader data will be in context of target process.
431 | const std::uintptr_t m_LoaderDataAddress = this->Allocate(sizeof(LoaderData_t), PAGE_EXECUTE_READWRITE, false);
432 |
433 | // Copy over loader data to target process.
434 | if (!this->Write(m_LoaderDataAddress, &LoaderData, sizeof(LoaderData_t)))
435 | {
436 | CH_LOG("Couldn't copy over loader data to target process.");
437 | return false;
438 | }
439 |
440 | if (m_eInjectionFlags & eManualMapInjectionFlags::INJECTION_MODE_THREADHIJACK)
441 | // Hijack an existing thread in target process to execute our shellcode.
442 | {
443 | }
444 | else
445 | // Simply CreateRemoteThread in target process to execute our shellcode.
446 | {
447 | // Address our shellcode will be in context of target process.
448 | const std::uintptr_t m_ShellcodeAddress = this->Allocate(4096, PAGE_EXECUTE_READWRITE, false);
449 |
450 | // Copy over shellcode to target process.
451 | if (!this->Write(m_ShellcodeAddress, Shellcode, 4096))
452 | {
453 | CH_LOG("Couldn't copy over loader shellcode to target process.");
454 | return false;
455 | }
456 |
457 | const std::int32_t m_nThreadResult = this->_CreateRemoteThread(CH_R_CAST(m_ShellcodeAddress), CH_R_CAST(m_LoaderDataAddress));
458 | if (m_nThreadResult <= 0)
459 | {
460 | CH_LOG("Failed to create remote thread in target process with code %i.", m_nThreadResult);
461 | return false;
462 | }
463 | }
464 |
465 | return true;
466 | }
467 |
468 | // Manual map injection from module on disk.
469 | bool Process_t::ManualMapInject(const char* m_szDLLPath, std::int32_t m_eInjectionFlags)
470 | {
471 | ByteArray_t m_FileImageBuffer = { 0 };
472 |
473 | // Fill local image buffer from file on disk.
474 | std::ifstream m_fFile(m_szDLLPath, std::ios::binary);
475 | (&m_FileImageBuffer)->assign((std::istreambuf_iterator(m_fFile)), std::istreambuf_iterator());
476 | m_fFile.close();
477 |
478 | if (!m_FileImageBuffer.size())
479 | {
480 | CH_LOG("Couldn't parse desired m_ImageBuffer to manual map.");
481 | return false;
482 | }
483 |
484 | return ManualMapInject_Internal(m_FileImageBuffer.data(), m_eInjectionFlags);
485 | }
486 |
487 | // Manual map injection from module in memory.
488 | bool Process_t::ManualMapInject(std::uint8_t* m_ImageBuffer, std::int32_t m_eInjectionFlags)
489 | {
490 | return ManualMapInject_Internal(m_ImageBuffer, m_eInjectionFlags);
491 | }
492 |
493 | // Manual map injection from ImageFile_t.
494 | bool Process_t::ManualMapInject(ImageFile_t& m_ImageFile, std::int32_t m_eInjectionFlags)
495 | {
496 | if (!m_ImageFile.m_ImageBuffer.size())
497 | {
498 | CH_LOG("Couldn't parse desired ImageFile_t to manual map.");
499 | return false;
500 | }
501 |
502 | return ManualMapInject_Internal(m_ImageFile.m_ImageBuffer.data(), m_eInjectionFlags);
503 | }
504 |
505 | // LoadLibrary injection from module on disk.
506 | bool Process_t::LoadLibraryInject(const char* m_szDLLPath)
507 | {
508 | // Allocate memory in target process.
509 | const std::uintptr_t m_AllocatedMemory = this->Allocate(strlen(m_szDLLPath), PAGE_READWRITE);
510 | if (!m_AllocatedMemory)
511 | {
512 | CH_LOG("Couldn't allocate memory to target process. Error code was #%i", GetLastError());
513 | return false;
514 | }
515 |
516 | // Write string name for our module in previously allocated space.
517 | const std::size_t m_nWrittenBytes = this->Write(m_AllocatedMemory, CH_C_CAST(m_szDLLPath));
518 | if (!m_nWrittenBytes)
519 | {
520 | CH_LOG("Couldn't write module name to target process. Error code was #%i", GetLastError());
521 | return false;
522 | }
523 |
524 | // Load DLL by invoking LoadLibrary(m_szDLLPath) in a target process
525 | const std::int32_t m_nThreadResult = this->_CreateRemoteThread(CH_R_CAST(LoadLibraryA), CH_R_CAST(m_AllocatedMemory));
526 | if (m_nThreadResult <= 0)
527 | {
528 | CH_LOG("Failed to create remote thread in target process with code %i.", m_nThreadResult);
529 | return false;
530 | }
531 |
532 | return true;
533 | }
534 |
535 | // Traverse and cache data about all threads in a target process.
536 | std::vector Process_t::EnumerateThreads()
537 | {
538 | std::vector m_EnumeratedThreads = {};
539 |
540 | HANDLE m_hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, this->m_nTargetProcessID);
541 | Thread_t::NtQueryInformationThread_fn NtQueryInformationThread =
542 | CH_R_CAST(GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtQueryInformationThread"));
543 |
544 | THREADENTRY32 mEntry = { 0 };
545 | mEntry.dwSize = sizeof(mEntry);
546 |
547 | if (!Thread32First(m_hSnapShot, &mEntry))
548 | return {};
549 |
550 | while (Thread32Next(m_hSnapShot, &mEntry))
551 | {
552 | // Ensure our target process owns this thread.
553 | if (mEntry.th32OwnerProcessID != this->m_nTargetProcessID)
554 | continue;
555 |
556 | // Open handle to this specific thread.
557 | HANDLE m_hThreadHandle = OpenThread(THREAD_ALL_ACCESS, FALSE, mEntry.th32ThreadID);
558 | if (!m_hThreadHandle || m_hThreadHandle == INVALID_HANDLE_VALUE)
559 | continue;
560 |
561 | ADD_SCOPE_HANDLER(CloseHandle, m_hThreadHandle);
562 |
563 | DWORD m_dThreadStartAddress = 0;
564 | if (NtQueryInformationThread(m_hThreadHandle,
565 | Thread_t::THREADINFOCLASS::ThreadQuerySetWin32StartAddress,
566 | &m_dThreadStartAddress,
567 | this->m_eProcessArchitecture == eProcessArchitecture::ARCHITECTURE_x64 ? sizeof(DWORD) * 2 : sizeof(DWORD),
568 | nullptr) != 0x00000000/*STATUS_SUCCESS*/)
569 | continue;
570 |
571 | const bool m_bIsThreadSuspended = WaitForSingleObject(m_hThreadHandle, 0) == WAIT_ABANDONED;
572 |
573 | m_EnumeratedThreads.push_back(
574 | { mEntry.th32ThreadID,
575 | m_dThreadStartAddress,
576 | m_bIsThreadSuspended }
577 | );
578 | }
579 |
580 | CloseHandle(m_hSnapShot);
581 |
582 | return m_EnumeratedThreads;
583 | }
584 |
585 | // Traverse and cache data about all loaded modules in a target process.
586 | std::vector Process_t::EnumerateModules(bool m_bUseCachedData)
587 | {
588 | // To take up less processing power, or if you know nothing will be loaded into the target process unexpectedly.
589 | if (m_bUseCachedData && this->m_bHasCachedProcessesModules)
590 | {
591 | if (!this->m_EnumeratedModulesCached.empty())
592 | return this->m_EnumeratedModulesCached;
593 | }
594 |
595 | if (!this->m_EnumeratedModulesCached.empty())
596 | // Wipe any previously cached data.
597 | this->m_EnumeratedModulesCached.clear();
598 |
599 | HANDLE m_hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, this->m_nTargetProcessID);
600 |
601 | MODULEENTRY32 mEntry = { 0 };
602 | mEntry.dwSize = sizeof(mEntry);
603 |
604 | while (Module32Next(m_hSnapShot, &mEntry))
605 | {
606 | wchar_t m_wszModPath[MAX_PATH];
607 | if (!GetModuleFileNameEx(this->m_hTargetProcessHandle, mEntry.hModule, m_wszModPath, sizeof(m_wszModPath) / sizeof(wchar_t)))
608 | continue;
609 |
610 | m_wszModPath[MAX_PATH - 1] = '\0';
611 |
612 | // Convert wstring->string.
613 | _bstr_t m_bszPreModulePath(m_wszModPath);
614 | _bstr_t m_bszPreModuleName(mEntry.szModule);
615 |
616 | std::string m_szModulePath(m_bszPreModulePath);
617 | std::string m_szModuleName(m_bszPreModuleName);
618 |
619 | this->m_EnumeratedModulesCached.push_back({
620 | m_szModuleName, m_szModulePath,
621 | CH_S_CAST(mEntry.modBaseSize),
622 | CH_R_CAST(mEntry.modBaseAddr) }
623 | );
624 | }
625 |
626 | CloseHandle(m_hSnapShot);
627 |
628 | return this->m_EnumeratedModulesCached;
629 | }
630 |
631 | // Sets debug privileges of a target process.
632 | bool Process_t::SetDebugPrivilege(bool m_bShouldEnable)
633 | {
634 | HANDLE m_hToken;
635 | if (!OpenProcessToken(this->m_hTargetProcessHandle, TOKEN_ALL_ACCESS, &m_hToken))
636 | return false;
637 |
638 | ADD_SCOPE_HANDLER(CloseHandle, m_hToken);
639 |
640 | LUID m_LUID;
641 | if (!LookupPrivilegeValue(NULL, L"SeDebugPrivilege", &m_LUID))
642 | return false;
643 |
644 | TOKEN_PRIVILEGES m_TokenPrivileges;
645 | m_TokenPrivileges.PrivilegeCount = 1;
646 | m_TokenPrivileges.Privileges[0].Luid = m_LUID;
647 | m_TokenPrivileges.Privileges[0].Attributes = m_bShouldEnable ? SE_PRIVILEGE_ENABLED : 0;
648 |
649 | const bool result =
650 | AdjustTokenPrivileges(m_hToken, false, &m_TokenPrivileges, sizeof(m_TokenPrivileges), NULL, NULL)
651 | && GetLastError() != ERROR_NOT_ALL_ASSIGNED;
652 |
653 | return result;
654 | }
655 |
656 | // Suspend every thread in a target process.
657 | void Process_t::Suspend()
658 | {
659 | NtSuspendProcess_fn NtSuspendProcess = CH_R_CAST(GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtSuspendProcess"));
660 | this->m_bIsProcessManuallySuspended = NtSuspendProcess(this->m_hTargetProcessHandle) == 0x00000000/*STATUS_SUCCESS*/;
661 |
662 | CH_ASSERT(false, this->m_bIsProcessManuallySuspended, "Failed to suspend process!");
663 | }
664 |
665 | // Resume every previously suspended thread in a target process.
666 | void Process_t::Resume()
667 | {
668 | // TODO: Is there any use case of resuming a suspended process (that WE didn't suspend??).
669 | CH_ASSERT(true, this->m_bIsProcessManuallySuspended, "Attempted to resume process that was never suspended!");
670 |
671 | NtResumeProcess_fn NtResumeProcess = CH_R_CAST(GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtResumeProcess"));
672 |
673 | this->m_bIsProcessManuallySuspended = NtResumeProcess(this->m_hTargetProcessHandle) != 0x00000000/*STATUS_SUCCESS*/;
674 | CH_ASSERT(false, this->m_bIsProcessManuallySuspended == false, "Failed to resume suspended process!");
675 | }
676 |
677 | // Get desired export address by name.
678 | std::uintptr_t Process_t::GetRemoteProcAddress(const char* m_szModuleName, const char* m_szExportName)
679 | {
680 | chdr::Module_t m_Module(*this,
681 | m_szModuleName,
682 | PEHeaderData_t::PEHEADER_PARSING_TYPE::TYPE_EXPORT_DIRECTORY // Only parse exports.
683 | );
684 |
685 | if (!m_Module.IsValid() || !m_Module.GetPEHeaderData().IsValid())
686 | return 0u;
687 |
688 | auto ExportData = m_Module.GetPEHeaderData().GetExportData();
689 | if (ExportData.empty() || ExportData.find(m_szExportName) == ExportData.end())
690 | // Ensure we even have this export in our map.
691 | return 0u;
692 |
693 | return ExportData[m_szExportName].m_nAddress;
694 | }
695 |
696 | // VirtualAllocEx implementation.
697 | std::uintptr_t Process_t::Allocate(std::size_t m_AllocationSize, DWORD m_dProtectionType, bool m_bShouldTrack)
698 | {
699 | const std::uintptr_t m_AllocatedMemory = CH_R_CAST(VirtualAllocEx(this->m_hTargetProcessHandle, nullptr, m_AllocationSize, MEM_COMMIT | MEM_RESERVE, m_dProtectionType));
700 | if (m_AllocatedMemory && m_bShouldTrack)
701 | this->m_AllocatedMemoryTracker.insert({ m_AllocatedMemory, m_AllocationSize });
702 |
703 | return m_AllocatedMemory;
704 | }
705 |
706 | // VirtualFreeEx implementation.
707 | bool Process_t::Free(std::uintptr_t m_FreeAddress)
708 | {
709 | const bool m_bDidFree = VirtualFreeEx(this->m_hTargetProcessHandle, (LPVOID)m_FreeAddress, NULL, MEM_RELEASE);
710 | if (m_bDidFree && this->m_AllocatedMemoryTracker.find(m_FreeAddress) != this->m_AllocatedMemoryTracker.end())
711 | this->m_AllocatedMemoryTracker.erase(this->m_AllocatedMemoryTracker.find(m_FreeAddress));
712 |
713 | return m_bDidFree;
714 | }
715 |
716 | // VirtualQueryEx implementation.
717 | std::size_t Process_t::Query(LPCVOID m_QueryAddress, MEMORY_BASIC_INFORMATION* m_MemoryInformation)
718 | {
719 | return VirtualQueryEx(this->m_hTargetProcessHandle, m_QueryAddress, m_MemoryInformation, sizeof(MEMORY_BASIC_INFORMATION));
720 | }
721 |
722 | // CreateRemoteThread implementation.
723 | std::int32_t Process_t::_CreateRemoteThread(LPVOID m_lpStartAddress, LPVOID m_lpParameter)
724 | {
725 | HANDLE m_hRemoteThread = CreateRemoteThread(this->m_hTargetProcessHandle,
726 | NULL,
727 | NULL,
728 | CH_R_CAST(m_lpStartAddress),
729 | m_lpParameter,
730 | NULL,
731 | NULL
732 | );
733 |
734 | if (!m_hRemoteThread || m_hRemoteThread == INVALID_HANDLE_VALUE)
735 | return 0;
736 |
737 | ADD_SCOPE_HANDLER(CloseHandle, m_hRemoteThread);
738 |
739 | if (WaitForSingleObject(m_hRemoteThread, INFINITE) == WAIT_FAILED)
740 | return -1;
741 |
742 | return 1;
743 | }
744 |
745 | // GetModule implementation.
746 | Module_t& Process_t::GetModule(const char* m_szModuleName, std::int32_t m_ParseType)
747 | {
748 | if (!IsValid())
749 | CH_LOG("Invalid process called GetModule : %s", m_szProcessName);
750 |
751 | if (m_AllocatedModules.count(m_szModuleName) > 0u)
752 | return m_AllocatedModules[m_szModuleName];
753 |
754 | Module_t m_Module(*this, m_szModuleName, m_ParseType);
755 | m_AllocatedModules[m_szModuleName] = m_Module;
756 |
757 | return m_AllocatedModules[m_szModuleName];
758 | }
759 | }
760 |
761 | // PEHeaderData_t definitions and functions.
762 | namespace chdr
763 | {
764 | // Parsing data out of this image's buffer.
765 | PEHeaderData_t::PEHeaderData_t(std::uint8_t* m_ImageBuffer, std::size_t m_ImageSize, std::int32_t m_ParseType)
766 | {
767 | if (m_ParseType & PEHEADER_PARSING_TYPE::TYPE_NONE)
768 | return;
769 |
770 | CH_ASSERT(true, m_ImageBuffer && m_ImageSize, "Failed to read PE image.");
771 |
772 | this->m_pDOSHeaders = CH_R_CAST(m_ImageBuffer);
773 | this->m_pNTHeaders = CH_R_CAST(m_ImageBuffer + this->m_pDOSHeaders->e_lfanew);
774 |
775 | this->m_bIsValidInternal =
776 | this->m_pDOSHeaders->e_magic == IMAGE_DOS_SIGNATURE &&
777 | this->m_pNTHeaders->Signature == IMAGE_NT_SIGNATURE;
778 |
779 | // Ensure image PE headers was valid.
780 | CH_ASSERT(true, this->IsValid(), "Couldn't find MZ&NT header.");
781 |
782 | PIMAGE_SECTION_HEADER m_pSectionHeaders = IMAGE_FIRST_SECTION(this->m_pNTHeaders);
783 | for (std::size_t i = 0u; i < m_pNTHeaders->FileHeader.NumberOfSections; ++i, ++m_pSectionHeaders)
784 | {
785 | this->m_SectionData.push_back(
786 | { CH_R_CAST(m_pSectionHeaders->Name),
787 | m_pSectionHeaders->VirtualAddress,
788 | m_pSectionHeaders->Misc.VirtualSize,
789 | m_pSectionHeaders->Characteristics,
790 | m_pSectionHeaders->PointerToRawData,
791 | m_pSectionHeaders->PointerToRawData }
792 | );
793 | }
794 |
795 | for (std::size_t i = 0u; i < IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR/*Maximum*/; ++i)
796 | this->m_DirectoryData.push_back(this->m_pNTHeaders->OptionalHeader.DataDirectory[i]);
797 |
798 | // Gather all needed directories, then ensure we're using the correct type for the image.
799 | IMAGE_DATA_DIRECTORY m_ExportDataDirectory = this->GetDataDirectory(IMAGE_DIRECTORY_ENTRY_EXPORT);
800 | IMAGE_DATA_DIRECTORY m_ImportDataDirectory = this->GetDataDirectory(IMAGE_DIRECTORY_ENTRY_IMPORT);
801 | IMAGE_DATA_DIRECTORY m_DebugDataDirectory = this->GetDataDirectory(IMAGE_DIRECTORY_ENTRY_DEBUG);
802 |
803 | if (this->m_pNTHeaders->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC)
804 | {
805 | #if defined (_WIN64) // Didn't already have NT_HEADERS32 data.
806 | const PIMAGE_NT_HEADERS32 m_NT32Temporary = CH_R_CAST(this->m_pNTHeaders);
807 | m_ExportDataDirectory = m_NT32Temporary->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
808 | m_ImportDataDirectory = m_NT32Temporary->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
809 | m_DebugDataDirectory = m_NT32Temporary->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG];
810 |
811 | if (!this->m_DirectoryData.empty())
812 | // Reset directory data to be filled with one of correct architecture.
813 | this->m_DirectoryData.clear();
814 |
815 | for (std::size_t i = 0u; i < IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR/*Maximum*/; ++i)
816 | this->m_DirectoryData.push_back(m_NT32Temporary->OptionalHeader.DataDirectory[i]);
817 | #endif
818 | }
819 | else if (this->m_pNTHeaders->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC)
820 | {
821 | #if defined (_WIN32) // Didn't already have NT_HEADERS64 data.
822 | const PIMAGE_NT_HEADERS64 m_NT64Temporary = CH_R_CAST(this->m_pNTHeaders);
823 | m_ExportDataDirectory = m_NT64Temporary->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
824 | m_ImportDataDirectory = m_NT64Temporary->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
825 | m_DebugDataDirectory = m_NT64Temporary->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG];
826 |
827 | if (!this->m_DirectoryData.empty())
828 | // Reset directory data to be filled with one of correct architecture.
829 | this->m_DirectoryData.clear();
830 |
831 | for (std::size_t i = 0u; i < IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR/*Maximum*/; ++i)
832 | this->m_DirectoryData.push_back(m_NT64Temporary->OptionalHeader.DataDirectory[i]);
833 | #endif
834 | }
835 |
836 | const bool bDebugEnabled = m_ParseType & PEHEADER_PARSING_TYPE::TYPE_DEBUG_DIRECTORY || m_ParseType & PEHEADER_PARSING_TYPE::TYPE_ALL;
837 | if (!bDebugEnabled ||
838 | !m_DebugDataDirectory.VirtualAddress ||
839 | !m_DebugDataDirectory.Size)
840 | {
841 | if (bDebugEnabled)
842 | CH_LOG("Debug table didn't exist for current region. 0x%x | 0X%x", m_DebugDataDirectory.VirtualAddress, m_DebugDataDirectory.Size);
843 | }
844 | else // Debug table parsing.
845 | {
846 | const PIMAGE_DEBUG_DIRECTORY m_DebugDirectoryData = CH_R_CAST(m_ImageBuffer + this->RvaToOffset(m_DebugDataDirectory.VirtualAddress));
847 | if (m_DebugDirectoryData->Type == IMAGE_DEBUG_TYPE_CODEVIEW)
848 | {
849 | const CV_INFO_PDB70* m_PDBInfo = CH_R_CAST(m_ImageBuffer + this->RvaToOffset(m_DebugDirectoryData->AddressOfRawData));
850 |
851 | wchar_t m_wszGUIDStr[MAX_PATH];
852 | if (!StringFromGUID2(m_PDBInfo->Signature, m_wszGUIDStr, sizeof(m_wszGUIDStr)))
853 | // Couldn't read GUID, whatever lol.
854 | this->m_DebugData = { (char*)m_PDBInfo->PdbFileName, "", m_PDBInfo->Age, m_PDBInfo->CvSignature };
855 | else
856 | {
857 | m_wszGUIDStr[MAX_PATH - 1] = '\0';
858 |
859 | // wchar_t->string
860 | _bstr_t m_szPreGUIDStr(m_wszGUIDStr);
861 |
862 | this->m_DebugData = {
863 | (char*)m_PDBInfo->PdbFileName, std::string(m_szPreGUIDStr),
864 | m_PDBInfo->Age, m_PDBInfo->CvSignature
865 | };
866 | }
867 | }
868 | }
869 |
870 | const bool bExportsEnabled = m_ParseType & PEHEADER_PARSING_TYPE::TYPE_EXPORT_DIRECTORY || m_ParseType & PEHEADER_PARSING_TYPE::TYPE_ALL;
871 | if (!bExportsEnabled ||
872 | !m_ExportDataDirectory.VirtualAddress ||
873 | !m_ExportDataDirectory.Size)
874 | {
875 | if (bExportsEnabled)
876 | CH_LOG("Export table didn't exist for current region. 0x%x | 0X%x", m_ExportDataDirectory.VirtualAddress, m_ExportDataDirectory.Size);
877 | }
878 | else // Export table parsing.
879 | {
880 | const PIMAGE_EXPORT_DIRECTORY m_pExportDirectory = CH_R_CAST(m_ImageBuffer + this->RvaToOffset(m_ExportDataDirectory.VirtualAddress));
881 |
882 | const std::uint16_t* m_pOrdinalAddress = CH_R_CAST(m_ImageBuffer + this->RvaToOffset(m_pExportDirectory->AddressOfNameOrdinals));
883 | const std::uint32_t* m_pNamesAddress = CH_R_CAST(m_ImageBuffer + this->RvaToOffset(m_pExportDirectory->AddressOfNames));
884 | const std::uint32_t* m_pFunctionAddress = CH_R_CAST(m_ImageBuffer + this->RvaToOffset(m_pExportDirectory->AddressOfFunctions));
885 |
886 | // Traverse export table and cache desired data.
887 | for (std::size_t i = 0u; i < m_pExportDirectory->NumberOfNames; ++i)
888 | {
889 | const std::uint16_t m_CurrentOrdinal = m_pOrdinalAddress[i];
890 | const std::uint32_t m_CurrentName = m_pNamesAddress[i];
891 |
892 | if (m_CurrentName == 0u || m_CurrentOrdinal > m_pExportDirectory->NumberOfNames)
893 | // Happend a few times, dunno.
894 | continue;
895 |
896 | char* m_szExportName = CH_R_CAST(m_ImageBuffer + this->RvaToOffset(m_CurrentName));
897 | this->m_ExportData[m_szExportName] = { m_pFunctionAddress[m_CurrentOrdinal], m_CurrentOrdinal };
898 | }
899 | }
900 |
901 | const bool bImportsEnabled = m_ParseType & PEHEADER_PARSING_TYPE::TYPE_IMPORT_DIRECTORY || m_ParseType & PEHEADER_PARSING_TYPE::TYPE_ALL;
902 | if (!bImportsEnabled ||
903 | !m_ImportDataDirectory.VirtualAddress ||
904 | !m_ImportDataDirectory.Size)
905 | {
906 | if (bImportsEnabled)
907 | CH_LOG("Import table didn't exist for current region. 0x%X | 0x%X",
908 | m_ImportDataDirectory.VirtualAddress, m_ImportDataDirectory.Size);
909 | }
910 | else // Import table parsing.
911 | {
912 | PIMAGE_IMPORT_DESCRIPTOR m_pImportDescriptor = CH_R_CAST(m_ImageBuffer + this->RvaToOffset(m_ImportDataDirectory.VirtualAddress));
913 |
914 | for (; m_pImportDescriptor->Name; ++m_pImportDescriptor)
915 | {
916 | // Read module name.
917 | char* m_szModuleName = CH_R_CAST(m_ImageBuffer + this->RvaToOffset(m_pImportDescriptor->Name));
918 |
919 | PIMAGE_THUNK_DATA m_pThunkData = CH_R_CAST(m_ImageBuffer + this->RvaToOffset(m_pImportDescriptor->OriginalFirstThunk));
920 |
921 | for (; m_pThunkData->u1.AddressOfData; ++m_pThunkData)
922 | {
923 | if (m_pThunkData->u1.Ordinal & IMAGE_ORDINAL_FLAG32)
924 | // TODO: Imports by ordinal, dunno how I will make this nice.
925 | continue;
926 |
927 | // Read function name.
928 | char* m_szFunctionName = CH_R_CAST(m_ImageBuffer + this->RvaToOffset(std::uint32_t(m_pThunkData->u1.AddressOfData + 2)));
929 |
930 | // Cache desired data.
931 | this->m_ImportData[m_szFunctionName] = { m_szModuleName };
932 | }
933 | }
934 | }
935 | }
936 |
937 | // Parsing data out of this image's process.
938 | PEHeaderData_t::PEHeaderData_t(Process_t& m_Process, std::int32_t m_ParseType, std::uintptr_t m_CustomBaseAddress)
939 | {
940 | if (m_ParseType & PEHEADER_PARSING_TYPE::TYPE_NONE)
941 | return;
942 |
943 | const std::uintptr_t m_BaseAddress = m_CustomBaseAddress != 0u ? m_CustomBaseAddress : m_Process.GetBaseAddress();
944 | CH_ASSERT(true, m_BaseAddress, "Couldn't find base address of target process.");
945 |
946 | IMAGE_DOS_HEADER m_pDOSHeadersTemporary = m_Process.Read(m_BaseAddress);
947 | this->m_pDOSHeaders = &m_pDOSHeadersTemporary;
948 |
949 | IMAGE_NT_HEADERS m_pNTHeadersTemporary = m_Process.Read(m_BaseAddress + this->m_pDOSHeaders->e_lfanew);
950 | this->m_pNTHeaders = &m_pNTHeadersTemporary;
951 |
952 | this->m_bIsValidInternal =
953 | this->m_pDOSHeaders->e_magic == IMAGE_DOS_SIGNATURE &&
954 | this->m_pNTHeaders->Signature == IMAGE_NT_SIGNATURE;
955 |
956 | // Ensure image PE headers was valid.
957 | CH_ASSERT(true, this->IsValid(), "Couldn't find MZ&NT header.");
958 |
959 | for (std::size_t i = 0u; i < m_pNTHeaders->FileHeader.NumberOfSections; ++i)
960 | {
961 | std::size_t m_nSectionOffset = sizeof(IMAGE_NT_HEADERS) + (i * sizeof(IMAGE_SECTION_HEADER));
962 | IMAGE_SECTION_HEADER m_pSectionHeaders = m_Process.Read(m_BaseAddress + this->m_pDOSHeaders->e_lfanew + m_nSectionOffset);
963 |
964 | this->m_SectionData.push_back(
965 | { CH_R_CAST(m_pSectionHeaders.Name),
966 | m_pSectionHeaders.VirtualAddress,
967 | m_pSectionHeaders.Misc.VirtualSize,
968 | m_pSectionHeaders.Characteristics,
969 | m_pSectionHeaders.PointerToRawData,
970 | m_pSectionHeaders.SizeOfRawData }
971 | );
972 | }
973 |
974 | for (std::size_t i = 0u; i < IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR/*Maximum*/; ++i)
975 | this->m_DirectoryData.push_back(this->m_pNTHeaders->OptionalHeader.DataDirectory[i]);
976 |
977 | // Gather all needed directories, then ensure we're using the correct type for the image.
978 | IMAGE_DATA_DIRECTORY m_ExportDataDirectory = this->GetDataDirectory(IMAGE_DIRECTORY_ENTRY_EXPORT);
979 | IMAGE_DATA_DIRECTORY m_ImportDataDirectory = this->GetDataDirectory(IMAGE_DIRECTORY_ENTRY_IMPORT);
980 | IMAGE_DATA_DIRECTORY m_DebugDataDirectory = this->GetDataDirectory(IMAGE_DIRECTORY_ENTRY_DEBUG);
981 |
982 | if (this->m_pNTHeaders->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC)
983 | {
984 | #if defined (_WIN64) // Didn't already have NT_HEADERS32 data.
985 | const PIMAGE_NT_HEADERS32 m_NT32Temporary = CH_R_CAST(this->m_pNTHeaders);
986 | m_ExportDataDirectory = m_NT32Temporary->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
987 | m_ImportDataDirectory = m_NT32Temporary->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
988 | m_DebugDataDirectory = m_NT32Temporary->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG];
989 | this->m_nMismatchedArchitecture = CH_S_CAST(Process_t::eProcessArchitecture::ARCHITECTURE_x64); // For future notifications of dangerous actions.
990 | #endif
991 | }
992 | else if (this->m_pNTHeaders->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC)
993 | {
994 | #if defined (_WIN32) // Didn't already have NT_HEADERS64 data.
995 | const PIMAGE_NT_HEADERS64 m_NT64Temporary = CH_R_CAST(this->m_pNTHeaders);
996 | m_ExportDataDirectory = m_NT64Temporary->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
997 | m_ImportDataDirectory = m_NT64Temporary->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
998 | m_DebugDataDirectory = m_NT64Temporary->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG];
999 | this->m_nMismatchedArchitecture = CH_S_CAST(Process_t::eProcessArchitecture::ARCHITECTURE_x64); // For future notifications of dangerous actions.
1000 | #endif
1001 | }
1002 |
1003 | const bool bDebugEnabled = m_ParseType & PEHEADER_PARSING_TYPE::TYPE_DEBUG_DIRECTORY || m_ParseType & PEHEADER_PARSING_TYPE::TYPE_ALL;
1004 | if (!bDebugEnabled ||
1005 | !m_DebugDataDirectory.VirtualAddress ||
1006 | !m_DebugDataDirectory.Size)
1007 | {
1008 | if (bDebugEnabled)
1009 | CH_LOG("Debug table didn't exist for current region. 0x%x | 0X%x", m_DebugDataDirectory.VirtualAddress, m_DebugDataDirectory.Size);
1010 | }
1011 | else // Debug table parsing.
1012 | {
1013 | const IMAGE_DEBUG_DIRECTORY m_DebugDirectoryData = m_Process.Read(m_BaseAddress + m_DebugDataDirectory.VirtualAddress);
1014 | if (m_DebugDirectoryData.Type == IMAGE_DEBUG_TYPE_CODEVIEW)
1015 | {
1016 | const CV_INFO_PDB70 m_PDBInfo = m_Process.Read(m_BaseAddress + m_DebugDirectoryData.AddressOfRawData);
1017 |
1018 | wchar_t m_wszGUIDStr[MAX_PATH];
1019 | if (!StringFromGUID2(m_PDBInfo.Signature, m_wszGUIDStr, sizeof(m_wszGUIDStr)))
1020 | // Couldn't read GUID, whatever lol.
1021 | this->m_DebugData = { (char*)m_PDBInfo.PdbFileName, "", m_PDBInfo.Age, m_PDBInfo.CvSignature };
1022 | else
1023 | {
1024 | m_wszGUIDStr[MAX_PATH - 1] = '\0';
1025 |
1026 | // wchar_t->string
1027 | _bstr_t m_szPreGUIDStr(m_wszGUIDStr);
1028 |
1029 | this->m_DebugData = {
1030 | (char*)m_PDBInfo.PdbFileName, std::string(m_szPreGUIDStr),
1031 | m_PDBInfo.Age, m_PDBInfo.CvSignature
1032 | };
1033 | }
1034 | }
1035 | }
1036 |
1037 | const bool bExportsEnabled = m_ParseType & PEHEADER_PARSING_TYPE::TYPE_EXPORT_DIRECTORY || m_ParseType & PEHEADER_PARSING_TYPE::TYPE_ALL;
1038 | if (!bExportsEnabled ||
1039 | !m_ExportDataDirectory.VirtualAddress ||
1040 | !m_ExportDataDirectory.Size)
1041 | {
1042 | if (bExportsEnabled)
1043 | CH_LOG("Export table didn't exist for current region. 0x%x | 0X%x",
1044 | m_ExportDataDirectory.VirtualAddress, m_ExportDataDirectory.Size);
1045 | }
1046 | else // Export table parsing.
1047 | {
1048 | IMAGE_EXPORT_DIRECTORY m_pExportDirectory = m_Process.Read(m_BaseAddress + (uintptr_t)m_ExportDataDirectory.VirtualAddress);
1049 |
1050 | // Read whole RVA block.
1051 | const auto m_FuncRVABlock = std::make_unique(m_pExportDirectory.NumberOfFunctions * sizeof(std::uint32_t));
1052 | const std::size_t m_nReadRVA = m_Process.Read(m_BaseAddress + m_pExportDirectory.AddressOfFunctions, m_FuncRVABlock.get(), m_pExportDirectory.NumberOfFunctions * sizeof(std::uint32_t));
1053 |
1054 | // Read whole name block.
1055 | const auto m_NameRVABlock = std::make_unique(m_pExportDirectory.NumberOfNames * sizeof(std::uint32_t));
1056 | const std::size_t m_nReadName = m_Process.Read(m_BaseAddress + m_pExportDirectory.AddressOfNames, m_NameRVABlock.get(), m_pExportDirectory.NumberOfNames * sizeof(std::uint32_t));
1057 |
1058 | // Read whole ordinal block.
1059 | const auto m_OrdinalBlock = std::make_unique(m_pExportDirectory.NumberOfNames * sizeof(std::uint16_t));
1060 | const std::size_t m_nReadOrdinal = m_Process.Read(m_BaseAddress + m_pExportDirectory.AddressOfNameOrdinals, m_OrdinalBlock.get(), m_pExportDirectory.NumberOfNames * sizeof(std::uint16_t));
1061 |
1062 | // Ye.
1063 | const std::uint32_t* m_pFuncBlock = m_FuncRVABlock.get();
1064 | const std::uint32_t* m_pNameBlock = m_NameRVABlock.get();
1065 | const std::uint16_t* m_pOrdinalBlock = m_OrdinalBlock.get();
1066 |
1067 | // Traverse export table and cache desired data.
1068 | for (std::size_t i = 0u; i < m_pExportDirectory.NumberOfNames; ++i)
1069 | {
1070 | if (m_nReadRVA == 0u || m_nReadName == 0u || m_nReadOrdinal == 0u)
1071 | // One or more read failed, no point to waste time here.
1072 | break;
1073 |
1074 | const std::uint16_t m_CurrentOrdinal = m_pOrdinalBlock[i];
1075 | const std::uint32_t m_CurrentName = m_pNameBlock[i];
1076 |
1077 | if (m_CurrentName == 0u || m_CurrentOrdinal > m_pExportDirectory.NumberOfNames)
1078 | // Happend a few times, dunno.
1079 | continue;
1080 |
1081 | // Read export name.
1082 | char m_szExportName[MAX_PATH];
1083 | const std::size_t m_ReadNameBytes = m_Process.Read(m_BaseAddress + m_CurrentName, m_szExportName, sizeof(m_szExportName));
1084 |
1085 | if (m_ReadNameBytes == 0u)
1086 | // Something went wrong while reading exp name, don't cache this.
1087 | continue;
1088 |
1089 | m_szExportName[MAX_PATH - 1] = '\0';
1090 |
1091 | // Cache desired data.
1092 | this->m_ExportData[m_szExportName] = { m_pFuncBlock[m_CurrentOrdinal], m_CurrentOrdinal };
1093 | }
1094 | }
1095 |
1096 | const bool bImportsEnabled = m_ParseType & PEHEADER_PARSING_TYPE::TYPE_IMPORT_DIRECTORY || m_ParseType & PEHEADER_PARSING_TYPE::TYPE_ALL;
1097 | if (!bImportsEnabled ||
1098 | !m_ImportDataDirectory.VirtualAddress ||
1099 | !m_ImportDataDirectory.Size)
1100 | {
1101 | if (bImportsEnabled)
1102 | CH_LOG("Import table didn't exist for current region. 0x%X | 0x%X",
1103 | m_ImportDataDirectory.VirtualAddress, m_ImportDataDirectory.Size);
1104 | }
1105 | else // Import table parsing.
1106 | {
1107 | // Read whole descriptor block.
1108 | const auto m_ImpDescriptorBlock = std::make_unique(m_ImportDataDirectory.Size);
1109 | m_Process.Read(m_BaseAddress + m_ImportDataDirectory.VirtualAddress, m_ImpDescriptorBlock.get(), m_ImportDataDirectory.Size);
1110 |
1111 | for (std::size_t i = 0u; ; ++i)
1112 | {
1113 | const IMAGE_IMPORT_DESCRIPTOR m_pImportDescriptor = CH_R_CAST(m_ImpDescriptorBlock.get())[i];
1114 | if (!m_pImportDescriptor.Name)
1115 | break;
1116 |
1117 | // Read module name.
1118 | char m_szModuleName[MAX_PATH];
1119 | const std::size_t m_nReadModuleName = m_Process.Read(m_BaseAddress + m_pImportDescriptor.Name, m_szModuleName, sizeof(m_szModuleName));
1120 |
1121 | if (m_nReadModuleName == 0u)
1122 | // Something went wrong while reading imp module, don't cache this.
1123 | continue;
1124 |
1125 | m_szModuleName[MAX_PATH - 1] = '\0';
1126 |
1127 | for (std::size_t n = m_pImportDescriptor.OriginalFirstThunk; ; n += sizeof(IMAGE_THUNK_DATA32))
1128 | {
1129 | IMAGE_THUNK_DATA m_pThunkData = m_Process.Read(m_BaseAddress + n);
1130 | if (!m_pThunkData.u1.AddressOfData)
1131 | break;
1132 |
1133 | if (m_pThunkData.u1.Ordinal & IMAGE_ORDINAL_FLAG32)
1134 | // TODO: Imports by ordinal, dunno how I will make this nice.
1135 | continue;
1136 |
1137 | // Read function name.
1138 | char m_szFunctionName[MAX_PATH];
1139 | const std::size_t m_ReadNameBytes = m_Process.Read(m_BaseAddress + std::uintptr_t(m_pThunkData.u1.AddressOfData) + 2, m_szFunctionName, sizeof(m_szFunctionName));
1140 |
1141 | if (m_ReadNameBytes == 0u)
1142 | // Something went wrong while reading imp name, don't cache this.
1143 | continue;
1144 |
1145 | m_szFunctionName[MAX_PATH - 1] = '\0';
1146 |
1147 | // Cache desired data.
1148 | this->m_ImportData[m_szFunctionName] = { m_szModuleName };
1149 | }
1150 | }
1151 | }
1152 | }
1153 |
1154 | // Ensure we found the target PE header.
1155 | bool PEHeaderData_t::IsValid()
1156 | {
1157 | return this->m_bIsValidInternal;
1158 | }
1159 |
1160 | // Helper function to get DOS header of PE image.
1161 | PIMAGE_DOS_HEADER PEHeaderData_t::GetDOSHeader()
1162 | {
1163 | return this->m_pDOSHeaders;
1164 | }
1165 |
1166 | // Helper function to get NT headers of PE image.
1167 | PIMAGE_NT_HEADERS PEHeaderData_t::GetNTHeader()
1168 | {
1169 | if (this->m_nMismatchedArchitecture != 0u)
1170 | CH_LOG("Warning: Architecture of PE image was not expected by build type. Actual was 0x%X",
1171 | this->m_nMismatchedArchitecture);
1172 |
1173 | return this->m_pNTHeaders;
1174 | }
1175 |
1176 | // Helper function to get specific data directory of PE image.
1177 | IMAGE_DATA_DIRECTORY PEHeaderData_t::GetDataDirectory(std::size_t m_nDirIndex)
1178 | {
1179 | if (m_nDirIndex > IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR)
1180 | // Prevent user from doing an oopsie OOB read.
1181 | return {};
1182 |
1183 | return this->m_DirectoryData[m_nDirIndex];
1184 | }
1185 |
1186 | // Helper function to get section data of PE image.
1187 | std::vector PEHeaderData_t::GetSectionData()
1188 | {
1189 | return this->m_SectionData;
1190 | }
1191 |
1192 | // Helper function to get exported functions' data of PE image.
1193 | std::map PEHeaderData_t::GetExportData()
1194 | {
1195 | return this->m_ExportData;
1196 | }
1197 |
1198 | // Helper function to get imported functions' data of PE image.
1199 | std::map PEHeaderData_t::GetImportData()
1200 | {
1201 | return this->m_ImportData;
1202 | }
1203 |
1204 | // Helper function to get debug directories data of PE image.
1205 | PEHeaderData_t::DebugData_t PEHeaderData_t::GetDebugData()
1206 | {
1207 | return this->m_DebugData;
1208 | }
1209 | // Convert relative virtual address to file offset.
1210 | std::uint32_t PEHeaderData_t::RvaToOffset(std::uint32_t m_nRva)
1211 | {
1212 | for (const auto& SectionData : this->GetSectionData())
1213 | {
1214 | if (m_nRva < SectionData.m_Address ||
1215 | m_nRva > SectionData.m_Address + SectionData.m_Size)
1216 | continue;
1217 |
1218 | return SectionData.m_PointerToRawData + m_nRva - SectionData.m_Address;
1219 | }
1220 | return NULL;
1221 | }
1222 |
1223 | std::uint32_t PEHeaderData_t::OffsetToRva(std::uint32_t m_nOffset)
1224 | {
1225 | for (const auto& SectionData : this->GetSectionData())
1226 | {
1227 | if (m_nOffset < SectionData.m_PointerToRawData ||
1228 | m_nOffset > SectionData.m_Address + SectionData.m_Size)
1229 | continue;
1230 |
1231 | return SectionData.m_Address + m_nOffset - SectionData.m_PointerToRawData;
1232 | }
1233 | return NULL;
1234 | }
1235 |
1236 | // Get certain section by address in memory.
1237 | PEHeaderData_t::SectionData_t PEHeaderData_t::GetSectionByAddress(std::uint32_t m_nAddress)
1238 | {
1239 | // Traverse all sections to find which one our address resides in.
1240 | for (const auto& SectionData : this->GetSectionData())
1241 | {
1242 | if (m_nAddress < SectionData.m_Address ||
1243 | m_nAddress > SectionData.m_Address + SectionData.m_Size)
1244 | continue;
1245 |
1246 | return SectionData;
1247 | }
1248 | return {}; // Wtf. Couldn't find.
1249 | }
1250 | }
1251 |
1252 | // ImageFile_t definitions and functions.
1253 | namespace chdr
1254 | {
1255 | // Used for parsing PE's from file.
1256 | ImageFile_t::ImageFile_t(const char* m_szImagePath, std::int32_t m_ParseType)
1257 | {
1258 | CH_ASSERT(true, std::filesystem::exists(m_szImagePath), "File at %s doesn't exist, or wasn't accessible.", m_szImagePath);
1259 |
1260 | // Fill image buffer.
1261 | std::ifstream m_fFile(m_szImagePath, std::ios::binary);
1262 | (&m_ImageBuffer)->assign((std::istreambuf_iterator(m_fFile)), std::istreambuf_iterator());
1263 | m_fFile.close();
1264 |
1265 | // Parse PE header information.
1266 | m_PEHeaderData = PEHeaderData_t((&m_ImageBuffer)->data(), (&m_ImageBuffer)->size(), m_ParseType);
1267 | }
1268 |
1269 | // Used for parsing PE's from memory.
1270 | ImageFile_t::ImageFile_t(std::uint8_t* m_ImageBuffer, std::size_t m_nImageSize, std::int32_t m_ParseType)
1271 | {
1272 | // Copy over to object-specific variable to possibly use later.
1273 | this->m_ImageBuffer.resize(m_nImageSize);
1274 | std::memcpy(&this->m_ImageBuffer[0], m_ImageBuffer, m_nImageSize);
1275 |
1276 | // Parse PE header information.
1277 | m_PEHeaderData = PEHeaderData_t(this->m_ImageBuffer.data(), this->m_ImageBuffer.size(), m_ParseType);
1278 | }
1279 |
1280 | // Ensure we found the target PE image.
1281 | bool ImageFile_t::IsValid()
1282 | {
1283 | return this->m_ImageBuffer.size() != 0;
1284 | }
1285 |
1286 | // Helper function to get PE header data of PE image.
1287 | PEHeaderData_t ImageFile_t::GetPEHeaderData()
1288 | {
1289 | return this->m_PEHeaderData;
1290 | }
1291 |
1292 | void ImageFile_t::WriteToFile(const char* m_szFilePath)
1293 | {
1294 | std::ofstream file(m_szFilePath, std::ios_base::out | std::ios_base::binary);
1295 | if (!file.is_open())
1296 | // Unable to setup desired file.
1297 | return;
1298 |
1299 | file.write(CH_R_CAST(this->m_ImageBuffer.data()), this->m_ImageBuffer.size());
1300 | file.close();
1301 | }
1302 | }
1303 |
1304 | // Driver_t definitions and functions.
1305 | namespace chdr
1306 | {
1307 | Driver_t::Driver_t(const char* m_szDriverPath)
1308 | {
1309 | // Object-specific driver path.
1310 | this->m_szDriverPath = m_szDriverPath;
1311 | }
1312 |
1313 | // Initialize by driver information.
1314 | Driver_t::Driver_t(const char* m_szDriverName, DWORD m_dDesiredAccess, DWORD m_dSharedMode, DWORD m_dCreationDisposition, DWORD m_dFlagsAndAttributes)
1315 | {
1316 | this->m_hTargetDriverHandle = CreateFileA(m_szDriverName,
1317 | m_dDesiredAccess,
1318 | m_dSharedMode,
1319 | nullptr,
1320 | m_dCreationDisposition,
1321 | m_dFlagsAndAttributes,
1322 | nullptr);
1323 |
1324 | CH_ASSERT(false, this->m_hTargetDriverHandle && this->m_hTargetDriverHandle != INVALID_HANDLE_VALUE, "Failed to get HANDLE to desired service!");
1325 | }
1326 |
1327 | // For opening an HANDLE to a currently loaded driver.
1328 | bool Driver_t::SetupHandle(const char* m_szDriverName, DWORD m_dDesiredAccess, DWORD m_dSharedMode, DWORD m_dCreationDisposition, DWORD m_dFlagsAndAttributes)
1329 | {
1330 | this->m_hTargetDriverHandle = CreateFileA(m_szDriverName,
1331 | m_dDesiredAccess,
1332 | m_dSharedMode,
1333 | nullptr,
1334 | m_dCreationDisposition,
1335 | m_dFlagsAndAttributes,
1336 | nullptr);
1337 |
1338 | return this->m_hTargetDriverHandle && this->m_hTargetDriverHandle != INVALID_HANDLE_VALUE;
1339 | }
1340 |
1341 | // For destroying a HANDLE to a currently loaded driver.
1342 | bool Driver_t::DestroyHandle()
1343 | {
1344 | if (!this->m_hTargetDriverHandle || this->m_hTargetDriverHandle == INVALID_HANDLE_VALUE)
1345 | {
1346 | // Sir, why have you tried to release this handle without ensuring it was setup correctly?!
1347 | CH_LOG("Tried to release an invalid HANDLE!");
1348 | return NULL;
1349 | }
1350 |
1351 | const bool m_bDidSucceed = CloseHandle(this->m_hTargetDriverHandle);
1352 | return m_bDidSucceed;
1353 | }
1354 |
1355 | // Send IOCTL request to the target driver, returning the response.
1356 | DWORD Driver_t::SendIOCTL(DWORD m_dControlCode, LPVOID m_pInBuffer, DWORD m_dBufferSize, LPVOID m_pOutBuffer, DWORD m_dOutBufferSize)
1357 | {
1358 | DWORD m_dBytesReturned = { 0 };
1359 | const BOOL m_bDidSucceed = DeviceIoControl(this->m_hTargetDriverHandle,
1360 | m_dControlCode, m_pInBuffer,
1361 | m_dBufferSize, m_pOutBuffer,
1362 | m_dOutBufferSize, &m_dBytesReturned,
1363 | nullptr);
1364 |
1365 | if (!m_bDidSucceed)
1366 | {
1367 | CH_LOG("DeviceIoControl failed with error code #%i!", GetLastError());
1368 | return NULL;
1369 | }
1370 |
1371 | return m_dBytesReturned;
1372 | }
1373 |
1374 | // Loads a target driver through the service manager. Obviously, these drivers must be SIGNED.
1375 | SC_HANDLE Driver_t::LoadDriver(const char* m_szDriverPaths, const char* m_szDriverName)
1376 | {
1377 | // Create HANDLE to the service manager.
1378 | const SC_HANDLE m_hServiceManager = OpenSCManagerA(nullptr, nullptr, SC_MANAGER_CREATE_SERVICE);
1379 | if (!m_hServiceManager || m_hServiceManager == INVALID_HANDLE_VALUE)
1380 | {
1381 | CH_LOG("Couldn't obtain valid HANDLE to service manager!");
1382 | return CH_R_CAST(INVALID_HANDLE_VALUE);
1383 | }
1384 | // Tell service manager to create&intialize our service.
1385 | const SC_HANDLE m_hCreatedService = CreateServiceA(m_hServiceManager,
1386 | m_szDriverName, m_szDriverName,
1387 | SERVICE_START | SERVICE_STOP | DELETE,
1388 | SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START, SERVICE_ERROR_IGNORE,
1389 | m_szDriverPaths, nullptr, nullptr, nullptr, nullptr, nullptr);
1390 |
1391 | if (!m_hCreatedService || m_hCreatedService == INVALID_HANDLE_VALUE)
1392 | {
1393 | CH_LOG("Failed to create desired service!");
1394 | return CH_R_CAST(INVALID_HANDLE_VALUE);
1395 | }
1396 |
1397 | ADD_SCOPE_HANDLER(CloseServiceHandle, m_hServiceManager);
1398 |
1399 | // Finally, start the service.
1400 | if (!StartServiceA(m_hCreatedService, NULL, nullptr))
1401 | {
1402 | CH_LOG("Failed to start desired service!");
1403 | return CH_R_CAST(INVALID_HANDLE_VALUE);
1404 | }
1405 |
1406 | // Release unneeded handle.
1407 | return m_hCreatedService;
1408 | }
1409 |
1410 | // Unloads a target driver that was previously loaded through the service manager.
1411 | void Driver_t::UnloadDriver(const SC_HANDLE m_hLoadedStartedService)
1412 | {
1413 | // Send STOP signal to desired service.
1414 | SERVICE_STATUS m_ServiceStatus{};
1415 | ControlService(m_hLoadedStartedService, SERVICE_CONTROL_STOP, &m_ServiceStatus);
1416 |
1417 | // Finally, delete the service.
1418 | DeleteService(m_hLoadedStartedService);
1419 | }
1420 | }
1421 |
1422 | // Thread_t definitions and functions.
1423 | namespace chdr
1424 | {
1425 | // Initialize with TID.
1426 | Thread_t::Thread_t(std::uint32_t m_dThreadID)
1427 | {
1428 | this->m_dThreadID = m_dThreadID;
1429 | this->m_hThreadHandle = OpenThread(THREAD_ALL_ACCESS, FALSE, this->m_dThreadID);
1430 |
1431 | // Only release this HANDLE if we've actually got access to it.
1432 | this->m_bShouldFreeHandleAtDestructor = this->m_hThreadHandle && this->m_hThreadHandle != INVALID_HANDLE_VALUE;
1433 | }
1434 |
1435 | // Initialize with TID&HANDLE.
1436 | Thread_t::Thread_t(HANDLE m_hThreadHandle)
1437 | {
1438 | this->m_hThreadHandle = m_hThreadHandle;
1439 | this->m_dThreadID = GetThreadId(this->m_hThreadHandle);
1440 |
1441 | // We should NEVER try to release this HANDLE, as it's a copy of another.
1442 | this->m_bShouldFreeHandleAtDestructor = false;
1443 | }
1444 |
1445 | // Default dtor
1446 | Thread_t::~Thread_t()
1447 | {
1448 | // Not allowed to release this HANDLE, or was already released.
1449 | CH_ASSERT(true,
1450 | this->m_bShouldFreeHandleAtDestructor &&
1451 | this->m_hThreadHandle &&
1452 | this->m_hThreadHandle != INVALID_HANDLE_VALUE,
1453 | "");
1454 |
1455 | // FIXME:
1456 | CloseHandle(this->m_hThreadHandle);
1457 | }
1458 |
1459 | #pragma warning( push )
1460 | #pragma warning( disable : 6258 ) // Using TerminateThread does not allow proper thread clean.
1461 | // Terminating a target thread.
1462 | void Thread_t::Terminate()
1463 | {
1464 | CH_ASSERT(true, this->m_hThreadHandle && this->m_hThreadHandle != INVALID_HANDLE_VALUE, "Tried to terminate a target thread with an empty HANDLE!");
1465 |
1466 | TerminateThread(this->m_hThreadHandle, EXIT_FAILURE/*To give some sort of graceful termination.*/);
1467 | CloseHandle(this->m_hThreadHandle); // Can we even do this? Common sense dictates no.
1468 | }
1469 | #pragma warning( pop )
1470 |
1471 | // Suspending a target thread.
1472 | void Thread_t::Suspend()
1473 | {
1474 | CH_ASSERT(true, this->m_hThreadHandle && this->m_hThreadHandle != INVALID_HANDLE_VALUE, "Tried to suspend a target thread with an empty HANDLE!");
1475 |
1476 | this->m_bIsThreadManuallySuspended = SuspendThread(this->m_hThreadHandle) != 0;
1477 | }
1478 |
1479 | // Resuming a target thread.
1480 | void Thread_t::Resume()
1481 | {
1482 | CH_ASSERT(true, this->m_hThreadHandle && this->m_hThreadHandle != INVALID_HANDLE_VALUE, "Tried to resume a target thread with an empty HANDLE!");
1483 |
1484 | this->m_bIsThreadManuallySuspended = ResumeThread(this->m_hThreadHandle) == 0;
1485 | CH_ASSERT(true, !this->m_bIsThreadManuallySuspended, "Failed to resume thread with TID %i!", this->m_dThreadID);
1486 | }
1487 |
1488 | // Get context of this thread.
1489 | CONTEXT Thread_t::GetThreadCTX()
1490 | {
1491 | CONTEXT result; { result.ContextFlags = CONTEXT_FULL; }
1492 | GetThreadContext(this->m_hThreadHandle, &result);
1493 | return result;
1494 | }
1495 |
1496 | // Set context of this thread.
1497 | void Thread_t::SetThreadCTX(CONTEXT m_Context)
1498 | {
1499 | SetThreadContext(this->m_hThreadHandle, &m_Context);
1500 | }
1501 |
1502 | // Check which module this thread is associated with.
1503 | std::string Thread_t::GetOwningModule(chdr::Process_t& m_Process, bool m_bUseCachedData)
1504 | {
1505 | const std::uintptr_t m_StartAddress = this->GetStartAddress();
1506 | if (m_StartAddress == 0u)
1507 | // Weird, shouldn't happen unless we have no valid HANDLE.
1508 | return "N/A (Couldn't find thread start address.)";
1509 |
1510 | // Traverse through all loaded modules, and determine where our thread lies in.
1511 | for (auto& CurrentModule : m_Process.EnumerateModules(m_bUseCachedData))
1512 | {
1513 | if (m_StartAddress < CurrentModule.m_BaseAddress ||
1514 | m_StartAddress > CurrentModule.m_BaseAddress + CurrentModule.m_nSize)
1515 | continue;
1516 |
1517 | return CurrentModule.m_szName;
1518 | }
1519 | return "N/A (Passing through false as default parameter should be your solution.)";
1520 | }
1521 |
1522 | // Ensure we found a HANDLE to the target thread.
1523 | bool Thread_t::IsValid()
1524 | {
1525 | return this->m_hThreadHandle && this->m_hThreadHandle != INVALID_HANDLE_VALUE;
1526 | }
1527 |
1528 | // Is the target thread suspended?
1529 | bool Thread_t::IsSuspended()
1530 | {
1531 | bool m_bIsThreadSuspended = WaitForSingleObject(this->m_hThreadHandle, 0) == WAIT_ABANDONED;
1532 | return m_bIsThreadSuspended;
1533 | }
1534 |
1535 | // Did we suspend the target thread ourselves?
1536 | bool Thread_t::IsManuallySuspended()
1537 | {
1538 | return this->m_bIsThreadManuallySuspended;
1539 | }
1540 |
1541 | // Get's the start address of a target thread.
1542 | std::uint32_t Thread_t::GetStartAddress()
1543 | {
1544 | NtQueryInformationThread_fn NtQueryInformationThread =
1545 | CH_R_CAST(GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtQueryInformationThread"));
1546 |
1547 | std::uint32_t m_dThreadStartAddress = NULL;
1548 | NtQueryInformationThread(this->m_hThreadHandle, THREADINFOCLASS::ThreadQuerySetWin32StartAddress, &m_dThreadStartAddress, sizeof(std::uint32_t), nullptr);
1549 | return m_dThreadStartAddress;
1550 | }
1551 | }
1552 |
1553 | // Module_t definitions and functions.
1554 | namespace chdr
1555 | {
1556 | // Setup module in process by (non-case sensitive) name.
1557 | Module_t::Module_t(chdr::Process_t& m_Process, const char* m_szModuleName, std::int32_t m_ParseType)
1558 | {
1559 | // Walk all loaded modules until we land on the wish module.
1560 | for (const auto& CurrentModule : m_Process.EnumerateModules())
1561 | {
1562 | if (std::strcmp(CurrentModule.m_szName.c_str(), m_szModuleName) != 0)
1563 | continue;
1564 |
1565 | this->m_dModuleBaseAddress = CurrentModule.m_BaseAddress;
1566 | this->m_dModuleSize = CurrentModule.m_nSize;
1567 | break; // Found what we needed, exit loop.
1568 | }
1569 |
1570 | CH_ASSERT(true, this->m_dModuleBaseAddress && this->m_dModuleSize, "Couldn't find desired module %s", m_szModuleName);
1571 |
1572 | this->SetupModule_Internal(m_Process, m_ParseType);
1573 | }
1574 |
1575 | // Setup module in process by address in processes' memory space.
1576 | Module_t::Module_t(chdr::Process_t& m_Process, std::uintptr_t m_dModuleBaseAddress, std::uint32_t m_dModuleSize, std::int32_t m_ParseType)
1577 | {
1578 | this->m_dModuleBaseAddress = m_dModuleBaseAddress;
1579 | this->m_dModuleSize = m_dModuleSize;
1580 |
1581 | this->SetupModule_Internal(m_Process, m_ParseType);
1582 | }
1583 |
1584 | void Module_t::SetupModule_Internal(chdr::Process_t& m_Process, std::int32_t m_ParseType)
1585 | {
1586 | // Read whole module to our buffer in heap.
1587 | const auto m_ModuleDataTemp = std::make_unique(this->m_dModuleSize);
1588 | const std::size_t m_nReadBytes = m_Process.Read(this->m_dModuleBaseAddress, m_ModuleDataTemp.get(), this->m_dModuleSize);
1589 |
1590 | CH_ASSERT(true, m_nReadBytes != 0u, "Failed to read image of desired module.");
1591 |
1592 | // Ensure vector holding image buffer has sufficient size.
1593 | this->m_ModuleData.resize(m_nReadBytes);
1594 |
1595 | // Copy over read data from this module.
1596 | std::memcpy(&this->m_ModuleData[0], m_ModuleDataTemp.get(), m_nReadBytes);
1597 |
1598 | this->m_PEHeaderData = PEHeaderData_t(m_Process, m_ParseType, this->m_dModuleBaseAddress);
1599 | }
1600 |
1601 | // Helper function to get PE header data of target process.
1602 | PEHeaderData_t Module_t::GetPEHeaderData()
1603 | {
1604 | return this->m_PEHeaderData;
1605 | }
1606 |
1607 | // Helper function to get module data of target process.
1608 | ByteArray_t Module_t::GetModuleData()
1609 | {
1610 | return this->m_ModuleData;
1611 | }
1612 |
1613 | // Ensure we found the target module in memory.
1614 | bool Module_t::IsValid()
1615 | {
1616 | return this->m_ModuleData.size() != 0;
1617 | }
1618 |
1619 | Address_t Module_t::FindIDASignature(std::string_view m_szSignature)
1620 | {
1621 | static auto ToBytes = [](std::string_view m_szSignature) {
1622 | std::vector byteArray = {};
1623 | const auto m_pStart = CH_C_CAST(m_szSignature.data());
1624 | const auto m_pEnd = m_pStart + m_szSignature.length();
1625 |
1626 | for (char* m_pCurrent = m_pStart; m_pCurrent < m_pEnd; ++m_pCurrent) {
1627 | if (*m_pCurrent == '?') {
1628 | ++m_pCurrent;
1629 |
1630 | if (*m_pCurrent == '?')
1631 | ++m_pCurrent;
1632 |
1633 | byteArray.push_back(-1);
1634 | }
1635 | else
1636 | byteArray.push_back(strtoul(m_pCurrent, &m_pCurrent, 16));
1637 | }
1638 | return byteArray;
1639 | };
1640 |
1641 | if (!m_dModuleSize || !IsValid()) {
1642 | CH_LOG("Invalid module provided.");
1643 | return Address_t();
1644 | }
1645 |
1646 | // can't be ByteArray_t type because of wildcards.
1647 | const auto m_byteArray = ToBytes(m_szSignature);
1648 | const std::size_t nSize = m_byteArray.size();
1649 | const int* pBytes = m_byteArray.data();
1650 |
1651 | // Find matching sequences.
1652 | for (std::size_t i = 0u; i < m_dModuleSize - nSize; ++i) {
1653 | bool bFound = true;
1654 |
1655 | for (std::size_t j = 0u; j < nSize; ++j) {
1656 | if (pBytes[j] != -1 && m_ModuleData[i + j] != pBytes[j]) {
1657 | bFound = false;
1658 | break;
1659 | }
1660 | }
1661 |
1662 | if (bFound)
1663 | return Address_t(&m_ModuleData[i]);
1664 | }
1665 |
1666 | CH_LOG("Couldn't find IDA signature %s.", m_szSignature.data());
1667 | return Address_t();
1668 | }
1669 | }
1670 |
1671 | // Miscelleanous functions.
1672 | namespace chdr
1673 | {
1674 | namespace misc
1675 | {
1676 |
1677 | }
1678 | }
--------------------------------------------------------------------------------
/chdr.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include