Hacking/Pwnable

NX-bit, ASLR

GunP4ng 2024. 5. 10. 22:22

NX-bit, ASLR


1. NX-bit (No-eXecute-bit)

1. NX-bit 란?

Nx-bit (No-eXecute-bit) 는 실행에 사용되는 메모리 영역과 쓰기에 사용되는 메모리 영역을 분리하는 보호 기법이다.

메모리 영역에 쓰기 권한과 실행 권한이 함께 있으면 시스템이 취약해지기 쉽다.

 

코드 영역에 쓰기 권한이 있다면 공격자는 코드를 수정하여 원하는 코드를 실행할 수 있게 되고,

스택 영역에 실행 권한이 있다면 Return to Shellcode 같은 공격을 시도할 수 있다.

 

즉, NX-bit 는 code 영역을 제외한 나머지 영역에 실행 권한을 넣지 않는 보호기법이다.

 

2. NX-bit 적용

gcc 는 기본적으로 NX-bit 보호기법을 적용해서 컴파일한다.

-z execstack

따라서 Nx-bit 보호기법을 해제 하려면 gcc 옵션에 -z execstack 옵션을 추가하면 된다.

 

[Dreamhack] Retrun to Shellcode 문제의 C 코드이다.

https://gunp4ng-study.tistory.com/66

// Name: r2s.c

#include <stdio.h>
#include <unistd.h>

void init() {
  setvbuf(stdin, 0, 2, 0);
  setvbuf(stdout, 0, 2, 0);
}

int main() {
  char buf[0x50];

  init();

  printf("Address of the buf: %p\n", buf);
  printf("Distance between buf and $rbp: %ld\n",
         (char*)__builtin_frame_address(0) - buf);

  printf("[1] Leak the canary\n");
  printf("Input: ");
  fflush(stdout); // 버퍼에 남은 데이터 초기화


  read(0, buf, 0x100);
  printf("Your input is '%s'\n", buf);

  puts("[2] Overwrite the return address");
  printf("Input: ");
  fflush(stdout);
  gets(buf);

  return 0;
}

 

$ gcc -o nx_r2s r2s.c				// 보호기법 적용
$ gcc -o r2s r2s.c -z execstack		// 보호기법 해제

하나는 NX-bit 보호기법을 적용해서 컴파일 하고

다른 하나는 적용하지 않고 컴파일한다.

 

checksec 명령어로 비교해보자.

$ checksec nx_r2s
[*] '/workspaces/codespaces-blank/Test/nx-bit/nx_r2s'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

nx_r2s 파일은 NX-bit 보호기법이 적용돼 있는 것을 확인할 수 있다.

 

$ checksec r2s
[*] '/workspaces/codespaces-blank/Test/nx-bit/r2s'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX unknown - GNU_STACK missing
    PIE:      PIE enabled
    Stack:    Executable
    RWX:      Has RWX segments

r2s 파일은 NX-bit 보호기법이 적용되지 않은 것을 확인할 수 있다.

NX-bit 보호기법이 적용된 nx_r2s 파일에 익스플로잇 코드를 실행해보면

$ python3 payload.py
[*] buf_addr: 7fffffffd0d0
[*] canary: ed84266a03180300
[*] Switching to interactive mode
$ 
[*] Process '/workspaces/codespaces-blank/Test/nx-bit/nx_r2s' stopped with exit code -11 (SIGSEGV) (pid 23316)
[*] Got EOF while sending in interactive

Segmentation fault 가 발생한다. 

NX-bit 가 적용되어 스택 영역에 실행권한이 사라지면서, 셸코드가 실행되지 못하고 종료된다.

 

 

2. ASLR (Address Space Layout Randomization)

1. ASLR 이란 ?

ASLR (Address Space Layout Randomization) 은 바이너리가 실행될 때마다

스택, 힙, 공유 라이브러리 등을 임의의 주소에 할당하는 보호 기법이다.

 

ASLR 은 커널에서 지원하는 보호기법이다.

$ cat /proc/sys/kernel/randomize_va_space
0

위의 명령어를 통해 ALSR 을 확인할 수 있다.

  • 0 (No ASLR) - ASLR 을 적용하지 않음
  • 1 (Conservation Randomization) - 스택, 힙, 라이브러리, vdso 등
  • 2 (Conservation Randomization + brk) - 1의 영역과 brk 로 할당한 영역

 

2. 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);  // 코드 영역의 함수 주소
  • 스택 영역의 buf_stack
  • 힙 영역의 buf_heap
  • 라이브러리 매핑 주소 libc_base
  • 라이브러리 함수 printf
  • 코드 영역의 함수 main

메모리 주소를 출력하는 C 코드이다.

 

ASLR 을 0으로 설정하고 파일을 실행해보자

$ ./addr
buf_stack addr: 0x7fffffffd190
buf_heap addr: 0x4052a0
libc_base addr: 0x7ffff7dc4000
printf addr: 0x7ffff7e25c90
main addr: 0x4011b6

$ ./addr
buf_stack addr: 0x7fffffffd190
buf_heap addr: 0x4052a0
libc_base addr: 0x7ffff7dc4000
printf addr: 0x7ffff7e25c90
main addr: 0x4011b6

$ ./addr
buf_stack addr: 0x7fffffffd190
buf_heap addr: 0x4052a0
libc_base addr: 0x7ffff7dc4000
printf addr: 0x7ffff7e25c90
main addr: 0x4011b6

메모리 주소가 항상 똑같은 것을 확인할 수 있다.

 

 

ASLR 을 2로 설정하고 파일을 실행해보자

$ ./addr
buf_stack addr: 0x7ffd6b873310
buf_heap addr: 0x19852a0
libc_base addr: 0x78f379741000
printf addr: 0x78f3797a2c90
main addr: 0x4011b6

$ ./addr
buf_stack addr: 0x7ffd92109890
buf_heap addr: 0xf5f2a0
libc_base addr: 0x7a2aaf5f2000
printf addr: 0x7a2aaf653c90
main addr: 0x4011b6

$ ./addr
buf_stack addr: 0x7ffe92a08640
buf_heap addr: 0x108d2a0
libc_base addr: 0x7ce07ff0f000
printf addr: 0x7ce07ff70c90
main addr: 0x4011b6

코드 영역의 main 함수를 제외한 다른 영역의 주소들은 실행할 때마다 변경된다.

실행할 때 마다 주소가 변경되기 때문에 바이너리를 실행하기 전에 해당 영역들의 주소를 예측할 수 없다.

 

바이너리를 반복해서 실행해도

libc_base 주소 하위 12bit (3byte), printf 주소 하위 12bit (3byte) 값은 변경되지 않는다.

→ 리눅스는 파일을 페이지(page) 단위로 임의 주소에 매핑하기 때문이다.

 

libc_base 와 printf 의 주소 차이는 항상 같다. 

ASLR 이 적용되면, 라이브러리는 임의 주소에 매핑된다.

라이브러리 파일을 그대로 매핑하기 때문에 매핑된 주소로부터

라이브러리의 다른 심볼들까지의 거리(offset) 는 항상 같다

>>> hex (0x7a2aaf5f2000-0x7a2aaf653c90)
'-0x61c90'
>>> hex (0x7ce07ff0f000-0x7ce07ff70c90)
'-0x61c90'