메모리 보호 기법 정리, checksec 사용하기

Last edited at 2025-10-27
39 Views

시스템해킹 문제를 풀다 보면 각종 메모리 보호가 적용된 바이너리 파일들을 확인하게 된다. 바이너리의 메모리 보호 방식들을 하나씩 정리해 보려고 한다.


바이너리의 보안 설정을 확인할 때 쓰는 checksec은 아래 링크를 통해 받을 수 있다.

https://slimm609.github.io/checksec/



gcc 컴파일 시 기본 메모리 보호


예시로 간단한 C 프로그램을 만들어 보았다.

#include <stdio.h>
#include <stdlib.h>

int main(void){
  int a = 42;
  int b;
  
  scanf("%d", &b);
  printf("Hello World! %d\n", a+b);
  
  return 0;
}


이를 gcc로 아무 flag 없이 컴파일한 후 메모리 보호 설정을 확인하면 다음과 같다.

$ gcc myprogram.c -o myprogram
$ checksec myprogram
[*] '/home/user/guardian2025/prac_checksec/myprogram'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        PIE enabled
    Stripped:   No


여러 옵션들이 보이는데, 이중에서 RELRO, Canary, NX, PIE의 의미를 알아보자.


무슨 뜻일까 (대략)

  • RELRO(RELocation Read Only): 특정 데이터 세그먼트들에 대한 read-only 설정을 말한다.
  • Canary: buffer overflow를 방지하기 위해, 스택 프레임에 삽입하는 랜덤 데이터를 말한다.
  • NX(Never eXecute): 특정 메모리 영역의 데이터가 실행되는 것을 방지하는 설정을 말한다.
  • PIE(Position Independent Execution): 실행되는 바이너리의 주소를 무작위로 설정한다.


이제 각각을 좀 더 자세히 알아보자.



RELRO


RELRO는 특정 메모리 세그먼트를 read-only로 바꿈으로써 공격자가 해당 영역을 overwrite할 수 없게 만드는 기법이다. 크게 Partial RELRO와 Full RELRO라는 두 가지 모드가 있는데, Partial RELRO에서는 GOT(Global Offset Table)를 덮어쓸 수 있는 반면, Full RELRO에서는 덮어쓸 수 없다.


이 차이를 한번 확인해 보자. 먼저 위 C 코드를 이용해, 각각 partial RELRO(myprogrma_p)와 full RELRO(myprogram_f) 설정으로 바이너리를 만든다.

$ gcc -o myprogram_p myprogram.c -no-pie
$ gcc -o myprogram_f myprogram.c -no-pie -z relro -z now


readelf를 이용해 각 바이너리의 메모리 section header를 살펴보면 다음과 같은 결과를 볼 수 있다.

$ readelf -S myprogram_p


Partial RELRO에서는 위 사진과 같이 .got.plt section에 write이 가능한 것을 확인할 수 있다.


$ readelf -S myprogram_f


Full RELRO에서는 이와 다르게 .got.plt가 write 가능한 section으로 존재하지 않는다. 즉, GOT에 주소를 새로 씀으로써 제어권을 탈취하는 것이 원천적으로 차단된다.



Canary


Canary는 프로그램의 런타임에서 스택 프레임의 시작 지점마다 8바이트의 특정 데이터를 삽입함으로써 buffer overflow를 통한 스택 영역의 침투를 보호하는 기법이다. Canary는 일반적으로 아래 그림과 같이 함수 호출 스택의 return address와 이전 스택의 rbp가 저장된 바이트들 바로 아래에 저장된다.


만약 canary를 모르는 상태로 buffer overflow를 통해 스택의 return address를 덮어쓰려 한다면, canary 값이 변경되어 stack smashing이 일어났음을 감지할 수 있게 된다. 따라서 retrun address를 덮어 써 프로그램 제어권을 탈취하려면 먼저 canary 값을 탈취해야 한다.



NX


NX는 특정 메모리 영역에 있는 데이터가 프로그램으로써 실행되지 못하도록 하는 기술이다. 예를 들어, NX가 적용된 경우 shellcode를 직접 스택 영역에 삽입해 실행하는 공격이 불가능해진다.



PIE


PIE는 코드, 즉 .text section의 실행 주소를 랜덤화하는 보호 기법을 말한다.


PIE가 설정되어 있을 때와 없을 때 주소 차이를 확인해보자. 먼저 gcc를 통해 pie가 설정된 바이너리(myprogram_pie)와 pie가 설정되지 않은 바이너리(myprogram_nopie)를 만든다.

$ gcc -o myprogram_nopie myprogram.c -no-pie
$ gcc -o myprogram_pie myprogram.c


pwndbg를 통해 각 바이너리의 main 함수에 breakpoint를 만들고 실행하여, main의 메모리 주소를 확인하면 다음과 같다.

$ gdb myprogram_nopie
pwndbg> b main
pwndbg> run


No PIE의 경우, .text section의 위치가 기본 설정인 0x400000에 고정되어 있음을 확인할 수 있다.



$ gdb myprogram_pie
pwndbg> b main
pwndbg> run


PIE의 경우, .text section의 위치가 무작위로 설정된 것을 확인할 수 있다. 따라서 .text section의 함수를 이용해 제어권을 탈취하기 위해선, 우선 런타임 도중 .text section의 위치를 먼저 탈취해야 한다.



이외에도, stack과 heap, 그리고 shared library의 주소는 프로그램 실행이 시작될 때마다 랜덤하게 재배치된다. 이를 ASLR(Address Space Layout Randomization)이라 한다. 따라서, 이 메모리 영역들도 런타임 중 base 주소를 탈취한 후에만 이용할 수 있다.