Dreamhack: 시스템 해킹 성장 프로젝트

Dreamhack: 시스템 해킹 성장 프로젝트

기간
Mar 1, 2025 → Jan 25, 2026
분야
보안
page icon
AI에 의존하지 않고 약 8개월 동안 Pwnable을 직접 분석하며, 단순한 메모리 overwrite에서 Kernel AAR·AAW와 cred overwrite까지 확장한 장기 시스템 해킹 프로젝트입니다.
154개의 문제를 풀고 259개의 개념·실험·트러블슈팅 기록을 남기며, 취약점의 이름을 외우는 대신 제가 통제할 수 있는 상태를 찾고 여러 primitive를 exploit chain으로 연결하는 능력을 길렀습니다.
page icon

왜 이 프로젝트를 시작했는가

2024년에는 시스템보안 연구실 인턴과 WHS 2nd를 하면서, 시스템(운영체제)와 관련된 장기간 프로젝트를 했었습니다. 그러면서 저에게 작은 욕구가 생겼습니다. 시스템(운영체제)를 정말 이해를 해보고 싶다. 그럴려면, 뭘 해야할까 고민하다가 “Dreamhack에서 바텀업 방식으로 시스템 관련된 문제를 정말 다양하게 많이 풀어보면서 직접 배워보자 , 그리고 나만의 학습노트를 꾸준히 기록해보자. “ 라는 결론이 내려졌습니다. 이 선택의 이유는 저의 순수한 역량을 높이고 싶어서였습니다. 그러한 관점에서 AI를 최대한 안써보고 제가 직접 많이 박아보고 실패를 해보면서 배웠습니다. AI를 사용하는 것 또한 능력이라고 생각합니다. 하지만, AI를 사용하기 전에 순수하게 역량을 높이는데 초점을 둔 프로젝트입니다.
 
 
 

PROJECT OVERVIEW

항목
내용
기간
2025.03.01–2026.01.25 · 3–4월 기초 학습 후, 7월부터 본격적으로 수행
형태
개인 장기 학습 프로젝트 · Dreamhack 교육용 Wargame 환경
목표
정답이나 완성된 exploit을 먼저 찾지 않고, 취약점의 원인과 실행 상태를 제 힘으로 분석해 재현 가능한 exploit으로 연결하기
 

DREAMHACK ACTIVITY — 분야를 가리지 않고 176문제를 해결한 93일

 

SKILL STACK — 프로젝트 전반에서 학습하고 적용한 대표 기술들

분류
Skill Stack
Programming & Architecture
C · C++ · Python · x86/x86-64 Assembly · ARM/AArch64
Binary Analysis
ELF · Disassembly · Decompilation · ABI · Process Memory Layout
Memory Corruption
BOF · OOB · Off-by-one · FPO · Canary · Buffer Leak
Shellcode & Sandbox
Shellcode · Syscall · NX · mprotect · Seccomp · chroot
Format String
Stack·PIE·Canary·libc Leak · %n Write · Double FSB
Stack & Control Flow
ret2libc · ROP · SROP · Stack Pivoting · one_gadget
Heap Exploitation
UAF · Double Free · Tcache · Fastbin · Unsorted Bin · Safe-Linking · House 계열
ELF·glibc·FSOP
GOT/PLT · Relocation · Dynamic Loader · Hook · _rtld_global · _IO_FILE · House of Apple
Special & Automation
C++ Object · Custom VM · AEG · Angr · Binary Patch · Pwntools Automation
Kernel Exploitation
KASLR · ret2usr · SMEP·SMAP · SLUB · pipe · Kernel AAR·AAW · task_struct·cred
Tools & Environment
GDB · pwndbg/GEF · IDA · objdump · readelf · Docker · QEMU · gdb-multiarch

PROBLEM BREAKDOWN — Level별 풀이 수

Level
문제 수
Level
문제 수
Lv.0
4
Lv.5
11
Lv.1
32
Lv.6
5
Lv.2
43
Lv.7
3
Lv.3
33
Lv.8
3
Lv.4
20
TOTAL
154
 
 
 

ROLE

System Exploitation Learner · CTF Solver
  • 문제 선택, 실행환경 구성, 정적·동적 분석, exploit 작성과 검증을 모두 개인으로 수행했습니다.
  • 풀이 성공보다 취약점이 생긴 이유, 확보한 primitive와 다음 단계에 필요한 조건을 설명할 수 있는지를 중요하게 봤습니다.
  • 성공한 payload뿐 아니라 잘못 세운 가정과 실패 원인을 기록하고 다음 문제의 분석 기준으로 다시 사용했습니다.
  • 모든 분석과 exploit은 Dreamhack이 제공한 교육용 환경에서 수행했습니다.

TECH STACK

C · C++ · Python · x86/x86-64 Assembly · ARM/AArch64 · Linux · Linux Kernel · glibc · Pwntools · GDB · pwndbg/GEF · IDA · objdump · readelf · Docker · QEMU · Angr
 

MY WORK

0. 모든 영역을 관통한 나만의 기준 정립

Binary 분석은 ROP나 Heap과 나란히 놓이는 하나의 공격 기법이 아니라, 모든 문제에서 반복한 공통 과정이었습니다. 처음에는 BOF처럼 눈에 보이는 취약점을 찾고 곧바로 payload를 만들었지만, 보호기법과 제약이 복잡해질수록 다음 순서를 지키게 됐습니다.
  1. 입력이 저장되고 사용되는 메모리 객체를 확인합니다.
  1. 경계 검사와 객체 수명에서 잘못된 상태가 만들어지는 지점을 찾습니다.
  1. 현재 확보한 능력을 Leak·AAR·AAW·Control Flow와 같은 primitive로 정의합니다.
  1. Canary·NX·PIE·ASLR·RELRO·Seccomp가 막는 경로를 구분합니다.
  1. 최종 목표에서 역으로 필요한 주소, 상태와 호출 조건을 계산합니다.
  1. 각 단계를 GDB와 작은 입력으로 독립 검증한 뒤 하나의 exploit chain으로 연결합니다.
이 기준을 Stack, Heap, glibc, ARM과 Kernel에 반복 적용하면서, 익숙한 공격 기법을 찾는 방식에서 프로그램이 허용한 상태 변화로 공격 경로를 설계하는 방식으로 성장했습니다.
 

1. Memory Corruption — overwrite에서 primitive를 정의하는 단계로 성장했습니다

GROWTH PATH Lv.0 · baby-bofLv.1 · out_of_boundLv.2 · off_by_one_000Lv.2 · ssp_001Lv.2 · master_canaryLv.3 · Master CanaryLv.4 · Platform 9½Lv.8 · tiny backdoor
처음에는 BOF가 있으면 Return Address를 덮는 정도만 알고 있었습니다. 하지만 문제를 계속 풀면서 변수 사이의 거리를 직접 계산하고, 단 한 바이트의 overwrite가 SFP와 실행 흐름을 어떻게 바꾸는지 이해하기 시작했습니다. 카나리를 만났을 때도 막혔다고 생각하기보다 OOB로 값을 leak하고, 스레드 스택과 TLS의 관계를 이용해 master canary까지 접근했습니다.
GDB에서는 동작하지만 리모트에서는 실패하거나, 잘못 채운 패딩 때문에 다시 실행한 main이 종료되기도 했습니다. 그때마다 답을 가져오기보다 스택과 실행환경을 다시 확인하며 원인을 찾았습니다. 결국 후반에는 완전한 주소 leak이 없어도 스택에 남은 값과 1바이트 overwrite만으로 다음 공격을 만들어낼 수 있게 됐습니다. 제게 Memory Corruption은 단순히 메모리를 덮는 기술이 아니라, 작은 단서 하나로 실행 흐름 전체를 끝까지 추적하는 과정이었습니다.
 

2. Shellcode·Syscall·Sandbox — 코드를 실행하는 것에서 제한 안에서 동작을 설계하는 단계로 성장했습니다

GROWTH PATH Lv.0 · shell_basicLv.2 · basic_exploitation_000Lv.2 · No movLv.2 · Bypass SECCOMP-1Lv.3 · Find CandyLv.4 · Secure ServiceLv.6 · pwn_patch_2
처음에는 셸코드를 실행할 수 있으면 곧바로 셸도 얻을 수 있다고 생각했습니다. 하지만 execveSIGSYS로 차단된 뒤, 코드 실행과 허용된 동작은 다르다는 것을 알게 됐습니다. 이후에는 금지된 바이트와 명령어를 다른 연산으로 바꾸고, seccomp가 허용한 syscall과 인자를 직접 분석해 openat, read, writev만으로 필요한 동작을 구성했습니다.
후반에는 write 하나만으로 메모리를 탐색하고, BOF로 seccomp filter 자체를 바꾸거나 chroot가 만든 경계에서 빠져나오는 방법까지 고민했습니다. 제게 셸코드는 단순히 /bin/sh을 실행하는 코드가 아니라, 제한된 조건 안에서 가능한 동작을 끝까지 찾아 조합하는 과정이었습니다.
 

3. Format String Bug — 출력 오류를 Leak과 Write primitive로 발전시켰습니다

GROWTH PATH Lv.1 · basic_exploitation_003Lv.1 · Format String BugLv.2 · basic_exploitation_002Lv.3 · TitanfullLv.4 · stringLv.4 · GaiaLv.6 · Hope Delivery v2.0
처음에는 %p로 스택을 읽고 %n으로 GOT를 덮는 것이 FSB의 전부라고 생각했습니다. 하지만 입력이 몇 번째 인자로 사용되는지 직접 확인하고, printf의 가변인자 처리 과정을 따라가면서 스택과 레지스터에서 PIE·Canary·libc 주소를 찾아낼 수 있게 됐습니다.
후반에는 FSB만으로 공격을 끝내려 하기보다, 다음 BOF·ROP·GOT overwrite에 필요한 정보와 쓰기 권한을 만드는 데 사용했습니다. 입력한 주소가 스택에 없는 상황에서도 기존 포인터를 연결해 Double FSB로 쓰기 대상을 만들었습니다. 제게 FSB는 단순한 출력 오류가 아니라, 막혀 있던 공격의 다음 단계를 열어주는 Leak과 Write primitive였습니다.

4. Stack·Control Flow — 단일 분기에서 ROP·SROP chain 설계로 성장했습니다

GROWTH PATH Lv.1 · oneshotLv.2 · basic_rop_x86·x64Lv.2 · SigReturn-Oriented ProgrammingLv.4 · stacknoteLv.5 · EASY ROPLv.7 · validator-revengeLv.7 · Operator
처음에는 유출된 libc 주소로 one-gadget이나 함수 하나를 호출하는 데 집중했습니다. 이후에는 호출 규약과 스택 정렬을 직접 계산하며, 첫 번째 ROP chain으로 주소를 leak하고 다시 입력을 받아 다음 chain으로 이어가는 방식으로 발전했습니다. 가젯이 부족한 상황에서는 SROP로 레지스터 전체를 구성하고, send()처럼 원래 목적이 다른 함수도 필요한 primitive로 활용했습니다.
후반에는 주어진 스택 공간에 chain을 억지로 넣기보다 $rbp와 leave; ret을 이용해 .bss로 스택을 옮겼습니다. 그 위에 여러 단계의 ROP chain을 나누어 적재하고 실행 흐름을 끝까지 연결했습니다. 제게 Control Flow는 단순히 Return Address를 바꾸는 것이 아니라, 제한된 가젯과 공간 안에서 다음 실행 상태를 계속 설계하는 과정이었습니다.
 

5. Heap Exploitation — 공격 기법을 외우는 대신 allocator 상태를 계산하게 됐습니다

GROWTH PATH Lv.1 · basic_heap_overflowLv.2 · tcache_dupLv.2 · uaf_overwriteLv.3 · Tcache PoisoningLv.4 · House of Force·SpiritLv.5 · note·kidheapLv.6 · [LINE CTF 2021] bank
처음에는 같은 크기의 청크가 다시 할당된다는 성질을 이용해 UAF와 Double Free를 구성했습니다. 하지만 glibc 버전이 바뀌자 같은 페이로드가 더 이상 동작하지 않았고, 그때부터 각 mallocfree 뒤에 청크가 어느 tcache와 bin으로 이동하는지 직접 확인하기 시작했습니다. Unsorted Bin을 만들기 위해 tcache 개수를 채우고, main_arena 포인터로 libc를 leak하는 과정도 할당 순서부터 계산했습니다.
후반에는 tcache key와 Safe-Linking, fastbin의 size·alignment 검사, top chunk의 위치까지 함께 고려했습니다. 기법의 이름을 먼저 떠올리기보다 현재 freelist와 bin, 청크의 메타데이터를 그린 뒤 다음 malloc이 반환할 주소를 예측했습니다. 최종적으로는 저한테 있어 Heap Exploitation은 공격 기법을 외우는 일이 아니라, 청크의 할당과 해제 과정을 직접 따라가며 다음 malloc이 반환할 주소를 예측하고
 

6. ELF·Dynamic Linking·glibc Runtime — 실행파일 밖의 동작까지 공격면으로 확장했습니다

GROWTH PATH Lv.1 · ssp_000Lv.1 · hookLv.2 · rtldLv.2 · Overwrite _rtld_globalLv.3 · Dirty StackLv.4 · GaiaLv.6 · Heap Basic 1
처음에는 Return Address를 덮을 수 없을 때 GOT나 __free_hook을 대신 덮는 정도로 접근했습니다. 이후에는 ELF의 Entry Point에서 main을 찾고, .rela.plt, .dynsym, .dynstr, link_map을 따라가며 동적 로더가 실제 함수 주소를 찾아 GOT에 채우는 과정까지 분석했습니다.
Stack과 PIE 주소를 전혀 알 수 없는 문제에서는 프로그램의 종료 경로를 따라가 _rtld_global의 함수 포인터를 덮었습니다. 바이너리에 free()가 없어도 glibc의 종료 루틴에서 호출되는 지점을 찾아 __free_hook으로 연결했고, 메인 바이너리의 GOT가 막힌 뒤에는 libc 내부의 PLT·GOT와 IFUNC 재배치 영역을 확인했습니다. 이후에는 실행파일 안에서 덮을 주소가 보이지 않으면, 프로그램이 시작하고 종료될 때 로더와 glibc가 어떤 포인터를 참조하는지부터 추적하게 됐습니다.
 

7. FSOP — 함수 포인터 overwrite에서 내부 상태와 호출 조건을 설계하는 단계로 성장했습니다

GROWTH PATH Lv.1 · iofile_vtableLv.2 · _IO_FILE Arbitrary Address WriteLv.3 · Bypass IO_validate_vtableLv.3 · _IO_FILE Arbitrary Address ReadLv.5 · iofile_vtable_checkLv.5 · FSisOPLv.7 · validator-revenge
초기에는 _IO_FILE_plus의 vtable 포인터를 덮어 실행 흐름을 바꾸는 기본적인 FSOP부터 시작했습니다. 이후 glibc의 libio 구현을 따라가며 _IO_FILE_complete, _IO_jump_t_IO_file_jumps의 구성을 분석했고, fread, fwrite, fflushunderflow, overflow, xsgetn, xsputn 중 어떤 점프 테이블 슬롯을 사용하는지 확인했습니다. 이를 바탕으로 _IO_buf_base, _IO_write_base, _IO_write_ptr, fileno를 조작해 임의 주소 읽기와 쓰기를 구성했습니다.
후반에는 IO_validate_vtable의 검증 범위를 분석하고, 정상적인 점프 테이블 내부의 _IO_str_overflow를 이용해 검증을 우회했습니다. 또한 House of Apple을 공부하며 정상적인 _IO_wfile_jumps로 검증을 통과한 뒤, 가짜 _wide_data와 검증되지 않는 _wide_vtable을 통해 실행 흐름을 가져오는 과정을 분석했습니다. 마지막에는 FSOP를 libc leak primitive로 사용하고 Stack Pivoting·ROP와 연결했습니다. 기법을 그대로 적용하는 데 그치지 않고, 실제 glibc 호출 경로와 분기 조건을 확인한 뒤 그 조건에 맞춰 fake FILE 구조체를 구성했습니다.

8. Architecture·Automation·특수 유형 — 같은 분석 기준의 적용 범위를 넓혔습니다

GROWTH PATH Lv.1 · Arm Training-v1·v2Lv.2 · armopLv.3 · Arm Training-lastLv.3 · tiny-machine·STACK_AEGLv.4 · bytechangerLv.6 · pwn_patch 1·2
처음에는 x86-64에서 익힌 방식을 ARM에 그대로 적용하려다 호출 규약과 스택 구조의 차이에서 막혔습니다. 이후 QEMU와 gdb-multiarch를 이용한 디버깅 환경을 직접 구성하고, ARM32의 r0~r3 caller-saved 규칙과 ARM64의 x29·x30, stp·ldp 동작을 분석하며 아키텍처에 맞게 ROP chain을 다시 설계했습니다. 마지막에는 ARM 환경에서도 GOT를 leak해 libc base를 구하고 다음 공격으로 연결할 수 있게 됐습니다.
Custom VM 문제에서는 opcode, 가상 레지스터, 메모리와 분기 방식을 직접 복원하고 디스어셈블러까지 작성했습니다. C++ 문제에서는 vector의 힙 버퍼, shared_ptr의 control block과 reference count, virtual method에 따른 vptr 생성을 실제 메모리에서 확인했습니다. 이후 AEG에서는 전달받은 바이너리에서 달라지는 스택 크기를 추출해 canary leak과 payload 생성을 반복 처리했고, 패치형 문제에서는 opcode를 바이트 단위로 수정하거나 별도의 코드로 분기시킨 뒤 정상 실행 흐름으로 복귀시키는 방법까지 다뤘습니다. 또한 반복되는 실행·디버깅·가젯 탐색은 Pwntools와 gdbserver 기반 도구로 정리해 분석에 집중할 수 있는 환경을 만들었습니다.
 
 

FINAL CHALLENGE — Kernel Exploit이라는 최종목표에 도전

처음부터 모든 문제를 오직 Kernel Exploit을 위해 풀었다고 말할 수는 없습니다. 하지만 프로젝트가 깊어질수록 제가 도달하고 싶은 곳은 점점 분명해졌습니다. 사용자 영역에서 주소 하나를 정확히 leak하고, 실행환경의 차이를 찾아내고, 제한된 primitive를 이어 긴 exploit chain을 만드는 능력을 충분히 쌓은 뒤에는 커널의 권한 경계를 제 힘으로 넘어보고 싶었습니다.
Kernel Exploit은 제게 새로운 공격 기법 하나가 아니라, 몇 달간 공부한 내용을 모두 요구하는 최종목표였습니다.
FINAL PATH Memory Corruption·ROP·Heap·Runtime2025.12 · kpwnoteRing·CR3·SMEP·SMAP·SLUB 분석2026.01 · kaleidoKernel AAR·AAW·cred overwrite
사용자 영역에서 쌓은 기반
Kernel에서 다시 마주한 문제
PIE·libc 주소 Leak과 base 계산
커널 함수 포인터를 Leak하고 KASLR base 계산
ROP의 레지스터·Stack 상태 관리
Ring 0에서 Ring 3로 복귀하기 위한 Trap Frame 구성
Heap 객체의 할당·해제와 재사용 분석
SLUB, kmalloc cache와 pipe 객체의 배치·재할당 분석
ELF·glibc 구조체와 함수 포인터 추적
struct file, private_data, pipe_buffer, task_structcred 추적
제한된 읽기·쓰기를 exploit chain으로 연결
Kernel AAR·AAW를 구성해 현재 프로세스의 권한 객체 변경

첫 번째 도전 — 커널의 실행 흐름과 권한 경계를 넘었습니다

처음 Kernel 문제를 시작했을 때는 exploit보다 환경부터 낯설었습니다. vmlinuz와 initramfs를 분리하고, 취약한 커널 모듈을 찾아 분석한 뒤 QEMU에 GDB를 연결해야 했습니다. 처음에는 GDB가 연결되지 않아 막혔지만, QEMU의 콘솔이 연결을 기다리면서 gdbstub까지 열리지 않는 초기화 순서 문제를 찾아 nowait 설정으로 해결했습니다.
첫 Kernel Exploit인 kpwnote에서는 커널 함수 포인터를 leak해 KASLR base를 계산하고, 모듈이 참조하는 함수 포인터를 사용자 영역의 코드로 연결했습니다. 이후 commit_creds(prepare_kernel_cred(0))를 호출해 권한을 변경하고, RIP·CS·RFLAGS·RSP·SS를 담은 Trap Frame과 swapgs, iretq를 이용해 사용자 영역으로 복귀했습니다.
처음 Root Shell이 실행됐을 때는 분명 기뻤지만, exploit이 동작했다는 사실만으로는 만족하지 않았습니다. 왜 CS=0x33, SS=0x2b가 필요한지, CPL이 어떻게 Ring 0에서 Ring 3로 바뀌는지, swapgs가 GS.base를 왜 교환하는지 다시 분석했습니다. CR3와 페이지 테이블, KASLR, SMEP·SMAP까지 확인하며 제가 작성한 코드가 어떤 CPU 상태를 만들었는지 이해하려고 했습니다.

마지막 도전 — 함수 하나를 호출하는 공격에서 Kernel 객체와 권한을 추적하는 공격으로 확장했습니다

다음 단계에서는 이미 주어진 함수 포인터를 바꾸는 데서 벗어나 Kernel Heap 자체를 이해해야 했습니다. SLUB의 per-CPU freelist와 active slab·partial slab을 공부하고, GFP_KERNELGFP_KERNEL_ACCOUNT가 서로 다른 kmalloc cache를 사용할 수 있다는 점까지 확인했습니다. 이어서 커널의 struct fileprivate_data, pipe의 할당과 resize 과정, pipe_buffer 내부의 함수 포인터를 분석했습니다.
kaleido에서는 취약한 객체가 재사용될 위치에 pipe 객체를 배치해 두 객체의 메모리 영역을 겹쳤습니다. 노출된 pipe_ops 함수 포인터로 KASLR base를 계산한 뒤, 손상된 객체의 포인터를 조작해 범용 Kernel AAR·AAW를 만들었습니다.
처음에는 AAW로 modprobe_path를 덮는 경로도 검토했지만, CONFIG_STATIC_USERMODEHELPER로 해당 값이 실제 실행 경로에 사용되지 않는다는 사실을 확인했습니다. 여기서 멈추지 않고 __per_cpu 영역에서 current_task를 찾고, task_structcred 포인터를 따라가 현재 프로세스의 권한 객체에 도달했습니다. 마지막으로 AAW를 이용해 UID와 GID를 변경하면서 Root 권한을 획득했습니다.
pipe_ops LeakKASLR base__per_cpucurrent_tasktask_struct->credKernel AAW
마지막 Root Shell이 실행됐을 때 가장 크게 느낀 것은, 그동안 공부한 내용이 서로 떨어진 지식이 아니었다는 점이었습니다. BOF에서 시작한 주소 계산, ROP에서 익힌 실행 상태 복원, Heap에서 반복한 객체 배치 예측, glibc에서 배운 구조체와 함수 포인터 추적이 하나의 Kernel exploit chain 안에서 다시 연결됐습니다.
Kernel Exploit은 몇 달 동안 쌓아온 역량으로 가장 낯설고 복잡하게 느껴졌던 권한 경계를 직접 넘어본, 제게는 이 프로젝트의 최종 증명이였습니다.

TROUBLESHOOTING & LEARNING

Troubleshooting

발생한 문제
확인한 원인
해결 및 이후의 기준
Local에서는 성공하지만 Remote에서는 실패했습니다
Docker 이미지의 빌드 시점이 달라 같은 glibc 2.35에서도 세부 버전과 심볼 오프셋이 달랐습니다
Dockerfile의 이미지 태그와 실제 libc 버전을 확인하고, 문제 환경의 loader와 libc를 먼저 맞추도록 했습니다
GDB에서는 성공하지만 일반 실행에서는 실패했습니다
gdbserver가 ASLR을 비활성화해 실제 실행과 스택 주소 및 배치가 달라졌습니다
ASLR을 활성화한 상태로 다시 디버깅하고, Debug와 Remote의 실행환경이 같은지 먼저 검증했습니다
ARM ROP에서 puts 호출 뒤 /bin/sh이 들어 있던 r3 값이 사라졌습니다
ARM의 r0~r3가 caller-saved 레지스터이므로 함수 호출 뒤 값이 보존되지 않았습니다
함수 호출 전후의 레지스터를 직접 비교하고, 보존되는 레지스터나 메모리를 이용해 chain을 다시 구성했습니다
system 진입 시 exploit이 중단됐습니다
movaps가 요구하는 16바이트 Stack alignment를 만족하지 못했습니다
ret만 추가하지 않고 함수 prologue의 push를 건너뛰거나, 스택 소비량이 다른 gadget을 선택해 정렬을 맞췄습니다
Remote에서 입력과 출력의 순서가 어긋났습니다
Pwntools의 recvline()이 내부 버퍼를 소비하는 방식과 네트워크 지연을 정확히 고려하지 못했습니다
recvuntil()sendafter()로 프롬프트를 기준 삼아 동기화하고, 필요한 시점에만 버퍼를 정리했습니다

풀이 이후의 Write-up 학습

제 방식으로 풀이를 끝낸 뒤에는 저는 항상 다른사람은 어떤 primitive와 실행 흐름을 선택했는지 비교했으며, 저와 왜 생각의 차이가 생겼는지. 그리고 다른 사람의 느낌있는 풀이들을 직접 사용해보면서 공부했습니다.
예를 들어, Master Canary 문제에서는 제가 TLS 영역까지 같은 포인터 값으로 채운 것과 달리 null padding을 사용하면 main으로 바로 돌아갈 수 있다는 사실을 발견했습니다. 이를 다시 재현하며 단순한 padding도 이후 실행에서 참조되는 메모리를 손상시킬 수 있다는 점을 배웠습니다.
또한 다른 사람의 풀이중 chunk의 size를 변조해 Heap Overlapping을 만든 뒤 Tcache Poisoning으로 연결하는 방법을 분석하고 학습했습니다. One-gadget bruteforce에서는 1/4096 확률의 partial overwrite를 사용해보고 분석하는거 그치지 않고, 더 최적화해서 스택에 남은 더 가까운 주소를 이용하면 덮어써야 하는 바이트와 실패 확률을 줄일 수 있다는 방법까지 찾고 비교했습니다. FILE 내부 버퍼에 데이터가 남는 문제도 Write-up의 주소 사용 방식을 따라가는 데서 끝내지 않고, 해당 버퍼가 왜 Heap에 생성되고 그 주소가 어떻게 Stack에 남는지 다시 확인했습니다.
이처럼 풀이 이후의 Write-up 분석은 정답을 확인하는 과정이 아니라, 제 풀이와 다른 선택지를 비교하고 더 안정적이거나 효율적인 방법을 추가로 배우는 복습 과정이었습니다. 새롭게 알게 된 공격법과 실패 원인은 별도의 학습 기록으로 남겨 다음 문제에서 다시 사용할 수 있도록 정리했습니다.

RESULTS

약 8개월 동안 Dreamhack Pwnable 문제 154개를 해결했으며, Pwnable 문제 풀이를 주력으로 4,901점을 기록해 당시 Dreamhack 워게임 전체 랭킹 300위권에 진입했습니다.
가장 큰 성과는 프로젝트의 최종목표였던 Kernel Exploit을 직접 완성한 것입니다. 사용자 영역에서 쌓은 Memory Corruption·ROP·Heap·Runtime 분석 경험을 바탕으로 KASLR 우회, Kernel AAR·AAW 구성과 cred overwrite까지 연결해 Root 권한을 획득했습니다.
결과적으로 단순한 BOF에서 출발해 제한된 Leak과 Write primitive를 조합하고, 복잡한 실행환경에서도 하나의 exploit chain을 끝까지 설계하고 검증할 수 있는 역량을 갖추게 됐습니다.
 
page icon

배운 점과 다음 단계

이 프로젝트는 시스템 보안 역량을 기르기 위해 시작했지만, 다양한 분석 환경을 직접 구성하고 Linux와 운영체제의 동작 원리, 그 위에서 보안 경계가 형성되는 방식까지 이해하는 과정으로 확장됐습니다. 제게는 단순히 공격 기법을 늘린 경험이 아니라, 앞으로 시스템 보안을 깊이 공부하기 위한 뿌리를 만든 시간이었습니다.
초반에는 한 문제를 몇분 안에 풀기도 했지만, 후반으로 갈수록 새로운 개념을 이해하고 정리하는 데에도 많은 시간이 필요했습니다. 막혔을 때는 같은 방법을 반복하기보다 문제를 오래 관찰하고, 당연하게 생각했던 가정을 내려놓은 뒤 새로운 관점에서 다시 접근했습니다. 학습 과정에서는 AI를 적극적으로 활용했지만, 문제를 분석하고 exploit을 설계할 때는 제 순수한 사고력과 검증 능력을 기르기 위해 AI를 거의 사용하지 않았습니다. AI를 거부하기 위해서가 아니라, AI를 본격적으로 보안 연구에 활용하기 전에 결과를 스스로 판단하고 검증할 수 있는 기반을 만들고 싶었기 때문입니다.
동시에 Wargame과 실제 소프트웨어 사이에는 분명한 차이가 있다는 점도 느꼈습니다. Kernel Exploit을 마지막으로 이 프로젝트를 정리하고, 다음에는 AI를 적극적인 분석 도구로 활용해 실제 환경의 취약점을 연구하려고 합니다. Linux Kernel과 널리 사용되는 상용 소프트웨어에서 CVE를 발굴하고, 패치 기여와 책임 있는 공개까지 완수하는 것이 다음 목표입니다.
 

전체 기록과 원본

  • Dreamhack Pwnable 문제별 분석·exploit 기록 154개 보관
  • 개념·실험·트러블슈팅 기록 259개 보관
  • 세부 기록은 재현성과 공개 범위를 검토한 뒤 Blog에서 단계적으로 공개 예정