1. 메모리 보호 기법
NX (No-eXecute)
실행에 사용되는 메모리 영역과 쓰기에 사용되는 메모리 영역을 엄격하게 분리하는 보호 기법. 바이너리가 실행될 때 각 메모리 영역에서 필요한 권한만 부여받는다.
- 아키텍처 및 OS별 명칭
- AMD: NX (No-eXecute)
- 인텔: XD (eXecute Disable)
- Windows: DEP (Data Execution Prevention)
- ARM: XN (eXecute Never)
- GDB vmmap 변화 및 원리
- NX 적용 X: 스택(Stack) 영역에 읽기(r), 쓰기(w), 실행(x) 권한이 모두 부여됨(rwxp). 해커가 스택에 쉘코드를 주입시키고 실행 흐름을 스택으로 돌리면 해당 쉘코드가 그대로 실행된다.
- NX 적용 O: 스택에 실행 권한이 제거됨(rw-p). 스택에 쉘코드를 주입시켜도 실행 권한이 없으므로 코드가 동작하지 않고 프로그램이 강제 종료(Segmentation Fault)된다.
- vmmap은 가상 메모리의 레이아웃과 각 영역에 부여된 권한(Permission)을 보여주는 명령어다.
ASLR (Address Space Layout Randomization)
바이너리가 실행될 때마다 스택, 힙, 공유 라이브러리 등(PIE 미적용 시 코드 영역 제외)을 임의의 랜덤한 주소에 할당하는 보호 기법. 현대 리눅스 시스템에 기본적으로 적용되어 있다.
- 페이지(Page) 단위 매핑과 하위 12bit 고정
- 리눅스는 메모리를 4KB(페이지) 단위로 관리하고 매핑한다. 4KB는 4096바이트($2^{12}$)이므로, 메모리가 아무리 랜덤하게 섞이더라도 주소의 하위 12bit(16진수 기준 마지막 3자리)는 절대 변하지 않는다. (예: 실행할 때마다 힙 주소는 항상 ...260으로 끝남)
- 오프셋(Offset) 고정
- 라이브러리 파일을 그대로 임의 주소에 통째로 매핑하기 때문에, 라이브러리 베이스 주소가 바뀌어도 라이브러리 내 함수들의 주소 차이(Offset)는 항상 같다. (예: printf와 libc_base의 주소 차이값은 항상 0x64f00으로 동일함) 하나의 함수 주소만 유출(Leak)해 내면 다른 함수의 주소도 계산할 수 있다.
2. 방어 우회 기법 (NX, ASLR 우회)
- RTL (Return to Library)
- NX 기법으로 인해 스택의 코드를 실행하지 못할 때 사용하는 공격. 스택에 쉘코드를 넣는 대신, 이미 실행 권한이 있는 표준 라이브러리(libc) 내부에 존재하는 system() 함수 주소로 리턴 주소를 덮어씌워 쉘을 획득한다.
- ROP (Return Oriented Programming)
- RTL을 발전시킨 기법. 메모리의 실행 가능 영역(코드 또는 라이브러리)에 존재하는 ret 명령어로 끝나는 짧은 기계어 조각(Gadget)들의 주소를 스택에 연쇄적으로 쌓아, 원하는 로직을 조립하여 실행하는 고도화된 해킹 기법이다.
3. 링크 (Linking)
소스코드가 실행파일(바이너리)이 되는 과정은 전처리(preprocessing) → 컴파일(compilation) → 어셈블(assemble) → 링크(linking) 순서로 진행된다. 여기서 링크는 프로그램과 외부 라이브러리의 함수를 연결해 주는 과정이다.
정적 링크와 동적 링크
| 구분 | 정적 링크 (Static Link) | 동적 링크 (Dynamic Link) |
| 개념 | 정적 라이브러리를 바이너리에 통째로 링크함 | 프로세스 실행 시 동적 라이브러리를 메모리에 매핑함 |
| 특징 | 바이너리에 필요한 모든 라이브러리 함수가 포함됨 | 여러 프로세스가 하나의 라이브러리를 공유함 |
| 탐색 | 주소 탐색 과정이 필요 없음 | 실행 중에 라이브러리 함수 주소를 찾아야 함 |
| 용량 | 바이너리 용량이 매우 커짐 | 바이너리 용량이 작음 |
| 호출 방식 | 함수의 주소로 직접 함수 호출 | PLT 참조를 통해 함수 주소를 찾아 호출 |
4. PLT와 GOT (동적 링크의 핵심)
정적 링크와는 다르게, 동적 링크는 라이브러리가 외부에 있기 때문에 함수의 주소를 탐색하고 알아오는 과정이 필요하다.
- PLT (Procedure Linkage Table): 외부 프로시저를 연결해 주는 징검다리 테이블. 코드 세그먼트에 위치하며 실행 권한(r-x)이 있다.
- GOT (Global Offset Table): PLT가 참조하는 테이블. 찾아낸 외부 프로시저들의 실제 주소가 이곳에 저장된다. 데이터 세그먼트에 위치하며 읽기/쓰기 권한(rw-)이 있다.
실행 과정 (Lazy Binding)
- 프로그램에서 외부 함수가 호출되면 우선 PLT를 참조한다.
- PLT에서 해당 함수의 GOT 엔트리(주소)로 이동한다.
- 첫 번째 호출 시: 아직 GOT에 실제 함수 주소가 저장되어 있지 않다. 링커가 dl_resolve 함수를 이용하여 라이브러리에서 실제 함수 주소를 탐색한다. 찾은 주소를 해당 GOT 엔트리에 저장한 후 함수를 실행한다.
- 두 번째 호출 시: 이미 GOT에 실제 주소가 저장되어 있으므로, 복잡한 탐색 과정 없이 GOT에 적힌 주소로 바로 점프하여 함수를 실행한다.
취약점 발생 원리 (GOT Overwrite)
프로그램은 GOT에 저장된 값이 원래의 정상적인 함수 주소인지 검증하지 않는다. 또한 GOT 영역은 쓰기 권한(rw-)을 가지고 있다. 따라서 포맷 스트링 버그(FSB)나 버퍼 오버플로우 등을 이용하여 해당 GOT 엔트리의 값을 악의적인 함수(get_shell 등)의 주소로 덮어쓰면(Overwrite), 이후 함수가 호출될 때 실행 흐름을 변조하여 쉘을 탈취할 수 있다.
'System Hacking > 인터루드 스터디' 카테고리의 다른 글
| 260528 [Dreamhack] Format String Bug (0) | 2026.05.28 |
|---|---|
| 260528 PIE, RELRO 개념정리 (0) | 2026.05.28 |
| 260521 [Dreamhack] basic_exploitation_002 (0) | 2026.05.21 |
| 260514 [picoCTF] format string 1 (0) | 2026.05.14 |
| 260514 [picoCTF] format string 0 (0) | 2026.05.14 |