경희대학교 장대희 교수님의 소프트웨어보안 수업을 기반으로 정리한 글입니다.
보호 기법 3가지 (보호 옵션)
1. ASLR
Address Space Layout Randomization의 약자로, 실행 파일과 관련된 공유 라이브러리, 스택, 힙이 매핑되는 메모리 영역의 주소를 랜덤으로 배치하는 것을 말한다. ('영역' 단위로 랜덤화, e.g., stack, heap, libc)
ASLR이 활성화되어 있으면 7-1의 실습 2 처럼 점프할 주소를 직접 입력하는 행위는 불가능하다.
- 주소 leak을 통해 점프할 주소를 알아와야 하는 번거로움 발생
- 리눅스 설치시 자동으로 켜지는 보호 기법
- 브루트 포싱을 한다고 하더라도 같은 프로세스 안에서 계속해서 브루트 포싱을 하는 게 쉽지가 않음

11~15행: main, secret 함수의 주소가 뭔지, 그리고 지금 할당한 버퍼의 주소가 뭔지에 대한 printf 함


이때 스택 주소는 랜덤화 되는데 함수 주소는 랜덤화가 안 되는 이유는,
aslr이 랜덤화 하는 단위는 '영역'이기 때문이다.
ASLR은 메모리 영역을 랜덤 위치에 배치하지만,
non-PIE 실행 파일의 코드 영역은 절대 주소로 고정되어 있어
ASLR만으로는 함수 주소를 랜덤화할 수 없다.
PIE를 사용해야 코드 영역도 ASLR의 대상이 된다.
2. NX
No-Execute bit의 약자로, 실행 가능한 메모리 영역이 아닌 스택, 힙과 같은 영역에서 코드가 실행되는 것을 막는 보호 기법이다.
- "-z execstack" 옵션을 빼고 빌드 -> nx 활성화
NX bit가 활성화되어 있으면, 스택에 쉘코드를 업로드하여 실행시키는 행위는 불가능하다.

3. PIE [파이]
Position-Independent Executable의 약자로, 바이너리가 로딩될 때 main, secret 함수와 같은 각가지 주소들이 랜덤한 주소에 매핑되는 기법으로 바이너리를 실행할 때마다 바이너리의 주소가 랜덤화되게 한다.
스택의 RET 주소에 함수 주소를 직접 확인하고 넣는 것이 불가능해진다.
- 함수 주소를 알 수 있을 때 동적으로 삽입할 수 있는 기술 필요

추가로, -fno-stack-protector 이 옵션이 스택 카다리 관련 옵션이다.
BoF 실습
- ASLR 활성화: 스택의 주소가 매번 변화하는 상황
- NX bit 활성화: 스택에서 코드 실행이 불가능한 상황
- PIE: 활성화: 매번 적재되는 함수의 주소가 달라지는 상황
즉, 스택에서 쉘코드 실행이나 임의 주소로 점프를 해서 무언가를 실행하는 게 불가능한 상황


바이너리 분석
함수 심볼 분석

main, vuln, get_shell 함수가 존재하는 것을 확인 가능
pwndbg를 사용해 bof_sample2 분석


main 함수 내에서는 간단하게 vuln 함수를 호출하는 것을 확인 가능
- main 함수는 vuln 함수 호출 외에 하는 게 없고, vuln이 핵심





secret 문제에서는 PIE가 비활성화되어 있어
disass에서 확인한 함수 주소가 실제 실행 주소와 동일했기 때문에
해당 주소로 직접 점프가 가능했다.
그러나 본 문제에서는 PIE가 활성화되어 있어
disass에서 확인되는 주소는 실제 실행 주소가 아닌 ELF 내부 오프셋이며,
프로그램 실행 시 base 주소가 결정되므로
주소 leak 없이는 해당 함수로 직접 점프할 수 없다.
- 주소 leak e.g., printf("%p", get_shell)


명령어 설명
- $(명령): 괄호 안 명령을 먼저 실행 후, 그 출력 결과를 문자열로 바꿈
- <<<: 문자열을 stdin으로 넣음

-> 일단, 기본적으로 main 함수의 스택이 얼만큼 벌어져 있는지, 내가 입력하고자 하는 그 버퍼로부터 rbp+8 이라는 return address가 얼만큼 떨어져 있는지를 디버깅을 통해 알아내야 한다.
그리고 get_shell의 주소가 매번 바뀌기에, 프로그램을 실행할 때마다 그 주소를 가지고 와서 passing을 통해 동적으로 페이로드를 작성할 수 있는 기술 스택이 있어야 한다.
-> 프로그램이 실행될 때 get_shell()의 주소를 알려주니, 그 정보를 이용해 코드를 작성해 get_shell()을 실행하고자 함



위아래 변수 추가시, payload 다시 계산 하는 연습
void vuln(){
int x; // 4바이트
char buf[0x40]; // 64바이트
long y; // 8바이트 (x86-64에서 long=8)
gets(buf);
}
buf만 있을 경우엔
- buf = rbp-0x40
- ret = rbp+8
- 거리 = 0x40 + 8 = 0x48
그런데 위 코드처럼 위아래로 변수를 추가하면 오프셋 값이 변경되어야 한다.
높은 주소
[rbp+8] : return address <-- 우리가 덮고 싶은 곳
[rbp+0] : saved rbp
--------------------------------
[rbp-0x8] : (padding or y의 일부 위치일 수도)
[rbp-0x10]: y (8 bytes)
[rbp-0x14]: padding (정렬 맞추기)
[rbp-0x18]: x (4 bytes) + padding
[rbp-0x58] ~ [rbp-0x19]: buf[0x40] (64 bytes)
낮은 주소
위 배치에서 buf 시작이 rbp-0x58이면 거리 = 0x58 + 8 = 0x60






전체 흐름
- 1. 프로그램이 출력하는 주소를 받아서 (addr)
- 2. 그 주소를 정수로 파싱하고
- 3. offset만큼 채운 뒤
- 4. ret에 addr을 덮어 쓴 payload를 보내고
- 5. 쉘이 뜨면 interactive()로 직접 조작

recvuntill(b"addr:"): "addr" 문자열이 나올 때까지 기다리겠다는 의미
recvline(): 한 줄 읽고,
decode(): 문자열로 변환하고,
strip(): 앞뒤 공백 자르고
-> line에 저장


쉘 획득 -> 서버 장악
'CS > 소프트웨어보안' 카테고리의 다른 글
| 9-1. 소프트웨어 퍼징의 개념과 기술 (0) | 2025.12.14 |
|---|---|
| 7-1. BoF Exploit 1 (0) | 2025.12.13 |
| 8. Toyransome (0) | 2025.12.09 |
| 6. Reversing (0) | 2025.12.06 |
| 5-2. 리눅스 사용 (0) | 2025.10.17 |