본문 바로가기

DreamHack: System Hacking/F Stage 5

Memory Corruption: Stack Buffer Overflow(개념)

스택 버퍼 오버플로우

스택 버퍼 오버플로우는 스택의 버퍼에서 발생하는 오버플로우를 뜻한다. 이를 이해하기 위해 먼저 버퍼오버플로우의 개념을 살펴보자

 

버퍼

데이터가 목적지로 이동되기 전에 보관되는 임시 저장소이다. 수신 측과 송신 측 사이에 버퍼라는 임시 저장소를 두고, 이를 통해 간접적으로 데이터를 전달하게 합니다. 송신 측은 버퍼로 데이터를 전송하고, 수신 측은 버퍼에서 데이터를 꺼내 사용한다.

 

버퍼 오버플로우

버퍼 오버플로우(Buffer Overflow)는 문자 그대로 버퍼가 넘치는 것을 의미한다.

버퍼는 제각기 크기를 가지고 있다. 만약 10바이트 크기의 버퍼에 20바이트 크기의 데이터가 들어가려 하면 오버플로우가 발생하게 된다.

일반적으로 버퍼는 메모리상에 연속해서 할당되어 있으므로, 어떤 버퍼에서 오버플로우가 발생하면, 뒤에 있는 버퍼들의 값이 조작될 위험이 있다.

위와 같은 이유로 버퍼 오버플로우가 발생하면 큰 보안 위협으로 이어질 수 있다.

 

버퍼 오버플로우 발생시 일어날 수 있는 위협

1) 중요 데이터 변조

버퍼 오버플로우가 발생하는 버퍼 뒤에 중요한 데이터가 있다면, 해당 데이터가 변조됨으로써 문제가 발생할 수 있다.

아래의 예제를 통해 데이터가 어떻게 변조될 수 있는지 살펴보자.

 

소스코드:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int check_auth(char *password) {
    int auth = 0;
    char temp[16];
   
    strncpy(temp, password, strlen(password));
   
    if(!strcmp(temp, "SECRET_PASSWORD"))
        auth = 1;
   
    return auth;
}
int main(int argc, char *argv[]) {
    if (argc != 2) {
        printf("Usage: ./sbof_auth ADMIN_PASSWORD\n");
        exit(-1);
    }
   
    if (check_auth(argv[1]))
        printf("Hello Admin!\n");
    else
        printf("Access Denied!\n");
}

위의 코드는 문자열 temp와 문자열을 비교하여 두 문자열이 같으면 "Hello Admin"을, 다르다면 "Access Denied"를 출력해주는 코드이다.

하지만 버퍼 오버플로우를 통해 temp 버퍼를 쓰레기값으로 채운 후, auth 버퍼의 값을 조작하면, 다음 그림과 같이 Auth의 값을 입력된 temp와는 무관하게 1이 되게 만들어줄 수 있다.

2) 데이터 유출

C언어에서 정상적인 문자열은 널바이트(\0)로 종결되며, 표준 문자열 출력 함수들은 널바이트를 문자열의 끝으로 인식한다. 만약 어떤 버퍼에 오버플로우를 발생시켜서 다른 버퍼와의 사이에 있는 널바이트를 모두 제거하면, 해당 버퍼를 출력시켜서 다른 버퍼의 데이터를 읽을 수 있게 된다. (널바이트를 만나기 전까지 계속해서 출력한다)

 

다음 예제를 통해 데이터 유출이 어떤 방식으로 일어날 수 있는지 살펴보자

 

소스코드:

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

int main(void) {
  char secret[16] = "secret message";
  char barrier[4] = {};
  char name[8] = {};

  memset(barrier, 0, 4);
  printf("Your name: ");
  read(0, name, 12);
 
  printf("Your name is %s.", name);
}

위의 소스코드는 secret에 16byte, barrier를 4byte, name을 8바이트를 배정하고, name을 입력받아 출력해주는 코드이다.

name 버퍼와 읽고자 하는 문자열인 secret 버퍼 사이에는 null바이트로 초기화 되어 있는 barrier 버퍼가 존재한다. 하지만 name 입력에서 버퍼를 오버플로우 시켜 null바이트로 초기화 되어 있는 barrier를 다른 더미값으로 덮어씌우면 secret의 내용을 유출시킬 수 있다.

아래 사진을 통해 이를 살펴보자

↑ 위의 사진에서 0xb에는 '\0'이 있어 secret 버퍼의 내용이 출력되고 있지 않다.

 

↑ 위의 사진에서와 같이 '\0'을 없애주면, 문자열의 끝을 인식하지 못 해 뒤의 버퍼 내용까지 모두 출력되는 것을 확인할 수 있다.

 

3) 실행 흐름 조작(ret 주소 변경)

함수는, 호출할 때 반환 주소를 스택에 쌓고, 함수에서 반환될 때 이를 꺼내어 원래의 실행 흐름으로 돌아간다.

이를  '스택 버퍼 오버플로우'를 통해 반환 주소(Return Address)를 조작하면, 프로세스의 실행 흐름을 바꿀 수 있다.

 

아래의 예제를 통해 버퍼 오버플로우로 ret 주소를 변경하면 어떤 일이 일어날 수 있는지 알아보자

 

소스 코드:

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

int main(void) {
    char buf[8];

    printf("Overwrite return address with 0x4141414141414141: ");
    gets(buf);
   
    return 0;
}

위의 소스 코드는 8바이트짜리 buf를 입력받는 코드이다.

 

↑ 위의 사진에서와 같이 buf 버퍼에 오버플로우를 일으켜 'ret 주소'를 0x4141414141414141로 변경시켜주면 프로세스의 흐름이 바뀌어 0x4141414141414141에 있는 함수가 실행되어, 메인함수엔 없는 "success!"가 출력되는 것을 확인할 수 있다.