C/C++典型漏洞产生原理与Demo

本文深入探讨了C/C++编程语言中的常见安全漏洞,包括栈溢出、堆溢出、整数溢出、格式化字符串漏洞、UAF、DoubleFree、数组越界等,详细分析了每种漏洞的产生原理及其可能造成的安全威胁。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转载:
https://blog.youkuaiyun.com/u010651541/article/details/76165216

本篇主要是自己学习笔记,快速读了下泉哥的《漏洞战争》的读书笔记。这里只涉及漏洞产生原理,即漏洞是怎么写出来。至于怎么分析0Day,怎么写代码执行的exp,后续将做深入研究。

C/C++的代码执行漏洞,很多时候是劫持程序的控制流。具体来说:对于C程序,一般是控制函数的返回地址,让程序跳转到我们指定的地方执行。对于C++程序,除了覆盖函数返回地址外,还可以覆盖虚函数表,在调用虚函数的时候,程序将到指定内存处执行。

一、栈溢出漏洞

栈溢出漏洞原理十分简单,只需要了解C语言函数调用时程序的内存结构即可。一句话总结,可控的参数覆盖了函数的返回地址。

#include<stdio.h>
#include<string.h>
void vulfunc(char* str){
    char src[10];
    strcpy(src,str);
}
int main(){
    char* str="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
    vulfunc(str);
    return;
}
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

执行程序,程序将crash,并触发Segmentation fault错误。

二、堆溢出漏洞

堆溢出漏洞和栈溢出漏洞原理类似,但是比栈溢出复杂得多,后面单独介绍原理。

#include<stdlib.h>
#include<string.h>
int main(int argc,char* argv[]){
    char* first,second;
    first = malloc(100);
    second = malloc(12);
    if(argc>1){
        strcpy(first,argv[1]);
    }
    free(fisrt);
    free(second);
    return 0;
}
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

同样程序执行时,触发Segmentation fault错误而crash掉。

三、整数溢出

C/C++非内存安全行语言,当输入的整数超出整数的取值范围时,编译器并不会报出错误,但是程序执行时,可能造成严重的安全后果。不过最终导致代码执行,还是归因到栈溢出或者堆溢出。

//栈的整数溢出
#include<stdio.h>
#include<string.h>
int main(int argc,char* argv){
    int i;
    char buf[8];
    unsigned short int size;
    char overflow[65550];
    memset(overflow,65,sizeof(overflow));
    printf("please input size:\n");
    scanf("%d",&i);
    size =i;
    printf("size:%d",size);
    printf("i:%d",i);
    if(size > 8){
        return -1;
    }
    //stack overflow
    memcpy(buf,overflow,i);
    return 0;
}
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

上面,size类型为unsigned int,最大取值为65535,当超过这个值时,截断导致越过size检查,然后,在memcpy()函数函数中造成栈溢出,程序crash,可能导致代码执行。堆上的整数溢出同理,只是溢出对象是堆数据,导致覆盖的时候后面的堆结构。

四、格式化字符串漏洞

#include<stdio.h>
#include<string.h>
int main(int argc,char* argv[]){
    char buff[1024];
    strncpy(buff,argv[1],sizeof(buff)-1);
    printf(buff);
    return 0;
}
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

当输入参数为mmm%s或者ttt%x时,程序将产生意向不到的结果。printf()函数的基本形式为:

printf("格式化字符串""参数列表");
 
  • 1

这样,当输入ttt%s时,%s不会被当做数据,而是被当成了格式化字符串处理,程序会读取栈上argv[1]后面一个栈上的数据,造成了内存数据的泄露。当输入中包含很多这样的字符时,将可以遍历栈上的数据。
这里写图片描述

五、UAF漏洞

#include<stdio.h>
#define size 32;
int main(int argc,char** argv){
    char *buf1;
    char *buf2;
    buf1=(char*) malloc(size);
    printf("buf1:0x%p\n",buf1);
    free(buf1);
    //buf1=null;

    buf = (char*)malloc(size);
    printf("buf2:0x%p\n",buf2);

    memset(buf2,0,size);
    printf("buf2:%d\n",*buf2);

    printf("After free\n");
    strncpy(buf1,"hack",5);

    printf("buf2:%s\n\n",buf2);
    free(buf2); 
}
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

理解这个漏洞的关键是系统对于内存的管理,(后面会深入分析)程序所能拥有的栈内存空间总是有限的,很多需要堆内存,对内存虚拟的内存空间中,堆内存是向上增长的。所以,上面buf1在被free之后,内存管理在再次分配buf2的时候,会认为buf1地址处的内存已经没有用,会分配给buf2,即buf2地址指向了之前buf1地址处。关键是,没有做buf1=NULL的操作,导致buf1成为”野指针”,依然有效。这样,后续如果能够操作到buf1的指针,就可以长生意想不到的结构。如果buf1处为执行指令,则导致了CE。
这里写图片描述

class CTest{
    int m=1;
public:
    virtual void vFunc1();
    virtual void vFunc2();
    int getM(){
        return m;
    }
}
void main(){
    CTest test; // 实例化;
}
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

在C++代码中,UAF导致CE更加普遍,以为我们常常需要通过new来创建一个对象。如果后面再free()了对应的对象指针后(体现free),没有对指针置NULL。那么攻击者可能通过”占坑式”来让一个指针指向对象的地址,然后利用此指针修改对象的虚函数表。此时,如果对象再次被使用,尤其是虚函数被调用时(体现use after),将导致CE。

六、Double Freee

#include<stdio.h>
int main(int argc,char **argv){
    void * p1,p2,p3;

    p1=malloc(100);
    printf("malloc:%p\n",p1);
    p2=malloc(100);
    printf("malloc:%p\n",p2);
    p3=malloc(100);
    printf("malloc:%p\n",p3);

    pritf("free p1\n");
    free(p1);
    pritf("free p3\n");
    free(p3);
    pritf("free p2\n");
    free(p2);

    printf("Double free\n");
    free(p2);
}

 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

理解这个漏洞并不那么容易,如何导致代码执行也会后面单独来研究,这里只吃别人吃剩的骨头,记录下结论,后面一定要自己研究!!!!!程序释放了p1,p3之后,在释放p2时,会发生堆内存的合并动作,将改变原有的堆头信息及前后向指针。再次释放时,free动作其实应该是引用了之前的地址,所以导致了崩溃。这里,后面研究时,应该要研究系统对堆内存的回收过程。按照泉哥的结论,这根本上是个UAF漏洞,UAF漏洞是比较容易理解的,所以理解内存回收应该是关键。
这里写图片描述

七、数组越界

数组越界通常包括读越界和写越界,写越界会造成溢出漏洞。

#include<"stdio.h">
int main(){
    int index;
    int array[3]={111,222,333};
    printf("please input index:\n");
    scanf("%d",&index);
    printf("array[%d]=%d\n",index,array[index]);
    //写越界,造成溢出;
    a[index]=233;
    return 0;
}
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

上面在读取时会造成数组读的越界,赋值时造成(栈)溢出。

以上总结了常见的漏洞类型,其中还有很多知识点需要单独补充:

  • 堆溢出原理分析;
  • linux对数据分配和回收的管理;
  • double free漏洞深入分析;

初次之外,后续要研究的包括:ROP,堆喷,地址随机化绕过,这些属于漏洞利用的知识。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值