第一章环境搭建请移步: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编码
[C++] 纯文本查看 复制代码
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,第二个参数是你给菜单的编码,在回调事件里会用到
[C++] 纯文本查看 复制代码
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 回调事件
[C++] 纯文本查看 复制代码
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[128];
char *moduleName2 = new char[128];
char *out = new char[128];
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[nn] == 0) continue;
DbgGetModuleAt(dstAdress[nn], moduleName2);
if (strcmp(moduleName1, moduleName2) == 0) {
outlen = sprintf(out + outlen, "[0x%x];", dstAdress[nn] - 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的快速启动界面中启动,必须从文件夹中打开对应的工程。
|