使用OllyDbg从零开始Cracking 第十五章-硬编码序列号寻踪-Part3
第十五章-硬编码序列号寻踪-Part3我们来接着完成上一章留下那个硬编码CrackMe的作业,名字叫”Splish”。用OD加载它。OD加载后停在了入口点处。我们通过在反汇编窗口中点击鼠标右键选择-Search for-Name(label)in current module查看API函数列表。我们可以看到当前模块使用了哪些API函数。我们可以看到使用了GetWindowTextA来获取序列号,MessageBoxA来提示序列号正确或者错误。我们可以给这两个API设置断点,这里我们先来看看该程序的字符串列表。单击鼠标右键选择-Search for-All referenced text strings。这里我们可以看到提示输入了正确硬编码序列号的字符串-”Gongratulations, you got the hard codedserial”。我们在这个字符串上面双击鼠标左键,就可以来到引用了该字符串的代码处。我们可以看到来到了验证序列号的代码块了。 可以看到使用GetWindowTextA获取用户输入的序列号,然后使用MessageBoxA来提示用户输入的序列号正确与否。我们在获取用户输入的序列号GetWindowTextA的调用处设置一个断点。按F9键运行CrackMe。为了找到上面的硬编码序列号,我们随便输入一个错误的序列号,然后单击Check Hardcoded按钮。我们可以看到断在了之前设置的断点处。我们先来看看堆栈中的情况,Buffer参数指向403215地址开始的内存单元,用于保存用户输入的序列号。我们在数据窗口中定位到该缓冲区该缓冲区会保存用户输入的序列号。我们按F8键单步执行该API函数。由于单击F8键,该API函数得以执行,所以序列号保存到了该缓冲区中。下一条指令会将401353保存到EAX中(记住,LEA指令并不是移动指定地址内存单元中的内容,而是移动方括号中的值,这里是401353)。我们通过在该指令上面单击鼠标右键选择-Follow in Dump-Memory address来在数据窗口中定位到401353这个地址。 401353指向字符串”HardCoded”,按F7键单步执行LEA指令。解释窗口中也提示401353这个地址指向字符串”HardCoded”,执行LEA指令后,EAX保存了401353这个地址。接下来的LEA指令,EBX会保存403215这个地址。按F7键单步,EBX保存403215。OD中显示,403215这个地址指向字符串”98989898”,也就是我们之前输入的错误序列号,我们像之前一样在数据窗口中转到403215这个地址。可以看到403215指向的内存单元中保存了我们输入的错误序列号。下一条指令检查EAX=401353这个地址指向内存单元的第一个字节是否为零。我们可以在数据窗口中转到401353这个地址,可以看到保存了字符串”HardCoded”。这里第一个字节是48,解释窗口中提示该ASCII码对应的字符是’H’,是”HardCoded”字符串的第一个字节,不为零。由于’H’不等于零,所以零标志位置0,JE不会跳转(记住,JE指令当零标志位Z置1的时候跳转)。继续单击F7键单步。可以清楚的看到该指令将EAX指向内存单元(即字符串”HardCoded”)的第一个字节,这里是48,保存到CL寄存器中,接下来一条指令将EBX指向的内存单元(即我们输入的错误序列号)的第一个字节保存到DL寄存器中。接着比较这两个字节是否相等,如果不相等,就跳转到4013D2地址处,弹出消息框提示”Sorry,please try again.”。下图中验证了这一点。按F7键,CL寄存器的值为48。接下来一行DL寄存器保存我们输入的错误序列号的第一个字节。按下F7键,可以看到DL的值为39。现在比较CL和DL的值。OD解释窗口中,字符’9’对应的ASCII码值为39,即我们输入的错误序列号的第一个字符与字符’H’对应的ASCII码值为48,即硬编码序列号”HardCoded”的第一个字符,进行比较。可以看到由于它们不相等,JNZ指令就会跳转到提示错误信息的代码处。如果它们相等的话,跳转将不会发生,我们可以通过双击零标志位Z来修改其值,让跳转不发生。现在零标志位Z置1了,表示比较的两个字节是相等的。接下来可以看到EAX,EBX的值递增1,然后JMP指令又跳转回了循环的开始。EAX现在指向字符串”HardCoded”的第二个字节,我们可以看到会逐个字符依次比较,直到EAX指向的字节值为0为止(字符串结束标志为0)。EAX,EBX分别加1以后将比较第二个字节,如果相等,则继续循环比较第3个字节,当”HardCoded”字符串都比较完毕了,CL和DL还是相等的时候,就不会跳转到提示错误的消息框代码处。由于现在比较完了”HardCoded”的所有字符,已经到了字符串的末尾,值为0。这里都为零,检测序列号结束。接着JZ指令跳出循环。现在我们到了显示正确消息框的代码块处。(由于我们修改零标志位Z的值)每次CL与DL进行比较的时候,我们通过修改零标志位Z的值,让程序以为它们两个是相等的,从而提示正确的消息框。不管怎么说,我们现在已经知道了正确的序列号是”HardCoded”(要注意字母的大小写)删除之前设置的所有断点,然后按下CheckHardcoded按钮。弹出Congratulations,you got the hard codedserial正确序列号的消息框。好了,现在来看最后一个硬编码序列号的CrackMe例子,第16章我们将探讨下一个话题。这个CrackMe与之前稍稍有点不同,名字叫做SamBo,我们用OD加载它。这个窗口提示说“该程序入口点不在代码段,可能是自解压或者自修改文件,这样文件我们称之为被加壳或者被压缩。我们后面会深入探讨壳,这里我们先还是用OD加载它,尽管是加过壳的,我们还是可以尝试找到序列号的。我们同意OD警告,将到达入口点处。 紧接着弹出一个窗口提示“代码段可能被压缩,加密,或者包含大量嵌入数据。代码分析可能是不可靠或者完全错误的。您仍要继续分析吗?”因为程序会在自己脱壳后继续运行原程序代码,所以我们选择NO。我们可以看到该CrackMe并没有像未加壳之前一样停在.text节的401000处。现在的入口点是4D4001。让我们来看看该程序的区段的情况,我们可以选择菜单项中的View-Memory或者单击工具栏中M按钮。我们可以看到该程序入口点所属区段起始地址为4D4000,大小为2000(十六进制),入口点为4D4001。这也是为什么OD提示入口点在.text节之外的缘故。.text区段开始于401000,OD提示包含code,我们当前的入口点4D4001属于另外一个区段,OD提示该程序可能包含大量嵌入数据。从当前入口点开始解密其他区段,然后会跳转到真正的入口点处,开始执行原程序。我们现在按F9键运行起来。此时弹出了一个CrackMe窗口,等待用户输入序列号,我们知道该程序已经在内存中解密区段结束了,现在在执行代码段中的代码,我们在.text区段上设置一个内存访问断点,让程序在执行代码段中的代码的中断下来。当我们打开CrackMe窗口的时候,这个时候OD断在了代码段中。我们可以看到程序正在解密内存中各种区段,我们来分析一下代码。单击鼠标右键选择-Analysis-Analyse code。可以看到现在OD展示给我们的是完美分析后的代码。现在我们位于代码段,我们查看当前模块使用了哪些API函数-但是我们在程序刚刚加载的时候不能这么做,因为那个时候我们查看当前模块使用的API的话是查看的壳所在模块使用的API函数,现在就不一样了。比较糟糕的是API函数列表中显示是一些比较陌生的字符串。那我们就来查看一下字符串列表,尝试寻找一点蛛丝马迹。我们又看到了一个糟糕的字符串列表。我们可以看到”You did it!”-用于提示成功的字符串之一。这里有一个比较指令和一个条件跳转指令跳转到提示成功的字符串代码块处,附近还有提示错误的代码块,但是并不是通过调用MessageBoxA函数来提示。我们在该条件跳转指令上面设置一个断点以便来验证是不是一个关键的跳转,并且通过单击鼠标右键选择-Breakpoint-Removememory breakpoint来删除之前设置的内存断点。接着运行程序。我们随便输入一个错误的序列号,这里我们输入”Narvajita”,然后按下Test-o-Doom按钮。 看到跳转将会实现,我们直接按F9键。然后弹出一个提示成功的窗口,我们单击接受按钮。接着弹出一个消息框提示”开个玩笑,序列号错误”。我们单击接受按钮又回到了等待用户输入序列号的窗口。我们再次按下Test-o-Doom按钮。我们修改影响跳转标志位,看看会不会弹出提示序列号正确的消息框。我们在零标志位Z上面双击改变其值,然后运行起来。弹出提示序列号正确的消息框,那么这个跳转就是决定序列号正确与否的关键跳转。我们再来看看这个关键跳转。这里的TEST CL,CL指令判断CL是否等于零,在判断之前还一个CALL指令,我们在这个CALL指令处设置一个断点。我们把程序运行起来,然后来到主窗口再次按下Test-o-Doom按钮,断在了CALL指令处。我们看看堆栈中的参数情况。堆栈中我们可以看到我们刚刚输入的错误序列号,我们通过单击鼠标右键选择-Followin Dump在数据窗口中定位到这个字符串。我们可以看到一个字符串”1556555”,可能是正确的序列号,我们来验证一下它到底是不是正确的序列号。我们对这个错误序列号设置一个内存访问断点,如果这个CALL中将我们输入的错误序列号与正确的序列号进行比较的话,就会断下来。我们将光标拖选中错误的序列号,然后单击鼠标右键选择-Breakpoint-Memory,onaccess。此时,如果我们运行起来,如果这个CALL中访问我们错误序列号的话,OD将中断下来。我们按下F9键。我们可以看到并没有中断下来,说明比较还在前面,所以我们可以考虑反复通过上面的方式在还要靠前的地方设置断点,或者我们可以以程序获取用户输入的序列号作为入手点,但是这里并没有使用GetWindowTextA函数,但是我们知道有将虚拟键消息转换为字符消息的函数。我们删除设置的内存断点。我们通过在命令栏中输入BP TranslateMessage给TranslateMessage函数设置一个断点。运行起来。断在了该API函数入口处,我们在断在这一行上面单击鼠标选择-Breakpoint-Conditional log我们设置条件为MSG == 202 即WM_LBUTTONUP。并且设置中断程序方式为条件中断,以及总是记录表达式的值以及函数的参数值。这里,可以看到一个粉红色的条件断点,我们运行起来。我们来到主窗口,由于之前的序列号还在内存中,为了不和之前的混淆,我们输入一个不同的序列号。我们通过单击按钮触发条件断点。当WM_LBUTTONUP消息到达的时候,断了下来。我们通过选择菜单项中Debug-Execute till return执行到返回。我们单击F7键单步返回到主程序模块中。现在我们有两种选择,第一种-我们对.text节设置内存访问断点,然后F9键运行起来看看哪里在进行序列号的比较,这对于我们来说是比较困难的,我们可以看到,代码量有点大。第二种选择-就是在内存搜索一下我们输入的序列号,我们单击工具栏中的M按钮打开内存窗口。这里有在对应内存块中搜索的功能,我们在当前内存块上面单击鼠标右键选择-Search。 在弹出的窗口的ASCII编辑框中输入我们的错误序列号。我们勾选上Entire block(这个内存块),然后按下OK。我们还可以使用CTRL + L来看看有没有其他内存区域有该错误序列号,我们发现当前区段中没有,其他区段中也没有找到错误序列号了。我们对错误序列号设置内存访问断点,当程序拿错误序列号进行比较的时候就会断下来。我们按下F9键运行起来。这里首先将错误序列号拷贝一块内存区域,然后紧接着有一个CALL。我们知道REP MOVS指令会将ESI指向内存单元的内容拷贝到EDI指向的内存单元中,所以我们通过在EDI寄存器上面单击鼠标右键选择-Follow in Dump在数据窗口中定位到EDI指向的内存单元。这里REP MOVS将进行序列号拷贝,我们按下F8执行该指令。当我们一直F8单步执行到下面的CALL指令处时,错误序列号拷贝完毕,我们继续在该错误序列号上面设置内存访问断点。我们运行起来,断了下来。正在进行比较,嘿嘿。ESI指向我们输入的错误序列号,EDI指向正确的序列号”1556555”,现在我们清楚所有断点。 弹出提示序列号正确的消息框。下一章我们将分析用户名和序列号生成算法。
本系列文章汉化版转载看雪论坛
感谢原作者:RicardoNarvaja(西班牙人) 原作者个人主页:http://www.ricardonarvaja.info/
感谢热心翻译的朋友:1~3章译者:BGCoder4~58章译者:安于此生
全集配套程序下载地址:链接: http://pan.baidu.com/s/1eQzTWfo 密码: vytv
[快捷回复]-感谢楼主热心分享! 先点评加好评再送币的说 [吾爱汇编论坛52HB.COM]-论坛的繁荣离不开你们的热心分享 不错哦,可以学习一下 跟着大佬少走弯路 楼主,辛苦啦,谢谢啦。
页:
[1]