当前位置:首页 > C++目录 > 正文内容

C++中的溢出

亿万年的星光4年前 (2022-01-22)C++目录23496

一、编程中的溢出

   溢出是C++语言中最常见的漏洞。最常见的溢出包括数组溢出数溢出缓冲区溢出指针溢出以及栈溢出

二、数组溢出

      数组溢出是最常见的一种溢出。因为在C++语言中,含N个元素的数组下标是从0开始,到N-1结束,而且C++语言没有提供数组越界检查的机制。 

 也就是说,一个含有n个元素的数组,其遍历元素的方式为:

             for (int i = 0; i <= n-1 ; ++i) 

一旦发生了数组的溢出,就会造成内存的非法访问。可能会导致程序崩溃,也可能什么都没发生。

三、数溢出

      数的溢出是指数的值超过了他的类型的表示范围。C++标准规定了每个算术类型的最小存储空间,但它并不阻止编译器使用更大的存储空间。 

比如下面代码:

unsigned char ch; 
for (ch = 0; ch <= UCHAR_MAX; ++ch) 
{ 
    // do something... 
}

当ch == UCHAR_MAX时,运行++ch的时候就会发生unsigned char类型的上溢,导致ch重新为0,此时程序会陷入死循环。 

再比如 我们经常使用的int。你如果去存一个10^64的数,一定会溢出。

四、缓冲区溢出

  程序在运行过程中,为了临时存取数据的需要,一般都要分配一些内存空间,通常称这些空间为缓冲区。如果向缓冲区写入超过其本身长度的数据,以致于缓冲区无法容纳,就会造成缓冲区以外的存储单元被改写,这种现象就被称为缓冲区溢出。 

比如下面的代码:

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

void func1(char *s) 
{ 
    char buf[10]; 
    strcpy(buf, s); 
}

void func2(void) 
{ 
    printf("Hacked by me./n"); 
    exit(0); 
}

int main (int argc, char* argv[]) 
{ 
    char badCode[] = "111122223333444455556666"; 
    DWORD* pEIP = (DWORD*)&badCode[16]; 
    *pEIP = (DWORD)func2; 
    func1(badCode); 
    return 0; 
}

   最重要的一点是把pEIP的地址及调用函数做成一串字符串,就会导致缓冲区溢出。 

        字符串处理操作是缓冲区溢出的常见根源,大部分是由于C/C++语言运行时库提供的标准字符串处理函数(strcat、strcpy、sprintf等)不会阻止超出缓冲区结尾的写入操作。所以需要使用更加安全的字符串函数来消除程序中许多潜在的缓冲区溢出。比如可以使用strncat、strncpy、snprintf等函数,来控制字符串操作的实际字符数,避免溢出的问题。而在最新的微软字符串操作库中,已经提供了一些安全的字符串操作函数,如strcpy_s、strcat_s等。

五、栈溢出

  系统的内核栈大小为一个可以设置的定值。一般内核栈的大小为4KB或8KB。因此由于程序的局部变量都分配在栈上,在写程序时模块的局部变量大小之和不应该超过这个栈的大小,否则就会发生栈溢出,而导致系统崩溃,例如下面的程序在Linux系统执行就会造成内核栈溢出:

#include <linux/module.h>

int init_module(void) 
{ 
    char buf[10000]; 
    memset(buf, 0, 10000); 
    printk("kernel stack./n"); 
    return 0; 
}

void cleanup_module(void) 
{ 
    printk("goodbye./n"); 
}

MODULE_LICENSE("GPL");

  buf[10000]分配在栈上,但10000的空间超过了栈的默认大小8KB,所以发生溢出。

        不仅系统栈会有溢出的问题,应用程序也可能会造成栈溢出。最常见的溢出可能就是当用递归算法写程序时,当递归嵌套过深,就会造成栈的溢出。因为递归调用的中间结果将保存在堆栈中。要防止递归造成的栈的溢出,可以跟踪递归的深度,当其大于某个深度就返回。也可以将递归算法改为非递归算法。

六、指针溢出


  指针的溢出可以理解为指针的错误运算,而指向了不该指向的地址。这个地址可能是NULL空间,可能是内核空间,也可能是无效的内存空间。例如:

void* memchr(void *pv, unsigned char ch, size_t size) 
{ 
    unsigned char *pch = (unsigned char *)pv; 
    unsigned char *pchEnd = pch + size; 
    while (pch < pchEnd) 
    { 
        if (*pch == pv) 
        { 
            return (pch); 
        } 
        pch++; 
    } 
    return (NULL); 
}


上面的代码用于查找内存中特定的字符位置。对于其中的while()循环,平时执行似乎都没有任何问题。但是考虑一种特别情况,即pv所指的内存位置为末尾若干个字节,那么因为pchEnd = pch + size,所以pchEnd指向最后一个字符的下一个字节,那么因为pchEnd所指的位置已经不存在,所以会发生指针溢出,因此在程序中应注意内存结尾的计算方式。 

        正确代码如下:

void* memchr(void *pv, unsigned char ch, size_t size) 
{ 
    unsigned char *pch = (unsigned char *)pv; 
    while (--size >= 0) 
    { 
        if (*pch == pv) 
        { 
            return (pch); 
        } 
        pch++; 
    } 
    return (NULL); 
}



    扫描二维码推送至手机访问。

    版权声明:本文由青少年编程知识记录发布,如需转载请注明出处。

    分享给朋友:

    相关文章

    最小生成树—Prim(普里姆)算法

    最小生成树—Prim(普里姆)算法

    一、算法概述Prim 算法是一种用于求解加权无向连通图的最小生成树(MST) 的贪心算法。它从一个顶点开始,逐步扩展生成树,每次选择连接已选顶点集和未选顶点集的最小权重边。二、算法思想初始化:从任意顶...

    如何使用code::blocks编写C++代码

    如何使用code::blocks编写C++代码

    在前面的文章中,已经简单介绍了如何下载code::blocks了,这篇文章介绍一下如何使用code::blocks编写一个C++代码我们打开code::blocks软件,点击”New File“然后点...

    排序算法中的一些分类

    排序算法中的一些分类

    一、比较和非比较的排序二、时间复杂度和稳定性如何界定一个排序算法是否是稳定的?假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=...

    【题解】奶牛的回音

    【题目描述】奶牛们灰常享受在牛栏中牟叫,因為她们可以听到她们牟声的回音。虽然有时候并不能完全听到完整的回音。Bessie曾经是一个出色的秘书,所以她精确地纪录了所有的牟叫声及其回声。她很好奇到底两个声...

    【高级篇】C++ 中string的用法

    【高级篇】C++ 中string的用法

    0.概述string是C++标准库的一个重要部分,本意是字符串,和字符数组不同的是,字符数组是通过一个一个字符模拟的字符串,而string本身就是字符串,string在处理字符串问题时,十分强大。1....

    最小生成树(1)

    最小生成树(1)

    一、定义一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。最小生成树可以用kruskal(克鲁斯卡尔)算法或prim(普里姆)算法求出...