C/C++典型漏洞產生原理與Demo

大囚長發表於2019-01-04

轉載:
https://blog.csdn.net/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,堆噴,地址隨機化繞過,這些屬於漏洞利用的知識。

相關文章