龙空技术网

C|输入输出缓冲区与缓冲区溢出

小智雅汇 119

前言:

此刻兄弟们对“c语言栈溢出攻击案件”大概比较着重,大家都需要学习一些“c语言栈溢出攻击案件”的相关内容。那么小编同时在网摘上汇集了一些有关“c语言栈溢出攻击案件””的相关知识,希望我们能喜欢,看官们快快来了解一下吧!

0 问题引出

0.1 连续使用两个scanf输入两个数字:

#include <stdio.h>int main(){    int a;     scanf("%d",&a);     // 连续输入若干个数字回车(标准输入输出是行缓冲)    printf("%d\n",a);    int b;    scanf("%d",&b);     // 连续输入若干个数字回车    printf("%d\n",b);    getchar();// 试图停住控制台窗口}

上面并没有停住控制台窗口,原因是在输入的缓冲区内有一个回车字符可读取,并不需要等待输入。

上面的输入流程是这样的:连续输入若干个数字回车,连续输入的数字给到a,但在输入缓冲区内还有一个回车字符可读取,当再输入一串数字回车后,scanf读取数值型数据的规则是,忽略掉前面的包括回车符在内的前导空白字符(white space)。所以会忽略掉前面的回车符,但后面的回车符还留在缓冲区内,此时被getchar()接收到,程序结束。

下面的程序可以验证并停住控制台窗口

#include <stdio.h>int main(){    int a;          scanf("%d",&a);     //  输入一串数字后回车,行缓冲    printf("%d\n",a);    int b;    scanf("%d",&b);     //  输入一串数字后回车    printf("%d\n",b);    char ch = getchar();// 试图停住控制台窗口    printf("%d\n",ch);  // 10,回车的ASCII是10    getchar();}

也就是说,字符串的输入并不会忽略掉空白字符:

#include <stdio.h>int main(){    int a;         scanf("%d",&a);     // 输入一串数字后回车    printf("%d\n",a);    char b;    scanf("%c",&b);     // 输入一个字符后回车    printf("%c\n",b);    char ch = getchar();// 试图停住控制台窗口    printf("%d\n",ch);  // 10,回车的ASCII是10    getchar();}

在两个输入之间清理一下缓冲区:

#include <stdio.h>int main(){    int a;         scanf("%d",&a);     // 输入一串数字后回车    printf("%d\n",a);    fflush(stdin);      // 将缓冲区的数据刷掉    char b;    scanf("%c",&b);     // 输入一个字符后回车    printf("%c\n",b);    fflush(stdin);      // 将缓冲区的数据刷掉    // c++:    // std::cin.ignore();// 忽略掉回车字符'\n'    getchar();}
1 输入输出缓冲区是什么

输入输出缓冲区是用于输入输出的,一块临时存储数据的内存区域,可以理解为一个一定大小(由函数库定义)的字符数组。

缓冲区中有一个隐式指针用于跟踪字符处理的位置。

C++的三类缓冲区:

2 缓冲区作用

2.1 数据批量准备后批量处理。

2.2 输入输出设备与内存读写速度匹配。

从内存中读取数据要比从文件中读取数据要快得多。

对文件的读写需要用到open、read、write等系统底层函数,而用户进程每调用一次系统函数都要从用户态切换到内核态,等执行完毕后再返回用户态,这种切换要花费一定时间成本(对于高并发程序而言,这种状态的切换会影响到程序性能)。

3 缓冲区的大小

在stdio.h中定义一个宏BUFSIZ,定义了缓冲区的大小。

// STDIO.H/* Buffered I/O macros */#if     defined(_M_MPPC)#define BUFSIZ  4096#else  /* defined (_M_MPPC) */#define BUFSIZ  512#endif /* defined (_M_MPPC) */

文件输入输出的缓冲区大小在定义文件的结构体中定义:

struct _iobuf {char *_ptr;    // 文件输入的下一个位置int _cnt;      // 当前缓冲区的相对位置char *_base;   // 文件的起始位int _flag;     // 文件标志int _file;     // 有效性验证int _charbuf;  // 缓冲区的检査,若无此成员,则不读取int _bufsiz;   // 文件大小char *_tmpfname; // 临时文件名};typedef struct _iobuf FILE;

也可以设置缓冲区:

setbuf(stdin,NULL);
4 缓冲区的类型

缓冲区 分为三种类型:全缓冲、行缓冲和不带缓冲。

4.1 全缓冲

在这种情况下,当填满标准I/O缓存后才进行实际I/O操作。全缓冲的典型代表是对磁盘文件的读写。

文件操作演示全缓冲

#include<fstream>using namespace std; int main(){    //创建文件test.txt并打开    ofstream outfile("test.txt");    //向test.txt文件中写入4096个字符’a’    for(int n=0;n< 4096;n++)    {        outfile << 'a';    }    //暂停,按任意键继续    system("PAUSE");    //继续向test.txt文件中写入字符’b’,也就是说,第4097个字符是’b’    outfile << 'b';    //暂停,按任意键继续    system("PAUSE");    return 0;}

上面这段代码的目的是验证Windows下全缓冲的大小是4096个字节,并验证缓冲区满后会刷新缓冲区,执行真正的I/O操作。

此时打开工程所在文件夹,您会发现test.txxt是空的,这说明4096个字符“a”还在缓冲区,并没有真正执行I/O操作。敲一下回车键,此时会发现test.txt文件的大小虽然是0KB,但打开文件你会发下该文件中已经有了4096个字符“a”。这说明全缓冲区的大小是4K(4096),缓冲区满后执行了I/O操作,而字符“b”还在缓冲区。

再次敲一下回车键,你会发现test.txt文件的大小变成了5KB,此时再打开test.txt文件,您就会发现字符“b”也在其中了。这一步验证了文件关闭时刷新了缓冲区。

4.2 行缓冲

在这种情况下,当在输入和输出中遇到换行符时,执行真正的I/O操作。这时,我们输入的字符先存放在缓冲区,等按下回车键换行时才进行实际的I/O操作。典型代表是键盘输入数据。

键盘操作演示行缓冲

#include <iostream>using namespace std; int main(){    char c;    //第一次调用getchar()函数    //程序执行时,您可以输入或复制一串字符并按下回车键,按下回车键后该函数才返回    c=getchar();    //显示getchar()函数的返回值    cout << c << endl;    //暂停    system("PAUSE");    //循环多次调用getchar()函数    //将每次调用getchar()函数的返回值显示出来    //直到遇到回车符才结束    while((c=getchar())!='\n')    {        printf("%c",c);    }    //暂停    system("PAUSE");    return 0;}

getchar()函数的执行就是采用了行缓冲。第一次调用getchar()函数,会让程序使用者(用户)输入一行字符并直至按下回车键 函数才返回。此时用户输入的字符和回车符都存放在行缓冲区。

再次调用getchar()函数,会逐步输出行缓冲区的内容。

你将上个实例文件test.txt中的字符复制粘入到控制台,您会发现无法继续输入字符,说明缓冲区已满。

4.3 不带缓冲

也就是不进行缓冲,标准出错情况stderr是典型代表,这使得出错信息可以直接尽快地显示出来。

5 缓冲区的刷新

下列情况会引发缓冲区的刷新:

① 缓冲区满时;

② 执行flush或fflsuh语句;(flush:冲洗,冲走)

③ 执行endl语句;(end line)

④ 关闭文件。

可见,缓冲区满或关闭文件时都会刷新缓冲区,进行真正的I/O操作。另外,在C++中,我们可以使用flush函数来刷新缓冲区(执行I/O操作并清空缓冲区),如:cout << flush; //将显存的内容立即输出到显示器上进行显示。刷新字面上的意思是用刷子刷,把原来旧的东西变新了,这里就是改变的意思,例如像缓冲区溢出的时候,多余出来的数据会直接将之前的数据覆盖,这样缓冲区里的数据就发生了改变。

endl控制符的作用是将光标移动到输出设备中下一行开头处,并且清空缓冲区。

cout < < endl;

相当于

cout << ”\n”<< flush;

6 缓存(cache)与缓冲(buffer)

cache分为好几类,有物理上的缓存,也有磁盘上对文件的暂存:

6.1 CPU的Cache

CPU的Cache,它中文名称是高速缓冲存储器,读写速度很快,几乎与CPU一样。由于CPU的运算速度太快,内存的数据存取速度无法跟上CPU的速度,所以在cpu与内存间设置了cache为cpu的数据快取区。当计算机执行程序时,数据与地址管理部件会预测可能要用到的数据和指令,并将这些数据和指令预先从内存中读出送到Cache。一旦需要时,先检查Cache,若有就从Cache中读取,若无再访问内存,现在的CPU还有一级cache,二级cache。简单来说,Cache就是用来解决CPU与内存之间速度不匹配的问题,避免内存与辅助内存频繁存取数据,这样就提高了系统的执行效率。

6.2 磁盘的Cache

磁盘也有cache,硬盘的cache作用就类似于CPU的cache,它解决了总线接口的高速需求和读写硬盘的矛盾以及对某些扇区的反复读取。

6.3 浏览器的Cache

浏览器缓存(Browser Caching)是为了节约网络的资源加速浏览,浏览器在用户磁盘上对最近请求过的文档进行存储,当访问者再次请求这个页面时,浏览器就可以从本地磁盘显示文档,这样就可以加速页面的阅览,并且可以减少服务器的压力。这个过程与下载非常类似,不过下载是用户的主动过程,并且下载的数据一般是长时间保存,游览器的缓存的数据只是短时间保存,可以人为的清空。

Buffer的核心作用是用来缓冲,缓和冲击(对输出设备的冲击,包括磁盘、打印机、显示器)。比如你每秒要写100次硬盘,对系统冲击很大,浪费了大量时间在忙着处理开始写和结束写这两件事嘛。用个buffer暂存起来,变成每10秒写一次硬盘,对系统的冲击就很小,写入效率高了,日子过得爽了,极大的缓和了冲击。

Cache的核心作用是加快取用的速度(加快读取速度,包括CPU读内存(块读取放到物理上的缓存)、内存读磁盘、用户通过浏览器请求资源)。比如你一个很复杂的计算做完了,下次还要用结果,就把结果放手边一个好拿的地方存着,下次不用再算了。加快了数据取用的速度。

缓存是内存上的一块临时存储区域。

7 缓冲区溢出

缓冲区溢出概念中的缓冲区与上述的缓冲区概念又稍有区别,缓冲区溢出的缓冲区是指应用程序用来保存用户输入输出的数据、临时存放数据的内存空间。

如果用户输入的数据长度超出了程序为其分配的内存空间,这些数据就会覆盖程序为其它数据分配的内存空间,形成缓冲区溢出。如果程序存在缓冲区溢出的漏洞,用户向程序传递一个走出其长度的字符串时,如果不是刻意构造的字符串,一般只会出现分段错误Segmentation fault),而不能达到攻击的目的。如果攻击者通过制造缓冲区溢出使程序运行一个用户shell,再通过shell执行其它命令。如果该程序属于root且有suid权限的话,攻击者就获得了一个有root权限的shell,可以对系统进行任意操作了。

#include <stdio.h>#include <string.h>#define PASS_WORD "1234567"int verify_password(char * password){    int authentitated;    char buffer[8];                             // 栈按字长对齐    authentitated = strcmp(password,PASS_WORD); // 如果两个字符串相等,返回值是0    strcpy(buffer,password);                    // 溢出后的字符存入authentitated    return authentitated;}int main(){    int valid_flag = 0;    char password[1024] = {0};    while (1)    {        printf("please input password:");        scanf("%s",password);   // 如果输入任意的8个字符,最后一个字符'\0'会溢出,                                // 溢出的字符会占用authentitated的空间        valid_flag = verify_password(password);        if(valid_flag)            printf("incorrect password!\r\n");        else            printf("Congratulation ! you have passed the verification !\r\n");    }    return 0;}

栈帧图:

-----------------| buffer[0] |-----------------| buffer[…] |-----------------| buffer[7] |-----------------  | authenticated |-----------------| EBP |-----------------

缓冲区溢出除了上述的静态数据溢出,通常还包括栈溢出和堆溢出,也就是数据超出了堆戈 栈的存储空间。

-End-

标签: #c语言栈溢出攻击案件