본문 바로가기
Security/System Hacking

[Dreamhack System Hacking] STAGE 6

by 단월໒꒱ 2022. 2. 6.

[Mitigation: NX & ASLR]

1. ASLR

 1) ASLR (Address Space Layout Randomization)

   - 바이너리가 실행될 때마다 스택, 힙, 공유 라이브러리 등을 임의의 주소에 할당하는 보호 기법

   - 커널에서 지원하는 보호 기법

   - 아래의 명령어로 확인 가능

 

 

 

 

   - 리눅스에서 이 값은 0, 1, 또는 2의 값을 가질 수 있음

   - 각 ASLR이 적용되는 메모리 영역

     ① No ASLR(0): ASLR을 적용하지 않음

     ② Conservative Randomization(1): 스택, 힙, 라이브러리, vdso 등

     ③ Conservative Randomization + brk(2): (1)의 영역과 brk로 할당한 영역

    - 예제 코드

 

// 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);  // 코드 영역의 함수 주소
}

 

 

 2) ASLR의 특징

   - 위의 예제 코드는 메모리의 주소를 출력하는 코드

   - gcc로 컴파일하고 실행해보면 아래와 같은 결과를 확인할 수 있음

 

 

 

 

   - 스택 영역의 buf_stack, 힙 영역의 buf_heap, 라이브러리 함수 printf, 코드 영역의 함수 main, 그리고 라이브러리 매핑 주소 libc_base가 출력됨

   - addr 파일을 수차례 실행했을 때 결과를 살펴보면 아래와 같은 특징이 있음

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

 

2. NX

 1) NX (No-eXecute)

   - 실행에 사용되는 메모리 영역과 쓰기에 사용되는 메모리 영역을 분리하는 보호 기법

   - 어떤 메모리 영역에 대해 쓰기 권한과 실행 권한이 함께 있으면 시스템이 취약해지기 쉬움 

   - CPU가 NX를 지원하면 컴파일러 옵션을 통해 바이너리에 NX를 적용할 수 있으며, NX가 적용된 바이너리는 실행될 때 각 메모리 영역에 필요한 권한만을 부여받음

   - gdb의 vmmap으로 NX 적용 전후의 메모리 맵을 비교하면, 다음과 같이 NX가 적용된 바이너리에는 코드 영역 외에 실행 권한이 없는 것을 확인할 수 있음

   - NX가 적용되지 않은 바이너리에는 스택, 힙, 데이터 영역에 실행 권한이 존재하는 것을 확인할 수 있음

 

 2) Checksec을 이용한 NX 확인

   - checksec을 이용하면 아래와 같이 바이너리에 NX가 적용됐는지 확인 가능

 

 

 

 

 3) NX의 다양한 명칭

   - 인텔 : XD (eXecute Disable)

   - AMD : NX

   - 윈도우 : DEP (Data Execution Prevention)

   - ARM : XN (eXecute Never)

   - 명칭만 다를 뿐 모두 비슷한 보호 기법

 

 4) Return to Shellcode w/t NX

   - Return to shellcode의 예제 r2s에 NX 보호기법을 적용한 후 동일한 익스플로잇을 실행했을 때의 결과를 확인해보자.

      - r2s.c를 -zexecstack 옵션을 제거해 컴파일하고 checksec으로 확인한다.

 

 

 

 

     - NX가 활성화되어있는 것을 확인할 수 있다.

     - 이 바이너리를 대상으로 익스플로잇 코드를 실행한다.

 

 

 

 

      - Segmentation fault 발생 (NX가 적용되어 스택 영역에 실행 권한이 사라지게 되면서 셸코드가 실행되지 못하고 종료된 것)

 

 

3. 마치며

 - NX와 ASLR이 적용되면 스택, 힙, 데이터 영역에는 실행 권한이 제거되며 이들이 할당되는 주소가 계속 변함

 - 그러나 바이너리의 코드가 존재하는 영역은 여전히 실행 권한이 존재하며, 할당되는 주소도 고정되어 있음

  - 코드 영역에는 유용한 코드 가젯들과 함수가 포함되어 있는데 반환 주소를 셸 코드로 직접 덮는 대신, 이들을 활용하면 NX와 ASLR을 우회하여 공격 가능

  - 관련된 대표적인 공격 방법

     ① RTL (Return to Libc)

     ② ROP (Return Oriented Programming)

 

 

 

[Background: Library - Static Link vs Dynamic Link]

1. 라이브러리

 1) 라이브러리

   - 컴퓨터 시스템에서, 프로그램들이 함수나, 변수를 공유해서 사용할 수 있게 함

   - 라이브러리를 사용하면 같은 함수를 반복적으로 정의해야 하는 수고를 덜 수 있어 코드 개발이 효율적

 

2. 링크

 1) 링크 

   - 많은 프로그래밍 언어에서 컴파일의 마지막 단계로 알려져 있음

   - 프로그램에서 어떤 라이브러리의 함수를 사용한다면, 호출된 함수와 실제 라이브러리의 함수가 링크 과정에서 연결됨

   - 리눅스에서 c 소스코드는 전처리, 컴파일, 어셈블 과정을 거처 ELF 형식을 갖춘 오브젝트 파일로 번역됨

   - 오브젝트 파일은 실행 가능한 형식을 갖추고 있지만, 라이브러리 함수들의 정의가 어디 있는지 알지 못하므로 실행은 불가능

   - 심볼과 관련된 정보들을 찾아서 최종 실행 파일에 기록하는 것이 링크 과정에서 하는 일 중 하나

   - 예제 코드

 

// Name: hello-world.c
// Compile: gcc -o hello-world hello-world.c
#include <stdio.h>
int main() {
  puts("Hello, world!");
  return 0;
}

 

   - 예제를 완전히 컴파일하고 아래의 명령어를 통해 링크되기 전과 비교해보자

 

 

 

   - libc에서 puts의 정의를 찾아 연결한 것을 확인할 수 있음

   - 여기서 libc를 같이 컴파일하지 않았음에도 libc에서 해당 심볼을 탐색한 것은, libc가 있는 /lib/x86_64-linux-gnu/가 표준 라이브러리 경로에 포함되어 있기 때문

   - 링크를 거치고 나면 프로그램에서 puts를 호출할 때, puts의 정의가 있는 libc에서 puts의 코드를 찾고, 해당 코드를 실행하게 됨

 

 2) 라이브러리와 링크의 종류

   ① 동적 링크

      - 동적 라이브러리를 링크하는 것

      - 동적 링크된 바이너리를 실행하면 동적 라이브러리가 프로세스의 메모리에 매핑됨

      - 실행 중에 라이브러리의 함수를 호출하면 매핑된 라이브러리에서 호출할 함수의 주소를 찾고, 그 함수를 실행

 

   ② 정적 링크

      - 정적 라이브러리를 링크하는 것

      - 정적 링크를 하면 바이너리에 정적 라이브러리의 모든 함수가 포함됨

      - 라이브러리에서 원하는 함수를 찾지 않아도 되니 탐색의 비용이 절감

      - 여러 바이너리에서 라이브러리를 사용하면 그 라이브러리의 복제가 여러 번 이루어지게 되므로 용량을 낭비하게 됨

 

 3) 동적 링크 vs 정적 링크

   ① 용량

      - static이 dynamic보다 100배 가까이 더 많은 용량을 차지

 

 

 

 

   ② 호출 방법

      - static에서는 puts가 있는 0x410230을 직접 호출합니다.

      - dynamic에서는 puts의 plt주소인 0x4003f0을 호출합니다.

      - 이러한 차이가 발생하는 이유 : 동적 링크된 바이너리는 함수의 주소를 라이브러리에서 찾아야하기 때문

 

 

 

 

3. PLT와 GOT

 1) PLT와 GOT

   - PLT (Procedure Linkage Table)

   - GOT(Global Offset Table)

   - 라이브러리에서 동적 링크된 심볼의 주소를 찾을 때 사용하는 테이블

   - runtime resolve 과정

      ① 바이너리가 실행되면 ASLR에 의해 라이브러리가 임의의 주소에 매핑됨

      ② 이 상태에서 라이브러리 함수를 호출하면, 함수의 이름을 바탕으로 라이브러리에서 심볼들을 탐색하고, 해당 함수의 정의를 발견하면 그 주소로 실행 흐름을 옮기게 됨

       - 반복적으로 호출되는 함수의 정의를 매번 탐색해야 한다면 비효율

       - 그래서 ELF는 GOT라는 테이블을 두고, resolve된 함수의 주소를 해당 테이블에 저장

       - 그리고 나중에 다시 해당 함수를 호출하면 저장된 주소를 꺼내서 사용

 

 2) resolve되기 전

   - got.c를 컴파일하고, 실행한 직후에 GOT를 확인

 

 

 

 

   - 아직 puts의 주소를 찾기 전이므로, 함수의 주소가 아닌 puts@plt+6라는 PLT 내부의 주소가 적혀있음

   - puts@plt를 호출하는 지점에 중단점 설정

 

 

 

 

   - PLT에서는 먼저 puts의 GOT인 0x601018에 쓰인 값으로 실행 흐름을 옮김

   - 현재 GOT에는 puts@plt+6의 주소가 쓰여있으므로, 바로 다음 줄의 코드를 실행하게 됨

   - 코드를 조금 더 실행

 

 

 

 

   - dl_runtime_resolve_xsavec 함수가 실행

   - 이 함수에서 puts의 주소가 구해지고, GOT에 주소가 써짐

 

 3) resolve된 후

   - 두 번째로 puts@plt를 호출할 때는 GOT에 puts의 주소가 쓰여있어서 바로 puts가 실행 됨

 

 

 

 

 4) 시스템 해킹의 관점에서 본 PLT와 GOT

   - PLT와 GOT는 동적 링크된 바이너리에서 라이브러리 함수의 주소를 찾고, 기록할 때 사용되는 중요한 테이블

   - PLT에서 GOT를 참조하여 실행 흐름을 옮길 때, GOT의 값을 검증하지 않는다는 보안상의 약점 존재

   - 앞의 예시에서 GOT에 저장된 puts의 주소를 공격자가 임의로 변경할 수 있으면, 두 번째로 puts가 호출될 때 공격자가 원하는 코드가 실행되게 할 수 있음

   - 이 공격 기법이 가능한지 gdb를 이용하여 실험해보자

      - 앞의 got바이너리의 두 번째 puts 호출 직전에 puts의 GOT 값을 “AAAAAAAA”로 변경하고 계속 실행시키면, 실제로 “AAAAAAAA”로 실행 흐름이 옮겨지는 것을 확인할 수 있다.

 

 

 

 

      - 이런 공격 기법을 GOT Overwrite라고 하며, 임의 주소에 값을 쓸 수 있을 때 RCE를 하기 위한 방법으로 사용될 수 있다. 

 

 

 

 

댓글