inline SmartHandle CreateRemoteThreadAndWait(Process const& process, LPTHREAD_START_ROUTINE func, DWORD timeout = INFINITE) { SmartHandle remote_thread{::CreateRemoteThread( process.GetHandle(), nullptr, 0, func, nullptr, 0, nullptr)}; if (!remote_thread.GetHandle()) { DWORD const last_error = ::GetLastError(); HADESMEM_DETAIL_THROW_EXCEPTION(Error{} << ErrorString{"CreateRemoteThread failed."} << ErrorCodeWinLast{last_error}); } DWORD const wait_res = ::WaitForSingleObject(remote_thread.GetHandle(), timeout); if (wait_res != WAIT_OBJECT_0) { if (wait_res == WAIT_TIMEOUT) { HADESMEM_DETAIL_THROW_EXCEPTION( Error{} << ErrorString{"WaitForSingleObject timeout."}); } DWORD const last_error = ::GetLastError(); HADESMEM_DETAIL_THROW_EXCEPTION( Error{} << ErrorString{"WaitForSingleObject failed."} << ErrorCodeWinLast{last_error}); } return remote_thread; }
std::basic_string<CharT> CheckedReadString(Process const& process, PeFile const& pe_file, void* address) { if (pe_file.GetType() == PeFileType::Image) { return ReadString<CharT>(process, address); } else if (pe_file.GetType() == PeFileType::Data) { void* const file_end = static_cast<std::uint8_t*>(pe_file.GetBase()) + pe_file.GetSize(); if (address >= file_end) { HADESMEM_DETAIL_THROW_EXCEPTION(Error{} << ErrorString{"Invalid VA."}); } // Handle EOF termination. // Sample: maxsecXP.exe (Corkami PE Corpus) return ReadStringBounded<CharT>(process, address, file_end); } else { HADESMEM_DETAIL_ASSERT(false); HADESMEM_DETAIL_THROW_EXCEPTION(Error{} << ErrorString{"Unknown PE file type."}); } }
inline ModuleRegionInfo GetModuleInfo(Process const& process, std::wstring const& module) { ModuleRegionInfo mod_info; if (module.empty()) { mod_info.module = std::make_shared<Module>(process, nullptr); } else { mod_info.module = std::make_shared<Module>(process, module); } auto const base = reinterpret_cast<std::uint8_t*>(mod_info.module->GetHandle()); PeFile const pe_file{process, base, hadesmem::PeFileType::Image, 0}; DosHeader const dos_header{process, pe_file}; NtHeaders const nt_headers{process, pe_file}; SectionList const sections{process, pe_file}; for (auto const& s : sections) { bool const is_code_section = !!(s.GetCharacteristics() & IMAGE_SCN_CNT_CODE); bool const is_data_section = !!(s.GetCharacteristics() & IMAGE_SCN_CNT_INITIALIZED_DATA); if (!is_code_section && !is_data_section) { continue; } auto const section_beg = static_cast<std::uint8_t*>( RvaToVa(process, pe_file, s.GetVirtualAddress())); if (section_beg == nullptr) { HADESMEM_DETAIL_THROW_EXCEPTION( Error() << ErrorString("Could not get section base address.")); } DWORD const section_size = s.GetVirtualSize(); if (!section_size) { continue; } auto const section_end = section_beg + section_size; auto& regions = is_code_section ? mod_info.code_regions : mod_info.data_regions; regions.emplace_back(section_beg, section_end); } if (mod_info.code_regions.empty() && mod_info.data_regions.empty()) { HADESMEM_DETAIL_THROW_EXCEPTION( Error() << ErrorString("No valid sections to scan found.")); } return mod_info; }
void Unload() { auto const ntdll = ::GetModuleHandleW(L"ntdll.dll"); if (!ntdll) { DWORD const last_error = ::GetLastError(); HADESMEM_DETAIL_THROW_EXCEPTION(Error{} << ErrorString{"GetModuleHandleW failed."} << ErrorCodeWinLast{last_error}); } typedef NTSTATUS(NTAPI * NtUnloadDriverFn)(PUNICODE_STRING DriverServiceName); auto const nt_unload_driver = reinterpret_cast<NtUnloadDriverFn>( ::GetProcAddress(ntdll, "NtUnloadDriver")); if (!nt_unload_driver) { DWORD const last_error = ::GetLastError(); HADESMEM_DETAIL_THROW_EXCEPTION(Error{} << ErrorString{"GetProcAddress failed."} << ErrorCodeWinLast{last_error}); } UNICODE_STRING reg_path; std::wstring const reg_path_buf = L"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\" + name_; ::RtlInitUnicodeString(®_path, reg_path_buf.c_str()); auto const unload_status = nt_unload_driver(®_path); if (!NT_SUCCESS(unload_status)) { HADESMEM_DETAIL_THROW_EXCEPTION(Error{} << ErrorString{"NtUnloadDriver failed."} << ErrorCodeWinStatus{unload_status}); } auto const reg_status = ::RegDeleteTreeW( HKEY_LOCAL_MACHINE, (L"SYSTEM\\CurrentControlSet\\Services\\" + name_).c_str()); if (reg_status != ERROR_SUCCESS) { DWORD const last_error = ::GetLastError(); HADESMEM_DETAIL_THROW_EXCEPTION(Error{} << ErrorString{"RegDeleteTreeW failed."} << ErrorCodeWinLast{last_error} << ErrorCodeWinHr{reg_status}); } }
inline void* GetStartAddress(Thread const& thread) { HMODULE const ntdll = GetModuleHandleW(L"ntdll"); if (!ntdll) { DWORD const last_error = ::GetLastError(); HADESMEM_DETAIL_THROW_EXCEPTION(Error{} << ErrorString{"GetModuleHandleW failed."} << ErrorCodeWinLast{last_error}); } FARPROC const nt_query_information_thread_proc = GetProcAddress(ntdll, "NtQueryInformationThread"); if (!nt_query_information_thread_proc) { DWORD const last_error = ::GetLastError(); HADESMEM_DETAIL_THROW_EXCEPTION(Error{} << ErrorString{"GetProcAddress failed."} << ErrorCodeWinLast{last_error}); } using NtQueryInformationThreadPtr = NTSTATUS(NTAPI*)(HANDLE thread_handle, detail::winternl::THREADINFOCLASS thread_information_class, PVOID thread_information, ULONG thread_information_length, PULONG return_length); auto const nt_query_information_thread = reinterpret_cast<NtQueryInformationThreadPtr>( nt_query_information_thread_proc); void* start_address = nullptr; NTSTATUS const status = nt_query_information_thread( thread.GetHandle(), detail::winternl::ThreadQuerySetWin32StartAddress, &start_address, sizeof(start_address), nullptr); if (!NT_SUCCESS(status)) { HADESMEM_DETAIL_THROW_EXCEPTION( Error{} << ErrorString{"NtQueryInformationThread failed."} << ErrorCodeWinStatus{status}); } return start_address; }
void* Find(Process const& process, ModuleRegionInfo::ScanRegion const& region, void* start, NeedleIterator n_beg, NeedleIterator n_end) { std::uint8_t* s_beg = region.first; std::uint8_t* const s_end = region.second; // Support custom scan start address. if (start) { // Use specified starting address (plus one, so we don't // just find the same thing again) if we're in the target // region. if (start >= s_beg && start < s_end) { s_beg = static_cast<std::uint8_t*>(start) + 1; if (s_beg == s_end) { HADESMEM_DETAIL_THROW_EXCEPTION( Error() << ErrorString("Invalid start address.")); } } // Skip if we're not in the target region. else { return nullptr; } } return FindRaw(process, s_beg, s_end, n_beg, n_end); }
void* Find(Process const& process, ModuleRegionInfo const& mod_info, NeedleIterator n_beg, NeedleIterator n_end, std::uint32_t flags, void* start, std::wstring const* name) { HADESMEM_DETAIL_ASSERT(n_beg != n_end); bool const scan_data_secs = !!(flags & PatternFlags::kScanData); auto const& scan_regions = scan_data_secs ? mod_info.data_regions : mod_info.code_regions; for (auto const& region : scan_regions) { if (void* const address = Find(process, region, start, n_beg, n_end)) { return !!(flags & PatternFlags::kRelativeAddress) ? static_cast<std::uint8_t*>(address) - reinterpret_cast<std::uintptr_t>( mod_info.module->GetHandle()) : address; } } if (!!(flags & PatternFlags::kThrowOnUnmatch)) { auto const name_narrow = name ? WideCharToMultiByte(*name) : std::string(); HADESMEM_DETAIL_THROW_EXCEPTION(Error{} << ErrorString{"Could not match pattern."} << ErrorStringOther{name_narrow}); } return nullptr; }
void* Find(Process const& process, std::pair<std::uint8_t*, std::uint8_t*> const& region, NeedleIterator n_beg, NeedleIterator n_end, std::uint32_t flags, void* start, std::wstring const* name) { HADESMEM_DETAIL_ASSERT(n_beg != n_end); if (void* const address = Find(process, region, start, n_beg, n_end)) { return !!(flags & PatternFlags::kRelativeAddress) ? static_cast<std::uint8_t*>(address) - reinterpret_cast<std::uintptr_t>(region.first) : address; } if (!!(flags & PatternFlags::kThrowOnUnmatch)) { auto const name_narrow = name ? WideCharToMultiByte(*name) : std::string(); HADESMEM_DETAIL_THROW_EXCEPTION(Error{} << ErrorString{"Could not match pattern."} << ErrorStringOther{name_narrow}); } return nullptr; }
void GetCallbacks(OutputIterator callbacks) const { using OutputIteratorCategory = typename std::iterator_traits<OutputIterator>::iterator_category; HADESMEM_DETAIL_STATIC_ASSERT( std::is_base_of<std::output_iterator_tag, OutputIteratorCategory>::value); auto const image_base = GetRuntimeBase(*process_, *pe_file_); auto callbacks_raw = reinterpret_cast<PIMAGE_TLS_CALLBACK*>( RvaToVa(*process_, *pe_file_, static_cast<DWORD>(GetAddressOfCallBacks() - image_base))); if (!callbacks_raw) { HADESMEM_DETAIL_THROW_EXCEPTION( Error{} << ErrorString{"TLS callbacks are invalid."}); } for (auto callback = Read<PIMAGE_TLS_CALLBACK>(*process_, callbacks_raw); callback; callback = Read<PIMAGE_TLS_CALLBACK>(*process_, ++callbacks_raw)) { auto const callback_offset = reinterpret_cast<ULONGLONG>(callback) - image_base; *callbacks = reinterpret_cast<PIMAGE_TLS_CALLBACK>( static_cast<ULONG_PTR>(callback_offset)); ++callbacks; } }
void EnsureValid() const { if (!IsValid()) { HADESMEM_DETAIL_THROW_EXCEPTION( Error{} << ErrorString{"DOS header magic invalid."}); } }
void EnsureValid() const { if (!IsValid()) { HADESMEM_DETAIL_THROW_EXCEPTION( Error{} << ErrorString{"NT headers signature invalid."}); } }
inline std::wstring CombinePath(std::wstring const& base, std::wstring const& append) { // Use newer and better PathCchCombineEx if it's available. detail::SmartModuleHandle const path_mod{ LoadLibraryW(L"api-ms-win-core-path-l1-1-0.dll")}; if (path_mod.IsValid()) { using PathCchCombineExFn = HRESULT(WINAPI*)(PWSTR pszPathOut, size_t cchPathOut, PCWSTR pszPathIn, PCWSTR pszMore, unsigned long dwFlags); auto const path_cch_combine_ex = reinterpret_cast<PathCchCombineExFn>( GetProcAddress(path_mod.GetHandle(), "PathCchCombineEx")); if (path_cch_combine_ex) { std::vector<wchar_t> buffer(HADESMEM_DETAIL_MAX_PATH_UNICODE); HRESULT const hr = path_cch_combine_ex(buffer.data(), buffer.size(), base.c_str(), append.c_str(), HADESMEM_PATHCCH_ALLOW_LONG_PATHS); if (!SUCCEEDED(hr)) { HADESMEM_DETAIL_THROW_EXCEPTION( Error{} << ErrorString{"PathCchCombineEx failed."} << ErrorCodeWinHr{hr}); } return buffer.data(); } } // Fall back to older API with MAX_PATH limit. std::vector<wchar_t> buffer(MAX_PATH); if (!::PathCombineW(buffer.data(), base.c_str(), append.c_str())) { DWORD const last_error = ::GetLastError(); HADESMEM_DETAIL_THROW_EXCEPTION(Error{} << ErrorString{"PathCombineW failed."} << ErrorCodeWinLast{last_error}); } return buffer.data(); }
explicit Driver(std::wstring const& name, std::wstring const& path) : name_(name), path_(path) { if (detail::IsPathRelative(path_)) { HADESMEM_DETAIL_THROW_EXCEPTION(Error{} << ErrorString{"Path must be absolute."}); } }
explicit PeFile(Process const& process, void* address, PeFileType type, DWORD size) : process_{&process}, base_{static_cast<std::uint8_t*>(address)}, type_{type}, size_{size} { HADESMEM_DETAIL_ASSERT(base_ != 0); if (type == PeFileType::Data && !size) { HADESMEM_DETAIL_THROW_EXCEPTION(Error{} << ErrorString{"Invalid file size."}); } if (type == PeFileType::Image && !size) { try { Module const module{process, reinterpret_cast<HMODULE>(address)}; size_ = module.GetSize(); } catch (...) { auto const region_alloc_size = detail::GetRegionAllocSize(*process_, base_); HADESMEM_DETAIL_ASSERT(region_alloc_size < (std::numeric_limits<DWORD>::max)()); size_ = static_cast<DWORD>(region_alloc_size); } } // Not erroring out anywhere here in order to retain back-compat. // TODO: Do this properly as part of the rewrite. try { if (size_ > sizeof(IMAGE_DOS_HEADER)) { auto const nt_hdrs_ofs = Read<IMAGE_DOS_HEADER>(process, address).e_lfanew; if (size_ >= nt_hdrs_ofs + sizeof(DWORD) + sizeof(IMAGE_FILE_HEADER)) { auto const nt_hdrs = Read<IMAGE_NT_HEADERS>( process, static_cast<std::uint8_t*>(address) + nt_hdrs_ofs); if (nt_hdrs.Signature == IMAGE_NT_SIGNATURE && nt_hdrs.FileHeader.Machine == IMAGE_FILE_MACHINE_AMD64) { is_64_ = true; } } } } catch (...) { } }
inline void Free(Process const& process, LPVOID address) { if (!::VirtualFreeEx(process.GetHandle(), address, 0, MEM_RELEASE)) { DWORD const last_error = ::GetLastError(); HADESMEM_DETAIL_THROW_EXCEPTION(Error{} << ErrorString{"VirtualFreeEx failed."} << ErrorCodeWinLast{last_error}); } }
void Cleanup() { if (ResumeThread(handle_) == static_cast<DWORD>(-1)) { DWORD const last_error = ::GetLastError(); HADESMEM_DETAIL_THROW_EXCEPTION( hadesmem::Error{} << hadesmem::ErrorString{"ResumeThread failed."} << hadesmem::ErrorCodeWinLast{last_error}); } }
inline void SetThreadContext(Thread const& thread, CONTEXT const& context) { if (!::SetThreadContext(thread.GetHandle(), &context)) { DWORD const last_error = ::GetLastError(); HADESMEM_DETAIL_THROW_EXCEPTION(Error() << ErrorString("SetThreadContext failed.") << ErrorCodeWinLast(last_error)); } }
inline void GetPrivilege(std::wstring const& name) { HANDLE process_token_temp = nullptr; if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &process_token_temp)) { DWORD const last_error = ::GetLastError(); HADESMEM_DETAIL_THROW_EXCEPTION(Error{} << ErrorString{"OpenProcessToken failed."} << ErrorCodeWinLast{last_error}); } detail::SmartHandle const process_token { process_token_temp }; LUID luid = {0, 0}; if (!::LookupPrivilegeValueW(nullptr, name.c_str(), &luid)) { DWORD const last_error = ::GetLastError(); HADESMEM_DETAIL_THROW_EXCEPTION( Error{} << ErrorString{"LookupPrivilegeValue failed."} << ErrorCodeWinLast{last_error}); } TOKEN_PRIVILEGES privileges{}; privileges.PrivilegeCount = 1; privileges.Privileges[0].Luid = luid; privileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; if (!::AdjustTokenPrivileges(process_token.GetHandle(), FALSE, &privileges, static_cast<DWORD>(sizeof(privileges)), nullptr, nullptr) || ::GetLastError() == ERROR_NOT_ALL_ASSIGNED) { DWORD const last_error = ::GetLastError(); HADESMEM_DETAIL_THROW_EXCEPTION( Error{} << ErrorString{"AdjustTokenPrivileges failed."} << ErrorCodeWinLast{last_error}); } }
WORD GetForwarderOrdinal() const { if (!IsForwardedByOrdinal()) { HADESMEM_DETAIL_THROW_EXCEPTION( Error{} << ErrorString{"Function is not exported by ordinal."}); } try { std::string const forwarder_function{GetForwarderFunction()}; return detail::StrToNum<WORD>(forwarder_function.substr(1)); } catch (std::exception const& /*e*/) { HADESMEM_DETAIL_THROW_EXCEPTION( Error{} << ErrorString{"Invalid forwarder ordinal detected."}); } }
void SetBaseOfData(DWORD base_of_data) { if (pe_file_->Is64()) { HADESMEM_DETAIL_THROW_EXCEPTION( Error{} << ErrorString{"Invalid field for architecture."}); } else { data_32_.OptionalHeader.BaseOfData = base_of_data; } }
DWORD GetBaseOfData() const { if (pe_file_->Is64()) { HADESMEM_DETAIL_THROW_EXCEPTION( Error{} << ErrorString{"Invalid field for architecture."}); } else { return data_32_.OptionalHeader.BaseOfData; } }
std::string GetName() const { auto const name_import = static_cast<std::uint8_t*>( RvaToVa(*process_, *pe_file_, static_cast<DWORD>(GetAddressOfData()))); if (!name_import) { HADESMEM_DETAIL_THROW_EXCEPTION( Error{} << ErrorString{"Invalid import name and hint."}); } return detail::CheckedReadString<char>( *process_, *pe_file_, name_import + offsetof(IMAGE_IMPORT_BY_NAME, Name)); }
inline CONTEXT GetThreadContext(Thread const& thread, DWORD context_flags) { if (::GetCurrentThreadId() == thread.GetId() && context_flags != CONTEXT_DEBUG_REGISTERS) { HADESMEM_DETAIL_THROW_EXCEPTION( Error() << ErrorString("GetThreadContext called for current thread.")); } CONTEXT context{}; context.ContextFlags = context_flags; if (!::GetThreadContext(thread.GetHandle(), &context)) { DWORD const last_error = ::GetLastError(); HADESMEM_DETAIL_THROW_EXCEPTION(Error() << ErrorString("GetThreadContext failed.") << ErrorCodeWinLast(last_error)); } return context; }
inline BY_HANDLE_FILE_INFORMATION GetFileInformationByHandle(HANDLE file) { BY_HANDLE_FILE_INFORMATION file_info{}; if (!::GetFileInformationByHandle(file, &file_info)) { DWORD const last_error = ::GetLastError(); HADESMEM_DETAIL_THROW_EXCEPTION( Error{} << ErrorString{"GetFileInformationByHandle failed."} << ErrorCodeWinLast{last_error}); } return file_info; }
inline FARPROC FindProcedure(Process const& process, Module const& module, WORD ordinal) { FARPROC const remote_func = detail::GetProcAddressInternal(process, module.GetHandle(), ordinal); if (!remote_func) { HADESMEM_DETAIL_THROW_EXCEPTION( Error{} << ErrorString{"GetProcAddressInternal failed."}); } return remote_func; }
inline DWORD GetFileAttributesWrapper(std::wstring const& path) { DWORD const attributes = ::GetFileAttributesW(path.c_str()); if (attributes == INVALID_FILE_ATTRIBUTES) { DWORD const last_error = ::GetLastError(); HADESMEM_DETAIL_THROW_EXCEPTION(Error{} << ErrorString{"GetFileAttributes failed."} << ErrorCodeWinLast{last_error}); } return attributes; }
void HandleWindowChange(HWND wnd) { WindowInfo& window_info = GetWindowInfo(); if (wnd == nullptr) { if (window_info.hooked_ && window_info.old_wndproc_ != nullptr) { ::SetWindowLongPtrW(window_info.old_hwnd_, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(window_info.old_wndproc_)); HADESMEM_DETAIL_TRACE_FORMAT_A("Reset window procedure located at %p.", window_info.old_wndproc_); } HADESMEM_DETAIL_TRACE_A("Clearing window hook data."); window_info.old_hwnd_ = nullptr; window_info.old_wndproc_ = nullptr; window_info.hooked_ = false; return; } if (!window_info.hooked_) { window_info.old_hwnd_ = wnd; ::SetLastError(0); window_info.old_wndproc_ = reinterpret_cast<WNDPROC>(::SetWindowLongPtrW( wnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(&WindowProc))); if (!window_info.old_wndproc_) { DWORD const last_error = ::GetLastError(); if (last_error) { HADESMEM_DETAIL_THROW_EXCEPTION( Error{} << ErrorString{"SetWindowLongPtrW failed."} << ErrorCodeWinLast{last_error}); } } window_info.hooked_ = true; HADESMEM_DETAIL_TRACE_FORMAT_A("Replaced window procedure located at %p.", window_info.old_wndproc_); } else { HADESMEM_DETAIL_TRACE_A("Window is already hooked. Skipping hook request."); } }
explicit TlsDir(Process const& process, PeFile const& pe_file) : process_{&process}, pe_file_{&pe_file} { NtHeaders const nt_headers{process, pe_file}; DWORD const data_dir_va = nt_headers.GetDataDirectoryVirtualAddress(PeDataDir::TLS); // Windows will load images which don't specify a size for the // TLS directory. if (!data_dir_va) { HADESMEM_DETAIL_THROW_EXCEPTION( Error{} << ErrorString{"PE file has no TLS directory."}); } base_ = static_cast<std::uint8_t*>(RvaToVa(process, pe_file, data_dir_va)); if (!base_) { HADESMEM_DETAIL_THROW_EXCEPTION( Error{} << ErrorString{"TLS directory is invalid."}); } UpdateRead(); }
inline std::wstring GetRootPath(std::wstring const& path) { int const drive_num = ::PathGetDriveNumberW(path.c_str()); if (drive_num == -1) { DWORD const last_error = ::GetLastError(); HADESMEM_DETAIL_THROW_EXCEPTION(Error{} << ErrorString{"PathGetDriveNumber failed."} << ErrorCodeWinLast{last_error}); } std::vector<wchar_t> drive_path(4); ::PathBuildRootW(drive_path.data(), drive_num); if (drive_path[0] == L'\0' || drive_path[1] == L'\0' || drive_path[2] == L'\0') { DWORD const last_error = ::GetLastError(); HADESMEM_DETAIL_THROW_EXCEPTION(Error{} << ErrorString{"PathBuildRoot failed."} << ErrorCodeWinLast{last_error}); } return drive_path.data(); }
void Config::LoadFile(std::wstring const& path) { pugi::xml_document doc; auto const load_result = doc.load_file(path.c_str()); if (!load_result) { HADESMEM_DETAIL_THROW_EXCEPTION( hadesmem::Error{} << hadesmem::ErrorString{"Loading XML file failed."} << hadesmem::ErrorCodeOther{static_cast<DWORD_PTR>(load_result.status)} << hadesmem::ErrorStringOther{load_result.description()}); } LoadImpl(doc); }