아직 윈도우 메모리 보호 기법은 다루지 못했다. 또한 다음 포스트로는 보호 기법 우회 실습을 다루려 한다.
0. Memory Exploit Mitigation Techniques
0.1. Linux
이어 말할 메모리 보호 기법을 더 잘 이해하기 위해 리눅스의 프로세스 메모리 구조를 명시한다.
.code(.text): RE |
---|
.rodata: R, 상수 저장 |
.data: RW, 초기화한 전역 변수 저장 |
.bss: RW, 초기화 되기 전의 전역 변수 저장 |
heap: RW, 동적으로 메모리를 할당할 때 사용 |
stack: RW, 지역 변수 저장 |
OS Kernel Space |
(Low to High address) .bss 영역에 있던 초기화 되기 전의 전역 변수들은 main이 실행되기 전에 0으로 초기화되어 .data 영역에 저장된다.
0.1.1. ASLR(Address Space Layout Randomization)
공유 라이브러리, 스택 및 힙, 실행 파일의 베이스가 매핑되는 메모리 영역의 주소를 Randomization 한다.
공격자가 공격에 필요한 정확한 메모리 주소를 알아내 공격에 사용하는 것을 막아주는 기법이다. 리눅스에서는 randomize_va_space
파일에 int 값으로 옵션이 저장되는데, 이 값이 0일 경우 해제, 1일 경우 스택과 라이브러리 랜덤화, 2일 경우 스택, 라이브러리 및 힙까지 랜덤화를 할 수 있다.
0.1.2. ASCII-Armor
공유 라이브러리 매핑 시, 주소값의 상위 1바이트를
0x00
이 되도록 배치한다.
0x00
은 ASCII로 NULL을 의미한다. 보통 문자열 관련 함수들은 NULL 문자를 문자의 끝이라고 인식하기 때문에, ASCII-Armor는 문자열 관련 함수를 이용한 라이브러리 주소 편집/사용이 불가능해지게 만든다. 따라서, RTL과 같이 라이브러리 주소를 이용한 공격을 까다롭게 만든다.
0.1.3. FORTIFY_SOURCE
strcpy, memset과 같은 메모리 버퍼 관련 함수 실행 시, 기존 함수 대신 src와 dst의 크기를 비교하는 검사를 수행하는 빌트인 함수로 대체해 실행시킨다.
입력값이 저장될 위치의 크기와 입력값의 크기를 비교하는 것이므로, SSP와 달리 스택에 할당되지 않은 변수에 대한 오버플로우 검사가 가능하다.
적용하기 위해서는 gcc 컴파일 시 -D_FORTIFY_SOURCE=N
옵션을 추가해야 하고, -O1
이상의 최적화 옵션도 추가해 주어야 한다.
0.1.4. NX-bit(No-eXecute bit)
데이터 영역의 메모리 페이지를 NX 특성으로 지정해 BOF를 이용한 쉘코드 실행 공격을 불가능하게 만든다.
리눅스의 프로세스 메모리 5개 영역 중, 코드 영역과 데이터 영역의 실행 권한을 명확하게 분리하려는 정책이다.
0.1.5. PIE(Position Independent Executable)
프로그램의 바이너리 코드가 매핑되는 메모리 영역이 매번 달라지게 하는 기법이다.
따라서, plt나 got 영역에 접근해 라이브러리 함수의 절대 주소를 알아내 익스플로잇에 이용하는 기존 기법이 어려워졌다. 또한 코드 영역의 주소가 실행될 때마다 변화하므로 ROP 등의 코드 재사용 공격을 막을 수 있다.
컴파일러가 제공하는 보안 기능이기 때문에, 컴파일러 옵션으로 -no-pie
를 주어서 끌 수 있다.
0.1.6. RELRO(RELocation Read-Only)
ELF binary의
.dynamic
섹션을 RO로 설정해 데이터 섹션의 보안을 강화하는 보호기술
Relocation Table이란 프로그램이 실행되기 위해 어느 부분에 relocation이 필요한지 보관하고 있는 영역으로, 라이브러리 함수의 주소를 담고 있는 .got
등이 이에 해당된다.
- Partial RELRO-Lazy Binding
dynamic
영역의 쓰기 권한을 없애 버린다.dynamic
영역이란?- 오브젝트 파일(소스)가 dynamic linking을 사용하면 해당 파일의 elf header 에
PT_DYNAMIC
타입의.dynamic
이라는 섹션이 생기는데, 이때 해당 섹션에는_DYNAMIC
이라는 심볼이 붙는다. _DYNAMIC
섹션 안에는 함수 엔트리를 가리키는 포인터가 있다.
- 오브젝트 파일(소스)가 dynamic linking을 사용하면 해당 파일의 elf header 에
- ELF Header에
RELRO
영역이 생기고, 해당영역의 권한은 RO이다.- 해당 영역에 포함되는 Section은
INIT_ARRAY
,FINI_ARRAY
이다. - 즉
.dynamic
에 속하지 않는.got
섹션은 덮어쓸 수 있다.
- 해당 영역에 포함되는 Section은
- Full RELRO-Now Binding
.bss
section 이외 모든 영역의 쓰기 권한이 없어진다.- ELF Header에
RELRO
영역이 생기고, 해당 영역의 권한은 RO이다.- 해당 영역에 포함되는 Section은
INIT_ARRAY
,FINI_ARRAY
,PLTGOT
이다.
- 해당 영역에 포함되는 Section은
- Section 영역에서
PLTRELSZ
,PLTREL
,JMPREL
이 제거되고BIND_NOW
,FLAGS_1
섹션이 추가된다. - 즉,
.got
섹션을 덮어쓸 수 없다.
0.1.7. SSP(Stack Smashing Protector)
Stack Smashing, 즉 스택을 violating하는 스택 버퍼 오버플로우를 막기 위해 고안된 방식이다.
보통은 Stack Canary라고 부른다.
함수가 실행될 때 스택의 RBP와 RET 사이에 랜덤으로 생성된 Canary를 삽입한다. Canary 가 변조된 것이 확인되면 __stack_chk_fail
함수가 호출되면서 프로그램이 종료되기 때문에, RET 변조를 이용한 공격을 방어할 수 있다.
1. How to Bypass them
0.1. Linux
0.1.1. ASLR(Address Space Layout Randomization)
- Address Leak: 가장 현실적
- ASLR Bypass Brute-Force attack
- NOP Sled
- Heap Spraying
- RTL using Symbolic Link
0.1.2. ASCII-Armor
- RTL: 쓰레기값에 심볼릭 링크를 거는 방식의 RTL(Omega Project)
- Chainning RTL
- PLT & GOT Overwrite
0.1.3. FORTIFY_SOURCE
- 입력값이 저장될 위치의 크기를 알 수 없다면 아무런 검사를 수행할 수 없기 때문에 빌트인 함수가 아닌 원본의 라이브러리 함수로 변경된다.
- 즉, 전달되는 크기 정보를 망가뜨려 버리거나 저장될 위치의 크기를 알 수 없다는 뜻의 플래그를 리턴하도록 한다면 얼마든지 회피할 수 있다.
- 한 예로, 저장 위치 포인터가 함수 호출을 통해 함수 간에 전달된다면 크기 정보가 모두 사라지는 이유로 검사 수행이 불가능하기 때문에 우회 가능해진다.
0.1.4. NX-bit
- RTL: RX 권한들이 동시에 존재하는 메모리 영역이 없기 때문에, 프로그램에 스택 버퍼 오버플로우 취약점이 존재하는 경우 X 권한이 있는 메모리 페이지에 위치한 코드들을 걸치면서 이동해 코드 플로우를 바꾸는 방식으로 익스플로잇할 수 있다.
0.1.5. PIE(Position Independent Executable)
- 코드 영역의 주소를 알아내는 것이 우선이다. 특정 코드 영역의 주소를 leak한 이후, 구한 주소를 이용해 베이스 주소를 구할 수 있다. 이렇게 구한 코드 베이스 주소에 대해 오프셋 계산을 함으로써 여타 시스템 함수들의 주소를 구할 수 있다.
0.1.6. RELRO(RELocation Read-Only)
- Partial RELRO
- plt, got는 보호받지 못하기 때문에 PLT&GOT Overwriting이 가능하다.
- Full RELRO
.ctors
,.dtors
,.jcr
,.dynamic
,.got
등의 영역에 RO가 설정되기 때문에, 그냥 이 영역들을 쓰지 않으면 쉽게 익스플로잇이 가능하다.
0.1.7. SSP(Stack Smashing Protector)
- Canary Leak: 가장 현실적! 함수의 프롤로그에서 스택에 카나리 값을 저장하므로, 이를 읽어낼 수 있다면 카나리와 똑같은 값을 오버플로우 페이로드에 집어넣어 우회할 수 있다.
- TLS(Thread Local Storage) 접근: 카나리 값이 프로세스가 시작될 때 TLS에 전역변수로 저장되고, 각 함수마다 프롤로그와 에필로그에서 이 값을 참조하기 때문에 TLS 주소를 Leak 한다면 카나리의 값을 알아내거나 조작할 수 있다.
- Brute Forcing Attack: 사실상 불가능하다.