安全中国首页 > 文章中心 > 安全防护
 
安全中国网友投稿专用上传FTP空间:
Ftp服务器:download.anqn.com
Ftp端口:21
用户名:anqn
密 码:anqn.com
 

4.3.2 控制程序的执行流程(图)

更新时间:2008-8-28 0:11:20
责任编辑:流火
热 点:

4.3.2 控制程序的执行流程


用键盘输入字符的ASCII表示范围有限,很多值(如0x11、0x12等符号)无法直接用键盘输入,所以我们把用于实验的代码稍加改动,将程序的输入由键盘改为从文件中读取字符串。

#include <stdio.h>
#define PASSWORD "1234567"
int verify_password (char *password)
{
int authenticated;
char buffer[8];
authenticated=strcmp(password,PASSWORD);
strcpy(buffer,password);//over flowed here!
return authenticated;
}
main()
{
int valid_flag=0;
char password[1024];
FILE * fp;
if(!(fp=fopen("password.txt","rw+")))
{
exit(0);
}
fscanf(fp,"%s",password);
valid_flag = verify_password(password);
if(valid_flag)
{
printf("incorrect password!\n");
}
else
{
printf("Congratulation! You have passed the verification!\n");
}
fclose(fp);
}

以上节实验中的代码为基础,稍作修改后得到上述代码。程序的基本逻辑和上一节中的代码大体相同,只是现在将从同目录下的password.txt文件中读取字符串,而不是用键盘输入。我们可以用十六进制的编辑器把我们想写入但不能直接键入的ASCII字符写进这个password.txt文件。
实验环境如表4-3-3所示。
表4-3-3 实验环境

 

推荐使用的环境

   

操作系统

Windows XP sp2

其他Win32操作系统也可进行本实验

编译器

Visual C++ 6.0

如使用其他编译器,需重新调试

编译选项

默认编译选项

VS2003VS2005中的GS编译选项会使栈溢出实验失败

build版本

debug版本

如使用release版本,则需要重新调试

如果完全采用实验指导所推荐的实验环境,将精确地重现指导中所有的细节;否则需要根据具体情况重新调试。

用VC6.0将上述代码编译链接(使用默认编译选项,BUILD成debug版本),在与PE文件同目录下建立password.txt并写入测试用的密码之后,就可以用OllyDbg加载调试了。

开始动手之前,我们先理清思路,看看要达到实验目的我们都需要做哪些工作。
(1)要摸清楚栈中的状况,如函数地址距离缓冲区的偏移量等。这虽然可以通过分析代码得到,但我还是推荐从动态调试中获得这些信息。
(2)要得到程序中密码验证通过的指令地址,以便程序直接跳去这个分支执行。
(3)要在password.txt文件的相应偏移处填上这个地址。

这样verify_password函数返回后就会直接跳转到验证通过的正确分支去执行了。
首先用OllyDbg加载得到可执行PE文件,如图4.3.5所示。

   
图4.3.5 提示验证通过的代码位置

阅读图4.3.5中显示的反汇编代码,可以知道通过验证的程序分支的指令地址为0x00401122。

0x00401102处的函数调用就是verify_password函数,之后在0x0040110A处将EAX中的函数返回值取出,在

0x0040110D处与0比较,然后决定跳转到提示验证错误的分支或提示验证通过的分支。

提示验证通过的分支从0x00401122处的参数压栈开始。如果我们把返回地址覆盖成这个地址,那么在0x00401102 处的函数调用返回后,程序将跳转到验证通过的分支,而不是进入0x00401107处分支判断代码。这个过程如图4.3.6所示。

 
图4.3.6 栈溢出攻击示意图

通过动态调试,发现栈帧中的变量分布情况基本没变。这样我们就可以按照如下方法构造password.txt中的数据。

仍然出于字节对齐、容易辨认的目的,我们将“4321”作为一个输入单元。

buffer[8]共需要两个这样的单元。

第3个输入单元将authenticated覆盖;第4个输入单元将前栈帧EBP值覆盖;第5个输入单元将返回地址覆盖。

为了把第5个输入单元的ASCII码值0x34333231修改成验证通过分支的指令地址0x00401122,我们将借助十六进制编辑工具UltraEdit来完成(0x40、0x11等ASCII码对应的符号很难用键盘输入)。

步骤1:创建一个名为password.txt的文件,并用记事本打开,在其中写入5个“4321”后保存到与实验程序同名的目录下,如图4.3.7所示。

 
图4.3.7 制作触发栈溢出的输入文件
步骤2:保存后用UltraEdit_32重新打开,如图4.3.8所示。
 
图4.3.8 制作触发栈溢出的输入文件
步骤3:将UltraEdit_32切换到十六进制编辑模式,如图4.3.9所示。
 
图4.3.9 制作触发栈溢出的输入文件
步骤4:将最后4个字节修改成新的返回地址,注意这里是按照“内存数据”排列的,由于“大顶机”的缘故,为了让最终的“数值数据”为0x00401122,我们需要逆序输入这4个字节,如图4.3.10所示。
 
图4.3.10 制作触发栈溢出的输入文件
步骤5:这时我们可以切换回文本模式,最后这4个字节对应的字符显示为乱码,如图4.3.11所示。
 
图4.3.11 制作触发栈溢出的输入文件

将password.txt保存后,用OllyDbg加载程序并调试,可以看到最终的栈状态如表4-3-4所示。
表4-3-4 栈帧数据

局部变量名

偏移3处的值

偏移2处的值

偏移1处的值

偏移0处的值

buffer[0~3]

0x0012FB14

0x31 (‘1’)

0x32 (‘2’)

0x33 (‘3’)

0x34 (‘4’)

buffer[4~7]

0x0012FB18

0x31 (‘1’)

0x32 (‘2’)

0x33 (‘3’)

0x34 (‘4’)

authenticated(被覆盖前)

0x0012FB1C

0x00

0x00

0x00

0x01

authenticated(被覆盖后)

0x0012FB1C

0x31 (‘1’)

0x32 (‘2’)

0x33 (‘3’)

0x34 (‘4’)

前栈帧EBP(被覆盖前)

0x0012FB20

0x00

0x12

0xFF

0x80

前栈帧EBP(被覆盖后)

0x0012FB20

0x31 (‘1’)

0x32 (‘2’)

0x33 (‘3’)

0x34 (‘4’)

返回地址(被覆盖前)

0x0012FB24

0x00

0x40

0x11

0x07

返回地址(被覆盖后)

0x0012FB24

0x00

0x40

0x11

0x22

程序执行状态如图4.3.12所示。

 
图4.3.12 栈溢出成功改变了程序执行流程
由于栈内EBP等被覆盖为无效值,使得程序在退出时堆栈无法平衡,导致崩溃。虽然如此,我们已经成功地淹没了返回地址,并让处理器如我们设想的那样,在函数返回时直接跳转到了提示验证通过的分支。

 
相关文章
一日一文章
 
一日一软件
一日一动画