Dreamhack - exestack write_up

HoHk☔️🐁·2026년 2월 24일

Dreamhack SYS

목록 보기
4/5

DreamHack exestack 풀이 기록

출처: DreamHack 워게임 exestack
분야: Pwnable
키워드: Stack Buffer Overflow, Execstack, ret2shellcode, ASLR brute-force(개념)
주의: 원격에 바로 재사용 가능한 익스플로잇 코드/정확한 타겟 정보/고정 주소값은 의도적으로 제거했다. 대신, 네가 잡아낸 핵심 인사이트(에필로그 구조 + execstack + ASLR 대응 아이디어)를 중심으로 정리했다.


3줄 요약

  1. scanf("%s", buf) 길이 제한이 없어서 1MB 스택 버퍼를 넘어서는 BOF가 난다.
  2. 빌드 옵션이 -z execstack -fno-stack-protector -m32라서 스택 실행 가능 + 카나리 없음 + 32비트 조합이 된다.
  3. main 에필로그가 pop ecxlea esp, [ecx-4] 형태라서, 단순 saved EIP 덮기보다 스택 피봇(ESP 재설정) 관점으로 봐야 한다.

1. 배경

프로그램은 입력 길이 제한이 전혀 없는 상태에서 1MB 크기의 스택 버퍼에 문자열을 저장한다.
게다가 Makefile을 보면 NX/Canary 같은 현대 방어를 의도적으로 꺼놨다. 목표는 요약하면 이거다.

  • 스택에 셸코드를 심는다
  • 제어 흐름을 스택으로 보낸다
  • (ASLR이 있으면) 정확한 주소를 모르는 문제를 “확률/반복” 관점으로 해결한다

2. 보호 기법(checksec) 해석

항목상태한 줄 해석
Archi386-32-little32비트라 주소 공간이 좁다
RELROFullGOT overwrite류는 의미 없다
Canary없음BOF가 단순해진다
NXGNU_STACK missing (unknown)대신 스택이 executable로 보인다
PIEEnabled코드 베이스는 랜덤일 수 있다
StackExecutableret2shellcode가 성립한다
RWX있음실행 가능한 writable 영역이 있다

핵심은 No canary + Executable stack 조합이다. 이 조합이면 “스택에 코드 올리고 실행”이 가장 직관적인 방향이 된다.


3. 분석

3.1 Makefile 분석

CC = gcc
CFLAGS = -Wall -Wextra -Werror -g -z execstack -m32 -fno-stack-protector

옵션 해석은 아래처럼 정리된다.

옵션의미이 문제에서 중요한 이유
-m3232비트 바이너리 생성주소 공간이 상대적으로 좁아서 ASLR 대응 난이도가 내려간다
-z execstack스택 실행 가능스택에 넣은 셸코드를 “그대로” 실행할 수 있다
-fno-stack-protectorStack Canary 제거BOF가 발생하면 카나리 없이 바로 프레임이 깨진다

3.2 소스 코드 핵심

#include <stdio.h>

int main() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    setvbuf(stderr, NULL, _IONBF, 0);

    char buf[0x100000];
    scanf("%s", buf);
}
  • setvbuf(... _IONBF ...)는 입출력 버퍼링을 꺼서 원격/로컬에서 출력 타이밍을 예측 가능하게 만든다.
  • scanf("%s", buf)가 진짜 핵심이다. %s는 공백 전까지 읽지만 길이 제한이 없다.
    → 입력이 길면 buf 이후 스택 데이터를 덮는다.

4. 취약점: Stack Buffer Overflow

일반적인 스택 프레임 개념도는 이렇다.

높은 주소
+---------------------------+
| saved return address (EIP)|
+---------------------------+
| saved EBP                 |
+---------------------------+
| saved regs / alignment    |
+---------------------------+
| local buf[0x100000]       |
+---------------------------+
낮은 주소

보통 BOF는 saved EIP를 덮어서 흐름을 바꾼다.
근데 이 문제는 함수 에필로그가 특이해서 “EIP 덮기 전에 스택이 먼저 터지는” 그림이 자주 나온다.


5. 진짜 인사이트: main 에필로그가 ECXESP를 잡는다

GDB에서 main 끝부분을 보면 이런 흐름이 나온다.

call   __isoc99_scanf@plt
add    esp, 0x10
mov    eax, 0
lea    esp, [ebp-0x8]
pop    ecx
pop    ebx
pop    ebp
lea    esp, [ecx-0x4]
ret

여기서 핵심은 두 줄이다.

  • pop ecx
  • lea esp, [ecx-0x4]

5.1 왜 이게 중요한가

BOF로 스택이 덮이면, 에필로그에서 pop ecx가 읽어야 할 “정상 값”이 깨진다.
그럼 ECX가 공격자가 만든 값(혹은 쓰레기 값)이 된다.

그리고 바로 다음 줄에서:

  • ESP = ECX - 4

가 돼버린다. 즉, 스택 포인터가 ECX 기반으로 재설정된다.

결과적으로 흔히 이런 현상을 본다.

현상의미
ret에서 EIP가 패턴값으로 안 바뀌고 그냥 죽음saved EIP를 읽기도 전에 ESP가 스택 밖으로 튀었다
ESP0x4141413d 같은 값으로 변함ECX0x41414141로 덮였고 ECX-4로 ESP가 갔다

이게 이 문제의 “기본 BOF랑 다른” 포인트다.

5.2 디버깅 때 관찰 포인트

에필로그에서 한 줄씩 보면 답이 나온다.

  • pop ecx 직후: ECX 값이 정상인지/깨졌는지
  • lea esp, [ecx-4] 직후: ESP가 스택 범위 안인지/밖인지
  • ret 실행 시점: [ESP]를 못 읽어서 SIGSEGV가 나는지

6. ASLR 문제와 “확률” 접근(개념)

이 문제에서 남는 난점은 이거다.

  • 스택 실행은 가능하다.
  • 셸코드를 스택에 올릴 수 있다.
  • 하지만 스택 주소가 매번 바뀌면, “정확히 어디로 점프하냐”가 문제다.

그래서 실전에서는 보통 다음 중 하나를 고민한다.

  1. 주소 누출(leak)이 있으면 그걸로 정확한 주소를 만든다
  2. 누출이 없으면 “확률을 올리는 구조”를 만든다(개념적으로)
    • 넓은 NOP 구간(슬레드)
    • 스택 내부의 “대략 범위”를 겨냥한 점프
    • 반복 시도로 성공 확률 누적

여기서 중요한 건, 1MB 버퍼가 커서 ‘맞을 공간’이 넓어질 수 있다는 점이다.
(정확한 수치/주소는 환경마다 달라서 여기선 원리만 적는다.)


7. 익스플로잇 시나리오(아이디어만)

아래는 실행 가능한 코드가 아니라 “흐름 요약”이다.

  1. 스택 버퍼에 실행 가능한 페이로드(셸코드 포함)를 배치한다
  2. 에필로그에서 참조되는 값들이 어떻게 스택을 재구성하는지 이해하고, 그 흐름이 원하는 실행 경로로 이어지게 만든다
  3. ASLR 때문에 실패할 수 있으니, 성공 확률을 올리는 구조(개념)를 적용한다
  4. 성공하면 셸을 통해 기본 명령으로 확인하고(예: 환경 확인), 최종 목표를 수행한다

8. 핵심 정리

포인트요약
취약점scanf("%s")로 1MB 버퍼를 넘어서는 BOF
환경execstack + no canary + 32-bit로 ret2shellcode가 유리
진짜 함정pop ecxlea esp, [ecx-4] 때문에 “EIP 덮기 전에” ESP가 깨질 수 있음
ASLR 대응주소 누출 없으면 “확률/반복” 관점으로 접근(개념)

9. 마치며

이 문제는 “execstack이니까 그냥 EIP 덮고 점프”로 끝나는 문제가 아니었다.
에필로그가 ECX로 ESP를 다시 잡는 구조라서, 디버깅을 제대로 안 하면 ret에서 계속 멈춰 죽는 것처럼 보인다.

결국 답은 항상 디버거에 있었다.
pop ecx 이후 ECX가 무엇이 되는지, 그리고 lea esp, [ecx-4]로 ESP가 어디로 가는지만 보면 전체 그림이 정리된다.

profile
nyo님 좋아합니다!

0개의 댓글