1.4 Crack小实验
在开始讲述漏洞利用原理之前,本节先用一个非常简单的破解小实验来帮助大家复习一下前面所讲述的概念和工具,消除对二进制文件本能的恐惧。
下面是一段用于密码验证的C代码:
#include <stdio.h> #define PASSWORD "1234567" int verify_password (char *password) { int authenticated; authenticated=strcmp(password,PASSWORD); return authenticated; } main() { int valid_flag=0; char password[1024]; while(1) { printf("please input password: "); scanf("%s",password); valid_flag = verify_password(password); if(valid_flag) { printf("incorrect password!\n\n"); } else { printf("Congratulation! You have passed the verification!\n"); break; } } }
如图1.4.1所示,我们必须输入正确的密码“1234567”才能得到密码验证的确认,跳出循环。看到程序源码后不难发现,程序是提示密码错误请求再次输入,还是提示密码正确跳出循环,完全取决于main函数中的if判断。
图1.4.1 程序运行情况
如果我们能在.exe文件中找到if判断对应的二进制机器代码,将其稍作修改,那么即使输入错误的密码,也将通过验证!本节实验就带领大家来完成这样一件事情,这实际上是一种最简单的软件破解,也被称为“爆破”。
题外话:软件破解技术是自成体系的另一门安全技术,其关键在于在调试时巧妙地设置断点,寻找关键代码段。本例的破解方法有很多,比如直接在PE中搜索密码、crack子函数等,在此只举其中一个来介绍。这个实验的目的在于练习使用工具,复习前面的概念,而并非真正研究破解技术。
实验环境如表1-4-1所示。
表1-4-1 实验环境
说明:如果完全采用实验指导所推荐的实验环境,将精确地重现指导中所有的细节,包括动态调试时的内存地址和静态调试的文件偏移地址;否则,一些地址可能需要重新调试来确定。
首先打开IDA,并把由VC 6.0得到的.exe文件直接拖进IDA,稍等片刻,IDA就会把二进制文件翻译成质量上乘的反汇编代码。
如图1.4.2所示,默认情况下,IDA会自动识别出main函数,并用类似流程图的形式标注出函数内部的跳转指令。如果按F12键,IDA会自动绘制出更加专业和详细的函数流程图,如图1.4.3所示。
图1.4.2 IDA的流程图界面1
图1.4.3 IDA的流程图界面2
在IDA的图形显示界面中,用鼠标选中程序分支点,也就是我们要找的对应于C代码中的if分支点,按空格键切换到汇编指令界面,如图1.4.4所示。
图1.4.4 用IDA定位破解点
光标仍然显示高亮的这条汇编指令就是刚才在流程图中看到的引起程序分支的指令。可以看到这条指令位于PE文件的.text节,并且IDA已经自动将该指令的地址换算成了运行时的内存地址VA:0040106E。
现在关闭IDA,换用OllyDbg进行动态调试来看看程序到底是怎样分支的。用OllyDbg把PE文件打开,如图1.4.5所示。
图1.4.5 加载PE文件
OllyDbg在默认情况下将程序中断在PE装载器开始处,而不是main函数的开始。如果您有兴趣,可以按F8键单步跟踪,看看在main函数被运行之前,装载器都做了哪些准备工作。一般情况下,main函数位于GetCommandLineA函数调用后不远处,并且有明显的特征:在调用之前有3次连续的压栈操作,因为系统要给main传入默认的argc、argv等参数。找到main函数调用后,按F7键单步跟入就可以看到真正的代码了,如图1.4.6所示。
图1.4.6 定位main函数
我们也可以按快捷键Ctrl+G直接跳到由IDA得到的VA:0x0040106E处查看那条引起程序分支的关键指令,如图1.4.7所示。
图1.4.7 定位if分支
选中这条指令,按F2键下断点,成功后,指令的地址会被标记成不同颜色。
按F9键让程序运行起来,这时候控制权会回到程序,OllyDbg暂时挂起。到程序提示输入密码的Console界面随便输入一个错误的密码,回车确认后,OllyDbg会重新中断程序,收回控制权,如图1.4.8所示。
图1.4.8 破解前的状态
密码验证函数的返回值将存在EAX寄存器中,if()语句通过以下两条指令实现。
TEST EAX,EAX JE XXXXX
也就是说,EAX中的值为0时,跳转将被执行,程序进入密码确认流程;否则跳转不执行,程序进入密码重输的流程。由于现在输入的是错误密码,所以可以在预执行区看到提示:“Jump is not taken”。
如果我们把JE这条指令的机器代码修改成JNE(非0则跳转),那么整个程序的逻辑就会反过来:输入错误的密码会被确认,输入正确的密码反而要求重新输入!当然,把
TEST EAX, EAX
指令修改成
XOR EAX, EAX
也能达到改变程序流程的目的,这时不论正确与否,密码都将被接受。
双击JE这条指令,将其修改成JNE,单击“Assemble”按钮将其写入内存,如图1.4.9所示。
图1.4.9 破解后的状态
OllyDbg将汇编指令翻译成机器代码后写入内存。原来内存中的机器代码74(JE)现在变成了75(JNE)。此外,在预执行区中的提示也发生了变化,提示跳转将要发生,也就是说,在修改了一个字节的内存数据后,错误的密码也将跳入正确的执行流程!后面您可以单步执行,看看程序是不是如我们所料执行了正确密码才应该执行的指令。
上面只是在内存中修改程序,我们还需要在二进制文件中也修改相应的字节。这就要用到第2章讲到的内存地址VA与文件地址之间的对应关系了。
用LordPE打开.exe文件,查看PE文件的节信息,如图1.4.10所示。
图1.4.10 计算文件偏移地址
我们已经知道跳转指令在内存中的地址是VA=0x0040106E,按照第2章VA与文件地址的换算公式:
文件偏移地址=虚拟内存地址(VA)-装载基址(Image Base)-节偏移
= 0x0040106E-0x00400000-(0x00001000-0x00001000)
= 0x106E
也就是说,这条指令在PE文件中位于距离文件开始处106E字节的地方。用UltraEdit按照二进制方式打开crack_me.exe文件,如图1.4.11所示。
图1.4.11 修改PE文件
按快捷键Ctrl+G,输入0x106E直接跳到JE指令的机器代码处,如图1.4.12所示。
图1.4.12 修改PE文件
将这一个字节的74(JE)修改成75(JNE),保存后重新运行可执行文件,如图1.4.13所示。原本正确的密码“1234567”现在反而提示错误了。
图1.4.13 成功破解密码验证