only998 发表于 2021-12-14 07:43

【c++】x64dbg插件开发,摸索前进:第二章第一个正式插件

第一章环境搭建请移步:https://www.52pojie.cn/thread-1560376-1-1.html
正式开始前先吐槽下x64dbg的手册,万能不更新,至今停留在0.1版,想要点关键内容,没有,摊手。。

首先说下x64dbg的插件工作方式:
启动后搜索 plugins 目录下以 .dp32 或者 .dp64结尾的文件(分别对应x86和x64版本,之后不提x64了,模式都是一样的)
载入dp32文件后,检索导出函数 pluginit 并且调用,调用成功后,再调用 plugsetup 函数。插件卸载或者程序退出时调用 plugstop 函数

跟其他GUI程序一样,x64dbg同样存在事件回调,有以下几类(英文直译,建议大家看手册,其实能知道发生的时机,只不过英文里写了很多其他提示,拗口,难懂)
CBINITDEBUG调试器载入某个exe或者dll时,或者采用“附加”的方式,则触发事件
CBSTOPDEBUG调试器退出exe或者dll或者脱离附加时,通常进行变量重置,或者
CBCREATEPROCESS 创建进程、初始化符号句柄(这个不太懂?)、数据库文件并且在TLS回调的入口处设置了断点(也不太懂?),触发事件
CBEXITPROCESS   退出进程、符号句柄清理之前,触发事件
CBCREATETHREAD在线程创建之后,线程添加到内部线程列表之后,“before breaking the debugger on thread creation and after setting breakpoints on the 线程入口”后面这句有点怪,我猜是指如果在线程入口添加了断点,那么在调试中断之前就已经触发了 CBCREATETHREAD事件
CBEXITTHREAD    在线程终止后、从内部线程列表中删除线程之前、同样,如果你在线程结束处设置了断点,CBEXITTHREAD 会在断点触发前触发
CBSYSTEMBREAKPOINT系统断点时触发
CBLOADDLL      DLL加载时
CBUNLOADDLL      DLL卸载时
CBOUTPUTDEBUGSTRING 字符串存储到系统日志之前触发
CBEXCEPTION    发生异常时触发,限定条件after setting the continue status, after locking the debugger to pause:
CBBREAKPOINT   遇到普通/内存/硬件断点时,并且调试器已经暂停,触发事件
CBPAUSEDEBUG   调试器暂停时触发,并且早于任何其他的与调试器暂停有关的回调函数
CBRESUMEDEBUG    调试器从暂停状态切换到继续调试时触发
CBSTEPPED      单步时触发
CBATTACH         附加到进程时触发
CBDETACH         脱离进程时触发
CBDEBUGEVENT   任何与调试有关的事件都会触发此回调,即使是内部处理的事件。避免在此处执行耗时的操作,这将大大降低调试器的速度!
CBMENUENTRY      单击插件创建的菜单项时调用,当此回调返回时,GUI将恢复
CBWINEVENT       windows事件在TranslateMessage和DispatchMessage之前触发此事件(预译信息)。注意不要在这里调用user32函数,如果不采取措施会造成递归调用,然后boom~
CBWINEVENTGLOBAL跟CBWINEVENT一样,不过这个是全局的,因此它还捕获热键(请参见Qt帮助文档). 在Qt5中,这个事件几乎不会被调用,请改用PLUG_CB_WINEVENT。
CBLOADSAVEDB      数据加载或者保存到数据库中时,以JSON格式检索数据,参考_plugin.h文件
CBFILTERSYMBOL    在将符号添加到标签之前触发,如果要过滤符号,请将retval设置为false
CBTRACEEXECUTE    在条件跟踪期间触发,将stop设置为true结束跟踪

举个例子,调试器载入某个exe时会触发 CBINITDEBUG 事件,这时候调试器会在插件中寻找名为 CBINITDEBUG 的导出函数,找到了就调用它。
每个一个事件的原型都有不同的调用函数,建议大家多看下手册,我这里就不一一列出了。

有回调事件就有正常的功能支持,直接 #include "_plugins.h" 就可以启用所有的功能操作。
操作函数分为三种,一种是gui相关的,均以Gui****开头;一种是调试相关的,均以Dbg*****开头;还有一种是Bridge函数,均以Bridge*****开头。
除此之外还有一种_plugin函数,用于插件注册,信息输出,等

了解了x64dbg的插件工作模式,其实用易语言都能写出满足要求的插件,首先你必须实现 pluginit 函数,有菜单的话需实现 plugsetup 函数,
事件回调你可以直接实现对应的事件回调函数,也可以通过 _plugin_registercallback 注册满足要求的回调函数。其次,导入x32_bridge.dll中对应的操作函数,参考手册就可以跟x64dbg交互了。不过x64dbg的手册稀烂,还是c++方面调试和查看函数原型。

基本情况介绍到这,看下我的第一个插件要实现的功能:我希望可以快速将dll的某个地址转换为 相对dll载入基址的地址,方便在后期编程爆破中直接操作。
为此我需要实现以下函数:
pluginit这个是每个插件必须的,设置插件版本和名称
plugsetup 注册菜单,没有菜单怎么行
CBMENUENTRY 菜单被单击时回调,以便我做操作

初始化没啥好说的,注意所有x64dbg中的字符串你都应该使用utf8编码

extern "C" __declspec(dllexport) bool pluginit(PLUG_INITSTRUCT* initStruct);
//初始化插件
bool pluginit(PLUG_INITSTRUCT* initStruct) {
        initStruct->sdkVersion = PLUG_SDKVERSION;
        initStruct->pluginVersion = 1;
        const char *name = "LoliTool";
        memset(initStruct->pluginName, 0, 128);
        memcpy(initStruct->pluginName, name, strlen(name));
        return true;
}

plugsetup注册菜单,注意不要使用GuiMenuAdd和GuiMenuAddEntry这两坑爹函数,注册出来的菜单不能触发回调,得用_plugin_menuaddentry,第二个参数是你给菜单的编码,在回调事件里会用到

extern "C" __declspec(dllexport) void plugsetup(PLUG_SETUPSTRUCT* setupStruct);
//初始化菜单
void plugsetup(PLUG_SETUPSTRUCT* setupStruct) {
        //“相对基址”,这里直接采用utf8编码
        char name1[] = { 0xe7,0x9b,0xb8,0xe5,0xaf,0xb9,0xe5,0x9f,0xba,0xe5,0x9d,0x80,0 };
        //注意添加的是二级菜单,一级的名称默认跟插件名称一致
        _plugin_menuaddentry(setupStruct->hMenuDisasm, 2, name1);
}

注意setupStruct有以下几个成员:
    HWND hwndDlg;      //x64dbg的窗口句柄,想自绘的可以用
    int hMenu;                //菜单栏的“插件”
    int hMenuDisasm;   //反汇编右键菜单
    int hMenuDump;   //内存窗口右键菜单
    int hMenuStack;       //堆栈窗口右键菜单
    int hMenuGraph;   // 流程图窗口右键菜单
    int hMenuMemmap;   //内存布局窗口右键菜单
    int hMenuSymmod;      // //符号窗口右键菜单
要注册哪个菜单就使用对应的变量

CBMENUENTRY 回调事件

extern "C" __declspec(dllexport) void CBMENUENTRY(CBTYPE cbType, PLUG_CB_MENUENTRY* info);
void CBMENUENTRY(CBTYPE cbType, PLUG_CB_MENUENTRY* info) {
        SELECTIONDATA sel;
        if(GuiSelectionGet(GUI_DISASSEMBLY, &sel)){ //选择位置指的是字节位置
                char *moduleName1 = new char;
                char *moduleName2 = new char;
                char *out = new char;
                int outlen;

                DbgGetModuleAt(sel.start, moduleName1);//获得选区起始位置的模块名
                duint baseAdress = DbgModBaseFromName(moduleName1);//取得模块的基址

                BASIC_INSTRUCTION_INFO asminfo;
                for (duint ii = sel.start; ii <= sel.end; ii += asminfo.size) {
                        outlen = 0;
                        memset(out, 0, 128);

                        //获取选区本行的反汇编信息
                        DbgDisasmFastAt(ii, &asminfo);
                        //分别是当前地址,jmp/call地址,内存读写地址
                        duint dstAdress[] = { ii, asminfo.value.value , asminfo.memory.value};
                        //提取出所有属于模块内的地址
                        for (int nn = 0; nn < 3; nn++) {
                                if (dstAdress == 0) continue;
                                DbgGetModuleAt(dstAdress, moduleName2);
                                if (strcmp(moduleName1, moduleName2) == 0) {
                                        outlen = sprintf(out + outlen, ";", dstAdress - baseAdress);
                                }
                        }

                        //结果写到注释里
                        DbgSetCommentAt(ii, out);
                }

                delete[] moduleName1;
                delete[] moduleName2;
                delete[] out;
        }
}

GuiSelectionGet:获取指定视窗的选区,第一个参数可以选择
    GUI_DISASSEMBLY,值为0,下面依次递增1, 反汇编窗口
    GUI_DUMP,    内存窗口
    GUI_STACK,    堆栈窗口
    GUI_GRAPH,流程图窗口
    GUI_MEMMAP,内存布局窗口
    GUI_SYMMOD,符号窗口
参数二为SELECTIONDATA变量

DbgGetModuleAt:获取指定地址的模块名称
DbgDisasmFastAt:获取指定地址的反汇编信息,产生的信息包括是否 jmp/call 内存操作 跳转操作等等
DbgSetCommentAt:添加注释

插件成果如下:




最后吐槽下,按第一章搭建的环境,不能从vs的快速启动界面中启动,必须从文件夹中打开对应的工程。

列明 发表于 2021-12-14 11:56

工具名字可爱

zobook 发表于 2021-12-26 19:28

谢谢分享,写的很详细

冥界3大法王 发表于 2021-12-26 19:41

@only998
帮助给的很混蛋;复制一下都费劲。很多都没有实例;
汉化的版的到处是错误;新版更新了内容,而有人却错把汉化版当宝。{:301_988:}

only998 发表于 2021-12-26 22:05

冥界3大法王 发表于 2021-12-26 19:41
@only998
帮助给的很混蛋;复制一下都费劲。很多都没有实例;
汉化的版的到处是错误;新版更新了内容,而 ...

真相了,很多函数的功能都要靠名字猜,虽然一共也没多少个函数,但是总感觉心累。我现在转到C#写插件了,c++虽然直接但是搞gui费劲,c#就比较完美了:lol。

normalfriend 发表于 2022-2-14 10:39

你好 大佬 如果我想在x64dbg 插件菜单中 显示自己插件 应该怎么做

only998 发表于 2022-2-14 17:47

normalfriend 发表于 2022-2-14 10:39
你好 大佬 如果我想在x64dbg 插件菜单中 显示自己插件 应该怎么做

extern "C" __declspec(dllexport) void plugsetup(PLUG_SETUPSTRUCT* setupStruct);
//初始化菜单
void plugsetup(PLUG_SETUPSTRUCT* setupStruct) {
        _plugin_menuaddentry(setupStruct->hMenu, 你给插件的ID整数, utf8的const char*);
}
页: [1]
查看完整版本: 【c++】x64dbg插件开发,摸索前进:第二章第一个正式插件