小议缓冲区溢出

简介: 小议缓冲区溢出

       最近一个朋友学习信息安全方面的知识,然后发来一题和我一起讨论,虽然觉得简单,但是实际还是有点意思的,就拿出来一起看看。题目如下:


     从图中可以看到一段C语言的代码,还能看到3个问题。这里我把代码提出来,代码如下:

#include <stdio.h>
int main(int argc, char* argv[])
{
    int apple;
    char buf[9];
    gets(buf);
    if (apple == 0x64636261) 
    {
        printf("hello world!");
    }
    return 0;
}

我把问题也写出来,问题有三个:


(1)分析是哪种溢出类型


(2)给出题目的变量 apple 的地址,例如 0x0012ff44,给出 buf 各字符的地址


(3)a、b、c、d 的 ASCII 码值分别为0x61、0x62、0x63 和 0x64 ,给出 buf 输入方式,使得程序可以输出 hello world

什么是缓冲区

       简单说,缓冲区就是一块存放数据的内存区域。根据存放数据的内存的分配方式,可以把内存分为栈内存和堆内存

       栈内存,用于存放局部变量、函数的参数等,对于函数调用时现场的保护,也会用到栈内存,比如保存函数的返回地址。栈内存,由 CPU 的来维护,在 32 位操作系统下,由 CPU 的 EBP 和 ESP 两个寄存器来维护。

       堆内存,是程序员通过特定的函数来申请的,比如 malloc 和 new 等函数。堆内存申请后由程序员来释放。而 栈内存 随着函数的返回 栈内存 也会被自动的回收。

什么是缓冲区溢出

       通常就是内存的覆盖,由于缓冲区分为 栈 和 堆,因此缓冲区溢出分为 栈溢出 和 堆溢出。因为 C/C++ 很多函数早期都不检查内存边界,所有的内存边界检查都由程序员自己去完成。这样就有可能因为疏忽造成缓冲区的溢出。而现在,大部分操作内存的函数,都在之前函数的基础上增加了安全检查,也就比以前安全了。


       有些安全书籍认为,避免缓冲区溢出,不要使用栈内存,而是去使用堆内存,这样的认识是错误的。因为堆内存的使用不当也会造成溢出,也是存在安全隐患的。

缓冲区溢出攻击

       缓冲区溢出攻击的本质是数据当作代码运行。在有存在缓冲区溢出攻击的程序中,攻击者将可执行的代码当作数据植入内存,再通过特定的方式使植入的数据运行,从而达到攻击的目的。

题目解析

       有了上面的铺垫,就来说说题目中的内容。

       第一题,上面的代码是哪种类型的溢出。在代码中可以看出,数组 buf[9] 是一块缓冲区,而 buf 是一个局部变量。局部变量是在栈中保存。代码中的 gets() 函数是接收用户输入的函数,但是它不对内存边界进行检查。buf[9] 的长度为 9 个字节,但是当使用 gets() 函数获取用户输入时,当超过 9 个字节时,也会全部接收。这样就造成了缓冲区溢出,更具体的说,就是栈溢出。这点是 C/C++ 语言的特点,数组越界是被允许的,因为在很多程序设计中,为了存储不定长数据,就会使用数组越界的方式。


       第二题,假如 apple 的内存地址是 0x0012ff44,那么给出 buf 中各个字符的地址。变量相当于给某个内存首地址起了一个名字,变量的类型限制了该变量的内存长度,比如 0x0012ff44 这个是一个内存的地址,给这个内存的地址起一个名字叫 apple,另外 变量 apple 的类型是 int,那么限制该变量的长度占用 4 个字节。


        第二题的题目,是给出我们 apple 的地址,然后让写出 buf 变量的地址。这里就又需要了解两个知识。首先,局部变量是在栈地址中这个是已知的,而栈地址的增长方向是由高到低的。第二,在 C 语言中,函数内部定义的局部变量,会按照变量定义的先后顺序来分配栈中的内存地址。那么,在代码中,先定义的 apple ,后定义的 buf 变量。那么,apple 的地址就比 buf 的地址要高(大、上),如图。

5a0baf22a0da83cdcd8569167a756d5b.png

       知道上面两点以后,那么 buf 的地址到底是多少呢?还是先来说说 apple 实际占用的地址,apple 变量的地址是 0x0012ff44,这个地址其实是 apple 变量的首地址,因为 0x0012ff44 只代表一个字节的内存空间,而 apple 是 int 类型的变量,占用 4 个字节,那么 apple 实际占用的是 0x0012ff44、0x0012ff45、0x0012ff46 和 0x0012ff47 四个内存空间,也就是 4 个字节。而 apple 就是首地址就是 0x0012ff44。

       再说 buf 变量,buf 的定义为 char buf[9],则说明 buf 占 9 个字节,而 buf 在 apple 之后定义的,那么 buf 在栈内存中的地址一定是小于 apple 的地址的那是不是只要用 apple 的地址减去 9 就是 buf 的地址呢?其实还不是。虽然 buf 占 9 个字节,但是在 32 位的 CPU 中,内存中的数据一般是按照 4 个字节对齐的(32 位刚好 4 个字节)那么,也就是通过 0x0012ff44 - 0xC 就是 buf 的首地址。内存结构如下图。

       在上图中,标注为红色的部分,就是 buf 变量的内存标注为绿色的部分,则是 apple 变量的内存。其中的白色内存,就是被用来对齐的内存这样是不是浪费了内存。是的!在 32 位系统下,内存按 4 字节对齐,CPU 访问速度是最快的。因此,浪费 3 个字节去进行内存对齐,从而换取 CPU 读取的速度更快,是划得来的。在计算机算法中,经常提到两句话,“用空间换时间”和“用时间换空间”,这显然是“用空间换时间”的情况。从上面的图可以看出,buf 的起始地址是 0x0012ff38。


       第三题,是要让程序输出“hello world”这个字符串。但是从代码中来看,只有在 apple 等于 0x64636261 的时候,才会输出"hello world"字符串。而整个代码中就没有对 apple 进行赋值的代码。而且 0x64636261 又是什么?在第三题的题目中给出提示,0x61 代表小写字母 a 的 ASCII 码,0x62 代表小写字母 b 的 ASCII 码。那么,也就是说让 apple 中填充为字母 abcd 即可。看下图。

cc9c5d2a3a6e3f4a25516e2e76c67d75.png

       只要我们在给 buf 通过 gets 赋值时,输入的内容超过 9 个字符,去覆盖其后面的内存即可。那么要输入多少个字符呢?buf 的长度是 9 个字节,对齐的字节是 3 个字节,apple 的长度是 4,那么一共输入 16 个字符即可,前 12 个随便输入,最后 4 个输入 abcd 即可。

    等等,代码中 apple == 0x64636261,看起来 apple 比较的是 dcba,但是为什么输入的是 abcd 呢?这个是字节顺序的问题,这里不展开讨论,只要了解了字节序的问题,就可以理解了,而字节序在开发网络程序和进行逆向分析时,也算是基础的基础。

演示

       这个程序,我使用 XP + VC6 来进行演示。为什么使用 VC6,因为在新版的 VS 中,已经没有 gets 函数了,因为它不安全,所以被丢弃了。

       把上面的代码录入 VC6 中,然后使用 DEBUG 进行编译(Release编译的话,生成的二进制会被优化,内存结构不明显,溢出的方式也不同,由于是试题,用最简单的方式表明问题即可)。


       编译后,在 gets() 的位置设置断点,然后打开“watch”窗口,来看一下 apple 和 buf 的内存地址,如下图。


       可以看出,apple 的地址是 0x0012ff7c,buf 的内存地址是 0x0012ff70。是不是有疑惑?跟题目中的地址不同!别急!相同的程序在不同的操作系统(比如,XP 和 Win7)上变量的内存地址是不同的,甚至在补丁不同的系统(XP SP2 和 XP SP3)上也可能是不同的。但是,我们注意两点,第一,apple 的地址比 buf 的地址大,第二,apple 的地址和 buf 的地址差 0xC。只要凭这两点来看,和我们前面分析的是相同的。

     接着打开“memory”窗口,来看内存,如下图。

       接着,在 if 的位置处下断点,然后让程序运行起来,我们就可以进行输入了,如下图。

       我这里输入了 12 个 1,因为前 12 个字符随便输入,然后输入了 abcd,输入完成后按下回车,我们在 if 位置处设置的断点被断住了,此时观察内存,如下图。

       从上图可以看到,在 0x0012ff7c 的位置处,也就是 apple 所在的栈空间中,被填充了0x61、0x62、0x63 和 0x64。虽然程序中没有任何位置给 apple 变量赋值,但是我们通过溢出的方式覆盖了 apple 的内存地址,成功的对它进行了赋值。让程序运行起来,观察程序的运行,如下图。

       可以看到,字符串“hello world”被输出了。

总结

       上面把整个题目分析了一下,没有难度,只是一些基础知识。这种题目有什么实际的意义呢?就拿这个题目的代码来举个例子,如果 gets 接收的是一串密码,只有在密码正确的情况下,才会执行特定的功能,而密码的对与否可能有一个标志位。那么及时不知道正确的密码,只要通过溢出去覆盖标志位是不是就可以执行特定的功能了?当然这只是一个简单的例子。对于缓冲区溢出、SQL 注入、XSS 等攻击,它们的问题都是检查不严格而导致 外部输入的数据被当作代码 执行了,从而产生安全的问题。因此,它们的本质是相同的。因此,对于程序员而言,就是不能相信任何的外部输入,一定要对外部输入做严格的检查。

相关文章
|
6天前
|
安全 网络安全 数据安全/隐私保护
XSS 漏洞可能会带来哪些危害?
【10月更文挑战第26天】XSS漏洞可能会给网站和用户带来诸多严重危害
|
2月前
|
安全 JavaScript 前端开发
XSS漏洞的危害
XSS漏洞的危害
|
3月前
|
SQL 安全 Unix
缓冲区溢出攻击
【8月更文挑战第17天】
65 2
|
4天前
|
存储 监控 安全
缓冲区溢出
【10月更文挑战第20天】缓冲区溢出是一种需要引起高度重视的计算机安全问题。开发人员在编程过程中应遵循安全的编程规范,采取有效的防范措施,以避免缓冲区溢出漏洞的出现,从而提高程序的稳定性和安全性。同时,系统管理员和安全防护人员也应加强对系统的监控和防护,及时发现并处理可能存在的缓冲区溢出攻击,保障系统的安全运行。
|
4天前
|
安全 测试技术 网络安全
缓冲区溢出攻击的防范措施有哪些?
【10月更文挑战第20天】缓冲区溢出攻击的防范需要从编程实践、系统配置、漏洞检测与防护以及安全意识教育等多个方面入手,采取综合的防范措施,才能有效地降低缓冲区溢出攻击的风险,保障计算机系统和网络的安全运行。
|
4天前
|
安全 测试技术 网络安全
除了安全的编程实践,还有哪些常见的缓冲区溢出攻击防范方法?
【10月更文挑战第20天】综上所述,防范缓冲区溢出攻击需要综合运用多种方法,从系统安全机制增强、漏洞检测与修复、网络安全防护到安全策略与管理等多个层面入手,形成全方位的安全防护体系,才能有效地抵御缓冲区溢出攻击,保障计算机系统和网络的安全。
|
14天前
|
存储 安全 编译器
内存缓冲区溢出
【10月更文挑战第14天】
30 1
|
6月前
|
存储 安全 测试技术
数组越界:深入理解、危害与防范
数组越界:深入理解、危害与防范
1078 18
|
1月前
|
SQL 监控 安全
sql注入场景与危害
sql注入场景与危害
|
6月前
|
安全 编译器 Shell
什么是缓冲区溢出? 缓冲区溢出攻击的类型?攻击者如何利用缓冲区溢出?如何防止缓冲区溢出攻击?
什么是缓冲区溢出? 缓冲区溢出攻击的类型?攻击者如何利用缓冲区溢出?如何防止缓冲区溢出攻击?
137 0