| Avi Drissman | e4622aa | 2022-09-08 20:36:06 | [diff] [blame] | 1 | // Copyright 2015 The Chromium Authors |
| rvargas | 86d7c90 | 2014-09-04 22:58:09 | [diff] [blame] | 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| Will Harris | d4b45e0 | 2022-05-21 00:30:48 | [diff] [blame] | 5 | #include "base/debug/handle_hooks_win.h" |
| rvargas | 86d7c90 | 2014-09-04 22:58:09 | [diff] [blame] | 6 | |
| Will Harris | d4b45e0 | 2022-05-21 00:30:48 | [diff] [blame] | 7 | #include <windows.h> |
| 8 | |
| rvargas | ae02eef | 2014-09-30 18:42:24 | [diff] [blame] | 9 | #include <psapi.h> |
| avi | 2729e44 | 2015-12-26 05:27:45 | [diff] [blame] | 10 | #include <stddef.h> |
| rvargas | 86d7c90 | 2014-09-04 22:58:09 | [diff] [blame] | 11 | |
| Tom Sepez | bee5f7cc | 2025-11-04 20:39:48 | [diff] [blame] | 12 | #include "base/compiler_specific.h" |
| Will Harris | d4b45e0 | 2022-05-21 00:30:48 | [diff] [blame] | 13 | #include "base/logging.h" |
| Keishi Hattori | 0e45c02 | 2021-11-27 09:25:52 | [diff] [blame] | 14 | #include "base/memory/raw_ptr.h" |
| Will Harris | d4b45e0 | 2022-05-21 00:30:48 | [diff] [blame] | 15 | #include "base/numerics/safe_conversions.h" |
| rvargas | 86d7c90 | 2014-09-04 22:58:09 | [diff] [blame] | 16 | #include "base/win/iat_patch_function.h" |
| rvargas | ae02eef | 2014-09-30 18:42:24 | [diff] [blame] | 17 | #include "base/win/pe_image.h" |
| rvargas | 86d7c90 | 2014-09-04 22:58:09 | [diff] [blame] | 18 | #include "base/win/scoped_handle.h" |
| avi | 2729e44 | 2015-12-26 05:27:45 | [diff] [blame] | 19 | #include "build/build_config.h" |
| wfh | 7969a476 | 2015-12-16 20:15:31 | [diff] [blame] | 20 | |
| rvargas | 86d7c90 | 2014-09-04 22:58:09 | [diff] [blame] | 21 | namespace { |
| 22 | |
| Will Harris | d4b45e0 | 2022-05-21 00:30:48 | [diff] [blame] | 23 | using CloseHandleType = decltype(&::CloseHandle); |
| 24 | using DuplicateHandleType = decltype(&::DuplicateHandle); |
| shrikant | bb11d70c | 2015-08-10 23:43:04 | [diff] [blame] | 25 | |
| Will Harris | d4b45e0 | 2022-05-21 00:30:48 | [diff] [blame] | 26 | CloseHandleType g_close_function = nullptr; |
| 27 | DuplicateHandleType g_duplicate_function = nullptr; |
| rvargas | 86d7c90 | 2014-09-04 22:58:09 | [diff] [blame] | 28 | |
| 29 | // The entry point for CloseHandle interception. This function notifies the |
| 30 | // verifier about the handle that is being closed, and calls the original |
| 31 | // function. |
| 32 | BOOL WINAPI CloseHandleHook(HANDLE handle) { |
| Will Harris | d4b45e0 | 2022-05-21 00:30:48 | [diff] [blame] | 33 | base::win::OnHandleBeingClosed(handle, |
| 34 | base::win::HandleOperation::kCloseHandleHook); |
| rvargas | 86d7c90 | 2014-09-04 22:58:09 | [diff] [blame] | 35 | return g_close_function(handle); |
| 36 | } |
| 37 | |
| shrikant | bb11d70c | 2015-08-10 23:43:04 | [diff] [blame] | 38 | BOOL WINAPI DuplicateHandleHook(HANDLE source_process, |
| 39 | HANDLE source_handle, |
| 40 | HANDLE target_process, |
| 41 | HANDLE* target_handle, |
| 42 | DWORD desired_access, |
| 43 | BOOL inherit_handle, |
| 44 | DWORD options) { |
| 45 | if ((options & DUPLICATE_CLOSE_SOURCE) && |
| 46 | (GetProcessId(source_process) == ::GetCurrentProcessId())) { |
| Will Harris | d4b45e0 | 2022-05-21 00:30:48 | [diff] [blame] | 47 | base::win::OnHandleBeingClosed( |
| 48 | source_handle, base::win::HandleOperation::kDuplicateHandleHook); |
| shrikant | bb11d70c | 2015-08-10 23:43:04 | [diff] [blame] | 49 | } |
| 50 | |
| 51 | return g_duplicate_function(source_process, source_handle, target_process, |
| 52 | target_handle, desired_access, inherit_handle, |
| 53 | options); |
| 54 | } |
| 55 | |
| wfh | b540830 | 2016-01-06 20:24:06 | [diff] [blame] | 56 | } // namespace |
| 57 | |
| 58 | namespace base { |
| 59 | namespace debug { |
| 60 | |
| 61 | namespace { |
| 62 | |
| rvargas | ae02eef | 2014-09-30 18:42:24 | [diff] [blame] | 63 | // Provides a simple way to temporarily change the protection of a memory page. |
| 64 | class AutoProtectMemory { |
| 65 | public: |
| 66 | AutoProtectMemory() |
| Keishi Hattori | d837ccb | 2020-10-12 04:37:38 | [diff] [blame] | 67 | : changed_(false), address_(nullptr), bytes_(0), old_protect_(0) {} |
| rvargas | ae02eef | 2014-09-30 18:42:24 | [diff] [blame] | 68 | |
| Peter Boström | 7319bbd | 2021-09-15 22:59:38 | [diff] [blame] | 69 | AutoProtectMemory(const AutoProtectMemory&) = delete; |
| 70 | AutoProtectMemory& operator=(const AutoProtectMemory&) = delete; |
| 71 | |
| Will Harris | d4b45e0 | 2022-05-21 00:30:48 | [diff] [blame] | 72 | ~AutoProtectMemory() { RevertProtection(); } |
| rvargas | ae02eef | 2014-09-30 18:42:24 | [diff] [blame] | 73 | |
| 74 | // Grants write access to a given memory range. |
| 75 | bool ChangeProtection(void* address, size_t bytes); |
| 76 | |
| 77 | // Restores the original page protection. |
| 78 | void RevertProtection(); |
| 79 | |
| 80 | private: |
| 81 | bool changed_; |
| Keishi Hattori | 0e45c02 | 2021-11-27 09:25:52 | [diff] [blame] | 82 | raw_ptr<void> address_; |
| rvargas | ae02eef | 2014-09-30 18:42:24 | [diff] [blame] | 83 | size_t bytes_; |
| 84 | DWORD old_protect_; |
| rvargas | ae02eef | 2014-09-30 18:42:24 | [diff] [blame] | 85 | }; |
| 86 | |
| 87 | bool AutoProtectMemory::ChangeProtection(void* address, size_t bytes) { |
| 88 | DCHECK(!changed_); |
| 89 | DCHECK(address); |
| 90 | |
| 91 | // Change the page protection so that we can write. |
| 92 | MEMORY_BASIC_INFORMATION memory_info; |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 93 | if (!VirtualQuery(address, &memory_info, sizeof(memory_info))) { |
| rvargas | ae02eef | 2014-09-30 18:42:24 | [diff] [blame] | 94 | return false; |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 95 | } |
| rvargas | ae02eef | 2014-09-30 18:42:24 | [diff] [blame] | 96 | |
| 97 | DWORD is_executable = (PAGE_EXECUTE | PAGE_EXECUTE_READ | |
| Will Harris | d4b45e0 | 2022-05-21 00:30:48 | [diff] [blame] | 98 | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY) & |
| rvargas | ae02eef | 2014-09-30 18:42:24 | [diff] [blame] | 99 | memory_info.Protect; |
| 100 | |
| 101 | DWORD protect = is_executable ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE; |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 102 | if (!VirtualProtect(address, bytes, protect, &old_protect_)) { |
| rvargas | ae02eef | 2014-09-30 18:42:24 | [diff] [blame] | 103 | return false; |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 104 | } |
| rvargas | ae02eef | 2014-09-30 18:42:24 | [diff] [blame] | 105 | |
| 106 | changed_ = true; |
| 107 | address_ = address; |
| 108 | bytes_ = bytes; |
| 109 | return true; |
| 110 | } |
| 111 | |
| 112 | void AutoProtectMemory::RevertProtection() { |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 113 | if (!changed_) { |
| rvargas | ae02eef | 2014-09-30 18:42:24 | [diff] [blame] | 114 | return; |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 115 | } |
| rvargas | ae02eef | 2014-09-30 18:42:24 | [diff] [blame] | 116 | |
| 117 | DCHECK(address_); |
| 118 | DCHECK(bytes_); |
| 119 | |
| 120 | VirtualProtect(address_, bytes_, old_protect_, &old_protect_); |
| 121 | changed_ = false; |
| Keishi Hattori | d837ccb | 2020-10-12 04:37:38 | [diff] [blame] | 122 | address_ = nullptr; |
| rvargas | ae02eef | 2014-09-30 18:42:24 | [diff] [blame] | 123 | bytes_ = 0; |
| 124 | old_protect_ = 0; |
| 125 | } |
| 126 | |
| Will Harris | d4b45e0 | 2022-05-21 00:30:48 | [diff] [blame] | 127 | #if defined(ARCH_CPU_32_BITS) |
| 128 | // Performs an EAT interception. Only supported on 32-bit. |
| 129 | void EATPatch(HMODULE module, |
| 130 | const char* function_name, |
| 131 | void* new_function, |
| 132 | void** old_function) { |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 133 | if (!module) { |
| wfh | 360c186 | 2015-12-21 19:48:06 | [diff] [blame] | 134 | return; |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 135 | } |
| rvargas | ae02eef | 2014-09-30 18:42:24 | [diff] [blame] | 136 | |
| 137 | base::win::PEImage pe(module); |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 138 | if (!pe.VerifyMagic()) { |
| wfh | 360c186 | 2015-12-21 19:48:06 | [diff] [blame] | 139 | return; |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 140 | } |
| rvargas | ae02eef | 2014-09-30 18:42:24 | [diff] [blame] | 141 | |
| 142 | DWORD* eat_entry = pe.GetExportEntry(function_name); |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 143 | if (!eat_entry) { |
| wfh | 360c186 | 2015-12-21 19:48:06 | [diff] [blame] | 144 | return; |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 145 | } |
| rvargas | ae02eef | 2014-09-30 18:42:24 | [diff] [blame] | 146 | |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 147 | if (!(*old_function)) { |
| rvargas | ae02eef | 2014-09-30 18:42:24 | [diff] [blame] | 148 | *old_function = pe.RVAToAddr(*eat_entry); |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 149 | } |
| rvargas | ae02eef | 2014-09-30 18:42:24 | [diff] [blame] | 150 | |
| 151 | AutoProtectMemory memory; |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 152 | if (!memory.ChangeProtection(eat_entry, sizeof(DWORD))) { |
| wfh | 360c186 | 2015-12-21 19:48:06 | [diff] [blame] | 153 | return; |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 154 | } |
| rvargas | ae02eef | 2014-09-30 18:42:24 | [diff] [blame] | 155 | |
| 156 | // Perform the patch. |
| Will Harris | d4b45e0 | 2022-05-21 00:30:48 | [diff] [blame] | 157 | *eat_entry = |
| 158 | base::checked_cast<DWORD>(reinterpret_cast<uintptr_t>(new_function) - |
| 159 | reinterpret_cast<uintptr_t>(module)); |
| rvargas | ae02eef | 2014-09-30 18:42:24 | [diff] [blame] | 160 | } |
| Will Harris | d4b45e0 | 2022-05-21 00:30:48 | [diff] [blame] | 161 | #endif // defined(ARCH_CPU_32_BITS) |
| rvargas | ae02eef | 2014-09-30 18:42:24 | [diff] [blame] | 162 | |
| shrikant | bb11d70c | 2015-08-10 23:43:04 | [diff] [blame] | 163 | // Performs an IAT interception. |
| Will Harris | d4b45e0 | 2022-05-21 00:30:48 | [diff] [blame] | 164 | std::unique_ptr<base::win::IATPatchFunction> IATPatch(HMODULE module, |
| 165 | const char* function_name, |
| 166 | void* new_function, |
| 167 | void** old_function) { |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 168 | if (!module) { |
| Will Harris | d4b45e0 | 2022-05-21 00:30:48 | [diff] [blame] | 169 | return nullptr; |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 170 | } |
| shrikant | bb11d70c | 2015-08-10 23:43:04 | [diff] [blame] | 171 | |
| Will Harris | d4b45e0 | 2022-05-21 00:30:48 | [diff] [blame] | 172 | auto patch = std::make_unique<base::win::IATPatchFunction>(); |
| shrikant | bb11d70c | 2015-08-10 23:43:04 | [diff] [blame] | 173 | __try { |
| 174 | // There is no guarantee that |module| is still loaded at this point. |
| 175 | if (patch->PatchFromModule(module, "kernel32.dll", function_name, |
| 176 | new_function)) { |
| Will Harris | d4b45e0 | 2022-05-21 00:30:48 | [diff] [blame] | 177 | return nullptr; |
| shrikant | bb11d70c | 2015-08-10 23:43:04 | [diff] [blame] | 178 | } |
| Will Harris | d4b45e0 | 2022-05-21 00:30:48 | [diff] [blame] | 179 | } __except ((GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION || |
| 180 | GetExceptionCode() == EXCEPTION_GUARD_PAGE || |
| 181 | GetExceptionCode() == EXCEPTION_IN_PAGE_ERROR) |
| 182 | ? EXCEPTION_EXECUTE_HANDLER |
| 183 | : EXCEPTION_CONTINUE_SEARCH) { |
| shrikant | bb11d70c | 2015-08-10 23:43:04 | [diff] [blame] | 184 | // Leak the patch. |
| Will Harris | d4b45e0 | 2022-05-21 00:30:48 | [diff] [blame] | 185 | std::ignore = patch.release(); |
| 186 | return nullptr; |
| shrikant | bb11d70c | 2015-08-10 23:43:04 | [diff] [blame] | 187 | } |
| 188 | |
| 189 | if (!(*old_function)) { |
| 190 | // Things are probably messed up if each intercepted function points to |
| 191 | // a different place, but we need only one function to call. |
| 192 | *old_function = patch->original_function(); |
| 193 | } |
| 194 | return patch; |
| 195 | } |
| 196 | |
| Will Harris | d4b45e0 | 2022-05-21 00:30:48 | [diff] [blame] | 197 | } // namespace |
| Peter Boström | 7319bbd | 2021-09-15 22:59:38 | [diff] [blame] | 198 | |
| Will Harris | d4b45e0 | 2022-05-21 00:30:48 | [diff] [blame] | 199 | // static |
| wfh | 360c186 | 2015-12-21 19:48:06 | [diff] [blame] | 200 | void HandleHooks::AddIATPatch(HMODULE module) { |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 201 | if (!module) { |
| wfh | 360c186 | 2015-12-21 19:48:06 | [diff] [blame] | 202 | return; |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 203 | } |
| rvargas | 86d7c90 | 2014-09-04 22:58:09 | [diff] [blame] | 204 | |
| Will Harris | d4b45e0 | 2022-05-21 00:30:48 | [diff] [blame] | 205 | auto close_handle_patch = |
| Nico Weber | 2638cbd | 2018-03-12 17:11:15 | [diff] [blame] | 206 | IATPatch(module, "CloseHandle", reinterpret_cast<void*>(&CloseHandleHook), |
| 207 | reinterpret_cast<void**>(&g_close_function)); |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 208 | if (!close_handle_patch) { |
| wfh | 360c186 | 2015-12-21 19:48:06 | [diff] [blame] | 209 | return; |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 210 | } |
| Will Harris | d4b45e0 | 2022-05-21 00:30:48 | [diff] [blame] | 211 | // This is intentionally leaked. |
| 212 | std::ignore = close_handle_patch.release(); |
| shrikant | bb11d70c | 2015-08-10 23:43:04 | [diff] [blame] | 213 | |
| Will Harris | d4b45e0 | 2022-05-21 00:30:48 | [diff] [blame] | 214 | auto duplicate_handle_patch = IATPatch( |
| 215 | module, "DuplicateHandle", reinterpret_cast<void*>(&DuplicateHandleHook), |
| 216 | reinterpret_cast<void**>(&g_duplicate_function)); |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 217 | if (!duplicate_handle_patch) { |
| wfh | 360c186 | 2015-12-21 19:48:06 | [diff] [blame] | 218 | return; |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 219 | } |
| Will Harris | d4b45e0 | 2022-05-21 00:30:48 | [diff] [blame] | 220 | // This is intentionally leaked. |
| 221 | std::ignore = duplicate_handle_patch.release(); |
| rvargas | 86d7c90 | 2014-09-04 22:58:09 | [diff] [blame] | 222 | } |
| 223 | |
| Will Harris | d4b45e0 | 2022-05-21 00:30:48 | [diff] [blame] | 224 | #if defined(ARCH_CPU_32_BITS) |
| 225 | // static |
| wfh | 360c186 | 2015-12-21 19:48:06 | [diff] [blame] | 226 | void HandleHooks::AddEATPatch() { |
| rvargas | ae02eef | 2014-09-30 18:42:24 | [diff] [blame] | 227 | // An attempt to restore the entry on the table at destruction is not safe. |
| wfh | 360c186 | 2015-12-21 19:48:06 | [diff] [blame] | 228 | EATPatch(GetModuleHandleA("kernel32.dll"), "CloseHandle", |
| Nico Weber | 2638cbd | 2018-03-12 17:11:15 | [diff] [blame] | 229 | reinterpret_cast<void*>(&CloseHandleHook), |
| 230 | reinterpret_cast<void**>(&g_close_function)); |
| wfh | 360c186 | 2015-12-21 19:48:06 | [diff] [blame] | 231 | EATPatch(GetModuleHandleA("kernel32.dll"), "DuplicateHandle", |
| Nico Weber | 2638cbd | 2018-03-12 17:11:15 | [diff] [blame] | 232 | reinterpret_cast<void*>(&DuplicateHandleHook), |
| wfh | 360c186 | 2015-12-21 19:48:06 | [diff] [blame] | 233 | reinterpret_cast<void**>(&g_duplicate_function)); |
| rvargas | ae02eef | 2014-09-30 18:42:24 | [diff] [blame] | 234 | } |
| Will Harris | d4b45e0 | 2022-05-21 00:30:48 | [diff] [blame] | 235 | #endif // defined(ARCH_CPU_32_BITS) |
| rvargas | ae02eef | 2014-09-30 18:42:24 | [diff] [blame] | 236 | |
| Will Harris | d4b45e0 | 2022-05-21 00:30:48 | [diff] [blame] | 237 | // static |
| 238 | void HandleHooks::PatchLoadedModules() { |
| rvargas | ae02eef | 2014-09-30 18:42:24 | [diff] [blame] | 239 | const DWORD kSize = 256; |
| 240 | DWORD returned; |
| Will Harris | d4b45e0 | 2022-05-21 00:30:48 | [diff] [blame] | 241 | auto modules = std::make_unique<HMODULE[]>(kSize); |
| 242 | if (!::EnumProcessModules(GetCurrentProcess(), modules.get(), |
| 243 | kSize * sizeof(HMODULE), &returned)) { |
| wfh | 360c186 | 2015-12-21 19:48:06 | [diff] [blame] | 244 | return; |
| rvargas | 86d7c90 | 2014-09-04 22:58:09 | [diff] [blame] | 245 | } |
| rvargas | ae02eef | 2014-09-30 18:42:24 | [diff] [blame] | 246 | returned /= sizeof(HMODULE); |
| 247 | returned = std::min(kSize, returned); |
| 248 | |
| 249 | for (DWORD current = 0; current < returned; current++) { |
| Tom Sepez | bee5f7cc | 2025-11-04 20:39:48 | [diff] [blame] | 250 | AddIATPatch(UNSAFE_TODO(modules[current])); |
| rvargas | ae02eef | 2014-09-30 18:42:24 | [diff] [blame] | 251 | } |
| rvargas | 86d7c90 | 2014-09-04 22:58:09 | [diff] [blame] | 252 | } |
| 253 | |
| wfh | b540830 | 2016-01-06 20:24:06 | [diff] [blame] | 254 | } // namespace debug |
| 255 | } // namespace base |