ASLR과 페이지 권한 관리로 메모리 공격면 줄이기
ASLR, 페이지 권한, W^X와 DEP/NX를 중심으로 메모리 기반 공격을 줄이는 운영체제·컴파일러 보호 기법을 정리한다.
2026-08-14 · 최초 발행 2026-01-16
공격자가 의존하는 메모리 조건
메모리 기반 공격은 대개 주소를 알아내거나, 데이터를 덮어쓰거나, 데이터가 놓인 곳을 실행하는 조건에 기대고 있다. 버퍼 오버플로우는 메모리 경계를 넘어 데이터를 덮어쓰며, Return-Oriented Programming(ROP)은 기존 코드 조각을 이어 악성 코드를 실행한다. 코드 인젝션은 메모리에 삽입한 코드를 실행하고, Use-After-Free는 해제된 메모리를 다시 이용해 임의 코드 실행으로 이어질 수 있다.
운영체제의 메모리 보호는 이 조건을 한 번에 제거하기보다 여러 겹으로 약화한다. 주소를 예측하기 어렵게 만들고, 쓰기와 실행 권한을 분리하며, 프로세스와 커널 메모리의 경계를 지킨다.
주소를 고정하지 않는 ASLR
ASLR(Address Space Layout Randomization)은 프로세스의 메모리 영역을 매번 무작위 주소에 배치한다. 스택, 힙, 공유 라이브러리, 실행 파일의 위치가 달라지므로 공격자는 필요한 주소를 예측하기 어려워지고 메모리 기반 공격의 성공률도 낮아진다.
스택 주소 무작위화는 버퍼 오버플로우 방어에 쓰이며 스택 카나리와 함께 적용할 수 있다. 힙의 위치가 바뀌면 동적 메모리 할당 패턴도 예측하기 어려워져 Use-After-Free 공격 방어에 도움이 된다. 공유 라이브러리의 .so, .dll 주소 무작위화는 Return-to-libc와 PLT/GOT 공격을 방어하는 요소다.
실행 파일의 텍스트 세그먼트까지 무작위화하려면 PIE(Position Independent Executable)가 필요하다. 이때 절대 주소 사용은 불가능하다.
커널 이미지 자체의 주소를 무작위화하는 방식은 KASLR이다. 부팅 시 커널 주소가 결정되며 커널 익스플로잇 방어에 사용된다. 사용자 수준 ASLR은 프로세스마다 독립적으로 적용되고, 프로세스 생성 시 주소가 결정된다.
무작위화 범위는 엔트로피로 표현한다. 엔트로피가 높을수록 주소를 예측하기 어려워진다. 32비트 시스템은 8-16비트 엔트로피를, 64비트 시스템은 28-32비트 엔트로피를 가진다.
// ASLR 상태 확인 (Linux)
cat /proc/sys/kernel/randomize_va_space
// 0: ASLR 비활성화
// 1: 스택과 라이브러리 무작위화
// 2: 스택, 라이브러리, 힙 무작위화
ASLR만으로는 충분하지 않다. 정보 유출로 메모리 주소가 노출되면 우회가 가능하므로 메모리 유출 취약점을 제거하고 메모리 태그를 사용할 수 있다. 반복 추측 공격에는 엔트로피 증가와 32비트에서 64비트 전환이 대응책이 된다. 주소 일부만 덮어쓰는 공격에는 전체 주소 검증과 스택 카나리 강화가 필요하다.
페이지마다 접근과 실행을 나누는 방식
페이지 권한은 읽기, 쓰기, 실행으로 나뉜다. 읽기는 대부분의 페이지에 부여되며 읽기 금지 페이지는 드물게 사용된다. 쓰기 권한은 스택·힙·데이터 세그먼트에 필요하지만 코드 세그먼트에는 부여하지 않는다. 실행 권한은 텍스트 세그먼트에 주고, 스택과 힙은 NX/DEP로 실행을 막는다.
// x86-64 페이지 테이블 엔트리
// Present (P), Read/Write (R/W), Execute Disable (XD/NX)
+----+----+----+
| XD | RW | P |
+----+----+----+
W^X(Write XOR Execute)는 쓰기 가능한 페이지를 실행할 수 없게, 실행 가능한 페이지를 쓸 수 없게 만드는 정책이다. 코드 인젝션 공격을 막기 위한 분리이며 대부분의 현대 운영체제에서 강제된다.
DEP/NX(Data Execution Prevention / No-eXecute)는 하드웨어와 운영체제가 이 분리를 구현하는 방식이다. Intel은 XD(eXecute Disable), AMD는 NX(No-eXecute), ARM은 XN(eXecute Never) 비트를 제공한다. 운영체제에서는 Windows의 DEP, Linux의 NX 비트 활용, macOS의 Stack/Heap 실행 방지가 이에 해당한다. 스택과 힙에서 코드를 실행하지 못하게 해 버퍼 오버플로우 공격을 무력화한다.
// Linux에서 페이지 권한 설정
mprotect(addr, len, PROT_READ | PROT_WRITE); // 읽기/쓰기
mprotect(addr, len, PROT_READ | PROT_EXEC); // 읽기/실행
권한에 맞지 않는 페이지 접근은 MMU의 검사에서 멈춘다. 권한 위반이면 운영체제가 Page Fault를 처리하고 프로세스에 SIGSEGV를 전달한다.
JIT 컴파일러는 실행 시점에 코드를 생성한 뒤, 쓰기 가능한 메모리에 코드를 작성하고 실행 전 권한을 읽기/실행으로 바꾼다. 자가 수정 코드는 런타임에 코드를 바꾸는 방식으로, 보안 위험 때문에 권장되지 않는다. 다만 디버깅 및 동적 계측 도구에서는 사용된다.
ASLR과 권한 제어를 보완하는 보호층
스택 카나리는 스택 프레임에 특수 값을 두고 함수 반환 전에 이를 확인한다. 값이 달라지면 스택 버퍼 오버플로우를 탐지한다. GCC에서는 -fstack-protector 옵션으로 적용할 수 있다.
// 스택 카나리 예시
void vulnerable_function(char *input) {
unsigned long canary = __stack_chk_guard; // 카나리 값 저장
char buffer[64];
strcpy(buffer, input); // 잠재적 오버플로우
if (canary != __stack_chk_guard) { // 카나리 검증
__stack_chk_fail(); // 오버플로우 탐지 시 종료
}
}
RELRO(Relocation Read-Only)는 GOT(Global Offset Table)를 보호한다. 부분 RELRO는 GOT 재배치 후 읽기 전용으로 만들고, 전체 RELRO는 전체 GOT를 읽기 전용으로 만들어 GOT 덮어쓰기 공격을 막는다.
ARM MTE(Memory Tagging Extension)는 메모리 주소에 태그를 부여하고 포인터와 메모리 태그의 일치를 검증한다. Use-After-Free와 버퍼 오버플로우 탐지에 사용된다.
SMAP/SMEP(Supervisor Mode Access/Execution Prevention)는 커널 모드에서 사용자 공간 접근을 제한한다. SMAP은 사용자 메모리 읽기와 쓰기를, SMEP은 사용자 메모리 코드 실행을 막아 커널 익스플로잇을 방어한다.
시스템과 바이너리에서 확인할 항목
Linux에서는 ASLR 설정, KASLR, 프로세스 메모리 맵을 다음 명령으로 확인할 수 있다.
# ASLR 설정 확인
cat /proc/sys/kernel/randomize_va_space
# KASLR 확인
dmesg | grep KASLR
# 프로세스 메모리 맵 확인
cat /proc/<pid>/maps
GCC와 Clang에서는 스택 카나리, PIE, RELRO, 버퍼 오버플로우 검사를 함께 지정할 수 있다.
# GCC/Clang 보호 옵션
gcc -fstack-protector-strong \ # 스택 카나리
-fPIE -pie \ # PIE 활성화
-Wl,-z,relro,-z,now \ # RELRO
-D_FORTIFY_SOURCE=2 \ # 버퍼 오버플로우 검사
-o program program.c
Linux는 ASLR 기본 활성화, NX 비트와 KASLR을 지원한다. Windows는 ASLR과 DEP를 기본 활성화하고 Control Flow Guard(CFG)를 제공한다. macOS는 System Integrity Protection(SIP), ASLR 강제 적용, Code Signing 요구를 적용한다.
바이너리와 시스템 보호 수준은 다음 도구로 확인할 수 있다.
# checksec: 바이너리 보호 기법 확인
checksec --file=/bin/ls
# paxtest: 시스템 보호 수준 테스트
paxtest kiddie
# hardening-check: Debian 패키지 보안 점검
hardening-check /usr/bin/*
보호 수준을 유지하면서 비용 줄이기
ASLR의 주소 계산 오버헤드는 미미함(1% 미만)이며, 캐시 효율성 감소에는 약간의 영향이 있다. 페이지 테이블 크기 증가는 거의 없다.
페이지 권한 변경에는 mprotect() 시스템 콜 오버헤드와 TLB 플러시 비용이 따른다. JIT 컴파일러에서는 이 비용이 주요 이슈가 될 수 있다. 빈번한 권한 변경을 줄이고, 대량 페이지를 한 번에 변경하며, 하드웨어 기능을 최대한 활용하는 방식으로 부담을 낮출 수 있다.
ASLR, 페이지 권한 관리, 스택 카나리, DEP/NX, RELRO는 서로 대체하는 기능이 아니다. 주소 예측, 실행 권한, 제어 흐름, 재배치 테이블, 커널과 사용자 공간의 경계를 각각 다루므로 함께 적용해야 메모리 기반 공격에 대한 방어층이 만들어진다.