0. 需求
今天是元宵节,祝各位元宵节快乐! 因为我在软件逆向方面有一些自己想出来的概念和思路需要去实践验证,于是想到了以x64dbg插件的形式作为基础设施的想法,因此写下了此文章来记录自己是怎么搭建x64dbg插件开发环境的 —— 2025/2/12 16:15
1. 官方地址
x64dbg 官网:https://x64dbg.com/#features
x64dbg 开发帮助手册页:https://help.x64dbg.com/en/latest/developers/index.html
https://github.com/x64dbg/x64dbg/wiki/Plugins
x64dbg插件模板github仓库:https://github.com/x64dbg/PluginTemplate
2. 下载插件模板
首先前往 https://github.com/x64dbg/PluginTemplate 去下载源码,然后解压到你自定义的一个路径里
我这边随便选择一个路径作示例
接着,以这个文件夹路径作为根目录,打开cmd
输入命令:cmake -B build -G "Visual Studio 17 2022" -A x64
(你需要先安装cmake)
成功了之后,我们可以看到文件夹里新增了一个build
文件夹
我们点进去,里边有个sln文件,这个就是x64dbg插件的项目文件了
插件的主要代码都是在PluginTemplate -> src -> plugin.cpp
这里
3. 如何编译调试
针对x64dbg插件的调试,其实就是针对dll的调试,按照dll的调试方法去配置即可。
- 首先,将 PluginTemplate 设置为启动项目
- 之后先试编译一下,拿到你的编译路径
比如我这里的路径是:E:\Chrome Download\PluginTemplate-main\build\Debug\PluginTemplate.dp64
之后找到你x64dbg.exe的路径,在这个路径上找到"plugin"这个文件夹,拿到路径名
我这里是 D:\Chrome Download\x64dbg_2023_06_15\release\x64\plugins
- 现在,我们构造一条批处理命令:
copy "E:\Chrome Download\PluginTemplate-main\build\Debug\PluginTemplate.dp64" "D:\Chrome Download\x64dbg_2023_06_15\release\x64\plugins\PluginTemplate.dp64" /y
这条命令的意思是,复制编译好的dp64插件到x64dbg的plugin目录中,后面的/y
表示复制的时候不需要重复确认
拼接好之后,我们重新回到vs,打开 PluginTemplate 的属性
之后打开配置属性 -> 生成事件 -> 生成后事件
,在右边找到命令行
我们把刚刚构造的复制命令填到命令行的最上边去
单击确认。
- 之后我们再来到
配置属性 -> 调试
,在右边中,我们在"本地 Windows 调试器"的基础上,在命令行的右边单击浏览
在里边找到你自己安装的x64dbg.exe的路径
确认后,我们就应用退出。
以下是我的plugin.cpp的代码,我删除了原先模板的一些代码:
#include "plugin.h"
// Examples: https://github.com/x64dbg/x64dbg/wiki/Plugins
// References:
// - https://help.x64dbg.com/en/latest/developers/plugins/index.html
// - https://x64dbg.com/blog/2016/10/04/architecture-of-x64dbg.html
// - https://x64dbg.com/blog/2016/10/20/threading-model.html
// - https://x64dbg.com/blog/2016/07/30/x64dbg-plugin-sdk.html
// Command use the same signature as main in C
// argv[0] contains the full command, after that are the arguments
// NOTE: arguments are separated by a COMMA (not space like WinDbg)
static bool cbExampleCommand(int argc, char** argv) {
auto parseExpr = [](const char* expression, duint& value){
bool success = false;
value = DbgEval(expression, &success);
if (!success)
dprintf("Invalid expression '%s'\n", expression);
return success;
};
if (strcmp(argv[1], "print") == 0) {
dprintf("Hello,World\n");
dprintf("rax: %llx\n", Script::Register::GetRAX());
dprintf("rbx: %llx\n", Script::Register::GetRBX());
dprintf("rcx: %llx\n", Script::Register::GetRCX());
dprintf("rdx: %llx\n", Script::Register::GetRDX());
dprintf("rsi: %llx\n", Script::Register::GetRSI());
dprintf("rdi: %llx\n", Script::Register::GetRDI());
dprintf("rsp: %llx\n", Script::Register::GetRSP());
dprintf("rbp: %llx\n", Script::Register::GetRBP());
dprintf("rip: %llx\n", Script::Register::GetRIP());
dprintf("r8: %llx\n", Script::Register::GetR8());
dprintf("r8: %llx\n", Script::Register::GetR9());
dprintf("r10: %llx\n", Script::Register::GetR10());
dprintf("r11: %llx\n", Script::Register::GetR11());
dprintf("r12: %llx\n", Script::Register::GetR12());
dprintf("r13: %llx\n", Script::Register::GetR13());
dprintf("r14: %llx\n", Script::Register::GetR14());
dprintf("r15: %llx\n", Script::Register::GetR15());
dprintf("zf: %d\n", Script::Flag::Get(Script::Flag::ZF));
dprintf("of: %d\n", Script::Flag::Get(Script::Flag::OF));
dprintf("cf: %d\n", Script::Flag::Get(Script::Flag::CF));
dprintf("pf: %d\n", Script::Flag::Get(Script::Flag::PF));
dprintf("sf: %d\n", Script::Flag::Get(Script::Flag::SF));
dprintf("tf: %d\n", Script::Flag::Get(Script::Flag::TF));
dprintf("af: %d\n", Script::Flag::Get(Script::Flag::AF));
dprintf("df: %d\n", Script::Flag::Get(Script::Flag::DF));
dprintf("if: %d\n", Script::Flag::Get(Script::Flag::IF));
}
return true;
}
// Initialize your plugin data here.
bool pluginInit(PLUG_INITSTRUCT* initStruct)
{
dprintf("pluginInit(pluginHandle: %d)\n", pluginHandle);
// Prefix of the functions to call here: _plugin_register
_plugin_registercommand(pluginHandle, PLUGIN_NAME, cbExampleCommand, true);
// Return false to cancel loading the plugin.
return true;
}
// Deinitialize your plugin data here.
// NOTE: you are responsible for gracefully closing your GUI
// This function is not executed on the GUI thread, so you might need
// to use WaitForSingleObject or similar to wait for everything to close.
void pluginStop(){
// Prefix of the functions to call here: _plugin_unregister
dprintf("pluginStop(pluginHandle: %d)\n", pluginHandle);
}
// Do GUI/Menu related things here.
// This code runs on the GUI thread: GetCurrentThreadId() == GuiGetMainThreadId()
// You can get the HWND using GuiGetWindowHandle()
void pluginSetup(){
// Prefix of the functions to call here: _plugin_menu
dprintf("pluginSetup(pluginHandle: %d)\n", pluginHandle);
}
我们直接调试运行
这时候,我们的x64dbg就运行出来了,随便挂载一个程序调试下
然后我们在最下边的命令
中输入 PluginTemplate print
,并来到x64dbg的日志窗口:
可以看到,它输出了寄存器和标志位的信息。
而且此时,我们也可以在vs中随便设置断点进行调试
4. 其他功能示例代码(供参考)
#include "plugin.h"
enum {
MENU_HELLO,
};
// Command use the same signature as main in C
// argv[0] contains the full command, after that are the arguments
// NOTE: arguments are separated by a COMMA (not space like WinDbg)
static bool cbExampleCommand(int argc, char** argv) {
auto parseExpr = [](const char* expression, duint& value){
bool success = false;
value = DbgEval(expression, &success);
if (!success) {
dprintf("Invalid expression '%s'\n", expression);
}
return success;
};
if (strcmp(argv[1], "print") == 0) {
dprintf("ProcessID: 0x%x\n", DbgValFromString("$pid")); // 被调试的可执行文件的进程ID
dprintf("hProcess: 0x%x\n", DbgValFromString("$hp")); // 调试的可执行文件句柄
dprintf("\n");
char modname[MAX_PATH] = { 0 };
char path[MAX_PATH] = { 0 };
Script::Module::GetMainModuleName(modname);
Script::Module::GetMainModulePath(path);
dprintf("name: %s\n", modname);
dprintf("base: %llx\n", Script::Module::GetMainModuleBase());
dprintf("entry: %llx\n", Script::Module::GetMainModuleEntry());
dprintf("path: %s\n", path);
dprintf("size: %llx\n", Script::Module::GetMainModuleSize());
ListInfo modSecs;
Script::Module::GetMainModuleSectionList(&modSecs);
Script::Module::ModuleSectionInfo* sectionInfo(static_cast<Script::Module::ModuleSectionInfo*>(modSecs.data));
for (int i = 0; i < modSecs.count; i++){
dprintf("section name [%d]: %s\n", i, sectionInfo[i].name);
dprintf("section addr [%d]: %llx\n", i, sectionInfo[i].addr);
dprintf("section size [%d]: %llx\n", i, sectionInfo[i].size);
dprintf("\n");
}
BridgeFree(sectionInfo);
Script::Module::ModuleInfo mainMod;
Script::Module::GetMainModuleInfo(&mainMod);
ListInfo modExports;
ListInfo modImports;
Script::Module::GetExports(&mainMod, &modExports);
Script::Module::GetImports(&mainMod, &modImports);
Script::Module::ModuleExport* ExportInfo(static_cast<Script::Module::ModuleExport*>(modExports.data));
Script::Module::ModuleImport* ImportInfo(static_cast<Script::Module::ModuleImport*>(modImports.data));
for (int i = 0; i < modExports.count; i++) {
dprintf("export name [%d]: %s\n", i, ExportInfo[i].name);
dprintf("export rva [%d]: %llx\n", i, ExportInfo[i].rva);
dprintf("export va [%d]: %llx\n", i, ExportInfo[i].va);
dprintf("export forwarded [%d]: %d\n", i, ExportInfo[i].forwarded);
dprintf("export forwardName [%d]: %s\n", i, ExportInfo[i].forwardName);
dprintf("export ordinal [%d]: %llx\n", i, ExportInfo[i].ordinal);
dprintf("\n");
}
for (int i = 0; i < modImports.count; i++) {
dprintf("import name [%d]: %s\n", i, ImportInfo[i].name);
dprintf("import ordinal [%d]: %llx\n", i, ImportInfo[i].ordinal);
dprintf("import rva [%d]: %llx\n", i, ImportInfo[i].iatRva);
dprintf("import va [%d]: %llx\n", i, ImportInfo[i].iatVa);
dprintf("\n");
}
BridgeFree(ExportInfo);
BridgeFree(ImportInfo);
ListInfo mods;
Script::Module::GetList(&mods);
Script::Module::ModuleInfo* modInfo(static_cast<Script::Module::ModuleInfo*>(mods.data));
for (int i = 0; i < mods.count; i++) {
dprintf("module name [%d]: %s\n", i, modInfo[i].name);
dprintf("module base [%d]: %llx\n", i, modInfo[i].base);
dprintf("module entry [%d]: %llx\n", i, modInfo[i].entry);
dprintf("module path [%d]: %s\n", i, modInfo[i].path);
dprintf("module sectionCount [%d]: %d\n", i, modInfo[i].sectionCount);
dprintf("module size [%d]: %llx\n", i, modInfo[i].size);
dprintf("\n");
}
BridgeFree(modInfo);
}
if (strcmp(argv[1], "print2") == 0) {
dprintf("rax: %llx\n", Script::Register::Get(Script::Register::RAX));
dprintf("rbx: %llx\n", Script::Register::Get(Script::Register::RBX));
dprintf("rcx: %llx\n", Script::Register::Get(Script::Register::RCX));
dprintf("rdx: %llx\n", Script::Register::Get(Script::Register::RDX));
dprintf("rsi: %llx\n", Script::Register::Get(Script::Register::RSI));
dprintf("rdi: %llx\n", Script::Register::Get(Script::Register::RDI));
dprintf("rsp: %llx\n", Script::Register::Get(Script::Register::RSP));
dprintf("rbp: %llx\n", Script::Register::Get(Script::Register::RBP));
dprintf("rip: %llx\n", Script::Register::Get(Script::Register::RIP));
dprintf("r8: %llx\n", Script::Register::Get(Script::Register::R8));
dprintf("r9: %llx\n", Script::Register::Get(Script::Register::R9));
dprintf("r10: %llx\n", Script::Register::Get(Script::Register::R10));
dprintf("r11: %llx\n", Script::Register::Get(Script::Register::R11));
dprintf("r12: %llx\n", Script::Register::Get(Script::Register::R12));
dprintf("r13: %llx\n", Script::Register::Get(Script::Register::R13));
dprintf("r14: %llx\n", Script::Register::Get(Script::Register::R14));
dprintf("r15: %llx\n", Script::Register::Get(Script::Register::R15));
dprintf("dr0: %llx\n", Script::Register::Get(Script::Register::DR0));
dprintf("dr1: %llx\n", Script::Register::Get(Script::Register::DR1));
dprintf("dr2: %llx\n", Script::Register::Get(Script::Register::DR2));
dprintf("dr3: %llx\n", Script::Register::Get(Script::Register::DR3));
dprintf("dr6: %llx\n", Script::Register::Get(Script::Register::DR6));
dprintf("dr7: %llx\n", Script::Register::Get(Script::Register::DR7));
dprintf("zf: %d\n", Script::Flag::Get(Script::Flag::ZF));
dprintf("of: %d\n", Script::Flag::Get(Script::Flag::OF));
dprintf("cf: %d\n", Script::Flag::Get(Script::Flag::CF));
dprintf("pf: %d\n", Script::Flag::Get(Script::Flag::PF));
dprintf("sf: %d\n", Script::Flag::Get(Script::Flag::SF));
dprintf("tf: %d\n", Script::Flag::Get(Script::Flag::TF));
dprintf("af: %d\n", Script::Flag::Get(Script::Flag::AF));
dprintf("df: %d\n", Script::Flag::Get(Script::Flag::DF));
dprintf("if: %d\n", Script::Flag::Get(Script::Flag::IF));
}
if (strcmp(argv[1], "debug") == 0) {
if (strcmp(argv[2], "run") == 0) {
Script::Debug::Run();
}
if (strcmp(argv[2], "pause") == 0) {
Script::Debug::Pause();
}
if (strcmp(argv[2], "stop") == 0) {
Script::Debug::Stop();
}
if (strcmp(argv[2], "wait") == 0) {
Script::Debug::Wait();
}
if (strcmp(argv[2], "in") == 0) {
Script::Debug::StepIn();
}
if (strcmp(argv[2], "over") == 0) {
Script::Debug::StepOver();
}
if (strcmp(argv[2], "out") == 0) {
Script::Debug::StepOut();
}
if (strcmp(argv[2], "setbp") == 0) {
duint addr = 0;
if (!parseExpr(argv[3], addr)) {
return false;
}
Script::Debug::SetBreakpoint(addr);
}
if (strcmp(argv[2], "delbp") == 0) {
duint addr = 0;
if (!parseExpr(argv[3], addr)) {
return false;
}
Script::Debug::DeleteBreakpoint(addr);
}
if (strcmp(argv[2], "disbp") == 0) {
duint addr = 0;
if (!parseExpr(argv[3], addr)) {
return false;
}
Script::Debug::DisableBreakpoint(addr);
}
if (strcmp(argv[2], "sethbp") == 0) {
duint addr = 0;
if (!parseExpr(argv[3], addr)) {
return false;
}
Script::Debug::SetHardwareBreakpoint(addr, Script::Debug::HardwareType::HardwareExecute);
}
if (strcmp(argv[2], "delhbp") == 0) {
duint addr = 0;
if (!parseExpr(argv[3], addr)) {
return false;
}
Script::Debug::DeleteHardwareBreakpoint(addr);
}
}
if (strcmp(argv[1], "unicorn") == 0) {
uc_engine* uc;
uc_err err;
err = uc_open(UC_ARCH_X86, UC_MODE_64, &uc);
if (err != UC_ERR_OK) {
return false;
}
//uc_mem_map
//uc_mem_map(uc, , 2 * 1024 * 1024, UC_PROT_ALL);
}
return true;
}
// Initialize your plugin data here.
bool pluginInit(PLUG_INITSTRUCT* initStruct){
dprintf("pluginInit(pluginHandle: %d)\n", pluginHandle);
// Prefix of the functions to call here: _plugin_register
_plugin_registercommand(pluginHandle, "zxc", cbExampleCommand, true);
// Return false to cancel loading the plugin.
return true;
}
// Deinitialize your plugin data here.
// NOTE: you are responsible for gracefully closing your GUI
// This function is not executed on the GUI thread, so you might need
// to use WaitForSingleObject or similar to wait for everything to close.
void pluginStop(){
// Prefix of the functions to call here: _plugin_unregister
}
PLUG_EXPORT void CBMENUENTRY(CBTYPE cbType, PLUG_CB_MENUENTRY* info){
switch (info->hEntry){
case MENU_HELLO:
MessageBoxW(NULL, L"Hello World", L"Hello", MB_OK);
break;
default:
break;
}
}
// Do GUI/Menu related things here.
// This code runs on the GUI thread: GetCurrentThreadId() == GuiGetMainThreadId()
// You can get the HWND using GuiGetWindowHandle()
void pluginSetup(){
// Prefix of the functions to call here: _plugin_menu
_plugin_menuaddentry(hMenu, MENU_HELLO, "Hello MessageBox");
}