0x00 缺陷代碼與利用
1、實驗環境
|
環境 |
備註 |
編譯器 |
VS2017 |
|
操作系統 |
Windows7專業版 |
|
實驗其他條件 |
VS2017中工程禁用DEP、SDL、GS、DYNAMICBASE編譯選項 |
爲研究GS作用,先關閉其查看程序運行狀態。爲使得實驗代碼漏洞容易利用和原理講解其他編譯選項一併關閉。否則本實驗無法重現。 |
2、實驗代碼
實驗代碼如下所示:
#include <stdio.h>
#include <stdlib.h>
#include<string.h>
#define PASSWORD "1234567"
int verify_password(char *password)
{
int authenticated = 128;
char buffer[8];
authenticated = strcmp(password, PASSWORD);
strcpy(buffer, password);//over flowed here!
return authenticated;
}
int main()
{
int valid_flag = 0;
char password[1024];
FILE * fp;
if (!(fp = fopen("gs.txt", "r")))
{
printf("open file failure!\n");
return 0;
}
fscanf(fp, "%s", password);
valid_flag = verify_password(password);
if (valid_flag)
{
printf("incorrect password!\n");
}
else
{
printf("Congratulation! You have passed the verification!\n");
}
fclose(fp);
system("pause");
return 0;
}
上述程序的功能是讀取txt文件中的字符串,並把讀取的字符串作爲參數調用verify_password函數進行密碼的驗證。
在verify_password函數中使用了不安全函數strcpy,由於沒有其他越界訪問的限制,此處會造成棧緩衝區溢出。
3、漏洞利用
我們知道棧緩衝區溢出能夠引起如下幾個問題:
- 修改溢出變量相鄰高地址的其他變量
- 修改函數的返回地址,改變程序執行流程或者調用帶入危險參數的函數
- 棧中植入惡意Shellcode代碼並執行
本篇文章重在解析/GS安全編譯選項的原理,以上漏洞的利用不會詳細講解(後續會出二進制漏洞方面的文章,敬請關注)。爲減少讀者學習成本,本文使用最簡單的修改返回地址進行漏洞利用與/GS的防禦作用,並且也不會有其他任何程序的正常恢復操作。
爲了使得溢出的數據能夠修改棧中保存的返回地址,必須要清楚程序運行時棧中數據存放的結構,首先使用正常不會溢出的數據運行程序,txt文件中的內容(二進制)我們設置如下:
程序運行斷點設置在strcpy後,此時程序的棧結構如下所示:
左邊是IDA調試時棧中的數據,通過ALT+G跳轉到ebp能夠很容易發現txt中對應的數據,並據此畫出verify_password函數棧幀,如上述右圖所示。程序運行後因爲密碼不正確,顯示如下:
設想如果溢出的數據覆蓋棧中保存的eip爲我們設置的值,則函數返回時將跳轉我們設置地址出的代碼進行執行。過程圖如下:
本程序爲密碼驗證程序,利用此漏洞我們設計程序返回時進行如下分支代碼:
printf("Congratulation! You have passed the verification!\n");
由於我們關閉了基地址隨機化功能,程序每次運行時按照PE鏡像進行部署,所以指令的地址是不變的。用IDA打開該程序(也可直接調試查看)查看到該語句的地址爲0x00411B40。如下所示:
所以在txt中我們設置如下的二進制:
注:由於程序fsanf的格式化字符串爲%s,讀取到00則會結束,所以填充數據不能爲00。由於本環境使用小端字節序,使得修改的地址高位00正好可以結束數據的讀取。
修改txt後運行程序並設置斷點在strcpy結束後,查看到保存eip的位置數據被修改成我們設置的值:
發現本應該返回地址爲0x00411B25被修改爲0x00411B40,也就是正確密碼時顯示消息的語句。直接點擊exe執行程序,程序顯示如下:
程序崩潰是由於棧內 EBP 等被覆蓋爲無效值,使得程序在退出時堆棧無法平衡。雖然如此,我們已經成功地淹沒了返回地址,並讓處理器如我們設想的那樣,在函數返回時直接跳轉到了提示驗證通過的分支。
0x01 安全編譯選項預防
爲了防止上述類似的棧緩衝區溢出的安全問題,VS編譯器提供了/GS編譯選項進行防護。通過菜單中項目->工程屬性->C/C++->配置屬性的代碼生成開啓/GS編譯選項,如下圖所示:
開啓該編譯選項後,重新編譯程序。再次使用上述惡意txt數據運行本程序,發現程序直接崩潰,沒有顯示任何密碼是否正確的消息內容。 所以開啓該編譯選項後棧溢出的利用失敗了。
0x02 /GS原理解讀
爲了瞭解/GS的原理,我們查看開啓該選項前後彙編代碼是如何變化的,如下圖分析所示:
根據上述分析,畫出開啓/GS編譯選項後程序棧情況,如下圖所示:
可以發現若惡意用戶想要通過棧溢出漏洞修改程序返回地址或者植入更多的shellcode代碼,則溢出過程中必然會修改棧中保存的cookie值。當程序退出時通過檢查棧中的cookie和.data段中的副本值是否相同就能夠判斷到是否溢出,若值不等程序會在返回之前進入異常處理函數,避免了惡意代碼和流程的執行。
0x03 /GS突破策略概要
然而/GS選項並不是萬能了,惡意用戶還可通過其他方式繞過Cookie的檢查,但其本質上是在程序退出檢查Cookie前就通過棧溢出執行了惡意代碼或修改了cookie保持和副本一致。這些繞過方法有如下三種:
- 覆蓋虛函數
- 攻擊異常處理
- 同時替換棧中和.data中的Cookie
還有一種就是由於考慮到性能原因,編譯器並不會爲每個函數增加Cookie的檢查,所以可以通過尋找未受/GS保護函數進行溢出操作。
後續文章將針對這四種突破/GS的方法進行試驗利用,並引出其他安全編譯選項進行對應的防護,敬請期待。