vs2015實現緩衝區溢出攻擊

實驗環境:

編譯器:vs2015
系統:win10 64位


實驗原理

這裏寫圖片描述

如上圖所示,棧地址增長方向是向低地址方向增長的,每次調用函數時,先把參數壓入棧底,然後會把被調用函數的返回地址(此地址爲call指令下一條指令)壓到棧底。另外還需要保存main函數的棧底地址在棧裏面,被調用函數的棧頂指針esp被保存爲該函數的棧底,接下來的低地址位分配局部變量。

如果c/c++沒有檢測局部變量內容越界問題,那麼,局部變量長度過長時,分配的地址空間直接覆蓋了old ebp,同時覆蓋了ret addr。而當ret addr指向的是有意義的地址,就會直接執行相關的代碼,這就是緩衝區攻擊。

參考:
Return-into-libc攻擊及其防禦


實驗步驟:

初建項目

在vs2015中新建一個項目,命名爲SecurityLab,創建Lab.cpp文件,並把以下代碼拷貝到項目中:

#include <stdio.h>
#include <string.h>
void overflow(const char* input)
{
    char buf[8];
    printf("Virtual address of 'buf' = Ox%p\n", buf);
    strcpy(buf, input);
}
void fun()
{
    printf("Function 'fun' has been called without an explicitly invocation.\n");
    printf("Buffer Overflow attack succeeded!\n");
    // your other codes, e.g. deleting files
    // what happens when return?
}
int main()
{
    printf("Virtual address of 'overflow' = Ox%p\n", overflow);
    printf("Virtual address of 'fun' = Ox%p\n", fun);
//  char input[] = "AAAbbbbbbaaa\x44\x12\xC4\x00";//bad input
    //注意這裏是7個A,但其實默認添加了’\0’在最後面
char input[] = "AAAAAAA";//good input, ASCII code of 'A' is 41
    overflow(input);
    return 0;
}

設置斷點

在overflow函數入口設置斷點,同時在overflow函數結束處設置斷點,啓動調試,發現出現以下錯誤:

這裏寫圖片描述

分析:這個是一個警告來的,如今上升到錯誤標準,導致無法進行編譯。

解決方案:
調試→Security屬性→C/C++→SDL檢查
這裏寫圖片描述

當啓動安全檢查時,如果啓動sdl檢查,就會把額外的安全警告作爲錯誤。

查看彙編指令

在main函數overflow入口的斷點處,查看反彙編指令(Ctrl+Alt+D)如下:
這裏寫圖片描述

按F10,轉到push語句,可以發現eax指向的是input存儲的地址0x00ccfec0,如下爲input指向地址空間存儲的內容:(內存窗口搜索得到)
這裏寫圖片描述

可見此時將參數壓入棧中,而add指令對應的地址是作爲call結束之後需要跳到的地址,0x00F319A6 這個地址就是overflow函數調用後需要返回的地址。

初探overflow

進入overflow函數內部,查看彙編指令:
這裏寫圖片描述

這裏ebp指向的值爲0x00ccfde4作爲該函數入口,然後push壓站操作都是爲保護現場。通過查看內存棧空間,可以發現該值再偏移4個字節的地址所存的值正是call overflow下一條指令的地址。寄存器存儲的值如下:
這裏寫圖片描述

而這句話:
這裏寫圖片描述
這裏開始分配空間,

內存細探

當執行到char buf[8]時,通過內存查找buf位置,位置爲0x00ccfdd4,得到內存地址如下:
這裏寫圖片描述

這裏地址是向低位增長的,留意前面一段:
這裏寫圖片描述

後面四位a6 19 f3 00正好是overflow的返回地址,實質爲0x00f319a6,可見此時已經將返回地址壓入裏面了,再前面四位恰好是edi的地址,繼續執行,直到執行完strcpy,發現棧空間變化如下:
這裏寫圖片描述

RTC檢查

查看彙編指令,我們會發現有這個函數:
這裏寫圖片描述

這個函數與基本運行時檢查有關
這裏寫圖片描述

當被設置如下表示時:
這裏寫圖片描述

編譯器會對棧空間情況進行檢查,如果buf越界,就會報以下錯誤:
這裏寫圖片描述

可見,編譯器是可以檢查數組越界問題的,這也是一種安全策略。

Security檢查

這裏寫圖片描述

這裏pop把之前push的內容彈出來,恢復現場,並進行安全檢查,這個與下面設置有關:
這裏寫圖片描述

其可以檢測出堆棧緩衝區溢出,而且能夠與sdl檢查結合,提醒用戶使用安全的類,規避緩衝區溢出風險。
當檢測出堆棧緩衝區溢出時,會報以下錯誤:
這裏寫圖片描述

返回main函數

這裏寫圖片描述
如上圖指令空間所示,爲即將彈出的地址,對比buf的地址0x00ccfdd4與彈出對應的地址相差16字節,而這裏的地址偏移量+4byte正好是overflow出口地址。
這裏寫圖片描述
在這個彙編指令裏,esp爲棧頂指針,始終指向棧頂,而ebp爲臨時指向棧頂的指針,是用來操作相關棧頂操作的,顯然,把棧頂彈出後,esp就會偏移4個字節

當執行完pop指令,esp指向棧頂地址,此時偏移4字節,正好指向overflow出口地址。

從這裏可以看出,我們在編譯器無安全檢查的機制下,完全可以把這個返回地址覆蓋掉,使得返回的地址指向其他函數入口地址,從而實現緩衝區攻擊。

緩衝區溢出攻擊準備

那麼接下來,我們把安全檢查禁用,基本運行檢查設置爲默認值,來通過調試實現緩衝區攻擊:

因爲vs在每次build過程中都會重新分配堆棧空間,所以每次fun函數地址都會更替,那麼我們可以在調試過程中,根據fun的地址直接修改input的值,來完成自定義的其他函數的調用。

另外一方面,在設置安全檢查,以及基本運行檢查,因爲涉及到保護現場狀態,故棧堆中會各增加4個字節,即去掉兩個保護時,buf初始化地址離返回地址會相差12個字節。

緩衝區溢出攻擊

在input定義的時候設置斷點,在沒有賦值前直接修改buf的值,因爲已經經過編譯,所以此時修改重新生成代碼fun的入口地址是一樣的。

這裏寫圖片描述

這裏修改input的值爲
這裏寫圖片描述

則會發現,返回的時候會直接調用到fun函數,觀察內存地址如下:
這裏寫圖片描述

後四位在fun函數調用時的壓棧變成41414141。
此時,窗口打印出fun函數裏的內容:

這裏寫圖片描述

執行完,查看彙編指令

這裏寫圖片描述

這裏寫圖片描述

此時esp指向0051F854地址,此地址存儲着
這裏寫圖片描述

0x0051f890 地址爲返回會調用地址,因爲該地址不是指向某函數入口或者可執行代碼地址,而是
這裏寫圖片描述

所以會有訪問衝突,出現以下信息:

這裏寫圖片描述

至此,緩衝區攻擊完成。

編譯器保護機制

就vs2015編譯器而言,其對於緩衝區溢出的保護是多重的,主要分爲以下幾種:

基本運行時檢查

a /RTCc
向較小的數據類型賦值導致數據丟失,如int a= 4; char c= a;
b./RTCs
堆棧運行時錯誤檢查,其可以檢測到局部變量的溢出和不足,也可以進行堆棧指針認證,確保操作指針不是損壞的。
c./RTCu
保證使用的變量被初始化。
d./RTC1 == /RTCs+/RTCu

參考:
運行時錯誤檢查

安全檢查

/GS 針對檢查緩衝區溢出
對於可能出現緩衝區溢出問題的函數,編譯器將在堆棧上返回地址之前分配空間,在進入函數時,用安全Cookie加載分配的空間。在推出函數時,以及在64bit操作系統上展開幀的過程中,將調用helper函數,以確保Cookie值保持不變,不同的值則表示可能已覆蓋堆棧。如果檢測到不同的值,則終止進程。
受保護項:
函數調用的返回地址,用於函數異常處理程序的地址,易受攻擊的函數參數

SDL安全週期檢查

啓用/GS後,將會執行緩衝區溢出檢測的嚴格模式,等同於#pragma strict_gs_checking(push,on)進行編譯,其會把可能造成緩衝區溢出的warning升級爲error提醒,導致程序無法編譯通過。


思考與總結

實驗的關鍵:

利用局部變量分配棧空間,以及棧空間向低地址增長的特點,來實現局部變量數據溢出而覆蓋高地址位的棧空間,通過恰到好處得覆蓋esp棧頂地址對應的值,從而跳轉到相應代碼的指令執行位置,進而執行相關惡意代碼,實現緩衝區溢出攻擊。

實驗的侷限性:

  1. 編譯器會有相關的安全機制防止緩衝區溢出。
  2. 代碼的執行位置是不可預測的。
  3. 指令地址過大也無法用輸入字符填充。因爲手工輸入的字符轉換爲16進制,只能表示7f以內的,以上的無法用字符填充。

參考:
簡明x86彙編語言教程_司徒彥南

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章