1. lazy binding
- 함수가 처음 호출될 때, 함수의 주소를 구하고 GOT 테이블에 업데이트하는 것을 의미한다.
- 이는 바이너리가 실행 중에 GOT테이블을 업데이트할 수 있는 쓰기 권한이 있어야 가능하다.
2. ELF의 데이터 세그먼트
- 프로세스 초기화 및 종료와 관련된 .init_array, .fini_array가 존재한다.
- .init_array, .fini_array는 프로세스의 시작과 종료에 실행할 함수들의 주소를 저장하고 있다.
이에 해당 위치에 공격자가 임의로 값을 쓸 수 있다면, 프로세스의 실행 흐름을 조작할 수 있는 것이다. - 이러한 문제점을 해결하고자, 프로세스의 데이터 세그먼트를 보호하는 "RELRO (RELocation Read-Only)"를 사용한다.
3. RELRO
- 쓰기 권한이 불필요한 데이터 세그먼트는 쓰기 권한을 제거하는 기법이다.
① Partial RELRO
- RELRO를 부분적으로 적용하는 것
② Full RELRO
- 가장 넓은 영역에 RELRO를 적용하는 것
4.실습
4-1. Partial RELRO
① Checksec을 이용하여, 보호 기법을 확인한다.
- gcc는 기본적으로 Full RELRO를 적용하며, PIE를 해제할 경우에는 Partial RELRO를 적용한다.
② 실습 코드는 바이너리의 메모리 맵을 출력해주는 것이다.
- 출력 결과를 확인해보면, 0x601000 ~ 0x602000까지 주소에 쓰기 권한이 있는 것을 확인할 수 있다.
③ objdump를 이용하여, Partial RELRO 바이너리의 섹션 헤더를 확인할 수 있다.
- 0x601000 ~ 0x602000의 영역에는 .got.plt , .data, .bss가 할당된 것을 볼 수 있으며,
이는 해당 섹션에 대한 쓰기 권한이 존재함을 알 수 있다. - 하지만 .init_array와 .fini_array에는 쓰기 권한이 존재하지 않음을 알 수 있다.
[참고] Partial RELRO가 적용된 바이너리는 got와 관련된 섹션이 .got와 .got.plt로 두가지가 존재한다. ① .got - 전역변수 중에서 실행되는 시점이 바인딩되는 변수는 .got에 위치한다. - 바이너리가 실행될 때, 이미 바인딩이 완료되어 있으므로 쓰기 권한이 부여되지 않는다. ② .got.plt - 실행 중에 바인딩되는 변수는 .got.plt에 위치한다. - 해당 영역은 실행 중에 값이 써져야 하므로 쓰기 권한이 부여된다. - Partial RELRO가 적용된 바이너리에서 대부분 함수들의 GOT엔트리는 .got.plt에 저장된다. |
4-2. Full RELRO
① Checksec을 이용하여, 보호 기법을 확인한다.
② 실습 코드는 바이너리의 메모리 맵을 출력해주는 것이다.
- 출력 결과를 확인해보면, 0x55e891cbe000 ~ 0x55e891cbf000까지 주소에 쓰기 권한이 있는 것을 확인할 수 있다.
③ objdump를 이용하여, Partial RELRO 바이너리의 섹션 헤더를 확인할 수 있다.
- .data과 .bss에만 쓰기 권한이 있는 것을 알 수 있다.
5. RELRO 우회
5-1. Partial RELRO
- .got.plt영역에 대한 쓰기 권한이 존재하므로 GOT Overwrite공격이 가능하다.
- Lazy binding을 사용하므로, 라이브러리 함수들의 GOT엔트리는 쓰기가 가능한 것이다.
- .init_array와 .fini_array에 대한 쓰기 권한이 제거되어 두 영역을 덮는 것은 어렵다.
5-2. Full RELRO
- Lazy binding을 사용하지 않으며, 라이브러리 함수들의 주소는 바이너리가 로드되는 시점에 바인딩된다.
- .init_array와 .fini_array, .got영역에 대한 쓰기 권한도 제거되어 있다.
- libc의 malloc hook, free hook과 같은 함수 포인터를 조작하는 공격으로 우회할 수 있다.
- HOOK Overwrite
- malloc 코드를 살펴보면, 함수의 시작 부분에서 __malloc_hook이 존재하는지 검사하고, 존재하면 이를 호출한다.
- __malloc_hook은 libc.so에서 쓰기 가능한 위치에 있기 때문에,
공격자가 libc가 매핑된 주소를 알 때, 이 변수를 조작하고 malloc을 호출하여 실행 흐름을 조작할 수 있다.
[참고] - hook : 해당 함수 포인터는 동적 메모리의 할당과 해제 과정에서 발생하는 버그를 디버깅하기 쉽게 하려고 만들어졌다. - 대표적으로 malloc hook과 free hook이 존재한다. |
[ glibc malloc 소스 코드 ]
void *
__libc_malloc (size_t bytes)
{
mstate ar_ptr;
void *victim;
void *(*hook) (size_t, const void *)
= atomic_forced_read (__malloc_hook); // read hook
if (__builtin_expect (hook != NULL, 0))
return (*hook)(bytes, RETURN_ADDRESS (0)); // call hook
#if USE_TCACHE
/* int_free also calls request2size, be careful to not pad twice. */
size_t tbytes;
checked_request2size (bytes, tbytes);
size_t tc_idx = csize2tidx (tbytes);
// ...
'Dreamhack > Lecture & Practice' 카테고리의 다른 글
[Lecture] OOB (Out of bounds) (0) | 2022.08.09 |
---|---|
[Practice] Hook Overwrite (0) | 2022.07.14 |
[Lecture] PIE (Position-Independent Executable) (0) | 2022.07.12 |
[Practice] ROP - GOT Overwrite (0) | 2022.07.02 |
[Practice] Return to Library (0) | 2022.07.01 |