第二十八章-VisualBasic程序的破解-Part3破解VB程序的又一手法本章我们的实现对象是CrackMe2。运行起来我们可以看到一个NAG窗口,通过上一章4C法我们可以很容易的剔除掉这个NAG窗口,其中与序列号相关的一部分涉及到了PCODE,等我们介绍到了PCODE的时候再来讨论。要找到这个CrackMe的序列号很简单,关键是如何剔除这个NAG窗口,通过4C法我们可以轻松的剔除掉NAG窗口,这里我就不再赘述了,大家可以自行尝试。这里我们来介绍另外一种方式。首先我们来看看正确的序列号是多少。我们单击Register按钮。出现了注册窗口,我们随便输入一个错误的序列号。首先我们来看看是否是用__vbaStrCmp这个函数来进行字符串比较的。我们在命令栏中输入bp__vbaStrCmp。接着按F9键运行起来,我们会发现会断下来很多次,所以我们可以换种思路,从错误提示入手,看看能不能在弹出错误提示框的时候断下来,那么序列号的比较应该就在附近了。现在我们删除刚刚对__vbaStrCmp设置的断点,然后给弹出消息框的函数rtcMsgBox设置断点。我们在命令栏中输入bprtcMsgBox。我们单击OK按钮。断在了rtcMsgBox的入口处。我猜是提示输入的序列号错误。我们来到堆栈窗口看看调用来至于哪里。我们在反汇编窗口中定位到该返回地址处。好了,现在我们来到返回地址处,我们看看前面几行有没有调用字符串比较之类的API函数。我们可以看到前面的确有进行字符串比较的API函数,我们给这处调用设置一个断点,我们按F9键运行起来,验证一下该处是不是我们要找的。断了下来,我们可以看到正在进行字符串的比较,其中一个是我们输入的错误序列号。另一个我这里是4887649,我们删除之前的断点,在注册窗口中输入这个字符串看看是否是正确的序列号。我们可以看到的确是正确的序列号。接下来,我们来尝试剔除这个NAG窗口。由于我们可能需要修改这个CrackMe,所以我们需要给这个CrackMe的代码段赋予写权限,我们首先在数据窗口中定位400000地址处。我们切换为PEheader模式显示。往下我们可以看到PESignature标志,我们继续往下定位到第一个区段。这里我们可以看到Characteristics字段,我们将为其修改为E0000020的话,就可以写入代码段了。好了,现在我们将刚刚所做的修改保存到文件。另存为CrackMeA.exe我们用olly_parcheado_para_vb这个Patch过的OD来加载这个CrackMeA。接着给代码段设置内存访问断点(实际上是内存执行断点)。我们多按几次F9键运行起来就可以来到这里。我们继续按F9键运行,停在了熟悉的多分支处,将会转向去执行程序的不同部分。第一次,停在了407710处,将JMP到40BD80地址处-程序将执行的第一部分,我们来看看再次停在其他JMP分支之前会不会弹出NAG窗口,我们删除之前设置的内存访问断点,接着给该多分支的各个JMP指令处都设置断点。我们来看看断在其他JMP指令之前会不会弹出NAG窗口,运行起来。我们可以看到断在了407759处,将跳转到40C470处,并没有弹出NAG窗口,我们继续运行。弹出了NAG窗口,说明是在40C470这个分支中弹出的NAG窗口,我们单击Register按钮。断在了第二个JMP处,所以说NAG是在跳转到40BDF0之前弹出的,我们直接跳过NAG窗口。我们将407759处的JMP40C4F0修改为JMP40BDF0看看会发生什么。我们保存到修改到文件,然后直接运行起来。我们可以看到刚刚做的修改并没有剔除掉NAG窗口,但是原本是需要我们单击NAG窗口的Register按钮才会弹出注册窗口的,现在是NAG窗口和注册窗口一起弹了出来。我们刚刚修改JMP指令并没有完全解决问题,别无选择了,我们只能修改VB的DLL的,很多人说我们不能修改系统库文件,会导致其他程序不能用的,实际上我们可以将VB的库文件拷贝至跟待破解的目标文件同一个目录即可,那么修改了的VB库文件只会的当前文件夹的目标文件起作用,操作系统里的其他应用程序还是会继续使用system32目录下的VB库文件,两者不会冲突。那么我们需要拷贝那个VB库文件呢?我们到system32目录下拷贝MSVBVM60.dll这个文件。首先将CrackMeA做个备份。目标文件如果需要加载特定的库文件的话,首先会搜索当前目录下有没有,如果当前目录下面有就直接加载,如果当期目录下面没有,就会继续前往system32目录下搜索,我们当前就是这种情况。我们用OllyDbg加载该CrackMeA,这里我们使用原版的OllyDbg,因为Patch过的那个OD添加代码有时候会失败。我们单击工具栏中E按钮打开模块列表窗口,看看是加载的当前目录下的MSVBVM60.dll还是system32下的。我们可以看到加载是当前目录下的MSVBVM60.dll。好了,现在我们需要确保对MSVBVM60.dll的代码段有写权限,我们做如下操作:我们打开一个OD然后单击菜单栏中Open选项。默认显示的exe文件,我们将文件类型改为DLL。将下拉选项选中Dynamic-Linklibrary(*.dll)就能打开DLL文件了,我们打开MSVBVM60.dll。我们停在了该动态库的入口点处。现在我们定位该动态库的头部(并不是像定位CrackMeA的头部那样直接定位到400000地址处),我们来看看加载了哪些模块,以及映像基址是多少。我们单击工具栏中的E按钮。我这里MSVBVM60.dll的基地址是66000000。可能跟你机器上的不一样。我们在数据窗口中定位到这个地址。定位到头部以后我们切换到PEheader模式显示,接着往下拉到.text区段。将Characteristics字段的值修改为E0000020,让代码段具有写权限。保存修改到文件。这里我们不重命名,因为重命名后CrackMe就不会加载了。我们用Patch过的OllyDbg加载CrackMeA,由于注册窗口和NAG窗口是一起弹出来的,所以我们给创建窗口的API函数CreateWindowExA设置一个断点。运行起来。由于会创建子窗口所以会断下来几次,我多按F9键几次,直到创建NAG窗口为止。判断是否是创建NAG窗口很简单,根据窗口标题名就可以很容易的看出来。可以看到现在正在创建NAG窗口,我们将其窗口风格值修改为40000000(子窗口风格),看看会发生什么。我们可以看到已经将窗口风格修改为WS_CHILD,你可以尝试输入不同的值,现在我们可以删除所有断点运行起来。我们可以看到只弹出了一个注册窗口,并没有弹出NAG窗口。如果不修改MSVBVM60.dll想要剔除这个NAG窗口就很复杂了,我们手工从系统目录复制一个MSVBVM60.dll将其置于与目标程序同一目录,通过修改这个文件并不会影响到其他应用程序,嘿嘿。好了,我们重复之前的步骤,依然还是先给CreateWindowExA这个函数设置一个断点。从堆栈中的信息我们可以看出调用来至于MSVBVM60.DLL,我们定位到调用处。我们清除掉CreateWindowExA入口处的断点,接着给6605A8D8处的CALLCreateWindowExA设置一个断点。我们重新启动程序,接着运行起来。断了下来,我们看看堆栈的情况。我们可以看到第一次断下来就是创建的NAG窗口,可能其他窗口的创建是其他地方调用的CreateWindowExA。更加不幸的是,注册窗口的创建的也是调用的此处。现在我们需要找到一块空闲的区域写入JMP地址,我们会发现代码段的最后有一块空闲的区域。在我的机器上,这块空闲的区域在代码段的最后,我们可以看到很多零,首先我们来验证一下看看这块区域是否可用。我们在这块区域上单击鼠标右键选择-View-Executablefile。我们可以看到准备保存到exe中的区域。好了,可以看到该空闲区域是在文件中是存在的,我们现在可以插入代码了。如果我们不检查的话,很可能所插入的代码不能保存到文件,因为可能这部分零只存在于内存中,并不是可执行文件的一部分。(我们在后面讲到脱壳的时候会详细解释)首先我们将6605A8D8处的CALLCreateWindowExA修改为JMP660FC500(你的机器上这个地址由你来选定),让其跳往刚刚的空闲区域。这里我们构造一个间接跳转,这样能够确保跟之前的CALLCreateWindowExA指令一样长,这样就不至于修改到后面MOVEDI,EAX等指令,代码也就能正常的执行。如果覆盖了MOVEDI,EAX的任何一个字节的话,调用下面空白区域的代码返回接着执行,可能会出错。我们将660FC500这个空闲区域的地址保存到660FC400内存单元中,这样JMP[660FC400]跟JMP660FC500就是等效的了。我们在660FC400内存单元中写入660FC500-我们将植入代码的首地址。我们接着来看看堆栈中给CreateWindowExA传入的参数,如窗口名称。我们必须确保第一次调用的时候存在这个名称,因为有时候调用,窗口名称是为空的,如果我们不做检查的话,就会出错。我们在堆栈地址上双击将显示方式切换为偏移形式。这说明ESP+8处保存的是窗口名称的首地址,我们将其保存到EAX寄存器中。我们单步跟踪到660FC500处,看看具体的细节。我们按F8单步,可以看到窗口名称的首地址被保存到了EAX中。现在EAX中保存了窗口名称的首地址,接着我们在下一行中检查EAX是否为零,如果为零说明该窗口可能没有标题名称。这里如果EAX为零,就不需要修改窗口的样式了,继续调用CreateWindowExA即可。如果EAX不为零,则需要做进一步的判断。这里我们判断窗口标题名称的前4个字节是否为756F7243(也就是NAG窗口标题CrouzCrackMe-1:Setup的前4个字节)。这里如果判断出的确是NAG窗口,那么继续往下执行修改窗口样式,如果不是NAG窗口的话,就不修改窗口样式,直接调用CALLCreateWindowExA。这里我们可以看到如果不是NAG窗口的标题的话,就直接跳转到CALLCreateWindowExA处,并不修改窗口样式。如果是NAG窗口的话,就将ESP+C指向的窗口样式参数值修改为40000000(子窗口样式)。当我们执行到这一行可以看到调用是CreateWindowExA,我们来看看参数情况:我们可以看到ESP+C指向的窗口样式参数值被修改为了40000000即WS_CHILD。接下来我们调用CreateWindowExA了,我们需要知道CreateWindowExA的入口地址,好的,那么我们来看看原先的调用是怎样的。我们可以看到这是一个间接调用,它是读取660014E8内存单元中的值然后再调用的,实际上就是:CALL[660014E8]OD的提示窗口中会提示CreateWindowExA的实际入口地址是多少,那么这里为什么要使用间接调用呢?是为了能够在任何一个机器上都能运行,我们在后面脱壳章节中介绍IAT的时候会更深入的解释。调用完CreateWindowExA以后我们已经返回到原来的下一行处继续执行MOVEDI,EAX。接着我们运行起来。我们可以看到只弹出了注册窗口,没有出现NAG创建。总结一下我们需要的处理-检查窗口标题是否为NAG窗口,如果是就改变其窗口标题。我们保存所有修改到文件直接运行起来看看效果。我们可以看到同样是直接弹出了注册窗口,没有出现NAG窗口,说明我们Patch的CrackMe和MSVBVM60.DLL成功剔除了NAG窗口。接着我们来输入正确的序列号。单击OK。我们可以看到提示序列号正确,说明我们成功搞定了这个VBCrackMe。下面你可以自己尝试剔除掉这个名为CrackMe的NAG窗口(必须的库MSVBVM50.DLL)。