x64dbg 手册:https://help.x64dbg.com/en/latest/developers/index.html
1. x64dbg扩展功能的三种方式
x64dbg扩展功能的方式有三种:
- 写脚本(python或者idc)
- 脚本DLL,就是编写一个DLL导出 AsyncStart() or Start(),然后通过命令scriptdll/dllscript来加载DLL执行代码
- 编写插件
在x64dbg的文档中并没有说明 "插件" 到底是一个什么东西,只是说插件的后缀名叫dp32或者dp64,通过观察其他的插件发现其实就是DLL,只不过是导出了一些指定函数的DLL
本文的目的就是说明编写x64dbg插件的步骤,然后编写一个简单的x64dbg插件
2. 环境配置
本机环境:win11 21h2、vs2022、x64dbg(snapshot_2023-01-25_11-53)
2.1 x64的SDK
要给x64dbg编写插件肯定需要x64dbg提供的sdk包,sdk包就在x64dbg的根目录之下的pluginsdk文件夹中:
1
2
3
4
5
6
7
8
9
|
├───pluginsdk
│ ├───dbghelp
│ ├───DeviceNameResolver
│ ├───jansson
│ ├───lz4
│ ├───TitanEngine
│ └───XEDParse
└───release
├───...
|
在pluginsdk中有x64dbg自己提供的头文件和lib文件,以及它使用的其他第三方库的头文件和lib文件,比如XEDParse、TitanEngine等
2.2 配置vs工程
先创建一个VS的DLL工程,这一步不必多说
将整个pluginsdk文件夹拷贝到工程目录之下,虽然多了很多东西,但是比少拷贝了文件去找要好
然后配置头文件目录和lib文件的目录以及引入lib文件:
- 在工程属性->vc++目录->外部包含目录加入pluginsdk路径
- 在工程属性->vc++目录->库目录加入pluginsdk路径
- 在工程属性->链接器->输入->附加依赖项中添加 x32bridge.lib和x32dbg.lib,这也是pluginsdk下唯二的两个lib
3. 基本导出函数
3.1 pluginit
函数声明:
1
|
extern
"C"
__declspec(dllexport)
bool
pluginit(PLUG_INITSTRUCT
*
initStruct);
|
pluginit函数是x64dbg插件必须导出的一个函数
在整个函数中,要做的事件就是填充参数initStruct:
1
2
3
4
5
6
7
|
struct PLUG_INITSTRUCT
{
[IN]
int
pluginHandle;
/
/
插件的句柄
[OUT]
int
sdkVersion;
/
/
填 PLUG_SDKVERSION 即可
[OUT]
int
pluginVersion;
/
/
填插件的版本
[OUT] char pluginName[
256
];
/
/
填插件指针
};
|
所以在pluginit中要做的事情就是填写插件的基本信息:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
/
/
必需的,插件初始化函数
extern
"C"
__declspec(dllexport)
bool
pluginit(PLUG_INITSTRUCT
*
initStruct)
{
_plugin_logprintf(
"[%s] pluginit\r\n"
, TAG);
/
/
打印日志
memcpy_s(
initStruct
-
>pluginName,
sizeof(initStruct
-
>pluginName),
TAG,
strlen(TAG));
initStruct
-
>sdkVersion
=
PLUG_SDKVERSION;
initStruct
-
>pluginVersion
=
1
;
if
(!InitPlugin())
/
/
做一些初始化动作
{
_plugin_logprintf(
"[%s] pluginit Failed\r\n"
, TAG);
/
/
打印日志
return
false;
}
return
true;
}
|
3.2 plugstop和plugsetup
这两个导出函数不是必须的,但是可以在里面做一些事情:
- plugstop:插件被移除的时候被调用,可以用来清除注册的回调和命令,清理资源
- plugsetup:当插件初始化成功的时候调用,可以在这里添加菜单、做其他的界面相关的事情
本例在plugsetup中添加了两个子菜单,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
/
/
非必需,插件被移除时调用
extern
"C"
__declspec(dllexport)
bool
plugstop()
{
_plugin_logprintf(
"[%s] plugstop\r\n"
, TAG);
return
true;
}
/
/
非必需,启动插件时调用
/
/
在这里执行UI操作,比如增加菜单
extern
"C"
__declspec(dllexport) void plugsetup(PLUG_SETUPSTRUCT
*
setupStruct)
{
_plugin_logprintf(
"[%s] plugsetup\r\n"
, TAG);
/
/
添加子菜单
_plugin_menuaddentry(setupStruct
-
>hMenu,
0
,
"enable UEH"
);
_plugin_menuaddentry(setupStruct
-
>hMenu,
1
,
"disable UEH"
);
}
|
4. 事件回调函数
当我们添加了插件的子菜单之后,要如何响应菜单的点击呢?
导出以CB开头的函数就可以去接收到对应的事件,比如
1
2
3
4
5
|
extern
"C"
__declspec(dllexport) void CBINITDEBUG(CBTYPE cbType, PLUG_CB_INITDEBUG
*
info);
/
/
初始化调试
extern
"C"
__declspec(dllexport) void CBSTOPDEBUG(CBTYPE cbType, PLUG_CB_STOPDEBUG
*
info);
/
/
停止调试
extern
"C"
__declspec(dllexport) void CBEXCEPTION(CBTYPE cbType, PLUG_CB_EXCEPTION
*
info);
/
/
异常
extern
"C"
__declspec(dllexport) void CBDEBUGEVENT(CBTYPE cbType, PLUG_CB_DEBUGEVENT
*
info);
/
/
调试事件
extern
"C"
__declspec(dllexport) void CBMENUENTRY(CBTYPE cbType, PLUG_CB_MENUENTRY
*
info);
/
/
点击子菜单
|
参数中所用到的结构体可以在这里找到:https://help.x64dbg.com/en/latest/developers/plugins/Callbacks/index.html,只要是满足CB*的导出函数,且属于这里面的类型https://help.x64dbg.com/en/latest/developers/plugins/API/registercallback.html,应该都可以注册成功,注意函数名中不要有下划线
本例中,我们只需要简单的响应一下子菜单的点击:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
/
/
菜单响应回调
extern
"C"
__declspec(dllexport) void CBMENUENTRY(
CBTYPE bType,
PLUG_CB_MENUENTRY
*
pEntry
)
{
switch (pEntry
-
>hEntry)
{
case
0
:
/
/
注册时填的菜单
ID
EnableUeh();
break
;
case
1
:
DisableUeh();
break
;
default:
break
;
}
}
|
5. 库函数
在插件中,我们可以调用Dbg开头的函数来辅助功能,函数列表:https://help.x64dbg.com/en/latest/developers/functions/debug/index.html
比如读写被调试进程可以使用
1
2
3
4
5
6
7
8
9
10
11
|
bool
DbgMemRead(
duint va,
void
*
dest,
duint size
);
bool
DbgMemWrite(
duint va,
void
*
dest,
duint size
);
|
其他功能就暂时还没探索,本例中用这两个就够啦
6. 对UnhandledExceptionFilter打补丁
本例中的插件需要实现对UnhandledExceptionFilter打补丁的功能,实现调试器可以调试UEH回调
6.1 为什么要打补丁
下面是小弟的通过测试的一点浅薄理解,没有跟踪系统的代码,如有不对的地方还请大佬们指出
应用层派发异常的流程大致如下:
根据上面的流程图,在异常从SEH中出来的时候,根据是否有调试器,要么派发给调试器,要么派发给UEH回调,二选一,所以在调试的过程中不会之下UEH回调的代码
为了能在调试器中调试UEH回调,我们需要改变一下系统异常分发的流程,使其走到另一个分支去
这个分支出现在kernelbase/kernel32!UnhandledExceptionFilter 中,所以需要对其打补丁
6.2 实现功能
剩下的步骤就很简单了,首先在插件初始化时,判断系统版本,获取UnhandledExceptionFilter 的地址,通过特征码找到需要打补丁的地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
bool
InitPlugin()
{
/
/
获取版本号
DWORD dwBuildVer
=
GetVerSion();
PUCHAR pFuncBegin
=
(PUCHAR)GetProcAddress(
GetModuleHandleA(
"kernelbase"
),
"UnhandledExceptionFilter"
);
if
(pFuncBegin
=
=
NULL)
{
pFuncBegin
=
(PUCHAR)GetProcAddress(
GetModuleHandleA(
"kernel32"
),
"UnhandledExceptionFilter"
);
if
(pFuncBegin
=
=
NULL)
{
_plugin_logprintf(
"[%s] Get kernelbase!UnhandledExceptionFilter Addr Failed\r\n"
, TAG);
bIsEnabledUeh
=
false;
return
false;
}
}
/
/
获取特征码
if
(dwBuildVer >
=
22000
)
{
/
/
win11
g_pUehSig
=
UehSigWin11;
_plugin_logprintf(
"[%s] OS Build Number: %d\r\n"
, TAG, dwBuildVer);
}
else
if
(dwBuildVer
=
=
7600
|| dwBuildVer
=
=
7601
)
{
/
/
win7
g_pUehSig
=
UehSigWin7;
_plugin_logprintf(
"[%s] OS Build Number: %d\r\n"
, TAG, dwBuildVer);
}
g_pUehPatchPoint
=
FindSignatureCode(pFuncBegin,
0x100
, g_pUehSig);
if
(g_pUehPatchPoint
=
=
NULL)
{
_plugin_logprintf(
"[%s] Signatrue Not Found!\r\n"
, TAG);
bIsEnabledUeh
=
false;
return
false;
}
return
true;
}
|
然后响应菜单事件,当点击EnableUeh时,打补丁,点击DisableUeh时,恢复补丁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
void EnableUeh()
{
/
/
保存原来的数据
DbgMemRead((duint)g_pUehPatchPoint, UehPatchRawData, strlen((const char
*
)g_pUehSig)
/
2
);
/
/
打补丁
if
(DbgMemWrite((duint)g_pUehPatchPoint, UehPathCode, sizeof(UehPathCode)))
{
_plugin_logprintf(
"[%s] Enable UEH Success!\r\n"
, TAG);
bIsEnabledUeh
=
true;
}
else
{
_plugin_logprintf(
"[%s] Enable UEH Failed!\r\n"
, TAG);
bIsEnabledUeh
=
false;
}
}
void DisableUeh()
{
if
(bIsEnabledUeh
=
=
true)
{
/
/
Patch
if
(DbgMemWrite((duint)g_pUehPatchPoint, UehPatchRawData, strlen((const char
*
)g_pUehSig)
/
2
))
{
_plugin_logprintf(
"[%s] Enable UEH Success!\r\n"
, TAG);
bIsEnabledUeh
=
false;
}
else
{
_plugin_logprintf(
"[%s] Enable UEH Failed!\r\n"
, TAG);
}
}
}
|
一个简单的插件框架就这样完成了