教程:修改x64dbg插件 Multiline Ultimate Assembler 完美支持中文
本帖最后由 lies2014 于 2024-1-18 23:21 编辑修改x64dbg插件 Multiline Ultimate Assembler 完美支持中文
一、分析篇
Multiline Ultimate Assembler 是一个调试器多行汇编插件,但它对中文的支持不好,体现在:
(1)不能显示中文注释,键盘输入中文会显示乱码;
(2)从调试器向插件同步中文注释会显示乱码;
(3)从插件向调试器同步中文注释会显示乱码。
近期对通过 x64dbg 插件存在的以上问题进行分析,最终实现了中文的支持。
通过网络搜索类似问题,发现这个插件的 OD 版本有人实现了中文显示,是通过修改插件的二进制文件,默认使用中文字体就能正常显示中文,但也只能显示,未能解决输入和同步中文的问题。既然有了前人的经验就试着按 OD 版本的修改方法修改二进制文件,修改后发现完全没有作用,不管改成什么字体界面显示字体都没有变化,就像没有改过一样,怀疑二进制里设置的字体并不是关键。最后把目光定位到调试器的配置文件,打开 x64dbg.ini,果然发现有小节,其中就有“font_name=Lucida Console”的项目,改成中文字体“font_name=Microsoft YaHei UI”,再打开插件,果然可以显示中文了,从外部粘贴中文过去也能正常显示。猜想二进制里的字体只是初始化设置,ini 里的才是实际使用的字体(后来看到源码也印证了猜想)。
接着是输入的问题,因为系统输入是正常的,显然插件自己处理了字符信息,只要不让插件处理 WM_CHAR 消息,交给系统去处理就好了(感谢网友 csjwaman 提供的思路,还有不明白插件为什么要自己处理输入字符)。
插件同步乱码的问题,(2)(3)其实是类似的问题,解决了一个另一个就迎刃而解。
查询 x64dbg 用户手册,在开发接口函数里发现有 DbgGetCommentAt 这个函数,虽然没有更进一步的介绍,但从字面上可以猜得出这是从调试器获取注释信息的。既然要从调试器同步注释过来第一步肯定是先获取注释,我们先看看取过来的注释是否正常,就用 x64dbg 自己来分析一下吧。
先打开一个 x64dbg,随便载入一个程序,在某条反汇编注释处输入中文字符串,再打开第二个 x64dbg,附加到第一个 x64dbg 上,找到 multiasm_x64dbg.dp64 里的跨模块调用,定位到 DbgGetCommentAt 函数并下断点。
在第一个 x64dbg 里中文注释的反汇编指令右键选择“Multiline Ultimate Assembler”,第二个 x64dbg 成功断下,F8 单步执行 DbgGetCommentAt 函数,看寄存器已经出现了我们输入的中文字符串,看起来一切正常。
一般乱码我们第一时间想到的就是编码问题,这时内存中的中文编码是 UTF-8,这和插件界面的编码是否一致呢,我们设定字体后显示的中文是正常的,我们把它们复制出来粘贴到 UltraEdit 去看,编码是 GBK,答案已经呼之欲出了。
我们手工将内存里取到的注释改成 GBK 编码,继续执行,发现同步到插件里的中文显示正常了。
同样,插件同步到调试器,我们定位到 DbgSetCommentAt 函数,在函数执行前将参数的 GBK 转换为 UTF-8 就可以正常显示中文。
这样,几个问题如何修改,我们就有了一个大致的思路:
(1)修改默认字体,使支持中文显示;
(2)拦截插件 WM_CHAR 消息,交给系统处理;
(3)DbgGetCommentAt 函数执行后将注释信息转码(UTF-8 -> GBK);
(4)DbgSetCommentAt 函数执行前将注释信息转码(GBK -> UTF-8)。
二、修改篇
(一)环境准备
1、源码下载
https://github.com/m417z/Multiline-Ultimate-Assembler
2、搭建环境
源码原来是 VS2015 的工程,搭配 Win SDK 7,支持 WinXP。
搭建环境这个环节踩了不少坑,原先图省事先后安装了 VS2015 便携版和 VS2017 便携版,结果编译一堆问题,还把注册表搞得乱七八槽,影响到后面的 VS2019 安装,最终清了半天注册表才搞定。想编译的朋友直接用 VS2019 在线安装就好,模块化的安装方式,增加删除模块都很方便。
VS2019 安装界面勾选“使用C++的桌面开发“,然后在“单个组件”下勾选下图的项目:
安装完成后将:
C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.16.27023\atlmfc\include\afxres.h
复制一份到:
C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.29.30133\atlmfc\include
否则编译可能会出现“error RC1015: cannot open include file 'afxres.h'“错误。
以上完成后打开项目解决方案,会出现以下提示,确定即可。
如果没出现上面提示,可以手动在工程属性中设置 SDK 版本和平台工具集设置如下:
这时可以编译一下,编译成功就可以着手进行代码修改了。
安装选项参考:
https://learn.microsoft.coAm/en-us/cpp/build/configuring-programs-for-windows-xp?view=msvc-170&viewFallbackFrom=vs-2019
(二)源码修改
1、修改默认字体(微软雅黑)
文件:assembler_dlg.c
找到 SetRAEditDesign 函数,修改如下:
if(!MyGetstringfromini(hInstance, _T("font_name"), lfLogFont.lfFaceName, LF_FACESIZE))
{
lstrcpy(lfLogFont.lfFaceName, _T("Microsoft YaHei UI")); // Default font
MyWritestringtoini(hInstance, _T("font_name"), lfLogFont.lfFaceName);
}
2、插件输入中文乱码
文件:assembler_dlg.c
找到 AssemblerPreTranslateMessage 函数,修改如下:
BOOL AssemblerPreTranslateMessage(LPMSG lpMsg)
{
if(hAsmDlg)
{
HWND hWnd = hAsmDlg;
HWND hFindReplaceWnd = AsmDlgParam.hFindReplaceWnd;
if(hFindReplaceWnd && IsDialogMessage(hFindReplaceWnd, lpMsg))
return TRUE;
if(GetActiveWindow() == hWnd)
{
if (lpMsg->message == WM_CHAR)
return FALSE;
if(hAccelerators && TranslateAccelerator(hWnd, hAccelerators, lpMsg))
return TRUE;
}
if(IsDialogMessage(hWnd, lpMsg))
return TRUE;
}
return FALSE;
}
3、从x64dbg同步中文注释
文件:plugin_x64dbg.c
找到 GetComment 函数,修改如下:
void Utf8ToGbk(char* utf8String, char* gbkString)
{
gbkString = utf8String;
wchar_t* unicodeStr = NULL;
int Len = 0;
Len = MultiByteToWideChar(CP_UTF8, 0, utf8String, -1, NULL, 0);
unicodeStr = (wchar_t*)malloc(Len * sizeof(wchar_t));
MultiByteToWideChar(CP_UTF8, 0, utf8String, -1, unicodeStr, Len);
Len = WideCharToMultiByte(CP_ACP, 0, unicodeStr, -1, NULL, 0, NULL, 0);
WideCharToMultiByte(CP_ACP, 0, unicodeStr, -1, gbkString, Len, NULL, 0);
free(unicodeStr);
}
int GetComment(DWORD_PTR addr, TCHAR *name)
{
if(!DbgGetCommentAt(addr, name))
return 0;
if(name == '\1') // Automatic comment
return 0;
char* gbkString;
Utf8ToGbk(name, gbkString);
return lstrlen(name);
}
4、向x64dbg同步中文注释
文件:plugin_x64dbg.c
找到 QuickInsertComment 函数,修改如下:
void GbkToUtf8(char* gbkString, char* utf8String)
{
utf8String = gbkString;
wchar_t* unicodeStr = NULL;
int Len = 0;
Len = MultiByteToWideChar(CP_ACP, 0, gbkString, -1, NULL, 0);
unicodeStr = (wchar_t*)malloc(Len * sizeof(wchar_t));
MultiByteToWideChar(CP_ACP, 0, gbkString, -1, unicodeStr, Len);
Len = WideCharToMultiByte(CP_UTF8, 0, unicodeStr, -1, NULL, 0, NULL, 0);
WideCharToMultiByte(CP_UTF8, 0, unicodeStr, -1, utf8String, Len, NULL, 0);
free(unicodeStr);
}
BOOL QuickInsertComment(DWORD_PTR addr, TCHAR *s)
{
char* utf8String;
GbkToUtf8(s, utf8String);
return DbgSetCommentAt(addr, s);
}
如果中文显示乱码请将x32dbg.ini和x64dbg.ini文件中
小节下“font_name”一项删除
操作成功了,感谢分享。 公布一下我修改的结果:
注释掉源代码
```
//if(IsDialogMessage(hWnd, lpMsg))
//return TRUE;
```
IsDialogMessage文档说明:
Determines whether a message is intended for the specified dialog box and, if it is, processes the message.
翻译:确定消息是否用于指定的对话框,如果是,则处理该消息。
原理:
通过逆向此函数发现IsDialogMessage发现其内部有自己的消息循环实现,只要调用了IsDialogMessage这个函数,则对应的窗口消息过程就会被接管。因为MUA作者多此一举,导致raedit一些中间过程被IsDialogMessage接管,raedit的WM_CHAR接受到的字符就会是unicode。如图所示:
结论:IsDialogMessage是一个很危险的函数,MUA的作者调用这函数的原因未知。我猜测可能是为了处理快捷键从别处借鉴来的代码。 本帖最后由 冥界3大法王 于 2024-1-19 00:04 编辑
@lies2014 最头大的就是这个参数1 (duint=》转过去 就是 UInt64)
我试着 搞了Delphi的插件去注释
就放一个按钮
procedure TDLLForm1.commentClick(Sender: TObject);
var
a1: UInt64;
begin
a1 := $4198420; <<=================不报错,但也没效果,是何故?
//a1 := StrToUInt64(Edit1.Text);
DbgGetCommentAt(a1, 'LaLaLa');
end;
https://gitee.com/suxuss/DELPHI-x96dbg-Plugins-SDK/blob/master/bridgemain.pas
他这里的注释是这样说的
{BRIDGE_IMPEXP bool} function DbgIsValidExpression(const expression: PAChar): Boolean; cdecl; external x32_BRIDGE; //检测CTRL+G里面将输入的内容是不是有效
我怀疑他给的注释可能也不太对。
感觉字面意思该是测试表达式,是否有效啊。
procedure TDLLForm1.Button1Click(Sender: TObject); //还有这个不报错,但与测试结果不相符。
begin
if DbgIsValidExpression(('{x:rbx} != 0')) = True then
begin
DbgCmdExec('log True');
DbgCmdExec('StepInto');
end
else
begin
DbgCmdExec('log False');
DbgCmdExec('StepOver');
end;
end; 难道这个 addr不代表一个地址?
DWORD_PTR addr
必须使用 DWORD型的?
是这么解释吧? 感谢楼主的分享 楼主楼主,我举报你了,优秀帖子必须全力加分!{:301_1006:} 楼主上个成品。 冥界3大法王 发表于 2024-1-18 23:58
@lies2014 最头大的就是这个参数1 (duint=》转过去 就是 UInt64)
我试着 搞了Delphi的插件去注释
duint 不一定是 UInt64,在32位系统下是 ULong,在 bridgemain 里有定义的,你直接用 duint 就成了
不报错也没效果有时很难直观从代码看到问题,这时用调试器跟进去,从汇编层面可能可以看出端倪,先看看函数执行前参数是否都正确,然后函数入口是不是正确,这两样都没问题,不可能执行没有结果
比如我上面修改的编码转换,在32位下没有问题,但是64位就会失去响应,对C又不熟,看了半天看不出代码问题,最后还是汇编进去发现最后一个 WideCharToMultiByte 第5个参数没有分配地址,所以第一句多加了一句赋值才解决问题 冥界3大法王 发表于 2024-1-19 00:08
https://gitee.com/suxuss/DELPHI-x96dbg-Plugins-SDK/blob/master/bridgemain.pas
他这里的注释是这样 ...
x64dbg 这个函数的定义:
BRIDGE_IMPEXP bool DbgIsValidExpression(const char* expression)
{
duint value = 0;
return _dbg_valfromstring(expression, &value);
}
应该就是像你理解的一样判断一个字符串表达式是否有效