경희대학교 장대희 교수님의 소프트웨어보안 수업을 기반으로 정리한 글입니다.
BoF (Buffer Overflow)
사용자의 입력 값에 대한 길일 검증을 하지 않아, 프로그래머가 할당한 크기의 버퍼보다 큰 데이터를 입력받아 메모리의 다른 영역을 오염시키는 취약점이다.
- 버퍼: 문자열을 입력받거나 특정 스택에서 공간을 할당 받는 곳

※ 버퍼는 임시 저장 곤간이고, 스택은 그 버퍼가 놓일 수 있는 여러 장소 중 하나이다.
1. 실습 준비
예제 코드 작성

gets 함수는 사용자의 입력 값에 대한 길이 제한이 없기에, 사용자는 프로그래머가 지정한 buf의 사이즈인 16 바이트를 넘어, 더 큰 입력을 입력할 수 있게 된다.
-> 스택에 할당된 16 바이트를 넘어서 return address까지 바꾸거나, 또는 그 이상의 행위를 할 수 있는 취약점
secret() 함수
- main 함수에서 호출되지 않는 함수 -> stack buffer overflow를 이용해 강제 실행 가능
- 이 함수를 강제로 호출하는 것이 '실습 1'의 목표
※ vi 사용법
- i (입력 시작)
- exc (입력 탈출)
- shift -> :wq (w: wrtie 저장 / q: 탈출 종료 0 -> vi 탈출 종료)
소스 코드 컴파일
: 초급 실습을 위해 모든 보호 기법 제거하여 컴파일

첫 번째 명령어 설명
- gcc: C 컴파일러
- bof_sample: 출력 파일 이름 (-o: output)
- bof_sample.c: 컴파일 할 소스 코드 (방금 작성한 예제 코드)
- -fno-stack-protector: 스택 보호 기법인 Stack Canary 비활성화
- canary: stack BOF 탐지 위해, 반환 주소 앞에 삽입되는 무결성 검사 값으로, canary 값이 변하면 BOF 발생으로 판단하여 프로그램을 즉시 종료함
- -z execstack: 스택에 실행 권한 부여 (기본적으론 컴파일 시 실행 권한 부여 X)
- -no-pie: PIE 비활성화 -> 실행 파일의 코드 주소가 고정되게 함
두 번째 명령어 설명
- ASLR을 비활성화하는 옵션
※ PIE: Position Independent Executable의 약자로, 실행 파일을 위치 독립적으로 로드될 수 있게 만들어, ASLR이 적용될 때 실행 파일 코드도 매 실행마다 다른 주소에 배치될 수 있게 하는 기능이다.
- ASLR: 공유 라이브러리의 위치를 랜덤화하여 ROP(Return Oriented Programming) 공격을 방지하기 위한 기술이다.
- ROP: 정상적으로 존재하는 어셈블리 Return 조각들을 연결하여 악성 코드의 조합으로 만들어 실행한다.

⭐ 2. 실습 1: BOF를 이용하여 호출되지 않는 함수를 호출
Return Address 덮어 쓰기에 대한 이해
1. 소스 코드를 모른다는 가정하에 어떤 함수가 존재하는지 탐색

-s: symbol table 출력 옵션
- 디버그 모드로 컴파일을 하거나 심볼을 살려놓고 컴파일을 하게 되면 위와 같이 심볼들이 존재함
- 어떤 함수들이 존재하는지 미리 확인 가능
- secret function이 존재하는 것을 확인할 수 있음
2. pwndbg를 사용해 bof_sample 분석 - main 함수 분석

- 디버거로 프로그램 실행 -> 메모리 확인 가능

어셈블리 코드 설명
- 1. main 함수 진입
- push rbp: 이전 함수의 rbp 값을 스택에 저장(백업) -> 나중에 함수 끝나고 원래대로 되돌리기 위함
- mov rbp, rsp: 현재 스택 포인터 rsp를 프레임 포인터 rbp로 고정 -> 현재 함수의 기준점을 rbp로 잡음
- sub rsp, 0x10: 스택에 16바이트만큼 공간 확보
- 2. 첫 번째 printf 함수 호출 준비/호출
- lea rax, [rip+0xe8b]: rip+0xe8b에 있는 문자열의 주소를 rax에 넣음
- mov rdi, rax: printf의 첫 번째 인자 rdi에 주소값을 넣음 -> printf(0x40202e) 준비 완료
- mov eax, 0x0: eax를 0으로 맞춤 (관례)
- call 0x401070 <printf@plt>: printf 호출 (@plt는 실제 libc의 printf로 연결해주는 점프 테이블 PLT 경유 호출)
- 3. gets 함수 호출 준비/호출
- lea rax, [rbp-0x10]: 버퍼 시작 주소 rbp-0x10를 rax에 넣음 (확보한 16바이트 버퍼의 시작점)
- mov rdi, rax: gets의 첫 번째 인자 rdi에 버퍼 주소를 넣음 -> gets(buf) 준비 완료
- mov eax, 0x0: eax를 0으로 맞춤 (관례)
- call 0x401080 <gets@plt>: gets(buf) 실행
- 4. 두 번째 printf 함수 호출 준비/호출
- lea rax, [rbp-0x10]: 버퍼 주소를 rax에 로드
- mov rsi, rax: printf의 두 번째 인자 rsi에 버퍼 주소를 넣음
- lea rax, [rip+0xe6c]: 두 번째 printf에서 쓸 포맷 스트링 주소를 rax에 로드
- mov rdi, rax: printf 첫 번째 인자 rdi에 포맷 스트링 주소를 넣음
- mov eax, 0x0: eax를 0으로 맞춤 (관례)
- call 0x401070 <printf@plt>: printf(fmt, buf) 호출
- 5. 함수 종료 (리턴)
- mov eax, 0x0: main의 반환값을 0으로 설정 (return 0;)
- leave: mov rsp, rbp + pop rbp를 합친 축약 명령 -> 스택 프레임 정리 (로컬 공간 해제 + rbp 복원)
- ret: pop rip 축약 명령 -> 리턴 주소로 점프해서 함수 종료
스택에 할당된 공간은 0x10
-> gets 함수를 사용할 경우 입력에 대한 크기 검증을 하지 않아 스택이 덮어 써지게 됨 (BOF)
3. pwndbg를 사용해 bof_sample 분석 - 프로그램을 실행 시켜 스택보다 큰 입력을 주어 테스트

스택보다 큰 값을 입력했을 때 SIGSEGV, Segmentation fault와 Invalid address ... 를 확인 가능
- 스택 BOF로 인해 rbp+8(Ret addr, 되돌아가야 할 주소)이 문자 'C'(0x42)로 덮어 써지면서 잘못된 주소로 인한 crash
※ rbp+8 == rip 인 것은, 'AAAAAAAAAAAAAAAABBBBBBBBCCCCCCCC'을 16바이트짜리 buf에 입력했을 때, B 8개는 rbp에, 그리고 C는 rip에 저장된 것을 보고 유추한 것이다.
- rbp에 8개가 찬 후, 넘친 것은 rip에 저장 -> rbp는 8바이트이고, rbp 다음엔 rip가 오는구나!
-> 다른 함수를 호출하기 위한 아이디어
- 스택 BOF를 이용하여 return address(rip, 다음에 실행될 코드 주소)를 변조 시킬 수 있음
- 해당 행위를 위해서 main 함수 내의 스택의 상태를 알고 있어야 덮어 쓸 수 있음
- secret 함수의 주소를 알고 있어야 함 -> 이 함수 주소를 강제로 rbp+8, 즉 return address에 넣어 강제 호출 가능

※ i r $rsp: info register $rsp


rbp+8 즉, 0x7fffffffffffe108은 main 함수가 끝나고 돌아갈 주소가 담겨 있는 스택의 주소이다. (pop 하며 위로가며 읽음)

rbp가 0x7fffffffffffe100 이므고, rax에는 0x7fffffffffffe0f0, 즉 현재 스택의 위치가 담긴다.
그래서 인자로는 스택의 첫 시작 주소를 넘겨주겠다는 의미이다.


-> 24개의 문자를 넣고, 그 다음에 원하는 주소를 넣게 되면 그 주소로 덮어 쓸 수 있다.
- e0f0 ~ e108까지를 24개의 문자로 채움
- 그 다음이 ret 주소 부분




-ne: n은 맨 끝에 줄바꿈 \n을 붙이지 말라는 의미이고, e는 이스케이프 문자로 출력하라는 의미이다.
이때, PIE가 켜져 있으면 시크릿 주소가 매번 바뀌기에 위와 같은 방법 적용이 어렵다. (시크릿 주소로 점프 어려움)

참고로, secret 함수 호출이 끝나고 되돌아가려고 rbp를 보면 aaaaaaaa로 깨져 있기에, 프로그램이 죽는다. (crash)
3. 실습 2: 64비트 환경에서 Shell Code 실행
보호기법이 적용되지 않은 스택에 쉘코드를 어떻게 올리고 실행할 수 있는지
실제 공격자는 Shell Code를 스택에 적재하여 shell을 실행시키는 것을 목표로 함
- 스택 영역에서 실행 권한이 존재할 때
- 권한이 없다면 다른 기법을 사용해야 함
공격의 원리는 실습 1과 같으나, 어떻게 스택에 쉘코드를 적재하고 실행할지 고민 필요
0. 실습 준비

1. 스택 영역의 실행 권한 확인

Stack: Executable -> 스택에서 코드 실행시킬 수 있구나!
2. exploit 도구 pwntools 설치

3. shell code



-> 쉘 명령어 쓸 수 있게되면 성공!
특정 버퍼로 점프를 하게 되어 거기에 있는 쉘 코드를 실행하는 식이다.
이때 만약, alr이 켜져 있으면 계속 랜덤으로 되기에 원하는 주소로 점프하는 상황이 조금 어렵게 된다.
즉, aslr이 켜져 있을 때 정확한 위치로 점프하고 싶으면 다른 취약점이나 버퍼 오버플로우를 체이닝 하여 그 주소를 알아내야 하는 번거로움이 생긴다.
- 체이닝: 최종 공격에 필요한 정보/조건을 만들기 위해, 여러 취약점(또는 여러 단계의 페이로드)을 순서대로 연결해서 쓰는 것
'CS > 소프트웨어보안' 카테고리의 다른 글
| 9-1. 소프트웨어 퍼징의 개념과 기술 (0) | 2025.12.14 |
|---|---|
| 7-2. BoF Exploit 2 (0) | 2025.12.14 |
| 8. Toyransome (0) | 2025.12.09 |
| 6. Reversing (0) | 2025.12.06 |
| 5-2. 리눅스 사용 (0) | 2025.10.17 |