Linux下对抗缓冲区溢出攻击

引言

csapp(深入理解计算机系统)一书中提到了对抗缓冲区溢出的方法,主要有三点:

  1. 栈随机化
  2. 栈破坏检测
  3. 限制可执行代码区域

今天我们主要来了解一下Linux系统是如何应对缓冲区溢出的;


目录

  • 什么是缓冲区溢出
  • 缓冲区溢出带来的危害
  • 栈随机化
  • 栈破坏检测

1.什么是缓冲区溢出

相信大家对于缓冲区溢出都不陌生,比如一个int a[10]的数组,当我们想为第11个元素赋值时就会发生溢出例如a[11] = 11;,C语言中并没有规定不能这样赋值,此时编译器也是不会报错的,C语言选择相信程序员让程序员自己来检查数组的溢出,java则带有对于缓冲区溢出的检测,溢出有的时候并不会带来危害;但是总有些心怀不轨的人利用溢出做坏事;

2.缓冲区溢出的危害

在说危害之前,我们先来说说函数是怎样被调用然后被执行的

int add(int a, int b)
{
	int c = a + b;
	
	return c;
}

int main()
{
	int a = 1;
	int b = 2;
	int c = add(a, b);
	printf("c = %d\n", c);
	
	return 0;
}

我们就用这个很简单的程序来简单解释一下程序是怎样调用函数的;
在这里插入图片描述
我们先执行main()函数的内容,当执行到c = add(a, b);这条语句时main()函数会调用add()函数,因为程序都是在栈中运行的,所以他会先将要传入的参数保存在寄存器中,保存参数的寄存器只有7个当参数数量超过了7个以后会将参数压栈,然后将call add下一条指令的地址压栈,然后创建被调用函数的局部变量,然后给传入的参数分配内存,然后开始执行被调用函数的指令;
基本的攻击就是通过改变保存的指令地址,改成攻击程序的指令地址,执行完add函数以后他就会不会返回main函数就会去执行攻击程序;


3.栈随机化

栈随机化的实现方式是:程序开始时,在栈上分配一段0~n字节之间的随机大小的空间,程序不使用这段空间,但是他会导致程序每次执行时后续的栈的位置发生了变化,分配的n要足够大才能获得足够多的栈地址变化,但是又要足够小,不至于浪费太多的空间;
画个图吧:
在这里插入图片描述
每次n的值都不同,所以导致每次程序开始的地方不同;
那么为什么这样能防止溢出攻击呢,攻击者要插入代码,就需要指向这段代码的指针,就需要知道这个指针存放的地址,如果每一次运行的地址都一样的话,那么很容易就能得到这个指针的地址,
在Linux 系统中栈随机化已经成为了一种标准行为,他是更大技术的一种,这类技术被称为地址空间布局随机化简称ASLR
举个栗子:

// 观察ASLR(地址随机化)

#include <stdio.h>

int main()
{
    int a = 1;
    float b = 1.0;
    char c = 'a';
    printf("a = %p, b = %p, c = %p\n", &a, &b, &c);

    return 0;
}

当我们在没有关闭ASLR时它的运行结果是这样的:
在这里插入图片描述
我们可以看到每一次的变量地址都不一样;
但是当我们关闭ASLR后:
在这里插入图片描述
我们可以发现每一次执行代码,他的地址都是一样的;
这个ASLR好像在windows中并没有,当我们在windows下的vc++6.0上运行这个程序时每一次代码的地址都是一样的;


4.栈破坏检测

如果说栈随机化是第一道防线,那么栈破坏检测就是第二道防线,在gcc4.1以后的版本加入了一种栈保护机制他在被调用函数的局部变量和传入参数之间添加一个特殊的值,被称为金丝雀值,也称为哨兵值,是在程序每次运行时随机产生的,攻击者没有简单的办法能知道这个值是多少,当这个值被改变了,程序就会在函数返回时终止;
下面我们用一个例子来说明这个东西:

#include <stdio.h>

int try(int a, int b)
{
    int c;
    c = a + b;
    int *p = &a;
    printf("%d\n", c);

    return 0;
}

int main()
{
    int a = 1;
    int b = 2;
    try(a, b);

    return 0;
}

我们首先使用这段代码来获取金丝雀值的位置;
我们通过查看反汇编代码来查找他的相对位置;
编译时gcc -fstack-protector-all 5.c加入-fstack-protector-all就会产生这个金丝雀值如果不加-all就只有在局部变量为char时才会产生这个哨兵;
编译完成过后执行objdump -S a.out就能获得反汇编的代码了:
在这里插入图片描述
看到6c1那一行可以看出来金丝雀值被存放在了rbp-0x8这个地方,而我们看到a的值被存放在了rbp-0x24这个位置;
这里我们就要说到函数传递参数的问题了;
在这里插入图片描述
我们来看722到738这几行,就是局部变量的赋值加参数的传递;现在大家只用知道,参数a, b通过esiedi这两个寄存器传递到了函数中;
知道了a的地址和哨兵的地址我们尝试修改哨兵的值来看看会发生什么;

#include <stdio.h>

int try(int a, int b)
{
    int c;
    c = a + b;
    int *p = (int *)((char *)&a+28);
    *p = 3;
    printf("%d\n", c);
    
    return 0;
}

int main()
{
    int a = 1;
    int b = 2;
    try(a, b);

    return 0;
}

执行结果:

在这里插入图片描述
为什么还会打印3,因为对于哨兵的检测发生在函数返回时,看try函数的反汇编代码的6f9行;就是检测哨兵值有没有被改变;
他发生在调用printf函数以后;

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值