virtual void RemovePatch() override { hadesmem::detail::AcquireSRWLock const lock( &GetSrwLock(), hadesmem::detail::SRWLockType::Exclusive); HADESMEM_DETAIL_TRACE_A("Unsetting DR hook."); auto& dr_hooks = GetDrHooks(); auto const thread_id = ::GetCurrentThreadId(); auto const iter = dr_hooks.find(thread_id); HADESMEM_DETAIL_ASSERT(iter != std::end(dr_hooks)); auto const dr_index = iter->second; Thread const thread(thread_id); auto context = GetThreadContext(thread, CONTEXT_DEBUG_REGISTERS); // Clear the appropriate DR *(&context.Dr0 + dr_index) = 0; // Clear appropriate L0-L3 flag context.Dr7 &= ~static_cast<std::uintptr_t>(1ULL << (dr_index * 2)); SetThreadContext(thread, context); auto const dr_hooks_removed = dr_hooks.erase(thread_id); (void)dr_hooks_removed; HADESMEM_DETAIL_ASSERT(dr_hooks_removed); auto& veh_hooks = GetVehHooks(); auto const veh_hooks_removed = veh_hooks.erase(target_); (void)veh_hooks_removed; HADESMEM_DETAIL_ASSERT(veh_hooks_removed); }
void DisassembleEp(hadesmem::Process const& process, hadesmem::PeFile const& pe_file, std::uintptr_t ep_rva, void* ep_va, std::size_t tabs) { if (!ep_va) { return; } std::wostream& out = GetOutputStreamW(); // Get the number of bytes from the EP to the end of the file. std::size_t max_buffer_size = GetBytesToEndOfFile(pe_file, ep_va); // Clamp the amount of data read to the theoretical maximum. std::size_t const kMaxInstructions = 10U; std::size_t const kMaxInstructionLen = 15U; std::size_t const kMaxInstructionsBytes = kMaxInstructions * kMaxInstructionLen; max_buffer_size = (std::min)(max_buffer_size, kMaxInstructionsBytes); auto const disasm_buf = hadesmem::ReadVector<std::uint8_t>(process, ep_va, max_buffer_size); std::uint64_t const ip = hadesmem::GetRuntimeBase(process, pe_file) + ep_rva; ud_t ud_obj; ud_init(&ud_obj); ud_set_input_buffer(&ud_obj, disasm_buf.data(), max_buffer_size); ud_set_syntax(&ud_obj, UD_SYN_INTEL); ud_set_pc(&ud_obj, ip); ud_set_mode(&ud_obj, pe_file.Is64() ? 64 : 32); // Be pessimistic. Use the minimum theoretical amount of instrutions we could // fit in our buffer. std::size_t const instruction_count = max_buffer_size / kMaxInstructionLen; for (std::size_t i = 0U; i < instruction_count; ++i) { std::uint32_t const len = ud_disassemble(&ud_obj); if (len == 0) { WriteNormal(out, L"WARNING! Disassembly failed.", tabs); // If we can't disassemble at least 5 instructions there's probably // something strange about the function. Even in the case of a nullsub // there is typically some INT3 or NOP padding after it... WarnForCurrentFile(i < 5U ? WarningType::kUnsupported : WarningType::kSuspicious); break; } char const* const asm_str = ud_insn_asm(&ud_obj); HADESMEM_DETAIL_ASSERT(asm_str); char const* const asm_bytes_str = ud_insn_hex(&ud_obj); HADESMEM_DETAIL_ASSERT(asm_bytes_str); auto const diasm_line = hadesmem::detail::MultiByteToWideChar(asm_str) + L" (" + hadesmem::detail::MultiByteToWideChar(asm_bytes_str) + L")"; WriteNormal(out, diasm_line, tabs); } }
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 WriteString(Process const& process, PVOID address, T const* const str) { HADESMEM_DETAIL_STATIC_ASSERT(detail::IsCharType<T>::value); HADESMEM_DETAIL_ASSERT(address != nullptr); HADESMEM_DETAIL_ASSERT(str != nullptr); WriteString(process, address, std::basic_string<T>(str)); }
explicit Allocator(Process const& process, SIZE_T size, PVOID base = nullptr, bool allocated = false) : process_{&process}, base_{allocated ? base : Alloc(process, size, base)}, size_{size} { HADESMEM_DETAIL_ASSERT(process_ != 0); HADESMEM_DETAIL_ASSERT(base_ != 0); HADESMEM_DETAIL_ASSERT(size_ != 0); }
inline void WriteVector(Process const& process, PVOID address, std::vector<T, Alloc> const& data) { HADESMEM_DETAIL_STATIC_ASSERT(detail::IsTriviallyCopyable<T>::value); HADESMEM_DETAIL_ASSERT(address != nullptr); HADESMEM_DETAIL_ASSERT(!data.empty()); std::size_t const raw_size = data.size() * sizeof(T); detail::WriteImpl(process, address, data.data(), raw_size); }
inline void Write(Process const& process, PVOID address, T const* beg, T const* end) { HADESMEM_DETAIL_STATIC_ASSERT(detail::IsTriviallyCopyable<T>::value); HADESMEM_DETAIL_ASSERT(address != nullptr); HADESMEM_DETAIL_ASSERT(beg != nullptr); HADESMEM_DETAIL_ASSERT(end != nullptr); std::size_t const count = static_cast<std::size_t>(std::distance(beg, end)); Write(process, address, beg, count); }
inline void Write(Process const& process, PVOID address, T const* ptr, std::size_t count) { HADESMEM_DETAIL_STATIC_ASSERT(detail::IsTriviallyCopyable<T>::value); HADESMEM_DETAIL_ASSERT(address != nullptr); HADESMEM_DETAIL_ASSERT(ptr != nullptr); HADESMEM_DETAIL_ASSERT(count != 0); std::size_t const raw_size = static_cast<std::size_t>(std::distance(ptr, ptr + count)) * sizeof(T); detail::WriteImpl(process, address, ptr, raw_size); }
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."}); } }
void Free() { if (!process_) { return; } HADESMEM_DETAIL_ASSERT(base_ != nullptr); HADESMEM_DETAIL_ASSERT(size_ != 0); ::hadesmem::Free(*process_, base_); process_ = nullptr; base_ = nullptr; size_ = 0; }
explicit ModuleIterator(Process const& process) : impl_{std::make_shared<Impl>()} { HADESMEM_DETAIL_ASSERT(impl_.get()); impl_->process_ = &process; // CreateToolhelp32Snapshot can fail with ERROR_PARTIAL_COPY for 'zombie' // processes. try { impl_->snap_ = detail::CreateToolhelp32Snapshot( TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, impl_->process_->GetId()); } catch (std::exception const&) { impl_.reset(); return; } hadesmem::detail::Optional<MODULEENTRY32> const entry = detail::Module32First(impl_->snap_.GetHandle()); if (!entry) { impl_.reset(); return; } impl_->module_ = Module{*impl_->process_, *entry}; }
BoundImportDescriptorIterator& operator++() { try { HADESMEM_DETAIL_ASSERT(impl_.get()); auto const cur_base = static_cast<PIMAGE_BOUND_IMPORT_DESCRIPTOR>( impl_->bound_import_desc_->GetBase()); auto const new_base = reinterpret_cast<PIMAGE_BOUND_IMPORT_DESCRIPTOR>( reinterpret_cast<IMAGE_BOUND_FORWARDER_REF*>(cur_base + 1) + impl_->bound_import_desc_->GetNumberOfModuleForwarderRefs()); auto const start_base = static_cast<PIMAGE_BOUND_IMPORT_DESCRIPTOR>( impl_->bound_import_desc_->GetStart()); impl_->bound_import_desc_ = BoundImportDescriptor{ *impl_->process_, *impl_->pe_file_, start_base, new_base}; if (IsTerminator(*impl_->bound_import_desc_)) { impl_.reset(); return *this; } } catch (std::exception const& /*e*/) { impl_.reset(); } return *this; }
ULONG WINAPI DirectInput8WProxy::Release() { hadesmem::detail::LastErrorPreserver last_error_preserver; refs_--; HADESMEM_DETAIL_ASSERT(refs_ >= 0); if (refs_ == 0) { Cleanup(); } last_error_preserver.Revert(); auto const ret = direct_input_->Release(); last_error_preserver.Update(); HADESMEM_DETAIL_TRACE_NOISY_FORMAT_A( "Internal refs: [%lu]. External refs: [%lld].", ret, refs_); if (ret == 0) { delete this; } return ret; }
RelocationBlockIterator& operator++() { try { HADESMEM_DETAIL_ASSERT(impl_.get()); auto const next_base = reinterpret_cast<PIMAGE_BASE_RELOCATION>( reinterpret_cast<std::uintptr_t>( impl_->relocation_block_->GetRelocationDataStart()) + (impl_->relocation_block_->GetNumberOfRelocations() * sizeof(WORD))); // TODO: Dump should warn for integer overflow or mis-aligned off-the-end // data. if (next_base < impl_->relocation_block_->GetBase() || next_base >= impl_->reloc_dir_end_) { impl_.reset(); return *this; } impl_->relocation_block_ = RelocationBlock{ *impl_->process_, *impl_->pe_file_, next_base, impl_->reloc_dir_end_}; // TODO: Dump should warn for this. if (impl_->relocation_block_->IsInvalid()) { impl_.reset(); return *this; } } catch (std::exception const& /*e*/) { impl_.reset(); } return *this; }
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; }
HRESULT WINAPI DirectInput8WProxy::QueryInterface(REFIID riid, LPVOID* obj) { hadesmem::detail::LastErrorPreserver last_error_preserver; last_error_preserver.Revert(); auto const ret = direct_input_->QueryInterface(riid, obj); last_error_preserver.Update(); if (SUCCEEDED(ret)) { HADESMEM_DETAIL_TRACE_NOISY_A("Succeeded."); if (*obj == direct_input_) { refs_++; *obj = this; } else { HADESMEM_DETAIL_TRACE_A("WARNING! Unhandled interface."); HADESMEM_DETAIL_ASSERT(false); static_cast<IUnknown*>(*obj)->Release(); return E_NOINTERFACE; } } else { HADESMEM_DETAIL_TRACE_NOISY_A("Failed."); } return ret; }
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* FindRaw(Process const& process, std::uint8_t* s_beg, std::uint8_t* s_end, NeedleIterator n_beg, NeedleIterator n_end) { HADESMEM_DETAIL_ASSERT(s_beg < s_end); std::ptrdiff_t const mem_size = s_end - s_beg; std::vector<std::uint8_t> const haystack{ReadVector<std::uint8_t>( process, s_beg, static_cast<std::size_t>(mem_size))}; auto const h_beg = std::begin(haystack); auto const h_end = std::end(haystack); auto const iter = std::search(h_beg, h_end, n_beg, n_end, [](std::uint8_t h_cur, detail::PatternDataByte const& n_cur) { return n_cur.wildcard || h_cur == n_cur.data; }); if (iter != h_end) { return s_beg + std::distance(h_beg, iter); } return nullptr; }
void ReadStringEx(Process const& process, PVOID address, OutputIterator data, std::size_t chunk_len, void* upper_bound) { HADESMEM_DETAIL_STATIC_ASSERT(detail::IsCharType<T>::value); HADESMEM_DETAIL_STATIC_ASSERT(std::is_base_of< std::output_iterator_tag, typename std::iterator_traits<OutputIterator>::iterator_category>::value); HADESMEM_DETAIL_ASSERT(chunk_len != 0); for (;;) { detail::ProtectGuard protect_guard{ process, address, detail::ProtectGuardType::kRead}; MEMORY_BASIC_INFORMATION const mbi = detail::Query(process, address); PVOID const region_next_real = static_cast<PBYTE>(mbi.BaseAddress) + mbi.RegionSize; void* const region_next = upper_bound ? (std::min)(upper_bound, region_next_real) : region_next_real; T* cur = static_cast<T*>(address); while (cur + 1 <= region_next) { std::size_t const len_to_end = reinterpret_cast<DWORD_PTR>(region_next) - reinterpret_cast<DWORD_PTR>(cur); std::size_t const buf_len_bytes = (std::min)(chunk_len * sizeof(T), len_to_end); std::size_t const buf_len = buf_len_bytes / sizeof(T); std::vector<T> buf(buf_len); detail::ReadUnchecked(process, cur, buf.data(), buf.size() * sizeof(T)); auto const iter = std::find(std::begin(buf), std::end(buf), T()); std::copy(std::begin(buf), iter, data); if (iter != std::end(buf) || region_next == upper_bound) { protect_guard.Restore(); return; } cur += buf_len; } address = region_next; protect_guard.Restore(); if (upper_bound && cur >= upper_bound) { return; } } }
inline void Read(Process const& process, PVOID address, std::size_t n, OutputIterator out) { HADESMEM_DETAIL_ASSERT(n ? address != nullptr : true); auto const data = ReadVector<T>(process, address, n); std::copy(std::begin(data), std::end(data), out); }
inline void Write(Process const& process, PVOID address, T const& data) { HADESMEM_DETAIL_STATIC_ASSERT(detail::IsTriviallyCopyable<T>::value); HADESMEM_DETAIL_ASSERT(address != nullptr); detail::WriteImpl(process, address, data); }
ThreadIterator& operator++() { HADESMEM_DETAIL_ASSERT(impl_.get()); Advance(); return *this; }
void WriteImpl(Process const& process, PVOID address, T const& data) { HADESMEM_DETAIL_STATIC_ASSERT(detail::IsTriviallyCopyable<T>::value); HADESMEM_DETAIL_ASSERT(address != nullptr); WriteImpl(process, address, std::addressof(data), sizeof(data)); }
void Unregister(std::size_t id) { hadesmem::detail::AcquireSRWLock lock( &srw_lock_, hadesmem::detail::SRWLockType::Exclusive); auto const num_removed = callbacks_.erase(id); HADESMEM_DETAIL_ASSERT(num_removed == 1); (void)num_removed; }
std::size_t Register(Callback const& callback) { hadesmem::detail::AcquireSRWLock lock( &srw_lock_, hadesmem::detail::SRWLockType::Exclusive); auto const cur_id = next_id_++; HADESMEM_DETAIL_ASSERT(next_id_ > cur_id); callbacks_[cur_id] = callback; return cur_id; }
inline std::vector<T, Alloc> ReadVector(Process const& process, PVOID address, std::size_t count) { HADESMEM_DETAIL_STATIC_ASSERT(detail::IsTriviallyCopyable<T>::value); HADESMEM_DETAIL_STATIC_ASSERT(std::is_default_constructible<T>::value); HADESMEM_DETAIL_ASSERT(count ? address != nullptr : true); return ReadVectorEx<T, Alloc>(process, address, count, ReadFlags::kNone); }
inline void WriteString(Process const& process, PVOID address, std::basic_string<T, Traits, Alloc> const& data) { HADESMEM_DETAIL_STATIC_ASSERT(detail::IsCharType<T>::value); HADESMEM_DETAIL_ASSERT(address != nullptr); return Write(process, address, data.c_str(), data.size() + 1); }
inline void WriteImpl(Process const& process, PVOID address, LPCVOID data, std::size_t len) { HADESMEM_DETAIL_ASSERT(address != nullptr); HADESMEM_DETAIL_ASSERT(data != nullptr); HADESMEM_DETAIL_ASSERT(len != 0); for (;;) { ProtectGuard protect_guard{process, address, ProtectGuardType::kWrite}; MEMORY_BASIC_INFORMATION const mbi = detail::Query(process, address); void* const region_next = static_cast<std::uint8_t*>(mbi.BaseAddress) + mbi.RegionSize; void* const address_end = static_cast<std::uint8_t*>(address) + len; if (address_end <= region_next) { WriteUnchecked(process, address, data, len); protect_guard.Restore(); return; } else { std::size_t const len_new = reinterpret_cast<std::uintptr_t>(region_next) - reinterpret_cast<std::uintptr_t>(address); WriteUnchecked(process, address, data, len_new); protect_guard.Restore(); address = static_cast<std::uint8_t*>(address) + len_new; data = static_cast<std::uint8_t const*>(data) + len_new; len -= len_new; } } }
inline void WriteUnchecked(Process const& process, PVOID address, LPCVOID data, std::size_t len) { HADESMEM_DETAIL_ASSERT(address != nullptr); HADESMEM_DETAIL_ASSERT(data != nullptr); HADESMEM_DETAIL_ASSERT(len != 0); SIZE_T bytes_written = 0; if (!::WriteProcessMemory( process.GetHandle(), address, data, len, &bytes_written) || bytes_written != len) { DWORD const last_error = ::GetLastError(); HADESMEM_DETAIL_THROW_EXCEPTION(Error{} << ErrorString{"WriteProcessMemory failed."} << ErrorCodeWinLast{last_error}); } }
inline void Read(Process const& process, PVOID address, OutputIterator out) { HADESMEM_DETAIL_STATIC_ASSERT(std::is_base_of< std::output_iterator_tag, typename std::iterator_traits<OutputIterator>::iterator_category>::value); HADESMEM_DETAIL_ASSERT(address != nullptr); auto const data = detail::ReadImpl<std::array<T, N>>(process, address); std::copy(&data[0], &data[0] + N, out); }