[System][Dreamhack] 메모리 보호기법 Mitigation: NX & ASLR
지난 글(Return to shellcode)를 보면 쉘을 딸 수 있었던 이유는 다음과 같다. return addr을 임의의 주소로 덮기 가능 → canary 도입 ⇒ canary leak 으로 우회 버퍼의 주소 알아내기 가능 버퍼안의 쉘코드 실
3omh4.tistory.com
요약
- return addr을 임의의 주소로 덮기 가능 → canary 도입 ⇒ canary leak 으로 우회 가능
- 버퍼의 주소 알아내기 가능 → ASLR 도입
- 버퍼안의 쉘코드 실행 가능 → NX-bit 도입
ASLR ( Address Space Layout Randomization)
바이너리가 실행될 때마다 스택, 힙, 공유 라이브러리 등을 임의의 주소에 할당하는 보호기법이다. ASLR이 꺼져있으면 다음과 같이 매 실행마다 buf의 주소가 같다.
ASLR on/off 확인
$ cat /proc/sys/kernel/randomize_va_space
2
cat /proc/sys/kernel/randomize_va_space 명령어를 입력했을 때 0, 1, 2의 값이 출력될 수 있다. 값에 따라 적용되는 영역은 다음과 같다.
- 0 : No ASLR = ASLR을 적용하지 않음
- 1 : Conservative Randomization = 스택, 힙, 라이브러리, vdso 등에 ASLR 적용
- 2 : Conservative Randomization + brk = 스택, 힙, 라이브러리, vdso 등의 영역 + brk로 할당한 영역
ASLR 단계에 따른 주소 확인
단계에 따라 스택, 힙, 라이브러리, 라이브러리 매핑 주소, 코드 영역의 주소가 랜덤한지 아닌지 확인해보자. 예제코드는 아래와 같다.
// Compile: gcc addr.c -o addr -ldl -no-pie -fno-PIE
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
char buf_stack[0x10]; // 스택 버퍼
char *buf_heap = (char *)malloc(0x10); // 힙 버퍼
printf("buf_stack addr: %p\n", buf_stack);
printf("buf_heap addr: %p\n", buf_heap);
printf("libc_base addr: %p\n", *(void **)dlopen("libc.so.6", RTLD_LAZY)); // 라이브러리 주소
printf("printf addr: %p\n", dlsym(dlopen("libc.so.6", RTLD_LAZY), "printf")); // 라이브러리 함수의 주소
printf("main addr: %p\n", main); // 코드 영역의 함수 주소
}
매 실행마다 buf의 주소, 라이브러리 주소, 라이브러리 매핑 주소가 달라진다. 이때 힙영역의 주소는 동일한데 이는 malloc이 호출될 때 상황에 따라 아래와 같이 분기하는데 brk로 syscall을 호출해 메모리를 확보했기 때문(brk영역은 ASLR값이 2일때 적용됨)으로 보인다. 자세한 내용은 다음에.. 정리하겠다..
https://sploitfun.wordpress.com/2015/02/11/syscalls-used-by-malloc/
Syscalls used by malloc.
Having landed on this page, you should know malloc uses syscalls to obtain memory from the OS. As shown in the below picture malloc invokes either brk or mmap syscall to obtain memory. brk: brk obt…
sploitfun.wordpress.com
스택 영역의 buf_stack, 힙 영역의 buf_heap, 라이브러리 함수의 printf, 코드 영역의 함수 main, 라이브러리 매핑 주소의 libc_base를 보면 특징이 있다.
- 코드 영역(main)을 제외한 다른 영역의 주소는 매번 바뀐다.
- 변수나 함수, 라이브러리의 주소는 매번 바뀌기 때문에 바이너리를 실행하기 전에 해당 영역들의 주소를 예측할 수 없다.
- libc_base, printf의 하위 12비트(비트임 바이트 아님)값은 변경되지 않는다
- 리눅스는 파일을 페이지(page) 단위로 임의 주소로 mapping
- 페이지 크기인 12비트이하의 주소는 바뀌지 않는다.
- 페이지는 일반적으로 4KB로 4KB = 2^12이므로 하위 12비트는 바뀌지 않는 것이다.
- libc_base와 printf의 주소 차이는 항상 동일
- 라이브러리의 시작주소부터 다른 심볼들까지의 거리(offset)은 항상 동일
- libc_base + printf offset(0x64f00) = printf addr
NX - No eXecute
코드 실행 가능한 메모리 영역과 쓰기 가능한 메모리 영역을 분리하는 보호기법이다. 코드 영역에 쓰기 권한이 있으면 코드를 수정해 원하는 코드를 실행할 수 있고, 스택이나 데이터 영역에 실행 권한이 있으면 입력으로 쉘코드를 주입해 쉘을 딸 수 있다.
NX bit는 컴파일러 옵션(-zexecstack : 스택에 실행권한 부여)을 통해 바이너리에 적용할 수 있고, NX bit가 적용된 바이너리는 각 메모리 영역에 필요한 권한만 부여받을 수 있다. gdb의 vmmap으로 NX bit 적용 전 후 메모리 맵을 비교하면, NX bit 적용된 바이너리는 코드영역 외에 실행권한이 없는 것을 알 수 있다. NX bit 적용되지 않은 바이너리는 스택, 힙, 데이터 영역에도 실행 권한이 존재한다.
'시스템' 카테고리의 다른 글
Nebula Level 10 (Race Condition) (0) | 2025.02.03 |
---|---|
Exploit tech : Return to Shellcode (펌) (0) | 2025.02.02 |
onone_gadget 설치 및 사용법 (펌) (0) | 2025.02.02 |
peda, pwndbg, gef 같이 쓰기 (펌) (0) | 2025.02.02 |
Dreamhack UAF (진행 중) (0) | 2025.01.31 |