首先来看一段用于密码验证的C语言代码:
#include<stdio.h> #include<string.h> #define PASSWORD "1234567" int verify_password(char *password) { int authenticated; authenticated = strcmp(password,PASSWORD); return authenticated; } int 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; } } return 0; }由代码逻辑可知,必须输入正确的密码“1234567”才能得到密码验证的确认,跳出循环。不难发现,程序是提示密码错误请求再次输入,还是提示密码正确跳出循环,完全取决于main函数中的if判断。
如果我们能在.exe文件中找到if判断对应的二进制机器代码,将其稍作修改,那么即使输入错误的密码,也能通过验证,下面,将一步步来实现该软件的“爆破”。
实验环境: 操作系统:WinXP Sp3 编译器:Visual C++ 6.0 编译选项:默认编译选项 build版本:release版本
1.首先打开IDA,并把由VC 6.0得到的.exe文件直接拖进IDA,稍等片刻,反汇编工作将完成。反汇编流程图如下所示:
2.在IDA图形显示界面,用鼠标选中程序分支点,即要寻找的对应C代码分支点的if分支点,按空格键切换到汇编指令界面:
光标仍然显示高亮的这条汇编指令就是刚才在流程图中看到的分支指令。可以看到这条指令位于PE文件的.text节,并且IDA已经自动将该指令的地址换算成了运行时的内存地址VA:004010D5。
3.现在关闭IDA,换成OllyDbg进行动态调试来看看程序到底怎样分支的。用OllyDbg把PE文件打开,如下:
OllyDbg在默认情况下将程序中断在PE装载处开始,而不是main函数的开始。一般情况下,main函数位于GetCommonLineA函数调用后不远处,并且有明显的特征:在调用之前有3次明显的压栈操作,因为系统要给main传入默认的argc、argv等参数。找到main函数后,按F7键单步跟入就可以看到真正的代码了。
4.也可以按快捷键Ctrl+G直接跳到由IDA得到的VA:0x004010D5处查看那条引起程序分支的关键指令:
5.选中这条指令,按F2键下断点,成功后,指令的地址会被标记成不同颜色。 按F9键让程序运行起来,这时候程序控制权会回到程序,OllyDbg暂时挂起。到程序提示输入密码的Console界面随便输入一个错误的密码,回车确认后,OllyDbg会重新中断程序,收回控制权。
密码验证函数的返回值将存在EAX寄存器中,if()语句通过下列两条指令实现:
TEST EAX,EAX JE XXXXX也就是说,EAX中的值为0时,跳转将被执行,程序进入密码确认流程;否则跳转不执行,程序进入密码重输的流程。如果我们把JE这条指令修改为JNE(非0则跳转),那么整个程序的逻辑就会反过来:输入错误的密码被确认,输入正确的密码反而要求重新输入!
6.双击JE这条指令,将其修改成JNE,单击“Assemble”按钮将其写入内存,如下所示:
OllyDbg将汇编指令翻译成机器代码写入内存。原来内存中的机器代码74(JE)现在变成了75(JNE)。
7.上面只是在内存中修改程序,还需要在二进制文件中也修改相应的字节,需要用到内存地址VA与文件地址之间的对应关系。利用PEiD或类似软件打开.exe文件,查看PE文件的节信息:
我们已经知道跳转指令在内存中的地址是VA = 0x004010D5,按照VA与文件地址的换算公式: 文件偏移地址 = 虚拟内存地址(VA) - 装载基址(Image Base) - 节偏移 = 0x004010D5 - 0x00400000 - (0x00001000 - 0x00001000) = 0x10D5 也就是说,这条指令在PE文件中距离文件开始处10D5字节的地方。用UltraEdit按照二进制方式打开.exe文件,按Ctrl+G,输入0x10D5直接跳到JE指令的机器代码处,将这一字节的74(JE)修改成75(JNE),保存后重新运行可执行文件。
运行结果如下所示,原本正确的密码“1234567”现在反而提示错误了,而输入其他密码均能正确通过。
本实验出自王清的《0day安全:软件漏洞分析技术》第一章入门内容。