x64dbg 插件卸载命令 plugunload 使用与源码解析

x64dbg 插件卸载命令 plugunload 使用与源码解析 x64dbg 插件卸载命令 plugunload 使用与源码解析【免费下载链接】x64dbgAn open-source user mode debugger for Windows. Optimized for reverse engineering and malware analysis.项目地址: https://gitcode.com/gh_mirrors/x6/x64dbgplugunload是 x64dbg 调试器内置的插件管理命令用于在运行期动态卸载已加载的插件。本文以官方命令文档 plugunload.md 为骨架结合调试器核心插件加载器的源码实现完整讲解该命令的三种别名、参数约定、返回值语义、名称规范化规则以及从plugstop回调到资源回收的完整卸载流程帮助你安全地在命令行、脚本中管理插件生命周期并理解插件卸载在 x64dbg 内部的真实工作原理。命令概述plugunload属于 x64dbg 内嵌的插件管理命令debugger-embedded plugin commands与plugload、plugreload同属一组。它的作用只有一个卸载一个已加载的插件。该命令在调试器启动阶段注册注册代码位于 src/dbg/x64dbg.cpp//plugins dbgcmdnew(StartScylla,scylla,imprec, cbDebugStartScylla, false); //start scylla dbgcmdnew(plugload,pluginload,loadplugin, cbInstrPluginLoad, false); //load plugin dbgcmdnew(plugunload,pluginunload,unloadplugin, cbInstrPluginUnload, false); //unload plugin dbgcmdnew(plugreload,pluginreload,reloadplugin, cbInstrPluginReload, false); //reload plugin从注册语句可以看出x64dbg 的命令系统允许一个命令绑定多个别名以逗号分隔因此plugunload命令实际有三个等价的名字别名用途plugunload主命令名pluginunload完整拼写别名unloadplugin动词在前的别名三个名字指向同一个处理函数cbInstrPluginUnload行为完全一致你可以按习惯任选其一。命令处理函数命令分发后实际执行的逻辑在 src/dbg/commands/cmd-plugins.cppbool cbInstrPluginUnload(int argc, char* argv[]) { if(IsArgumentsLessThan(argc, 2)) return false; return pluginunload(argv[1]); }处理流程非常简单先检查参数个数是否至少为 2即除了命令名本身外还必须携带一个参数否则直接返回false随后把argv[1]作为插件名传给插件加载器的核心函数pluginunload()。参数与返回值官方文档 plugunload.md 对参数和返回值的定义如下argumentsarg1— Name of the plugin.插件的名称resultThis command does not set any result variables.本命令不设置任何结果变量也就是说arg1必填要卸载的插件名称。x64dbg 的插件使用与调试器位数对应的扩展名——32 位为.dp3264 位为.dp64见 src/dbg/plugin_loader.cpp 中的ArchValue(L.dp32, L.dp64)。插件名在多数情况下可省略扩展名传入详见下文“名称规范化”。返回值该命令不会设置任何脚本结果变量例如$result无法通过脚本变量直接判断卸载是否成功。判断成败的方式是观察命令输出——成功时日志区会打印[PLUGIN] name unloaded失败时打印[PLUGIN] name not found。参数解析名称规范化虽然文档只要求传入“插件名”但pluginunload的底层实现实际上相当宽容。卸载前会先调用pluginNormalizeName()对参数做规范化处理该函数位于 src/dbg/plugin_loader.cppstatic std::string pluginNormalizeName(std::string pluginName) { auto pathPos pluginName.find_last_of(\\/); if(pathPos ! std::string::npos) pluginName pluginName.substr(pathPos 1); auto extPos pluginName.rfind(StringUtils::Utf16ToUtf8(gPluginExtension)); if(extPos ! std::string::npos) pluginName pluginName.substr(0, extPos); return pluginName; }规范化规则可以总结为两点剥离路径如果传入的是完整路径含\或/只取最后一个路径分隔符之后的部分剥离扩展名如果参数以.dp32或.dp64结尾去掉扩展名。因此以下写法在语义上是等价的假设插件实际名为MyPlugin.dp64plugunload MyPlugin plugunload MyPlugin.dp64 plugunload C:\x64dbg\plugins\MyPlugin.dp64规范化后的名字会与插件列表gPluginList中的plugname做不区分大小写的比较_stricmp所以大小写混用也不影响匹配。需要说明的是这一宽容性同样适用于配套的plugload命令——后者在 plugload.md 中明确支持“插件名走默认插件目录查找”“直接.dp32/.dp64路径”“目录简写dir\basename(dir).dp32/.dp64”三种形式。卸载流程源码级解析核心实现pluginunload()位于 src/dbg/plugin_loader.cpp。整个卸载过程可以拆解为以下步骤1. 查找插件先规范化名字然后在全局插件列表gPluginList中按名称查找目标插件bool pluginunload(const char* pluginName, bool unloadall) { // Normalize the plugin name. std::string name pluginNormalizeName(pluginName); auto found gPluginList.end(); { EXCLUSIVE_ACQUIRE(LockPluginList); found std::find_if(gPluginList.begin(), gPluginList.end(), name { return _stricmp(a.plugname, name.c_str()) 0; }); } ...gPluginList中的每个元素是PLUG_DATA结构定义见 src/dbg/plugin_loader.h记录着插件路径plugpath、规范化名称plugname、模块句柄hPlugin以及pluginit/plugstop/plugsetup三个关键导出函数指针。查找过程通过LockPluginList锁保证线程安全。2. 调用 plugstop 回调找到插件后第一件事是调用插件的plugstop导出如果存在if(found ! gPluginList.end()) { bool canFreeLibrary true; auto currentPlugin *found; if(currentPlugin.plugstop) canFreeLibrary currentPlugin.plugstop();plugstop是 x64dbg 插件 SDK 约定的生命周期回调之一。官方插件开发文档 docs/developers/plugins/basics.md 明确说明plugstopcalled when the plugin is about to be unloaded. Remove all registered commands and callbacks here. Also clean up plugin data.即插件被卸载前会收到plugstop通知插件应在此回调中注销自己注册的命令、回调并清理内部数据。值得注意的细节是plugstop的返回值bool会赋值给canFreeLibrary——如果插件在plugstop中返回false加载器将跳过FreeLibrary不会强制释放插件 DLL。这为插件提供了“拒绝卸载/延迟释放”的机制但从日志看卸载仍会被记为成功见步骤 5。3. 注销命令、表达式函数与格式化函数接下来把该插件注册到调试器的三类扩展点全部注销src/dbg/plugin_loader.cppint pluginHandle currentPlugin.initStruct.pluginHandle; plugincmdunregisterall(pluginHandle); pluginexprfuncunregisterall(pluginHandle); pluginformatfuncunregisterall(pluginHandle);plugincmdunregisterall遍历gPluginCommandList删除属于该插件句柄的所有自定义命令并调用dbgcmddel从命令系统中移除见 src/dbg/plugin_loader.cpppluginexprfuncunregisterall注销该插件注册的表达式函数供exprfunc表达式语法调用pluginformatfuncunregisterall注销该插件注册的格式化函数用于自定义数据显示格式。同时会从gPluginCallbackList中按pluginHandle清除该插件注册的全部事件回调{ EXCLUSIVE_ACQUIRE(LockPluginCallbackList); for(auto cbList : gPluginCallbackList) { for(auto it cbList.begin(); it ! cbList.end();) { if(it-pluginHandle pluginHandle) it cbList.erase(it); else it; } } }这意味着卸载之后该插件注册的CB_*系列回调如CB_EXCEPTION、CB_BREAKPOINT、CB_MENUENTRY等将不再被触发。4. 清理插件菜单插件在 GUI 中注册的各级菜单也会被逐一清理调用pluginmenuclear(handle, true)并传入erasetruesrc/dbg/plugin_loader.cpp{ EXCLUSIVE_ACQUIRE(LockPluginList); pluginmenuclear(currentPlugin.hMenu, true); pluginmenuclear(currentPlugin.hMenuDisasm, true); pluginmenuclear(currentPlugin.hMenuDump, true); pluginmenuclear(currentPlugin.hMenuStack, true); pluginmenuclear(currentPlugin.hMenuGraph, true); pluginmenuclear(currentPlugin.hMenuMemmap, true); pluginmenuclear(currentPlugin.hMenuSymmod, true);对应PLUG_DATA中保存的七个菜单句柄插件主菜单hMenu以及反汇编视图hMenuDisasm、转储视图hMenuDump、栈视图hMenuStack、图形视图hMenuGraph、内存映射视图hMenuMemmap、符号/模块视图hMenuSymmod——这些都是插件在plugsetup阶段通过GuiMenuAdd申请到的见 src/dbg/plugin_loader.cpp。卸载后这些菜单条目会从 x64dbg 的 GUI 中消失。5. 移除列表记录并释放 DLL默认非unloadall批量模式情况下插件会从gPluginList中移除避免后续操作例如“全部卸载”再次尝试释放已卸载的模块if(!unloadall) { //remove from main pluginlist. We do this so unloadall doesnt try to unload an already released plugin gPluginList.erase(found); } } if(canFreeLibrary) FreeLibrary(currentPlugin.hPlugin); dprintf(QT_TRANSLATE_NOOP(DBG, [PLUGIN] %s unloaded\n), actualName.c_str()); return true;最后若canFreeLibrary为真则调用FreeLibrary释放插件 DLL并在日志区打印[PLUGIN] name unloaded函数返回true表示卸载成功。6. 失败路径如果gPluginList中找不到与规范化名称匹配的插件则打印[PLUGIN] name not found并返回falsesrc/dbg/plugin_loader.cppdprintf(QT_TRANSLATE_NOOP(DBG, [PLUGIN] %s not found\n), actualName.c_str()); return false;命令使用示例在 x64dbg 的命令栏或脚本、scriptcmd调用中直接输入即可。以下以卸载名为ExamplePlugin的插件为例plugunload ExamplePlugin等价写法别名 带扩展名 / 带路径pluginunload ExamplePlugin.dp64 unloadplugin ExamplePlugin卸载成功时日志区输出[PLUGIN] ExamplePlugin unloaded如果插件名拼写错误或插件本就没有加载则输出[PLUGIN] ExamplePlugin not found与 plugload / plugreload 的配合plugunload通常与另外两个插件管理命令搭配使用三者共用同一组底层 APIpluginload/pluginunload构成完整的插件生命周期闭环命令作用plugload加载插件支持插件名、.dp32/.dp64路径或目录简写见 plugload.mdplugunload卸载插件plugreload先卸载再加载实现插件热重载plugreload的实现src/dbg/commands/cmd-plugins.cpp展示了plugunload与plugload的组合用法先调用pluginunload(argv[1])随后默认弹出一个“Press OK to reload the plugin...”确认框再调用pluginload(argv[1])。这也提示一个实践要点卸载/重载会触发插件 DLL 的FreeLibrary/LoadLibrary插件内部持有的全局状态会全部丢失因此依赖持久状态的插件需要自行在plugstop/pluginit中完成保存与恢复。退出时的批量卸载除命令外pluginunloadall()会在 x64dbg 退出时被调用src/dbg/x64dbg.cpp遍历gPluginList的快照并逐个调用pluginunload(plugin.plugname, true)src/dbg/plugin_loader.cpp。批量模式unloadalltrue与单插件卸载的区别在于插件不会在卸载过程中从gPluginList移除步骤 5 中if(!unloadall)分支而是在全部卸载完成后统一clear()。这保证了遍历过程中的线程安全与一致性。插件侧的卸载契约plugstop如果你正在编写 x64dbg 插件需要为卸载场景做好准备。插件 SDK 要求的生命周期导出定义如下docs/developers/plugins/basics.mdextern C __declspec(dllexport) bool pluginit(PLUG_INITSTRUCT* initStruct); extern C __declspec(dllexport) bool plugstop(); extern C __declspec(dllexport) void plugsetup(PLUG_SETUPSTRUCT* setupStruct);pluginit加载时调用必须存在plugsetup初始化成功后调用用于注册菜单等 GUI 资源plugstop卸载时调用必须在此注销自己注册的命令、回调、表达式函数、格式化函数并清理插件数据。另外SDK 还提供了_plugin_unload/_plugin_load接口src/dbg/_plugins.cpp允许一个插件在运行时通过 SDK 主动加载或卸载其他插件其内部同样是转发到pluginunload/pluginload。相关源码与文档索引命令文档plugunload.md、plugload.md、plugins 命令索引命令注册src/dbg/x64dbg.cpp命令处理函数src/dbg/commands/cmd-plugins.cpp插件加载器核心实现src/dbg/plugin_loader.cpp插件数据结构与接口声明src/dbg/plugin_loader.h插件 SDK 卸载接口src/dbg/_plugins.cpp插件开发基础导出与生命周期docs/developers/plugins/basics.md【免费下载链接】x64dbgAn open-source user mode debugger for Windows. Optimized for reverse engineering and malware analysis.项目地址: https://gitcode.com/gh_mirrors/x6/x64dbg创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考