From 567d8b024d100208ab89d05bca9f5cd7554e9fb0 Mon Sep 17 00:00:00 2001 From: Forst Date: Sat, 25 Apr 2026 16:37:26 +0800 Subject: [PATCH] fix: uninstall all inline hooks on DLL_PROCESS_DETACH Add a hook_registry that collects uninstall callbacks from every hook site. On DLL_PROCESS_DETACH the registry restores all patched call-sites in LIFO order before the DLL image is unmapped, preventing the 0xC0000005 crash observed in OneCommander on system shutdown. --- src/shell/contextmenu/hooks.cc | 11 +++++++++++ src/shell/entry.cc | 8 ++++++++ src/shell/hook_registry.cc | 17 +++++++++++++++++ src/shell/hook_registry.h | 12 ++++++++++++ src/shell/res_string_loader.cc | 5 +++++ 5 files changed, 53 insertions(+) create mode 100644 src/shell/hook_registry.cc create mode 100644 src/shell/hook_registry.h diff --git a/src/shell/contextmenu/hooks.cc b/src/shell/contextmenu/hooks.cc index 7306d550..41770cb6 100644 --- a/src/shell/contextmenu/hooks.cc +++ b/src/shell/contextmenu/hooks.cc @@ -10,6 +10,7 @@ #include "nanovg.h" #include "shell/config.h" #include "shell/entry.h" +#include "shell/hook_registry.h" #include "shell/utils.h" #include "blook/blook.h" @@ -343,6 +344,8 @@ void sync_native_menu_item_update(HMENU hMenu, UINT item, BOOL fByPosition, BODY; \ return result; \ }); \ + hook_registry::register_uninstaller( \ + []() { HOOK_NAME##Hook->uninstall(); }); \ } while (false) void mb_shell::context_menu_hooks::set_active_root_menu_widget( @@ -510,6 +513,8 @@ void mb_shell::context_menu_hooks::install_NtUserTrackPopupMenuEx_hook() { mb_shell::context_menu_hooks::block_js_reload.fetch_sub(1); return (int32_t)selected_menu.value_or(0); }); + hook_registry::register_uninstaller( + []() { NtUserTrackHook->uninstall(); }); } void mb_shell::context_menu_hooks::install_menu_mutation_hooks() { @@ -773,6 +778,8 @@ void mb_shell::context_menu_hooks::install_SHCreateDefaultContextMenu_hook() { } return res; }); + hook_registry::register_uninstaller( + []() { CreateWindowExWHook->uninstall(); }); /** prototype: SHSTDAPI SHCreateDefaultContextMenu( @@ -839,6 +846,8 @@ void mb_shell::context_menu_hooks::install_SHCreateDefaultContextMenu_hook() { return res; }); + hook_registry::register_uninstaller( + []() { SHCreateDefaultContextMenuHook->uninstall(); }); } #pragma optimize("", off) @@ -962,5 +971,7 @@ HRESULT GetUIObjectOf( return res; }); + hook_registry::register_uninstaller( + []() { GetUIObjectOfHook->uninstall(); }); spdlog::info("GetUIObjectOf hook installed"); } diff --git a/src/shell/entry.cc b/src/shell/entry.cc index 4915398f..9cef942f 100644 --- a/src/shell/entry.cc +++ b/src/shell/entry.cc @@ -7,6 +7,7 @@ #include "contextmenu/hooks.h" #include "entry.h" #include "error_handler.h" +#include "hook_registry.h" #include "res_string_loader.h" #include "script/binding_types.hpp" #include "breeze-js/quickjspp.hpp" @@ -220,6 +221,13 @@ int APIENTRY DllMain(HINSTANCE hInstance, DWORD fdwReason, LPVOID lpvReserved) { mb_shell::main(); break; } + case DLL_PROCESS_DETACH: { + // Restore all inline-hooked call-sites before the DLL image is + // unmapped, otherwise the patched code jumps into freed memory and + // crashes (observed as 0xC0000005 in OneCommander on shutdown). + mb_shell::hook_registry::uninstall_all(); + break; + } } return 1; } diff --git a/src/shell/hook_registry.cc b/src/shell/hook_registry.cc new file mode 100644 index 00000000..08f951f5 --- /dev/null +++ b/src/shell/hook_registry.cc @@ -0,0 +1,17 @@ +#include "hook_registry.h" + +namespace mb_shell { +static std::vector> g_uninstallers; + +void hook_registry::register_uninstaller(std::function fn) { + g_uninstallers.push_back(std::move(fn)); +} + +void hook_registry::uninstall_all() { + // Uninstall in reverse order (LIFO) so dependent hooks are removed first. + for (auto it = g_uninstallers.rbegin(); it != g_uninstallers.rend(); ++it) { + (*it)(); + } + g_uninstallers.clear(); +} +} // namespace mb_shell diff --git a/src/shell/hook_registry.h b/src/shell/hook_registry.h new file mode 100644 index 00000000..fe7e1f14 --- /dev/null +++ b/src/shell/hook_registry.h @@ -0,0 +1,12 @@ +#pragma once +#include +#include + +namespace mb_shell { +// Collects hook-uninstall callbacks so DLL_PROCESS_DETACH can restore every +// patched call-site before the DLL image is unmapped. +struct hook_registry { + static void register_uninstaller(std::function fn); + static void uninstall_all(); +}; +} // namespace mb_shell diff --git a/src/shell/res_string_loader.cc b/src/shell/res_string_loader.cc index 7b47605a..a7ce2a84 100644 --- a/src/shell/res_string_loader.cc +++ b/src/shell/res_string_loader.cc @@ -8,6 +8,7 @@ #include #include "config.h" +#include "hook_registry.h" #include "utils.h" #include "blook/blook.h" @@ -75,6 +76,8 @@ void res_string_loader::init_hook() { } return res; }); + hook_registry::register_uninstaller( + []() { LoadStringWHook->uninstall(); }); static auto LoadStringAHook = kernelbase->exports("LoadStringA")->inline_hook(); @@ -95,6 +98,8 @@ void res_string_loader::init_hook() { return res; }); + hook_registry::register_uninstaller( + []() { LoadStringAHook->uninstall(); }); } std::string res_string_loader::string_to_id_string(std::wstring str) { auto id = string_to_id(str);