棧是一種運算受限的線性表
其限制是僅允許在表的一端進行插入和刪除運算
這一端稱為棧頂(TOP),相對的另一端稱為棧底(BASE)
向一個棧插入新元素,稱作進棧、入棧或壓棧(PUSH)
它是把新元素放到棧頂元素的上邊,使之成為新的棧頂元素;
從一個棧刪除元素,又稱出棧或退棧(POP)
它是把棧頂元素刪除掉,使其相鄰的元素成為新的棧頂元素
進程使用的內存可以分成4個部分
代碼區:存儲二進制機器碼,存儲器在這里取指令
數據區:用於存儲全局變量
堆區:動態分配和全局變量
棧區:動態存儲函數間的調用關系,保證被調用函數返回時恢復到母函數中繼續運行
寄存器與函數棧幀
ESP:棧頂指針寄存器,永遠指向系統棧頂
EBP:基址指針寄存器,永遠指向系統棧最上邊一個棧的棧底
ESP和EBP之間的內存空間為當前棧幀
2.棧的溢出
棧溢出是由於C語言系列沒有內置檢查機制來確保復制到緩沖區的數據不得大於緩沖區的大小
因此當這個數據足夠大的時候,將會溢出緩沖區的范圍
3.如何利用
通過程序的緩沖區寫超出其長度的內容,造成緩沖區的溢出,
從而破壞程序的堆棧,使程序轉而執行其它指令,以達到攻擊的目的
造成緩沖區溢出的原因是 程序中沒有仔細檢查用戶輸入的參數
覆蓋鄰接變量
例如buffer大小是8字節
輸入8個字符,加上字符串截斷字符NULL字符,即可覆蓋相鄰變量,改變程序運行流程
修改函數返回地址
上述覆蓋相鄰變量的方法雖然很管用,但是漏洞利用對代碼環境很苛刻
更通用的攻擊緩沖區的方法是,瞄准棧幀最下方EBP和函數返回地址等棧幀的狀態值
如果繼續增加輸入字符,超出buffer[8]字符邊界
將依次淹沒 相鄰變量、前棧幀EBP、返回地址
4.實例
1)創建一個password.txt文件,內容為1234
2)C語言實例代碼
代碼環境
操作系統 | Widows XP SP2 |
---|---|
編譯器 | Visual C++ 6.0 |
編譯選項 | 默認編譯選項 |
build版本 | debug版本 |
運行測試一下,更改密碼文件對比結果
根據函數棧溢出原理,實現棧溢出需要以下過程
(1) 分析並調試程序,獲得淹沒返回地址的偏移
(2) 獲得buffer的起始地址,根據獲得的偏移將其覆蓋返回地址,使得函數返回時執行buffer起始地址保存的代碼
(3) 提取彈框操作的機器碼並保存於buffer的起始地址處,在函數返回時得到執行
為什么會覆蓋?
如果在password.txt中寫入恰好44個字符,那么第45個隱藏的截斷符 null 將沖刷
變量authenticated低字節中的 1,從而突破密碼驗證的限制
出於字節對齊、容易辨認的目的,我們把"4321"作為一個輸入單元
buffer[44]共需要11個這樣的單元
第12個輸入單元將authenticated覆蓋;
第13個單元將前棧幀EBP的值覆蓋;
第14個單元將返回地址覆蓋;
調試棧的布局
通過動態調試,可以得到以下信息
(1) buffer數組的起始地址為:0x0012FAF0
(2) password.txt 文件中第53~56個字符的ASCII碼值,將寫入棧幀中的返回地址,成為函數返回后執行的指令地址
也就是說,在buffer的起始地址寫入password.txt文件中的第53~56個字節
在 verify_password 函數返回時,會跳到我們輸入的字符串開始取指執行
(3) 給password.txt中植入機器碼,彈出消息框
MessageBoxA是動態鏈接庫user32.dll的導出函數,本實驗中未默認加載
在匯編語言中調用這個函數需要獲得這個函數的入口地址。
獲取彈窗函數入口參數信息
MessageBoxA的入口參數可以通過user32.dll 在系統中加載的基址和MessageBoxA在庫中的偏移得到。
用VC6.0自帶的小工具"Dependency Walker"可以獲得這些信息(可在Tools目錄下找到)
隨便把一個有GUI界面的程序扔進去,結果如圖所示
user32.dll的基址為:0x77D10000
MessageBoxA 的偏移地址為:0x000407EA
基址+偏移地址=MessageBoxA內存中的入口地址:0x77D507EA
我們要彈窗的字符設成"wintry",用python轉換成16進制的ASCII
然后借助OD寫匯編代碼,獲得機器碼
將上邊的機器碼,以十六進制形式逐字寫入到 password.txt
第53~56字節填入buffer的起址:0x0012FAF0 ,其余字節用 90(nop) 填充
上邊的機器碼可能是字符沒對齊的原因,會彈出內存讀取錯誤,把字符串改為"wintry00"
成功彈出窗口