Fascination
article thumbnail

Mitigation: NX & ASLR

 


# 들어가며

  • r2s를 통한 공격자의 침입을 더 어렵게 하려면
    • 공격자가 메모리에서 임의 버퍼의 주소를 알기 어렵게 하고
    • 메모리 영역에서 불필요한 실행 권한을 제거하는 보호 기법을 추가로 도입해야 함

 

 

# ASLR

  • Address Space Layout Randomization(ASLR): 바이너리가 실행될 마다 스택, 힙, 공유 라이브러리 등을 임의의 주소에 할당하는 보호 기법
  • ASLR은 커널에서 지원하는 보호 기법이며, 다음의 명령어로 확인 가능
    • 리눅스에서 이 값은 0, 1, 또는 2의 값을 가질 수 있음

  • 각 ASLR이 적용되는 메모리 영역은 다음과 같음
    • No ASLR(0): ASLR을 적용하지 않음
    • Conservative Randomization(1): 스택, 힙, 라이브러리, vdso 등
    • Conservative Randomization + brk(2): (1)의 영역과 brk로 할당한 영역
  • Figure 1. ASLR 예제 코드
// Name: addr.c
// 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);  // 코드 영역의 함수 주소
}
  • addr.c: 메모리의 주소를 출력하는 코드
    • gcc로 컴파일하고 실행해보면 다음과 같은 결과를 확인할 수 있음

  • 스택 영역의 buf_stack, 힙 영역의 buf_heap, 라이브러리 함수 printf, 코드 영역의 함수 main, 그리고 라이브러리 매핑 주소 libc_base가 출력됨
    • 코드 영역의 main 함수를 제외한 다른 영역의 주소들은 실행할 때마다 변경됨 → 실행할 때 마다 주소가 변경되기 때문에 바이너리를 실행하기 전에 해당 영역들의 주소를 예측할 수 없음
    • 바이너리를 반복해서 실행해도 printf 주소의 하위 12비트 값은 변경되지 않음
      • 리눅스는 ASLR이 적용되었을 때, 파일을 페이지(page) 단위로 임의 주소에 매핑함
      • 따라서 페이지 크기인 12비트 이하로는 주소가 변경되지 않음
    • libc_base와 printf의 주소 차이는 항상 같음
      • ALSR이 적용되면, 라이브러리는 임의 주소에 매핑됨
      • 그러나 라이브러리 파일을 그대로 매핑하는 것이므로 매핑된 주소로부터 라이브버리의 다른 심볼들 까지의 거리(offset)는 항상 같음

 

 

# NX

  • No-eXecute(NX)는 실행에 사용되는 메모리 영역과 쓰기 영역에 사용되는 메모리 영역을 분리하는 보호 기법
    • 어떤 메모리 영역에 대해 쓰기 권한과 실행 권한이 함께 있으면 시스템이 취약해지기 쉬움
    • 코드 영역에 쓰기 권한이 있으면 공격자는 코드를 수정하여 원하는 코드가 실행되게 할 수 있고, 반대로 스택이나 데이터 영역에 쓰기 권한이 있으면 Return to Shellcode와 같은 공격을 시도할 수 있음
  • CPU가 NX를 지원하면 컴파일러 옵션을 통해 바이너리에 NX를 적용할 수 있으며, NX가 적용된 바이너리는 실행될 때 각 메모리 영역에 필요한 권한만을 부여받음
  • gdbvmmap으로 NX 적용 전후의 메모리 맵을 비교하면, 다음과 같이 NX가 적용된 바이너리에는 코드 영역 외에 실행 권한이 없는 것을 확인할 수 있음
  • 반면, NX가 적용되지 않은 바이너리에는 스택, 힙, 데이터 영역에 실행 권한이 존재하는 것을 확인할 수 있음

  • Checksec을 이용한 NX 확인
    • checksec을 이용하면 다음과 같이 바이너리에 NX가 적용됐는지 확인할 수 있음

  • Return to Shellcode w/t NX
    • 이전에 실습한 Return to Shellcode의 예제인 r2s에 NX 보호기법을 적용한 후, 동일한 익스플로잇을 실행했을 때의 결과를 볼 것
    • 우선 checksec로 확인하면 NX가 활성화되어 있음
    • 이 바이너리를 대상으로 사용했던 익스플로잇 코드를 실행하면, segmentation fault가 발생하게 되는데 그 이유는NX가 적용되어 스택 영역에 실행 권한이 사라지게 되면서 쉘코드가 실행되지 못하고 종료되기 때문임

 

💡 NX의 다양한 명칭

  • NX를 인텔은 XD(eXecute Disable), AMD는 NX, 윈도우는 DEP(Data Execution Prevention), ARM에서는 XN(eXecute Never)라고 칭함
  • 명칭만 다를 뿐 모두 비슷한 보호 기법

 

 

# 마치며

  • NX와 ASLR이 적용되면 스택, 힙, 데이터 영역에는 실행 권한이 제거되며, 이들이 할당되는 주소가 계속 변함
    • Address Space Layout Randomization(ASLR): 메모리 무작위 주소에 할당하는 보호 기법으로 최신 커널들은 대부분 적용되어 있음. 리눅스에서는 페이지 단위로 할당이 이루어지므로 하위 12비트는 변하지 않는다는 특징이 있음
    • NX(No-eXecute bit): 프로세스의 각 세그먼트에 필요한 권한만 부여하는 보호 기법으로 일반적으로 코드 영역에는 읽기와 실행을, 나머지 영역에는 읽기와 쓰기 권한이 부여됨
  • but, 바이너리의 코드가 존재하는 영역은 여전히 실행권한이 존재하며, 할당되는 주소도 고정되어 있음
  • 코드 영역에는 유용한 코드 가젯들과 함수가 포함되어 있음
    • 반환 주소를 쉘 코드로 직접 덮는 대신, 이들을 활용하면 NX와 ASLR을 우회하여 공격할 수 있음
    • RTL
    • ROP

 

profile

Fascination

@euna-319

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!