OllyDBG插件开发实战:从plug110源码解析到自定义调试工具

发布时间:2026/10/11 10:03:14
OllyDBG插件开发实战:从plug110源码解析到自定义调试工具
简介这是一份面向逆向工程初学者与OllyDBG插件开发者的实战源码包以Plug110插件为例系统展示动态反汇编器插件从接口声明到功能实现的完整脉络。资源共21个文件压缩包约209KB涵盖c与cpp源文件、h头文件、def导出定义、hlp帮助文档、rtf说明以及mak、dsp、dsw等工程配置另有lib库文件与bpr编译配置分别对应书签标记、命令执行、命令处理等核心模块并兼顾Borland C与Visual C两套编译环境。已有229人学习下载适合希望理解插件生命周期、OllyDBG API调用与自定义命令扩展机制的读者。通过研读源码与配套帮助文档可掌握插件注册、消息处理、内存读写与断点控制等关键技能为开发自有插件及后续漏洞分析、软件逆向任务打下基础。1. plug110 插件源码一份能让你看懂 OllyDBG 插件骨架的逆向工程素材如果你做逆向分析时用过 OllyDBG大概率装过插件——反反调试、内存断点增强、字符串解密辅助这些功能背后都挂着一个 DLL。但很多人用了几年插件从没打开过插件的源码看一眼更别说自己写一个。plug110 这份资源就是 OllyDBG 的插件源码包它把插件从导出函数到事件回调的完整骨架摊开给你看。适合两类人一是想给 OllyDBG 写自定义插件但不知道从哪下手的逆向新手二是想通过插件机制理解调试器扩展架构的从业者。它解决的核心问题是让你不再把插件当黑匣子而是能改、能扩、能自己编译出一个能加载的 DLL。2. 插件机制拆解OllyDBG 怎么加载一个 DLL 并让它干活2.1 插件导出函数与加载流程OllyDBG 的插件本质上是一个标准的 Windows DLL但它要求 DLL 必须导出三个特定名称的函数否则 OllyDBG 在启动扫描插件目录时会直接忽略这个文件。这三个导出函数分别是ODBG_Plugindata、ODBG_Plugininit、ODBG_Pluginmenu名字一个都不能错大小写敏感。ODBG_Plugindata是 OllyDBG 第一个调用的导出函数它的作用是告诉 OllyDBG 这个插件叫什么名字、版本号是多少、支持哪些特性。函数签名大致如下// ODBG_Plugindata: 返回插件名称填充版本信息 extc int __cdecl ODBG_Plugindata(char *shortname) { // shortname 缓冲区由 OllyDBG 提供写入插件短名 strcpy(shortname, MyPlugin); return PLUGIN_VERSION; // 返回插件版本号 }参数shortname是一个由 OllyDBG 分配的字符缓冲区插件需要把自己的名字写进去这个名字会显示在 OllyDBG 的插件菜单里。返回值是插件版本号通常用宏PLUGIN_VERSION表示。这里有个容易翻车的点shortname缓冲区长度有限写超了会直接踩内存OllyDBG 不会给你任何提示表现就是加载插件后主界面花屏或者直接崩溃。ODBG_Plugininit是初始化入口OllyDBG 在加载插件后调用它传入 OllyDBG 的版本号和HWND句柄。这个函数里通常做几件事保存 OllyDBG 的窗口句柄、注册插件自己的菜单项、初始化全局变量。返回 0 表示初始化失败OllyDBG 会卸载插件返回非 0 表示成功。// ODBG_Plugininit: 初始化插件注册菜单 extc int __cdecl ODBG_Plugininit(int ollydbg_version, HWND hwnd, ulong *features) { if (ollydbg_version PLUGIN_VERSION) { return 0; // 版本不匹配拒绝加载 } g_hwndOlly hwnd; // 保存主窗口句柄后续弹窗要用 // 注册菜单项AddMenu 是 OllyDBG 提供的回调注册接口 AddMenu(0, MyPlugin, 0, 0, 0, 0, 0, 0, 0, 0); return 1; // 返回非零表示初始化成功 }ODBG_Pluginmenu是菜单点击后的回调分发函数。当用户在 OllyDBG 菜单里点击插件项时OllyDBG 调用这个函数传入菜单项 ID 和一个void*参数。你在这个函数里根据 ID 做switch分发执行具体逻辑。// ODBG_Pluginmenu: 菜单事件分发 extc void __cdecl ODBG_Pluginmenu(int origin, char *data, void *item) { switch (origin) { case PM_MAIN: // 主菜单点击 if ((int)item 0) { // 执行你的功能比如弹窗 MessageBox(g_hwndOlly, Hello from plugin, Info, MB_OK); } break; case PM_DISASM: // 反汇编窗口右键菜单 // 处理反汇编窗口的菜单事件 break; } }这三个函数构成了插件的最小骨架。plug110 源码里对这三个函数的实现是完整的你可以直接对照着改。2.2 事件回调与调试器交互光有菜单还不够插件真正有价值的地方在于能监听调试器事件——比如程序断下、内存写入、模块加载。OllyDBG 提供了一套回调注册机制插件通过RegisterPlugin或者直接设置回调函数指针来挂接这些事件。常见的事件回调包括回调类型触发时机典型用途PLUGIN_ANALYSE反汇编分析阶段自动标注函数、识别加密字符串PLUGIN_BREAKPOINT断点命中记录断点上下文、条件过滤PLUGIN_MEMORY内存读写监控关键内存区域变化PLUGIN_PAINT窗口重绘在反汇编窗口叠加自定义信息注册回调的方式通常是在ODBG_Plugininit里调用 OllyDBG 提供的注册函数把回调函数指针传进去。plug110 源码里能看到具体的注册代码它用的是RegisterPlugin接口传入回调类型和函数地址。// 在 Plugininit 中注册断点回调 RegisterPlugin(PLUGIN_BREAKPOINT, (void*)OnBreakpoint, 0); // 回调函数实现 void __cdecl OnBreakpoint(t_reg *reg, void *ip, void *dummy) { // reg 包含当前寄存器上下文 // ip 是断点地址 // 可以在这里读取内存、修改寄存器、记录日志 char msg[256]; wsprintf(msg, Breakpoint hit at %08X, (unsigned int)ip); // 输出到 OllyDBG 日志窗口 Plugingetvalue(VAL_HPROCESS); // 获取当前进程句柄 }这里的关键参数是t_reg结构体它包含了 EAX、EBX、ECX、EDX、ESI、EDI、ESP、EBP 以及标志寄存器的值。你可以在回调里直接读写这些字段来修改程序执行状态。比如把reg-eax改成 0程序断下后 EAX 就变成 0 了。plug110 源码里对事件回调的处理比较基础只挂了一个断点回调做演示。但骨架是完整的你照着加自己的回调逻辑就行。2.3 编译环境与依赖配置OllyDBG 插件是 32 位 DLL编译环境必须是 32 位工具链。用 Visual Studio 的话需要装 x86 工具集新建项目时选 Win32 DLL 模板。plug110 源码里带了.dsp和.dsw文件这是老版本 VC 6.0 的工程格式。如果你用 VS2019 或 VS2022 打开需要先转换工程格式或者手动新建一个 DLL 项目把源码文件加进去。编译时需要链接 OllyDBG 的导入库ollydbg.lib这个库文件在 OllyDBG 的插件开发包里有。plug110 源码目录下应该包含这个 lib 文件如果没有需要从 OllyDBG 官方插件 SDK 里找。链接器设置里要把ollydbg.lib加到附加依赖项里。# 如果用命令行编译以 MSVC 为例 cl /LD /Fe:MyPlugin.dll MyPlugin.c /link ollydbg.lib user32.lib/LD表示生成 DLL/Fe指定输出文件名/link后面跟需要链接的库。user32.lib是因为代码里用了MessageBox等 API。编译成功后得到一个 DLL 文件把它复制到 OllyDBG 安装目录的plugin文件夹下重启 OllyDBG 就能在菜单里看到插件项。注意OllyDBG 1.10 和 2.01 的插件接口有差异plug110 是针对 1.10 版本的。如果你用的是 2.01导出函数名和结构体定义可能不兼容需要对照 2.01 的 SDK 做适配。3. 从源码到可加载插件编译、部署与功能验证3.1 源码结构梳理与关键文件说明plug110 源码包解压后文件数量不多但每个都有明确用途。先搞清楚哪个文件是入口、哪个是头文件、哪个是工程文件能省掉大量瞎翻的时间。文件名类型作用plugin.cC 源文件插件主逻辑包含三个导出函数的实现plugin.h头文件结构体定义、宏定义、函数声明ollydbg.lib导入库链接 OllyDBG 导出函数用plugin.dsp工程文件VC 6.0 项目文件记录编译配置plugin.dsw工作区文件VC 6.0 工作区双击可打开工程resource.h头文件资源 ID 定义如果有对话框资源的话plugin.c是核心三个导出函数都在里面。plugin.h里定义了PLUGIN_VERSION宏和t_reg结构体的前置声明。ollydbg.lib是链接阶段必须的没有它编译会报一堆unresolved external symbol错误。如果你用 VS2022 打开.dsw文件VS 会提示工程格式过旧需要转换。转换后检查项目属性配置类型应该是“动态库 (.dll)”平台是 Win32字符集建议用多字节字符集OllyDBG 1.10 的接口用的是char*而不是wchar_t*。3.2 编译参数与常见链接错误处理编译 plug110 时最容易遇到两类错误链接错误和导出符号错误。链接错误通常是ollydbg.lib没加对或者平台选成了 x64。OllyDBG 1.10 是 32 位程序插件也必须是 32 位如果你在 VS 里选了 x64 平台链接阶段会报LNK1112: module machine type x64 conflicts with target machine type x86。导出符号错误是另一个高频翻车点。OllyDBG 要求导出函数名必须是_ODBG_Plugindata、_ODBG_Plugininit、_ODBG_Pluginmenu32 位 C 编译后函数名前面会加下划线。如果你在代码里写了__declspec(dllexport)但名字不对OllyDBG 加载时会提示“不是有效的插件”。检查导出符号的方法用dumpbin /exports MyPlugin.dll命令查看 DLL 的导出表。正确的输出应该包含三个函数名前面带下划线。dumpbin /exports MyPlugin.dll # 期望输出片段 # ordinal hint RVA name # 1 0 00001000 _ODBG_Plugindata # 2 1 00001010 _ODBG_Plugininit # 3 2 00001020 _ODBG_Pluginmenu如果导出表里名字不对检查代码里的函数声明。plug110 源码里用的是extc宏这个宏在plugin.h里定义为__declspec(dllexport)。确保你没有改这个宏的定义。另一个坑是调用约定。OllyDBG 要求导出函数用__cdecl调用约定如果你写成了__stdcall函数名会变成_ODBG_Plugindata4这种带后缀的形式OllyDBG 同样认不出来。3.3 部署到 OllyDBG 并验证插件加载编译出 DLL 后部署步骤很简单但有几个细节不注意就会导致插件不加载。第一步找到 OllyDBG 安装目录下的plugin文件夹。如果没有这个文件夹手动建一个名字必须是plugin全小写。第二步把编译好的 DLL 复制进去。DLL 文件名可以任意但建议和插件短名一致方便管理。第三步重启 OllyDBG。OllyDBG 只在启动时扫描plugin目录运行中放进去的 DLL 不会被加载。第四步检查插件是否加载成功。点菜单Options-Plugins如果插件加载成功列表里会显示插件短名和版本号。如果列表是空的说明 DLL 没被识别。// 在 ODBG_Plugindata 里加一行日志辅助排查 extc int __cdecl ODBG_Plugindata(char *shortname) { strcpy(shortname, Plug110Demo); // 写一个临时文件确认函数被调用了 FILE *f fopen(C:\\plugin_debug.txt, a); if (f) { fprintf(f, Plugindata called\n); fclose(f); } return PLUGIN_VERSION; }如果plugin_debug.txt没有生成说明 OllyDBG 根本没调用ODBG_Plugindata问题出在 DLL 导出符号或者文件放错了目录。如果文件生成了但插件列表里没有检查ODBG_Plugininit的返回值是不是 0。验证插件功能是否正常点击插件菜单项看是否弹出预期的对话框或者日志输出。plug110 的演示功能比较简单就是一个弹窗。你可以在此基础上加自己的逻辑比如读取当前调试进程的模块列表、扫描内存中的特定字符串。4. 避坑与排查插件开发中那些让人抓狂的瞬间4.1 插件加载后 OllyDBG 直接崩溃现象把编译好的 DLL 放进plugin目录重启 OllyDBG主界面刚出来就闪退没有任何错误提示。原因最常见的是ODBG_Plugindata里strcpy写超了shortname缓冲区。OllyDBG 给这个缓冲区分配的长度很短通常只有几十字节如果你的插件名太长或者strcpy的源字符串没有\0结尾就会踩到后面的内存。另一个原因是ODBG_Plugininit里访问了空指针比如hwnd参数没保存就直接用。解决把strcpy换成strncpy并且手动保证结尾有\0。插件名控制在 16 个字符以内。在ODBG_Plugininit里加空指针检查hwnd为 NULL 时直接返回 0。// 安全的字符串拷贝 strncpy(shortname, Plug110Demo, 15); shortname[15] \0; // 强制结尾4.2 导出函数明明写了但 OllyDBG 认不出来现象dumpbin /exports能看到导出函数但 OllyDBG 的插件列表里就是没有。原因导出函数名不对。32 位 C 编译后函数名前面会加下划线但如果你用了.def文件或者extern C的方式名字可能变成不带下划线的形式。OllyDBG 1.10 要求的是带下划线的_ODBG_Plugindata这种格式。另外如果函数用了__stdcall名字会变成_ODBG_Plugindata4同样不被识别。解决用dumpbin /exports确认导出名必须是_ODBG_Plugindata、_ODBG_Plugininit、_ODBG_Pluginmenu三个。如果名字不对检查函数声明有没有加__cdecl有没有用extern C包裹C 编译时需要。// C 编译时需要用 extern C 防止名字修饰 extern C { extc int __cdecl ODBG_Plugindata(char *shortname); extc int __cdecl ODBG_Plugininit(int version, HWND hwnd, ulong *features); extc void __cdecl ODBG_Pluginmenu(int origin, char *data, void *item); }4.3 菜单项点击没反应现象插件加载成功了菜单里也能看到插件名但点击之后什么也不发生。原因ODBG_Pluginmenu里的switch分发逻辑写错了。origin参数表示菜单来源主菜单是PM_MAIN反汇编窗口右键菜单是PM_DISASM转储窗口是PM_DUMP。如果你在PM_MAIN分支里判断的item值和注册菜单时用的 ID 不一致就进不了正确的分支。另一个可能是AddMenu注册时参数传错菜单项根本没注册上。解决在ODBG_Pluginmenu入口加日志确认函数被调用了并且打印origin和item的值。对照AddMenu的调用参数确保菜单 ID 一致。extc void __cdecl ODBG_Pluginmenu(int origin, char *data, void *item) { char dbg[128]; wsprintf(dbg, origin%d item%d, origin, (int)item); // 输出到 OllyDBG 日志窗口 // 确认 origin 和 item 的值是否符合预期 if (origin PM_MAIN (int)item 0) { MessageBox(g_hwndOlly, Menu clicked, OK, MB_OK); } }4.4 回调函数里读内存导致 OllyDBG 卡死现象挂上断点回调后每次断点命中 OllyDBG 就卡住几秒调试体验极差。原因在回调函数里做了耗时操作比如遍历整个内存空间、读写大块数据、调用ReadProcessMemory读大量字节。OllyDBG 的事件回调是在调试循环里同步调用的回调不返回调试器就卡住。解决回调里只做最轻量的操作比如记录地址、修改寄存器。需要做重活的把数据存到全局变量里另起一个线程处理或者用PostMessage发消息到主窗口异步处理。// 回调里只记录不处理 void __cdecl OnBreakpoint(t_reg *reg, void *ip, void *dummy) { g_lastBreakAddr (unsigned int)ip; // 只存地址 // 不在这里做内存扫描 }4.5 编译出来的 DLL 在别人机器上加载失败现象自己机器上插件正常复制到同事的 OllyDBG 里就加载不了。原因运行库依赖问题。如果你编译时用了/MD动态链接 CRT目标机器没装对应版本的 VC 运行库DLL 就加载失败。OllyDBG 加载插件失败时通常不报错只是静默忽略。解决编译时改用/MT静态链接 CRT这样 DLL 不依赖外部运行库。在 VS 项目属性里C/C - 代码生成 - 运行库选“多线程 (/MT)”。# 命令行编译时指定静态 CRT cl /LD /MT /Fe:MyPlugin.dll plugin.c /link ollydbg.lib user32.lib5. 进阶玩法把 plug110 改造成自己的调试辅助工具plug110 的骨架搭好之后真正有意思的是往里填自己的逻辑。我一般会从两个方向入手一是自动标注二是内存监控。自动标注的思路是在PLUGIN_ANALYSE回调里扫描反汇编代码识别常见的加密函数特征。比如某个函数开头是push ebp; mov ebp, esp; sub esp, 0x100后面跟着大量xor指令大概率是字符串解密函数。识别到之后用 OllyDBG 的Setlabel接口给函数地址打上标签下次反汇编时一眼就能看到。// 在分析回调里自动标注 void __cdecl OnAnalyse(ulong addr, ulong size, void *dummy) { // 读取指定地址的字节 unsigned char code[16]; // Readmemory 是 OllyDBG 提供的接口 if (Readmemory(code, addr, 16, 0) 16) { // 判断是否是 xor 密集区域 int xorCount 0; for (int i 0; i 16; i) { if (code[i] 0x31 || code[i] 0x33) xorCount; } if (xorCount 4) { // 打标签 char label[64]; wsprintf(label, maybe_decrypt_%08X, addr); Setlabel(addr, label); } } }内存监控的思路是在PLUGIN_MEMORY回调里记录关键内存区域的写入操作。比如你正在分析一个注册码校验程序想知道校验结果写到了哪个地址就监控堆栈区域或者全局变量区域的写入。回调触发时打印地址和写入的值很快就能定位到关键跳转。// 内存写入回调 void __cdecl OnMemoryWrite(ulong addr, ulong size, void *dummy) { // 只监控特定范围 if (addr 0x00400000 addr 0x00401000) { char msg[128]; wsprintf(msg, Write at %08X size %d, addr, size); // 输出到日志 } }验证改造效果的方法找一个简单的 crackme 程序用改造后的插件跑一遍看自动标注有没有生效、内存监控有没有抓到关键写入。如果标注没生效检查Readmemory的返回值可能是地址无效或者权限不够。如果内存回调没触发确认RegisterPlugin的调用时机——必须在ODBG_Plugininit里注册晚了就挂不上。从那以后我每次写新插件都强制先跑一遍 plug110 的原始代码确认编译链路和加载流程没问题再往里加自己的逻辑。这样出问题时能快速定位是骨架的问题还是自己代码的问题。希望帮到你。本文还有配套的精品资源点击获取