← 목록으로

스택 버퍼 오버플로우: 가장 기본적인 메모리 손상 공격

[Pwn] Pwnable · 작성: 2026-07-19 19:28:48 · 조회 2

스택에 선언된 버퍼(배열)에, 그 크기보다 긴 데이터를 길이 검사 없이 써넣을 수 있을 때 발생하는 가장 기본적인 메모리 손상 취약점이다. Pwnable을 처음 배울 때 제일 먼저 만나는 주제이기도 하다.

취약한 코드 예시

void vuln() {
    char buf[64];
    gets(buf);  // 길이 검사가 없다 — 64바이트를 넘겨도 그냥 계속 쓴다
}

gets, strcpy, sprintf처럼 목적지 버퍼의 크기를 모른 채 복사하는 함수들이 전형적인 취약점 원인이다. buf가 64바이트인데 100바이트를 입력하면, 나머지 36바이트가 buf 뒤에 있는 saved RBP와 리턴 주소를 덮어쓴다.

익스플로잇 흐름

  1. 오프셋 찾기: 리턴 주소까지 정확히 몇 바이트를 채워야 하는지 알아내야 한다. pwntoolscyclic 패턴(aaaabaaacaaadaaa... 식으로 4바이트마다 겹치지 않는 패턴)을 입력하고, 크래시가 난 시점의 RIP 값을 cyclic_find에 넣으면 오프셋이 나온다.
  2. 크래시 확인: gdb로 실행하고 해당 오프셋만큼 A를 채운 뒤 그 다음 8바이트를 컨트롤 가능한 값(예: 0x4141414141414141)으로 채워서, 실제로 RIP가 그 값으로 바뀌는지 확인한다.
  3. 실행 흐름 결정: RIP를 뭘로 덮을지 정한다 — 셸코드 주소로 바로 뛰게 할지(NX가 없을 때), 아니면 ROP 체인으로 이어질지(NX가 있을 때, 다음 글들에서 다룸).

pwntools로 오프셋 찾기

from pwn import *

elf = context.binary = ELF('./vuln')
p = process()

payload = cyclic(200)
p.sendline(payload)
p.wait()

core = p.corefile
offset = cyclic_find(core.read(core.rsp, 4))
print("offset:", offset)

왜 지금은 예전만큼 쉽지 않은가

2000년대에는 이 정도로 셸코드 직접 실행까지 끝났지만, 지금은 컴파일러/OS가 기본으로 여러 보호 기법을 켜둔다.

checksec으로 이 보호 기법들이 뭐가 걸려있는지 먼저 확인하고, 각각을 어떻게 우회하는지는 이어지는 글에서 하나씩 다룬다.

$ checksec ./vuln
Canary   : Disabled
NX       : Enabled
PIE      : Disabled
RELRO    : Partial

이 예시라면 카나리는 없고 NX만 걸려있으니, 셸코드를 직접 스택에 심는 대신 ROP로 우회해야 한다는 뜻이다.

Pwnable 카테고리의 글 (2/14)

  1. 메모리 구조 기초: 스택, 힙, 레지스터부터 이해하기
  2. 스택 버퍼 오버플로우: 가장 기본적인 메모리 손상 공격
  3. Format String 취약점: printf가 위험해지는 순간
  4. Stack Canary: 원리와 우회 방법
  5. NX/DEP 우회와 Return-to-libc
  6. ASLR과 정보 유출(Leak)
  7. ROP (Return-Oriented Programming) 완전 정복
  8. 힙 익스플로잇 기초: Use-After-Free와 Double Free
  9. pwntools 실전 활용법
  10. UDF(User-Defined Function) 기반 DB 권한 상승: 원리부터 실전까지
  11. Pass-the-Hash / Pass-the-Ticket: 원리와 실전
  12. eBPF 기반 지속성(Persistence) 기법
  13. Full RELRO 환경에서의 GOT Overwrite와 우회
  14. ret2dlresolve: 심볼 정보 없이 임의 함수 호출하기
← 메모리 구조 기초: 스택, 힙, 레지스터부터 이해하기 Format String 취약점: printf가 위험해지는 순간 →