Lazy binding은 ELF 바이너리에서 라이브러리 함수의 시작주소를 구하지 않다가, 함수를 처음 호출할 때 해당 주소를 구해 GOT에 저장하는 것을 말한다. Lazy binding을 하는 바이너리는 실행 중 GOT를 업데이트 해야하므로, GOT에 쓰기 권한이 부여된다. 이로 인해 GOT Overwrite 공격이 가능한 것이다.
GOT 영역 외에도, ELF의 데이터 세그먼트에는 프로세스 초기화 및 종료와 관련된 .init_array, .fini_array가 있다. 해당 영역들은 프로세스 시작과 종료에 실행할 함수의 주소를 저장하고 있는데, 여기에 임의의 값을 쓸 수 있다면 프로세스 흐름 조작이 가능하다.
이런 overwrite로부터 데이터 세그먼트를 보호하고자 RELocation Read-Only(RELRO)개념이 도입되었다. RELRO는 쓰기권한이 불필요한 데이터 세그먼트에 쓰기 권한을 제거하는 것이다. 적용 범위에 따라 Partial RELRO와 Full RELRO로 나뉜다.
두 방식의 차이를 보기 위해, 자신의 메모리 맵을 출력하는 아래의 예제코드로 사용할 것이다.
// Name: relro.c
// Compile: gcc -o prelro relro.c -no-pie -fno-PIE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
FILE *fp;
char ch;
fp = fopen("/proc/self/maps", "r");
while (1) {
ch = fgetc(fp);
if (ch == EOF) break;
putchar(ch);
}
return 0;
}
gcc는 기본적으로 Full RELRO를 적용하고, PIE를 해제하면 Partial RELRO를 적용한다. RELRO 여부도 checksec으로 확인할 수 있다.
Partial RELRO
PIE를 해제하고 checksec으로 확인해보면 아래와 같이 Partial RELRO가 적용된 것을 볼 수 있다.
Partial RELRO 권한
prelro를 실행해보면, 0x404000부터 0x405000까지 쓰기권한이 있는 것을 확인할 수 있다.
objdump를 이용해 해당 영역의 섹션 헤더를 참조해보면, 0x404000-0x405000에는 .got.plt, .data, .bss가 할당되어 있다. 즉, 이 섹션에는 쓰기가 가능하다. 하지만, .init_array와 .fini_array는 0x403e10과 0x403e18에 할당되어 있어 쓰기권한이 없는 영역에 존재한다.
.got vs .got.plt
Partial RELRO가 적용된 바이너리는 GOT와 관련된 섹션이 .got와 .got.plt로 두 개가 존재한다. 전역 변수 중에서 바이너리가 실행되는 시점에 바인딩(now binding)되는 변수는 .got섹션에 위치한다. 바이너리가 실행될 때는 이미 바인딩 된 후 이므로 쓰기권한을 부여하지 않는다.
lazy binding의 경우 실행 중 값을 업데이트 해야되므로, 쓰기권한이 부여된다. Partial RELRO가 적용된 바이너리에서 대부분 함수들의 GOT 엔트리는 .got.plt에 저장된다.
Full RELRO
gcc는 기본적으로 Full RELRO를 적용하기 때문에, 옵션을 제거한 후 컴파일하여 checksec으로 확인한다.
frelro 실행 결과와 objdump로 확인한 섹션 헤더를 종합해보면, data와 bss에만 쓰기 권한이 있는 것을 확인할 수 있다.
frelro가 매핑된 0x5652e529d000에 .data섹션의 오프셋인 0x4000을 더하면 0x5652e52a1000이고, 이는 쓰기 권한이 있는 영역에 속한다. .bss섹션도 계산해보면 0x5652e52a1010으로 쓰기 권한이 존재하는 영역이다.
Full RELRO가 적용되면 라이브러리 함수의 주소가 바이너리의 로딩 시점에 모두 바인딩 되기 때문에, got에 쓰기권한이 부여되지 않는다. → GOT overwrite 불가능
RELRO 우회
Partial RELRO의 경우, .init_array, .fini_array에는 쓰기 권한이 없지만 .got.plt 영역에 쓰기 권한이 있기 때문에 GOT Overwrite로 공격할 수 있다.
Full RELRO의 경우, .init_array, .fini_array, .got 모두 쓰기 권한이 없다. 그래서 라이브러리에 위치한 hook을 이용해 공격한다. 라이브러리 함수의 대표적인 hook은 malloc hook과 free hook이다.
malloc 함수의 코드를 보면, 함수 시작 부분에서 __malloc_hook이 존재하는지 검사하고, 존재하면 이를 호출한다. __malloc_hook은 libc.so에서 쓰기 가능한 영역에 위치한다. 따라서 공격자가 libc가 매핑된 주소를 알면, __malloc_hook을 조작하고 malloc을 호출해, 실행 흐름을 조작할 수 있다. 이런 공격기법을 Hook Overwrite라고 한다.
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);
// ...
'CS > system' 카테고리의 다른 글
[System][Dreamhack] Exploit Tech: Hook Overwrite(2/2) (0) | 2024.03.04 |
---|---|
[System][Dreamhack] Exploit Tech: Hook Overwrite(1/2) (0) | 2024.01.24 |
[System][Dreamhack] PIE - Position-Independent Executable (0) | 2023.12.26 |
[System][Dreamhack] Exploit Tech : Return Oriented Programming(ROP) - ret2main (0) | 2023.12.22 |
[System][Dreamhack] Exploit Tech : Return Oriented Programming(ROP) (0) | 2023.12.22 |