본문 바로가기
Quality control (Univ. Study)/Information Security

Buffer-overflow attack lab (1)

by 생각하는 이상훈 2024. 12. 2.
728x90

Intro

버퍼 오버플로우는 프로그램이 정해진 경계 밖에 데이터를 쓰려고 하는 상황으로 정의된다. 공격자는 이 취약점을 이용하여 프로그램의 컨트롤을 변경하고 이를 통해서 악의적인 코드를 수행할 수 있다. 본 실험에서는 버퍼 오버플로우 취약점을 이해하고 버퍼 오버플로우를 악용하여 공격하는 방법을 익힌다. 본 실험에는 버퍼 오버플로우 취약점을 가진 서버가 주어진다. 프로그램의 버퍼 오버플로우 취약점을 악용하여 루트 권한을 얻는 것이 최종 목적이다. 추가로 버퍼 오버플로우에 대한 대응책을 수행하고 공격에 어떤 영향을 미치는지 확인해본다.

본 실험은 아래의 내용을 포함한다:

- 버퍼 오버플로우 취약성과 공격

- 스택의 구조

- 버퍼 오버플로우 대응법: 주소 난수화

- 쉘 코드

실험 환경: SEED Ubuntu 20.04 Virtual Machine

 

Step을 세세하게 나눠뒀는데 실제 과제가 그런 것은 아니고 내가 수행한 순서대로 차근차근 정리하고자 세세하게 나눠두었다. 추후에 다시 돌아봤을때 잘 이해할 수 있길...


Lab 환경 설정

$ wget https://seedsecuritylabs.org/Labs_20.04/Files/Buffer_Overflow_Server/Labsetup.zip

Labsetup.zip이라는 lab 수행에 필요한 코드들이 담긴 폴더를 다운 받자.

 

$ sudo /sbin/sysctl -w kernel.randomize_va_space=0

address randomization countermeasure를 끄자. buffer overflow attack을 기초적인 방식으로 수행해보려는데 countermeasure가 전부 방어해버리면 당연히 실패할 것이다. 위 코드를 이용하여 비활성화하자.

 

실험에서 사용할 코드들에 접근하기 위해 다운 받은 zip 파일은 아래와 같이 그대로 접근할 수 없다. unzip을 하고 폴더의 내용들에 접근하고 사용해야한다.

이제 Labsetup/server-code/ 위치로 가면 stack.c 코드를 찾을 수 있다. 해당 코드는 attack을 해볼 vulnerable program이다.

/* Vunlerable program: stack.c */
/* You can get this program from the lab's website */

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

/* Changing this size will change the layout of the stack.
 * Instructors can change this value each year, so students
 * won't be able to use the solutions from the past.
 * Suggested value: between 100 and 400  */
#ifndef BUF_SIZE
#define BUF_SIZE 200
#endif

void printBuffer(char * buffer, int size);
void dummy_function(char *str);

int bof(char *str)
{
    char buffer[BUF_SIZE];

#if __x86_64__
    unsigned long int *framep;
    // Copy the rbp value into framep, and print it out
    asm("movq %%rbp, %0" : "=r" (framep));
#if SHOW_FP
    printf("Frame Pointer (rbp) inside bof():  0x%.16lx\n", (unsigned long) framep);
#endif
    printf("Buffer's address inside bof():     0x%.16lx\n", (unsigned long) &buffer);
#else
    unsigned int *framep;
    // Copy the ebp value into framep, and print it out
    asm("mov %%ebp, %0" : "=r" (framep));
#if SHOW_FP
    printf("Frame Pointer (ebp) inside bof():  0x%.8x\n", (unsigned) framep);
#endif
    printf("Buffer's address inside bof():     0x%.8x\n", (unsigned) &buffer);
#endif

    // The following statement has a buffer overflow problem 
    strcpy(buffer, str);       

    return 1;
}

int main(int argc, char **argv)
{
    char str[517];

    int length = fread(str, sizeof(char), 517, stdin);
    printf("Input size: %d\n", length);
    dummy_function(str);
    fprintf(stdout, "==== Returned Properly ====\n");
    return 1;
}

// This function is used to insert a stack frame of size 
// 1000 (approximately) between main's and bof's stack frames. 
// The function itself does not do anything. 
void dummy_function(char *str)
{
    char dummy_buffer[1000];
    memset(dummy_buffer, 0, 1000);
    bof(str);
}

void printBuffer(char * buffer, int size)
{
   int i;
   for  (i=0; i<size; i++){

     if (i % 20 == 0) printf("\n%.3d: ", i);
     printf("%.2x ", (unsigned char) buffer[i]);
   }
}

프로그램은 버퍼 오버플로우 취약점을 갖고 있으며 이번 실험의 목표는 이 취약점을 악용하여 root 권한을 취득하는 것이다.

이 프로그램의 버퍼 오버플로우 취약성에 대해 설명하자면 다음과 같다. 표준 입력으로부터 데이터를 읽어오고, bof()를 호출하여 해당 입력을 다른 버퍼로 넘긴다. 유저의 입력은 최대 517 바이트의 길이를 가질 수 있으나 bof()에서 사용하는 버퍼의 크기는 BUF_SIZE 바이트이고 이는 517 보다 작다. strcpy()에서는 버퍼의 길이를 확인하지 않으므로 버퍼 오버플로우가 발생한다.

프로그램은 root 권한을 가진 서버에서 실행된다. 그리고 표준 입력은 서버와 사용자간의 TCP 연결을 통해서 전달된다. 즉, 이 프로그램을 실제로 데이터를 원격으로 연결된 사용자로부터 받는다. 사용자는 이 버퍼 오버플로우 취약점을 이용할 수 있으며, 서버의 root shell 을 취득할 수 있다.

위의 취약한 프로그램을 컴파일 하기 위해서, 우리는 StackGuard 와 non-executable stack 을 비활성화 해야한다. 이를 위해서 “-fno-stack-protector” 및 “-z exestack” 옵션과 함께 컴파일 해야한다. 아래는 컴파일 옵션의 예시이고, 여기서 L1 환경변수는 BUF_SIZE 를 지정하는 숫자이다.

 

//Makefile
FLAGS    = -z execstack -fno-stack-protector
FLAGS_32 = -static -m32
TARGET   = server stack-L1 stack-L2 stack-L3 stack-L4
 
L1 = 100
L2 = 180
L3 = 200
L4 = 80

all: $(TARGET)

server: server.c
        gcc -o server server.c

stack-L1: stack.c
        gcc -DBUF_SIZE=$(L1) -DSHOW_FP $(FLAGS) $(FLAGS_32) -o $@ stack.c

stack-L2: stack.c
        gcc -DBUF_SIZE=$(L2) $(FLAGS) $(FLAGS_32) -o $@ stack.c

stack-L3: stack.c
        gcc -DBUF_SIZE=$(L3) -DSHOW_FP $(FLAGS) -o $@ stack.c

stack-L4: stack.c
        gcc -DBUF_SIZE=$(L4) -DSHOW_FP $(FLAGS) -o $@ stack.c

clean:
        rm -f badfile $(TARGET)

install:
        cp server ../bof-containers
        cp stack-* ../bof-containers

컴파일 코드는 Makefile 에 이미 제공되어 있고, 컴파일을 위해서는 make 명령어를 실행하면 된다. 변수 L1, L2, L3, L4 이 Makefile 에 정의되어 있는데, 이는 컴파일 과정에서 사용된다. 컴파일이 끝나면 해당 바이너리가 컨테이너에서 사용될 수 있도록, 생성된 바이너리를 bofcontainer 폴더로 이동해야 한다(설치). 아래 커멘드를 수행하면 컴파일과 설치가 모두 수행된다. 

 

마지막으로 컨테이너 설정과 명령어를 알아보고 환경설정을 마치자.

아래는 docker와 compose에서 사용할 수 있는 유용한 명령어다. 명령어를 alias를 이용해서도 사용할 수 있다.

모든 컨테이너는 백그라운드에서 동작한다. 컨테이너 상에서 커멘드를 수행하기 위해서는 커멘드의 shell 에 접근할 필요가 있다. 이를 위해서 ‘docker ps’명령어를 활용하여 컨테이너의 ID 를 찾고, ‘docker exec’를 이용하여 해당 컨테이너의 shell 을 수행할 수 있다. 이에 해당하는 alias 도 .bashrc 에 지정되어 있으며, 아래의 예시처럼 사용할 수 있다.

‘docker-compose build’를 수행하기 이전에 반드시 프로그램을 컴파일하고 코드를 bof-containers 로 복사해 두어야 한다.

 

다음 글부터 본격적으로 Buffer overflow attack을 시작해보자.


728x90

'Quality control (Univ. Study) > Information Security' 카테고리의 다른 글

Buffer-overflow attack lab (3)  (0) 2024.12.07
Network security  (2) 2024.12.06
Buffer-overflow attack lab (2)  (0) 2024.12.04
추가 개념정리(Midterm)  (4) 2024.10.22
Basic of Information Security(midterm)  (1) 2024.10.22