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

来源:互联网 发布:软件测试职位 编辑:程序博客网 时间:2024/06/11 03:51

本篇主要是自己学习笔记,快速读了下泉哥的《漏洞战争》的读书笔记。这里只涉及漏洞产生原理,即漏洞是怎么写出来。至于怎么分析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;}

执行程序,程序将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;}

同样程序执行时,触发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;}

上面,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;}

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

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

这样,当输入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); }

理解这个漏洞的关键是系统对于内存的管理,(后面会深入分析)程序所能拥有的栈内存空间总是有限的,很多需要堆内存,对内存虚拟的内存空间中,堆内存是向上增长的。所以,上面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; // 实例化;}

在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);}

理解这个漏洞并不那么容易,如何导致代码执行也会后面单独来研究,这里只吃别人吃剩的骨头,记录下结论,后面一定要自己研究!!!!!程序释放了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;}

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

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

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

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

原创粉丝点击