第十二章-消息断点 本章将重点介绍Windows消息。 下面的引言简要的描述了Windows消息的概念: Windows消息被广泛用于各种事件的通知上面,如果你想操作窗口或者控件(UI元素其实也是一种窗口,如:按钮,编辑框,工具栏,树形控件等)的话,给它发送消息即可。消息也可以来至于其他应用程序。你也可以通过消息来实现系统通知,移动鼠标,按下键盘上的某键等操作。 正如我们前面所讨论的,OD中大部分的API函数我们可以使用普通CC断点来下断,但是少数检测CC断点的情况,使用消息断点会更加有效。消息断点在内核调试器SoftICE中也称为BMSG。 Windows窗口程序至少有一个消息循环,消息循环有特定的API函数构成,最常见的是GetMessage和DispatchMessage函数,有的消息循环也会用到其他的API函数。想要深入了解Windows的消息的话,可以参考下面的链接中的教程”理解消息循环”(该教程的中文版见附件): 让我们来看一个简单的例子:用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,程序断在我们设置的断点处。 注意堆栈。 可以看到该函数有一个参数是缓冲区,编辑框中的内容会被拷贝至该缓冲区。 所以,我们在数据窗口中定位到该缓冲区,堆栈窗口中选中该缓冲区参数,单击鼠标右键选择-Followin 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],然后Pauseprogram(中断程序)选择Never(不中断),Log value of expression(记录表达式的值)选择Always(总是记录),Logfunction 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,Logvalue of expression选择Always,Log function arguments选择Always。接着同样的设置第二个断点。 这样,我们的条件断点就设置好了。 因为记录的结果可能会很多,所以我们最好是把它们保存到文件中。 在日志窗口中单击鼠标右键选择-Log to file。 添加上文本文件的名称。我们单击运行。 现在,我们日志中就记录了窗口过程函数接收到的所有消息。有个这个日志文件,我们就可以在其中挑选我们感兴趣的消息。然后设置相应的消息断点来印证我们的猜想。 在以后的内容你会逐步体会到运行消息断点来去除NAG等各种保护机制的方便之处的。 下一章,我们来提取CrueHead’a的CrackMe的正确序列号。
本系列文章汉化版转载看雪论坛
感谢原作者:RicardoNarvaja(西班牙人)
感谢热心翻译的朋友: 1~3章译者:BGCoder 4~58章译者:安于此生
全集配套程序下载地址:
|