오늘 끝나면
메모리 취약점
- ✓메모리 취약점의 핵심 문제를 한 문장으로 설명한다
- ✓오른쪽 실습에서 메모리이 어떻게 움직이는지 관찰한다
- ✓다음 강의와 이어지는 한계를 말할 수 있다
실습 미션
버퍼 오버플로우 — 스택을 넘어 쓰면 이 문장이 실제로 무슨 뜻인지 실습에서 한 번 손으로 확인한다.
성공 조건
- □실습의 기본값을 먼저 관찰
- □입력값이나 모드를 한 번 이상 바꿔 결과 비교
- □왜 결과가 바뀌었는지 한 문장으로 설명
화이트해킹 · 10
메모리
취약점
입력이 그릇 크기를 넘으면 옆 메모리로 흘러넘침.
그 옆에 함수의 반환주소가 있음.
거길 덮으면 실행 흐름이 통째로 넘어감.
스택 — 함수가 쓰는 메모장
함수가 돌 때 자기 변수들을 임시로 쌓아두는 칸이 있음. 그게 스택임.
입력을 담는 그릇(버퍼)도 여기 잡힘.
그런데 버퍼 바로 위에 함수가 끝나고 돌아갈 자리, 반환주소가 같이 얹혀 있음.
반환주소는 “이 함수 끝나면 여기로 가”라고 적힌 쪽지임.
함수가 끝나면 CPU는 묻지도 따지지도 않고 그 쪽지대로 점프함.
버퍼와 반환주소가 한 메모장에 붙어 있다는 게 이 강의 모든 위험의 출발점임.
크기를 안 재면 흘러넘침
버퍼는 크기가 정해져 있음. 8칸이면 8글자까지임.
그런데 C/C++의 옛 함수들은 입력 길이를 안 잼.
strcpy·gets는 들어온 만큼 그냥 복사함 / 그릇이 넘쳐도 멈추질 않음.
8칸짜리 그릇에 12글자를 부으면 4글자가 밖으로 샘.
새는 글자는 사라지는 게 아니라 바로 옆 칸을 덮어씀.
옆 칸엔 다른 변수가, 더 위엔 반환주소가 있음 / 입력이 길수록 더 깊이 침범함.
넘치면 반환주소를 덮는다
오른쪽에서 직접 넘쳐 봄. 입력 길이 슬라이더를 올리면 글자가 버퍼부터 위로 차오름.
8칸을 넘는 순간 옆 변수를 침범함.
더 올리면 반환주소 칸까지 빨갛게 덮임.
반환주소가 덮이면 함수가 끝날 때 공격자가 심어둔 주소로 점프함.
그럼 공격자가 짠 코드로 실행 흐름이 넘어감 / 이게 제어 흐름 탈취임.
“경계검사 켜짐” 버튼을 누르면 8칸까지만 받고 잘라냄 — 넘쳐도 반환주소가 멀쩡함.
버퍼 8칸 · 저장변수 2칸 · 반환주소 2칸
입력이 버퍼 안에 들어맞음. 인접 메모리가 안 건드려짐.
왜 C/C++에서 흔한가
파이썬·자바스크립트에선 이게 안 터짐. C/C++에서만 단골임. 이유가 있음.
C/C++는 속도를 위해 안전장치를 뺐음.
배열에 몇 칸이 있는지 실행 중에 안 따짐 / 9번째 칸에 써도 언어가 안 말림.
대신 그 책임을 전부 개발자에게 넘김.
파이썬은 범위를 넘으면 바로 오류를 내고 멈춤 / C는 조용히 옆 메모리를 망가뜨림.
이 “조용함” 때문에 버그가 숨고, 그 틈을 공격자가 노림.
| 언어 | 자동 경계검사 |
|---|---|
| C / C++ | 안 함조용히 옆 칸 덮음 |
| Python | 함IndexError로 멈춤 |
| JavaScript | 함범위 밖이면 undefined |
| Rust | 함컴파일·실행 중 차단 |
방어 — 경계를 재고 멈춰라
방어의 핵심은 한 줄임. 쓰기 전에 크기를 재고, 넘치면 멈추는 것.
길이를 안 재는 함수를 안 씀.
strcpy 대신 길이를 받는 strncpy·snprintf / gets 대신 fgets를 씀.
입력을 받을 땐 항상 버퍼 크기와 비교하는 경계검사를 넣음.
컴파일러·OS도 거듦 — 스택에 몰래 심은 표식(카나리)이 깨졌는지 보고, 주소를 매번 섞음.
가장 확실한 건 경계를 언어가 챙겨주는 Rust·Go 같은 도구로 옮기는 것.
뚫는 법을 배우는 이유는 결국 이 경계를 어디에 둘지 알기 위해서임.
Q. 버퍼 오버플로우가 위험한 이유는? (속도가 느려져서 · 반환주소를 덮어 실행 흐름을 가로채서 · 화면이 깨져서 · 디스크가 차서)
정답은 반환주소를 덮어 실행 흐름을 가로챌 수 있어서임.버퍼를 넘친 입력이 옆에 붙은 반환주소까지 덮으면, 함수가 끝날 때 공격자가 심은 주소로 점프함.
그럼 공격자 코드로 제어가 넘어감 / 단순한 데이터 손상이 아니라 기기 장악으로 이어짐.
그래서 방어는 항상 경계검사 — 쓰기 전에 크기를 재고 넘치면 멈추는 것임.
길이를 받아 자름
최대 크기를 넘김
버퍼 크기로 제한