OD调试器断点——消息断点

Windows消息被广泛用于各种事件的通知上面,如果你想操作窗口或者控件(UI元素其实也是一种窗口,如:按钮,编辑框,工具栏,树形控件等)的话,给它发送消息即可。消息也可以来至于其他应用程序。你也可以通过消息来实现系统通知,移动鼠标,按下键盘上的某键等操作。

正如我们前面所讨论的,OD中大部分的API函数我们可以使用普通CC断点来下断,但是少数检测CC断点的情况,使用消息断点会更加有效。消息断点在内核调试器SoftICE中也称为BMSG。

Windows窗口程序至少有一个消息循环,消息循环有特定的API函数构成,最常见的是GetMessage和DispatchMessage函数,有的消息循环也会用到其他的API函数。想要深入了解Windows的消息的话,可以参考下面的链接中的教程”理解消息循环”(该教程的中文版见附件):

http://winprog.org/tutorial/message_loop.html

让我们来看一个简单的例子:用OD加载CrueHead`s的CrackMe。
在这里插入图片描述

首先我们尝试第一种提取序列号的方法,然后再来尝试消息断点提取序列号的方法。我们来看看导入到程序的API函数,看看有没有获取输入文本的函数。

在反汇编窗口中单击鼠标右键选择-Search-Name(label) in current module。
在这里插入图片描述

获取编辑框中文本我们通常使用的API是GetDlgItemTextA或者GetWindowTextA。当然,也可以使用Unicode版的API函数GetDlgItemTextW或者GetWindowTextW,再者,也可以发送消息直接获取编辑框中文本。但是,不要指望从GetDlgItemTextA或者GetWindowTextA下手获取一些保护强度比较高的编辑框控件中的文本。但是我们还是先来看看这种方法吧。
在这里插入图片描述

尽管该列表中有GetDlgItemTextA,但是并不意味着这个函数就是用来读取用户输入的用户名和序列号的(可能仅仅是获取用户输入的其他字段的)。有可能作者是故意添加该函数来误导我们的。还是就是,该API函数可以通过各种不同的方式来动态加载,不一定要通过导入表。因此,可能CrackMe真的使用的是GetDlgItemTextA,但是我们在导入表中找不到这个函数。为了不把问题复杂化,我们假设CrackMe就是使用GetDlgItemTextA来获取编辑框中文本的。

我们在命令栏中使用BP GetDlgItemTextA设置断点:
在这里插入图片描述

或者
在这里插入图片描述

设置了断点以后,运行程序,输入用户名和序列号:
在这里插入图片描述

单击OK,程序断在我们设置的断点处。
在这里插入图片描述

注意堆栈。
在这里插入图片描述

可以看到该函数有一个参数是缓冲区,编辑框中的内容会被拷贝至该缓冲区。

所以,我们在数据窗口中定位到该缓冲区,堆栈窗口中选中该缓冲区参数,单击鼠标右键选择-Follow in Dump。或者在数据窗口中单击鼠标右键选择-Goto-Expression输入40218E。
在这里插入图片描述

现在缓冲区还是空的,因为该函数还没有被执行。
在这里插入图片描述

我们通过选择主菜单项Debug-Execute till return来执行该函数。
在这里插入图片描述

现在缓冲区中保存了我们在注册窗口中输入的用户名。
在这里插入图片描述

接着,按F9键运行程序,会再次触发我们的断点。
在这里插入图片描述

现在,缓冲区参数的地址为40217E,我们在数据窗口中转到这个地址:
在这里插入图片描述

我们依然选择主菜单项Debug-Execute till return,执行到返回。
在这里插入图片描述

这是我们输入的序列号。

整个过程想必很清楚了吧,为了找到正确的序列号,在程序获取我们输入数据(这里是用户名和序列号)的时候应该让其中断下来。更进一步的分析我们后面再讨论。我们现在再通过消息断点来提取序列号。很多有经验的程序员不使用API函数来获取编辑框中文本,而是直接通过发送消息来获取编辑框中的文本。

我们单击工具栏中【B】按钮删除所有的普通CC断点。
在这里插入图片描述在这里插入图片描述

F9键将程序运行起来,打开注册窗口输入用户名和序列号,但是不要点确定。
在这里插入图片描述

消息断点与普通CC断点的区别在于,普通CC断点在程序启动之前就可以设置,但是对于消息断点来说,只有在窗口创建之后才能够设置消息断点以及拦截消息。

单击工具栏中的【W】按钮打开Windows窗口(并不会暂停程序,依然显示的是运行)。
在这里插入图片描述
在这里插入图片描述

如果【W】按钮弹出的窗口列表为空,你可以单击鼠标右键选择-Actualize。
在这里插入图片描述

我们找到Class(类名)为Button,Title(标题)为OK的窗口。

我们在找到的窗口这一行单击鼠标右键选择-Message breakpoint on ClassProc。
在这里插入图片描述

在这里插入图片描述

在打开的窗口中,我们展开下拉列表选择我们感兴趣的消息类型:
在这里插入图片描述

下拉列表显示有静态文本控件,按钮控件,鼠标,剪贴板等类型的消息,如果你不知道需要拦截什么消息的的话,选择第一项Any Message即可。这里我们关注消息属于Button(按钮)这一项。当我们单击鼠标左键的时候,系统会发送WM_LBUTTONDOWN消息(L代表左边)。当我们松开鼠标左键的时候,系统会发送WM_LBUTTONUP消息。我们设置了消息断点以后,当我们松开鼠标左键的时候,窗口会收到值为0x202的消息。
在这里插入图片描述

如下:
在这里插入图片描述

我们选择值为0x202的WM_LBUTTONUP消息。并且选择Break on any window(当前程序的任何窗口接收到该消息都中断),以及Pause program(中断程序),还要选中下面的Log WinProc arguments(记录消息过程函数的参数值)。
在这里插入图片描述

我们可以看到我们选择的Any window(任意窗口)包括了OK(确定),Cancel(取消)按钮。我们单击OK。
在这里插入图片描述在这里插入图片描述

到了这里,很多新手可能会犯迷糊,因为我们触发的消息断点断在了一段陌生的代码中(不属于主程序的代码)。实际上,要回到主程序的代码处也很容易。
在这里插入图片描述

我们知道主程序的代码是401000开头的这个区段,我们选中这个区段,单击鼠标右键选择-Set memory breakpoint on access。
在这里插入图片描述

F9键运行起来,不一会儿程序就断下来了。
在这里插入图片描述

不要清除内存访问断点,我们按F9键运行,我们发现单步执行了一行,我们继续F9单步。
在这里插入图片描述

我们一直F9,直到RET返回,然后我们又回到了401253处,我们继续F9,然后就跳转到了我们感兴趣的通过GetDlgItemTextA获取用户名和序列号代码的附近。对不对,我们再一次定位到了我们感兴趣的代码,这一次我们并没有直接给API下断点。

如果应用程序并不是通过API函数来获取用户输入的序列号的话,我们可以通过消息断点来定位,这是消息断点的优点。

为了让我们确定的时候,程序能断下来,我们单击鼠标右键选择-Breakpoint-Remove memory breakpoint来删除内存访问断点。
在这里插入图片描述

我们单击工具栏中【B】按钮打开断点列表窗口,单击鼠标右键选择-Remove删除所有消息断点。
在这里插入图片描述

我们再次运行程序,打开注册窗口,但是这次我们不输入任何东西。
在这里插入图片描述

单击工具栏中的[W]按钮打开窗口列表。
在这里插入图片描述

重复之前的步骤,但是这次我们选择:
在这里插入图片描述

值为0x101的WM_KEYUP消息-当按下键盘上面的某个键的时候产生该消息。
在这里插入图片描述
在这里插入图片描述

当我们输入用户名的第一个字符的时候,消息断点并没有触发,因为当前程序并没有通过这种方式来获取用户输入的用户名和序列号,但是有些程序员喜欢通过这种方式来获取用户输入的信息,我们不妨考虑一下这种可能性。

现在我们有个疑问,我们只想在OD中记录下程序接受到消息,但是不希望程序中断下来。我们该怎么做呢?我可以使用另一种形式的消息断点。

我们选择工具栏中的【B】按钮打开断点窗口,删除所有断点。然后设置一个针对于值为0x202的WM_LBUTTONUP的消息断点。

接着我们注册窗口中的OK按钮。
在这里插入图片描述
在这里插入图片描述

我们设置的内存断点触发了。现在我们来改进一下,让其能捕捉所有的消息并且记录到日志中。

我们单击工具栏中的[B]按钮打开断点列表。
在这里插入图片描述
在这里插入图片描述

在我们设置的消息断点这一行上单击鼠标右键选择-Edit condition。
在这里插入图片描述
在这里插入图片描述

我们可以看到消息断点实际上也是一个条件断点,当前条件为[ESP + 8] == 0x202,即WM_LBUTTONUP。我们看看堆栈的情况:

在这里插入图片描述

ESP+8存放的值为0x202,正好触发消息断点。

如果你不清楚[ESP+8],请双击栈顶地址:

在这里插入图片描述

我们看看栈顶:
在这里插入图片描述

现在栈地址是相对ESP显示的,$+8相当于ESP+8。

[ESP+8]的值对应的就是消息的值,我们现在想在日志中记录当前消息,所以改变条件断点参数如下:
在这里插入图片描述

Expression编辑框我们填上[ESP + 8],然后Pause program(中断程序)选择Never(不中断),Log value of expression(记录表达式的值)选择Always(总是记录),Log function arguments(记录函数参数)也选择Always。

单击OK,然后打开注册窗口输入用户名和序列号,单击OK,接着来看看日志中的消息:
在这里插入图片描述

在日志窗口中,我们可以看到首先是值为0x201的WM_LBUTTONDOWN消息,然后又是值为0x202的WM_LBUTTONUP消息。没有WM_KEYDOWN和WM_KEYUP消息,因为我们并没有按键盘上的键。

为了记录下程序接收到的(按钮,输入的文本内容)等所有信息,我们可以对消息处理函数TranslateMessage或者DefWindowProcA设置条件断点。

如果想完整的记录下这两个API函数的参数信息,我们可以

通过命令栏BP给TranslateMessage和DefWindowProcA设置断点。
在这里插入图片描述


在这里插入图片描述

这样我们就成功的给这两个API函数设置了断点,接下来我们给这两个断点设置条件。我们单击工具栏中的【B】按钮打开断点列表窗口,然后在第一个断点上单击鼠标右键选择-Follow in disassembler。
在这里插入图片描述

在这里插入图片描述

然后断点这一行上单击鼠标右键选择-Conditional log。

在这里插入图片描述

Expression编辑框我们填上MSG,例如:WM_LBUTTONUP的值为0x202,那么MSG就等于0x202。

然后Pause program选择Never,Log value of expression选择Always,Log function arguments选择Always。接着同样的设置第二个断点。
在这里插入图片描述

这样,我们的条件断点就设置好了。
在这里插入图片描述

因为记录的结果可能会很多,所以我们最好是把它们保存到文件中。
在这里插入图片描述

在日志窗口中单击鼠标右键选择-Log to file。
在这里插入图片描述

添加上文本文件的名称。我们单击运行。
在这里插入图片描述

现在,我们日志中就记录了窗口过程函数接收到的所有消息。有个这个日志文件,我们就可以在其中挑选我们感兴趣的消息。然后设置相应的消息断点来印证我们的猜想。

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
如果您在 Visual Studio 2022 中调试 DLL 时发现断点无法进入,请确保您已经进行了以下步骤: 1. 确保 DLL 项目已经以调试模式进行构建。在解决方案资源管理器中,右键单击 DLL 项目,选择 "属性"。在属性页中,选择 "配置属性" -> "常规",确保 "配置类型" 设置为 "动态库(.dll)",并且 "调试信息格式" 设置为 "调试"。 2. 确保您的应用程序正确加载了 DLL。在 Visual Studio 中,您可以通过在 "调试" 菜单中选择 "附加到进程" 来手动附加到正在运行的应用程序。选择您的应用程序进程,并点击 "附加" 按钮。 3. 确保您设置了正确的断点。在 DLL 项目的代码中,单击行号旁边空白处设置断点。确保您设置的断点是在实际会执行的代码行上。 4. 如果您的 DLL 是由另一个应用程序加载的,而不是直接运行的,那么请确保您在正确的环境下进行调试。有些应用程序可能会以不同的用户权限或沙盒环境运行,这可能会导致断点无法进入。您可以尝试以管理员权限运行 Visual Studio 或目标应用程序,或者将目标应用程序的启动路径设置为可访问的路径。 5. 确保您的代码没有被优化。在 Visual Studio 中,您可以在 DLL 项目的属性页中的 "配置属性" -> "C/C++" -> "优化" 下,将 "优化" 选项设置为 "无(/Od)",以确保代码没有被优化,从而使断点能够进入。 如果您仍然遇到断点无法进入的问题,建议检查您的代码和项目配置是否正确,并尝试重启 Visual Studio 和目标应用程序。有时候,重新构建项目和清理解决方案也有助于解决此类问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值