我的编程空间,编程开发者的网络收藏夹
学习永远不晚

详解C语言之缓冲区溢出

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

详解C语言之缓冲区溢出

一、缓冲区溢出原理

栈帧结构的引入为高级语言中实现函数或过程调用提供直接的硬件支持,但由于将函数返回地址这样的重要数据保存在程序员可见的堆栈中,因此也给系统安全带来隐患。若将函数返回地址修改为指向一段精心安排的恶意代码,则可达到危害系统安全的目的。此外,堆栈的正确恢复依赖于压栈的EBP值的正确性,但EBP域邻近局部变量,若编程中有意无意地通过局部变量的地址偏移窜改EBP值,则程序的行为将变得非常危险。

由于C/C++语言没有数组越界检查机制,当向局部数组缓冲区里写入的数据超过为其分配的大小时,就会发生缓冲区溢出。攻击者可利用缓冲区溢出来窜改进程运行时栈,从而改变程序正常流向,轻则导致程序崩溃,重则系统特权被窃取。

例如,对于下图的栈结构:

若将长度为16字节的字符串赋给acArrBuf数组,则系统会从acArrBuf[0]开始向高地址填充栈空间,导致覆盖EBP值和函数返回地址。若攻击者用一个有意义的地址(否则会出现段错误)覆盖返回地址的内容,函数返回时就会去执行该地址处事先安排好的攻击代码。最常见的手段是通过制造缓冲区溢出使程序运行一个用户shell,再通过shell执行其它命令。若该程序有root或suid执行权限,则攻击者就获得一个有root权限的shell,进而可对系统进行任意操作。

除通过使堆栈缓冲区溢出而更改返回地址外,还可改写局部变量(尤其函数指针)以利用缓冲区溢出缺陷。

注意,本文描述的堆栈缓冲区溢出不同于广义的“堆栈溢出(Stack OverFlow)”,后者除局部数组越界和内存覆盖外,还可能由于调用层次太多(尤其应注意递归函数)或过大的局部变量所导致。

二、缓冲区溢出实例

本节给出若干缓冲区溢出相关的示例性程序。前三个示例为手工修改返回地址或实参,后两个示例为局部数组越界访问和缓冲区溢出。更加深入的缓冲区溢出攻击参见相关资料。

示例函数必须包含stdio.h头文件,并按需包含string.h头文件(如strcpy函数)。

【示例1】改变函数的返回地址,使其返回后跳转到某个指定的指令位置,而不是函数调用后紧跟的位置。实现原理是在函数体中修改返回地址,即找到返回地址的位置并修改它。代码如下:


//foo.c
void foo(void){
    int a, *p;
    p = (int*)((char *)&a + 12);  //让p指向main函数调用foo时入栈的返回地址,等效于p = (int*)(&a + 3);
    *p += 12;    //修改该地址的值,使其指向一条指令的起始地址
}
int main(void){
    foo();
    printf("First printf call\n");
    printf("Second printf call\n");
    return 0;
}

编译运行,结果输出Second printf call,未输出First printf call。

下面详细介绍代码中两个12的由来。

编译(gcc main.c –g)和反汇编(objdump a.out –d)后,得到汇编代码片段如下:

从上述汇编代码可知,foo后面的指令地址(即调用foo时压入的返回地址)是0x80483b8,而进入调用printf("Second printf call“)的指令地址是0x80483c4。两者相差12,故将返回地址的值加12即可(*p += 12)。

指令<804838a>将-8(%ebp)的地址赋值给%eax寄存器(p = &a)。可知foo()函数中的变量a存储在-8(%ebp)地址上,该地址向上8+4=12个单位就是返回地址((char *)&a + 12)。修改该地址内容(*p += 12)即可实现函数调用结束后跳转到第二个printf函数调用的位置。

用gdb查看汇编指令刚进入foo时栈顶的值(%esp),如下所示:

可见%esp值的确是调用foo后main中下条待执行指令的地址,而代码所修改的也正是该值。%eip则指向当前程序(foo)的指令地址。

【示例2】暂存RunAway函数的返回地址后修改其值,使函数返回后跳转到Detour函数的地址;Detour函数内尝试通过之前保存的返回地址重回main函数内。代码如下:


//RunAway.c
int gPrevRet = 0; //保存函数的返回地址
void Detour(void){
    int *p = (int*)&p + 2;  //p指向函数的返回地址
    *p = gPrevRet;
    printf("Run Away!\n"); //需要回车,或打印后fflush(stdout);刷新缓冲区,否则可能在段错误时无法输出
}
int RunAway(void){
    int *p = (int*)&p + 2;
    gPrevRet = *p;
    *p = (int)Detour;
    return 0;
}
int main(void){
    RunAway();
    printf("Come Home!\n");
    return 0;
}

编译运行后输出:

Run Away!

Come Home!

Run Away!

Come Home!

Segmentation fault

运行后出现段错误?There must be something wrong!错误原因留待读者思考,下面给出上述代码的另一版本,借助汇编获取返回地址(而不是根据栈帧结构估算)。


register void *gEbp __asm__ ("%ebp");
void Detour(void){
    *((int *)gEbp + 1) = gPrevRet;
    printf("Run Away!\n");
}
int RunAway(void){
    gPrevRet = *((int *)gEbp + 1);
    *((int *)gEbp + 1) = Detour;
    return 0;
}

【示例3】在被调函数内修改主调函数指针变量,造成后续访问该指针时程序崩溃。代码如下:


//Crasher.c
typedef struct{
    int member1;
    int member2;
}T_STRT;
T_STRT gtTestStrt = {0};
register void *gEbp __asm__ ("%ebp");

void Crasher(T_STRT *ptStrt){
    printf("[%s]: ebp    = %p(0x%08x)\n", __FUNCTION__, gEbp, *((int*)gEbp));
    printf("[%s]: ptStrt = %p(%p)\n", __FUNCTION__, &ptStrt, ptStrt);
    printf("[%s]: (1)    = %p(0x%08x)\n", __FUNCTION__, ((int*)&ptStrt-2), *((int*)&ptStrt-2));
    printf("[%s]: (2)    = %p(0x%08x)\n", __FUNCTION__, (int*)(*((int*)&ptStrt-2)-4), *(int*)(*((int*)&ptStrt-2)-4));
    printf("[%s]: (3)    = %p(0x%08x)\n", __FUNCTION__, (int*)(*((int*)&ptStrt-2)-8), *(int*)(*((int*)&ptStrt-2)-8));
    *(int*)( *( (int*)&ptStrt - 2 ) - 8 ) = 0;  //A:此句将导致代码B处发生段错误
}

int main(void){
    printf("[%s]: ebp    = %p(0x%08x)\n", __FUNCTION__, gEbp, *((int*)gEbp));
    T_STRT *ptStrt = &gtTestStrt;
    printf("[%s]: ptStrt = %p(%p)\n", __FUNCTION__, &ptStrt, ptStrt);

    Crasher(ptStrt);
    printf("[%s]: ptStrt = %p(%p)\n", __FUNCTION__, &ptStrt, ptStrt);
    ptStrt->member1 = 5;  //B:需要在此处崩溃
    printf("Try to come here!\n");
    return 0;
}

运行结果如下所示:

根据打印出的地址及其存储内容,可得到以下堆栈布局:

&ptStrt为形参地址0xbff8f090,该地址处在main函数栈帧中。(int*)&ptStrt - 2地址存储主调函数的EBP值,根据该值可直接定位到main函数栈帧底部。(*((int*)&ptStrt - 2) - 8)为主调函数中实参ptStrt的地址,而*(int*) (*((int*)&ptStrt - 2) - 4) = 0将该地址内容置零,即实参指针ptStrt设置为NULL(不再指向全局结构gtTestStrt)。这样,访问ptStrt->member1时就会发生段错误。

注意,虽然本例代码结构简单,但不能轻率地推断main函数中局部变量ptStrt位于帧基指针EBP-4处(实际上本例为EBP-8处)。以下改进版本用于自动计算该偏移量:


static int gOffset = 0;
void Crasher(T_STRT *ptStrt){
   *(int*)( *(int*)gEbp - gOffset ) = 0;
}

int main(void){
    T_STRT *ptStrt = &gtTestStrt;
    gOffset = (char*)gEbp - (char*)(&ptStrt);
    Crasher(ptStrt);
    ptStrt->member1 = 5;  //在此处崩溃
    printf("Try to come here!\n");
    return 0;
}

当然,该版本已失去原有意义(不借助寄存器层面手段),纯为示例。

【示例4】越界访问造成死循环。代码如下:


//InfinteLoop.c
void InfinteLoop(void){ 
    unsigned char ucIdx, aucArr[10]; 
    for(ucIdx = 0; ucIdx <= 10; ucIdx++)
        aucArr[ucIdx] = 1;
}

在循环内部,当访问不存在的数组元素aucArr[10]时,实际上在访问数组aucArr所在地址之后的那个位置,而该位置存放着变量ucIdx。因此aucArr[10] = 1将ucIdx重置为1,然后继续循环的条件仍然成立,最终将导致死循环。

【示例5】缓冲区溢出。代码如下:


//CarelessPapa.c
register int *gEbp __asm__ ("%ebp");
void NaughtyBoy(void){
    printf("[2]EBP=%p(%#x), EIP=%p(%#x)\n", gEbp, *gEbp, gEbp+1, *(gEbp+1));
    printf("Catch Me!\n");
}
void CarelessPapa(const char *pszStr){
    printf("[1]EBP=%p(%#x)\n", gEbp, *gEbp);
    printf("[1]EIP=%p(%#x)\n", gEbp+1, *(gEbp+1));
    char szBuf[8];
    strcpy(szBuf, pszStr);
}
int main(void){
    printf("[0]EBP=%p(%#x)\n", gEbp, *gEbp);
    printf("Addr: CarelessPapa=%p, NaughtyBoy=%p\n", CarelessPapa, NaughtyBoy);
    char szArr[]="0123456789AB\xe4\x83\x4\x8\x23\x85\x4\x8";
    CarelessPapa(szArr);
    printf("Come Home!\n");
    printf("[3]EBP=%p\n", gEbp);
    return 0;
}

编译运行结果如下:

可见,当CarelessPapa函数调用结束后,并未直接执行Come Home的输出,而是转而执行NaughtyBoy函数(输出Catch Me),然后回头输出Come Home。该过程重复一次后发生段错误(具体原因留待读者思考)。

结合下图所示的栈帧布局,详细分析本示例缓冲区溢出过程。注意,本示例中地址及其内容由内嵌汇编和打印输出获得,正常情况下应通过gdb调试器获得。

首先,main函数将字符数组szArr的地址作为参数(即pszStr)传递给函数CarelessPapa。该数组内容为"0123456789AB\xe4\x83\x4\x8\x23\x85\x4\x8",其中转义字符串"\xe4\x83\x4\x8"对应NaughtyBoy函数入口地址0x080483e4(小字节序),而"\x23\x85\x4\x8"对应调用CarelessPapa函数时的返回地址0x8048523(小字节序)。CarelessPapa函数内部调用strcpy库函数,将pszStr所指字符串内容拷贝至szBuf数组。因为strcpy函数不进行越界检查,会逐字节拷贝直到遇见'\0'结束符。故pszStr字符串将从szBuf数组起始地址开始向高地址覆盖,原返回地址0x8048523被覆盖为NaughtyBoy函数地址0x080483e4。

这样,当CarelessPapa函数返回时,修改后的返回地址从栈中弹出到EIP寄存器中,此时栈顶指针ESP指向返回地址上方的空间(esp+4),程序跳转到EIP所指地址(NaughtyBoy函数入口)开始执行,首先就是EBP入栈——并未像正常调用那样先压入返回地址,故NaughtyBoy函数栈帧中EBP位置相对CarelessPapa函数上移4个字节!此时,"\x23\x85\x4\x8"可将EBP上方的EIP修改为CarelessPapa函数的返回地址(0x8048523),从而保证正确返回main函数内。

注意,返回main函数并输出Come Home后,main函数栈帧的EBP地址被改为0x42413938("89AB"),该地址已非堆栈空间,最终产生段错误。EBP地址会随每次程序执行而改变,故试图在szArr字符串中恢复EBP是非常困难的。

从main函数return时将返回到调用它的启动例程(_start函数)中,返回值被启动例程获得并用其作为参数调用exit函数。exit函数首先做一些清理工作,然后调用_exit系统调用终止进程。main函数的返回值最终传给_exit系统调用,成为进程的退出状态。以下代码在main函数中直接调用exit函数终止进程而不返回到启动例程:


//CarelessPapa.c
register int *gEbp __asm__ ("%ebp");
void NaughtyBoy(void){
    printf("[2]EBP=%p(%#x), EIP=%p(%#x)\n", gEbp, *gEbp, gEbp+1, *(gEbp+1));
    printf("Catch Me!\n");
}
void CarelessPapa(const char *pszStr){
    printf("[1]EBP=%p(%#x)\n", gEbp, *gEbp);
    printf("[1]EIP=%p(%#x)\n", gEbp+1, *(gEbp+1));
    char szBuf[8];
    strcpy(szBuf, pszStr);
}
int main(void){
    printf("[0]EBP=%p(%#x)\n", gEbp, *gEbp);
    printf("Addr: CarelessPapa=%p, NaughtyBoy=%p\n", CarelessPapa, NaughtyBoy);
    char szArr[]="0123456789AB\x14\x84\x4\x8\x33\x85\x4\x8"; //转义字符串稍有变化
    CarelessPapa(szArr);
    printf("Come Home!\n");
    printf("[3]EBP=%p\n", gEbp);
    exit(0); //#include <stdlib.h>
}

编译运行结果如下:

这次没有重复执行,也未出现段错误。

三、缓冲区溢出防范

防范缓冲区溢出问题的准则是:确保做边界检查(通常不必担心影响程序效率)。不要为接收数据预留相对过小的缓冲区,大的数组应通过malloc/new分配堆空间来解决;在将数据读入或复制到目标缓冲区前,检查数据长度是否超过缓冲区空间。同样,检查以确保不会将过大的数据传递给别的程序,尤其是第三方COTS(Commercial-off-the-shelf)商用软件库——不要设想关于其他人软件行为的任何事情。

若有可能,改用具备防止缓冲区溢出内置机制的高级语言(Java、C#等)。但许多语言依赖于C库,或具有关闭该保护特性的机制(为速度而牺牲安全性)。其次,可以借助某些底层系统机制或检测工具(如对C数组进行边界检查的编译器)。许多操作系统(包括Linux和Solaris)提供非可执行堆栈补丁,但该方式不适于这种情况:攻击者利用堆栈溢出使程序跳转到放置在堆上的执行代码。此外,存在一些侦测和去除缓冲区溢出漏洞的静态工具(检查代码但并不运行)和动态工具(执行代码以确定行为),甚至采用grep命令自动搜索源代码中每个有问题函数的实例。

但即使采用这些保护手段,程序员自身也可能犯其他许多错误,从而引入缺陷。例如,当使用有符号数存储缓冲区长度或某个待读取内容长度时,攻击者可将其变为负值,从而使该长度被解释为很大的正值。经验丰富的程序员还容易过于自信地"把玩"某些危险的库函数,如对其添加自己总结编写的检查,或错误地推论出使用潜在危险的函数在某些特殊情况下是"安全"的。

本节将主要讨论一些已被证明危险的C库函数。通过在C/C++程序中禁用或慎用危险的函数,可有效降低在代码中引入安全漏洞的可能性。在考虑性能和可移植性的前提下,强烈建议在开发过程中使用相应的安全函数来替代危险的库函数调用。

以下分析某些危险的库函数,较完整的列表参见表3-1。

3.1、gets

该函数从标准输入读入用户输入的一行文本,在遇到EOF字符或换行字符前,不会停止读入文本。即该函数不执行越界检查,故几乎总有可能使任何缓冲区溢出(应禁用)。

gcc编译器下会对gets调用发出警告(the `gets' function is dangerous and should not be used)。

3.2、strcpy

该函数将源字符串复制到目标缓冲区,但并未指定要复制字符的数目。若源字符串来自用户输入且未限制其长度,则可能引发危险。规避的方法如下:

1) 若知道目标缓冲区大小,则可添加明确的检查(不建议该法):


if(strlen(szclass="lazy" data-src) >= dwDstSize){
    
}
else{
    strcpy(szDst, szclass="lazy" data-src);
}

2) 改用strncpy函数:


strncpy(szDst, szclass="lazy" data-src, dwDstSize-1);
szDst[dwDstSize-1] = '\0';  //Always do this to be safe!

若szclass="lazy" data-src比szDst大,则该函数不会返回错误;当达到指定长度(dwDstSize-1)时,停止复制字符。第二句将字符串结束符放在szDst数组的末尾。

3) 在源字符串上调用strlen()来为其分配足够的堆空间:


pszDst = (char *)malloc(strlen(szclass="lazy" data-src));
strcpy(pszDst, szclass="lazy" data-src);

4) 某些情况下使用strcpy不会带来潜在的安全性问题:


strcpy(szDst, "Hello!");  //Usually by initialization, such as char szDst[] = “Hello!”;

即使该操作造成szDst溢出,但这几个字符显然不会造成危害——除非用其它方式覆盖字符串“Hello”所在的静态存储区。

安全的字符串处理函数通常体现在如下几个方面:

  • 显式指明目标缓冲区大小
  • 动态校验
  • 返回码(以指明成功或失败原因)

与strcpy函数具有相同问题的还有strcat函数。

3.3、 strncpy/strncat

该对函数是strcpy/strcat调用的“安全”版本,但仍存在一些问题:

1) strncpy和strncat要求程序员给出剩余的空间,而不是给出缓冲区的总大小。缓冲区大小一经分配就不再变化,但缓冲区中剩余的空间量会在每次添加或删除数据时发生变化。这意味着程序员需始终跟踪或重新计算剩余的空间,而这种跟踪或重新计算很容易出错。

2) 在发生溢出(和数据丢失)时,strncpy和strncat返回结果字符串的起始地址(而不是其长度)。虽然这有利于链式表达,但却无法报告缓冲区溢出。

3) 若源字符串长度至少和目标缓冲区相同,则strncpy不会使用NUL来结束字符串;这可能会在以后导致严重破坏。因此,在执行strncpy后通常需要手工终止目标字符串。

4) strncpy还可复制源字符串的一部分到目标缓冲区,要复制的字符数目通常基于源字符串的相关信息来计算。这种操作也会产生未终止字符串。

5) strncpy会在源字符串结束时使用NUL来填充整个目标缓冲区,这在源字符串较短时存在性能问题。

3.4、sprintf

该函数使用控制字符串来指定输出格式,该字符串通常包括"%s"(字符串输出)。若指定字符串输出的精确指定符,则可通过指定输出的最大长度来防止缓冲区溢出(如%.10s将复制不超过10个字符)。也可以使用"*"作为精确指定符(如"%.*s"),这样就可传入一个最大长度值。精确字段仅指定一个参数的最大长度,但缓冲区需要针对组合起来的数据的最大尺寸调整大小。

注意,"字段宽度"(如"%10s",无点号)仅指定最小长度——而非最大长度,从而留下缓冲区溢出隐患。

3.5、scanf

scanf系列函数具有一个最大宽度值,函数不能读取超过最大宽度的数据。但并非所有规范都规定了这点,也不确定是否所有实现都能正确执行这些限制。若要使用这一特性,建议在安装或初始化期间运行小测试来确保它能正确工作。

3.6、streadd/strecpy

这对函数可将含有不可读字符的字符串转换成可打印的表示。其原型包含在libgen.h头文件内,编译时需加-lgen [library ...]选项。

char *strecpy(char *pszOut, const char *pszIn, const char *pszExcept);

char *streadd(char *pszOut, const char *pszIn, const char *pszExcept);

strecpy将输入字符串pszIn(连同结束符)拷贝到输出字符串pszOut中,并将非图形字符展开为C语言中相应的转义字符序列(如Control-A转为“\001”)。参数pszOut指向的缓冲区大小必须足够容纳结果字符串;输出缓冲区大小应为输入缓冲区大小的四倍(单个字符可能转换为\abc共四个字符)。出现在参数pszExcept字符串内的字符不被展开。该参数可设为空串,表示扩展所有非图形字符。strecpy函数返回指向pszOut字符串的指针。

streadd函数与strecpy相同,只不过返回指向pszOut字符串结束符的指针。

考虑以下代码:


#include <libgen.h>
int main(void){
    char szBuf[20] = {0};
    streadd(szBuf, "\t\n", "");
    printf(%s\n", szBuf);
    return 0;
}

打印输出\t\n,而不是所有空白。

3.7、strtrns

该函数将pszStr字符串中的字符转换后复制到结果缓冲区pszResult。其原型包含在libgen.h头文件内:

char * strtrns(const char *pszStr, const char *pszOld, const char *pszNew, char *pszResult);

出现在pszOld字符串中的字符被pszNew字符串中相同位置的字符替换。函数返回新的结果字符串。

如下示例将小写字符转换成大写字符:


#include <libgen.h>
int main(int argc,char *argv[]){
    char szLower[] = "abcdefghijklmnopqrstuvwxyz";
    char szUpper[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    if(argc < 2){
        printf("USAGE: %s arg\n", argv[0]);
        exit(0);
    }
    char *pszBuf = (char *)malloc(strlen(argv[1]));
    strtrns(argv[1], szLower, szUpper, pszBuf);
    printf("%s\n", pszBuf);
    return 0;
}

以上代码使用malloc分配足够空间来复制argv[1],因此不会引起缓冲区溢出。

3.8、realpath

该函数在libc 4.5.21及以后版本中提供,使用时需要limits.h和stdlib.h头文件。其原型为:

char *realpath(const char *pszPath, char *pszResolvedPath);

该函数展开pszPath字符串中的所有符号链接,并解析pszPath中所引用的/./、/../和'/'字符(相对路径),最终生成规范化的绝对路径名。该路径名作为带结束符的字符串存入pszResolvedPath指向的缓冲区,长度最大为PATH_MAX字节。结果路径中不含符号链接、/./或/../。

若pszResolvedPath为空指针,则realpath函数使用malloc来分配PATH_MAX字节的缓冲区以存储解析后的路径名,并返回指向该缓冲区的指针。调用者应使用free函数去释放该该缓冲区。

若执行成功,realpath函数返回指向pszResolvedPath(规范化绝对路径)的指针;否则返回空指针并设置errno以指示该错误,此时pszResolvedPath的内容未定义。

调用者需要确保结果缓冲区足够大(但不应超过PATH_MAX),以处理任何大小的路径。此外,不可能为输出缓冲区确定合适的长度,因此POSIX.1-2001规定,PATH_MAX字节的缓冲区足够,但PATH_MAX不必定义为常量,且可以通过pathconf函数获得。然而,pathconf输出的结果可能超大,以致不适合动态分配内存;另一方面,pathconf函数可返回-1表明结果路径名超出PATH_MAX限制。pszResolvedPath为空指针的特性被POSIX.1-2008标准化,以避免输出缓冲区长度难以静态确定的缺陷。

应禁用或慎用的库函数如下表所示:

表3-1

函数

危险性

解决方案

gets

最高

禁用gets(buf),改用fgets(buf, size, stdin)

strcpy

检查目标缓冲区大小,或改用strncpy,或动态分配目标缓冲区

strcat

改用strncat

sprintf

改用snprintf,或使用精度说明符

scanf

使用精度说明符,或自己进行解析

sscanf

使用精度说明符,或自己进行解析

fscanf

使用精度说明符,或自己进行解析

vfscanf

使用精度说明符,或自己进行解析

vsprintf

改为使用vsnprintf,或使用精度说明符

vscanf

使用精度说明符,或自己进行解析

vsscanf

使用精度说明符,或自己进行解析

streadd

确保分配的目标参数缓冲区大小是源参数大小的四倍

strecpy

确保分配的目标参数缓冲区大小是源参数大小的四倍

strtrns

手工检查目标缓冲区大小是否至少与源字符串相等

getenv

不可假定特殊环境变量的长度

realpath

高(或稍低,实现依赖)

分配缓冲区大小为PATH_MAX字节,并手工检查参数以确保输入参数和输出参数均不超过PATH_MAX

syslog

高(或稍低,实现依赖)

将字符串输入传递给该函数之前,将所有字符串输入截成合理大小

getopt

高(或稍低,实现依赖)

将字符串输入传递给该函数之前,将所有字符串输入截成合理大小

getopt_long

高(或稍低,实现依赖)

将字符串输入传递给该函数之前,将所有字符串输入截成合理大小

getpass

高(或稍低,实现依赖)

将字符串输入传递给该函数之前,将所有字符串输入截成合理大小

getchar

若在循环中使用该函数,确保检查缓冲区边界

fgetc

若在循环中使用该函数,确保检查缓冲区边界

getc

若在循环中使用该函数,确保检查缓冲区边界

read

若在循环中使用该函数,确保检查缓冲区边界

bcopy

确保目标缓冲区不小于指定长度

fgets

确保目标缓冲区不小于指定长度

memcpy

确保目标缓冲区不小于指定长度

snprintf

确保目标缓冲区不小于指定长度

strccpy

确保目标缓冲区不小于指定长度

strcadd

确保目标缓冲区不小于指定长度

strncpy

确保目标缓冲区不小于指定长度

vsnprintf

确保目标缓冲区不小于指定长度

以上就是详解C语言之缓冲区溢出的详细内容,更多关于C语言 缓冲区溢出的资料请关注编程网其它相关文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

详解C语言之缓冲区溢出

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

c++缓冲区溢出如何解决

C++缓冲区溢出是一种常见的安全漏洞,可以通过一些方法来解决:1. 检查输入长度:在接收用户输入或处理外部数据时,始终检查输入长度,确保不会超过缓冲区的容量。可以使用函数如`strncpy()`或`strlcpy()`来复制字符串,并指定目
2023-09-13

缓冲区溢出之LINUX实例

缓冲区溢出是一种常见的安全漏洞,当输入数据超过了程序分配的缓冲区大小时,多余的数据会溢出到相邻的内存区域,导致程序崩溃或者被攻击者利用来执行恶意代码。下面是一个LINUX实例,展示了一个简单的缓冲区溢出漏洞:```c#include #in
2023-09-15

c语言strcpy引起的缓冲区溢出问题怎么解决

解决strcpy引起的缓冲区溢出问题可以采取以下几种方法:使用更安全的函数:可以使用strncpy函数来替代strcpy函数,strncpy函数会限制复制的长度,避免溢出。手动检查字符串长度:在使用strcpy函数复制字符串之前,可以手动检
c语言strcpy引起的缓冲区溢出问题怎么解决
2024-03-02

Redis缓冲区溢出怎么解决

这篇文章主要介绍“Redis缓冲区溢出怎么解决”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Redis缓冲区溢出怎么解决”文章能帮助大家解决问题。缓冲区(buffer),是内存空间的一部分。也就是说
2023-07-06

c++缓冲区溢出的原因有哪些

C++缓冲区溢出的原因可以归纳为以下几点:输入验证不足:当用户输入的数据长度超过了程序预留的缓冲区长度时,就会导致缓冲区溢出。如果在读取用户输入之前没有对输入数据进行合法性检查和长度验证,缓冲区溢出就有可能发生。字符串处理函数的不安全使用:
2023-10-23

win10缓冲区溢出问题怎么解决

Win10缓冲区溢出问题可以通过以下几种方法来解决:1. 更新操作系统:缓冲区溢出问题通常是由于操作系统中的漏洞引起的。因此,更新操作系统可以修复这些漏洞,从而减少缓冲区溢出的风险。2. 使用防病毒软件:许多病毒和恶意软件利用缓冲区溢出漏洞
2023-06-10

了解 Go 通道处理/缓冲区溢出

珍惜时间,勤奋学习!今天给大家带来《了解 Go 通道处理/缓冲区溢出》,正文内容主要涉及到等等,如果你正在学习Golang,或者是对Golang有疑问,欢迎大家关注我!后面我会持续更新相关内容的,希望都能帮到正在学习的大家!问题内容我继承了
了解 Go 通道处理/缓冲区溢出
2024-04-05

Redis缓冲区溢出及解决方案分享

目录一、Redis缓冲区溢出影响1、缓冲区溢出导致网络连接关闭2、缓冲区溢出导致命令数据丢失或者崩溃二、客户端缓冲区三、复制缓冲区1、复制缓冲区2、复制积压缓冲区四、AOF缓冲区1、AOF缓冲区2、AOF重写缓冲区五、总结缓冲区(buffe
2023-04-13

win11基于堆栈的缓冲区溢出怎么解决

基于堆栈的缓冲区溢出是一种常见的安全漏洞,攻击者通过向程序输入超过预留内存空间的数据,覆盖了程序的堆栈空间,从而控制程序的执行流程,可能导致程序崩溃、代码执行、拒绝服务等问题。为了解决这个问题,可以采取以下几种方法:输入验证:在接收用户输入
2023-10-26

Linux文件系统之缓冲区详解

本文介绍了Linux文件系统中的缓冲区,阐述了其作用是缓存经常访问的数据块,从而提高性能、减少I/O负载和提高并发性。缓冲区主要包括页面缓存和块设备缓存。其操作由read_block()、write_block()、invalidate_page()和drop_cache()等函数处理。虽然缓冲区有优势,但也有内存占用、数据一致性和电源故障风险等缺点。最佳实践包括调整缓存大小、定期刷新缓存、禁用不必要的缓存和使用预读。
Linux文件系统之缓冲区详解
2024-04-02

c语言中缓冲区问题的示例分析

这篇文章主要介绍了c语言中缓冲区问题的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。发现问题你是不是总会出现当你输入的时候(你想的是只输出一个内容),但是最后却输入两
2023-06-25

c语言内存溢出如何解决

在C语言中,内存溢出是指程序在运行过程中使用的内存超出了系统所分配给它的内存空间。内存溢出可能导致程序崩溃、数据丢失或安全漏洞等问题。以下是一些常见的解决内存溢出问题的方法:1. 动态内存分配管理:使用malloc()、calloc()和r
2023-09-13

c语言内存溢出问题如何解决

C语言内存溢出问题可以通过以下几种方式来解决:1. 检查代码逻辑:检查代码中的循环、递归、动态内存分配等地方是否存在错误,比如没有正确释放内存或者使用了未初始化的指针。2. 动态内存管理:在使用动态内存分配函数(如malloc、calloc
2023-10-10

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录