返回介绍

第十二章 - 消息断点

发布于 2025-01-31 21:06:55 字数 12693 浏览 0 评论 0 收藏 0

本章将重点介绍 Windows 消息。

下面的引言简要的描述了 Windows 消息的概念:

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。

添加上文本文件的名称。我们单击运行。

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

在以后的内容你会逐步体会到运行消息断点来去除 NAG 等各种保护机制的方便之处的。

下一章,我们来提取 CrueHead’a 的 CrackMe 的正确序列号。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文