Fascination
article thumbnail

Memory Corruption: Stack Buffer Overflow


# 서론

  • 모리스 웜: 스택 버퍼 오버플로우 공격을 통해 전파됨
  • CVE details에 따르면 스택 버퍼 오버플로우를 포함한 오버플로우 취약점은 이제까지 18,081개가 발견되어 전체에서 3번째로 많이 발견되었으며, 2019년에도 1,247개가 추가로 발견됨

 

 

# 스택 버퍼 오버플로우

1. 버퍼 오버플로우

1.1 버퍼

  • 버퍼(Buffer): 데이터가 목적지로 이동되기 전에 보관되는 임시 저장소
  • 데이터가 처리속도가 다른 두 장치가 있을 때, 이 둘 사이에 오가는 데이터를 임시로 저장해두는 것은 일종의 완충 작용을 함
    • 예를 들어 키보드에서 데이터가 입력되는 속도보다 데이터를 처리하는 속도가 느린 프로그램이 있음
    • 이런 키보드를 사용하는데 별도의 장치가 없다면, 키보드의 입력 중에 프로그램에서 수용되지 못한 데이터는 모두 유실될 것임
    • abcdefgh를 입력했는데 프로그램에는 abcd만 전달될 수 있음을 의미
  • 이런 문제를 해결하고자 수신 측과 송신 측 사이에 버퍼라는 임시 저장소를 두고, 이를 통해 간접적으로 데이터를 전달하게 함
  • 송신 측은 버퍼로 데이터를 전송하고, 수신 측은 버퍼에서 데이터를 꺼내 사용함
  • 버퍼가 가득 찰 때까지는 유실되는 데이터 없이 통신할 수 있음
  • 버퍼의 역할 - 빠른 속도로 이동하는 데이터를 안정적으로 목적지에 도착할 수 있도록 완중 작용을 함
  • 버퍼의 다른 의미 - 완충의 의미가 많이 희석되어 데이터가 저장될 수 있는 모든 단위를 의미하게 됨
    • 스택에 있는 지역 변수: 스택 버퍼
    • 힙에 할당된 메모리 영역: 힙 버퍼

💡 버퍼링

  • 버퍼에서 유래된 단어로, 송신 측의 전송 속도가 느려서 수신 측의 버퍼가 채워질 때까지 대기하는 것을 의미

 

1.2 버퍼 오버플로우

  • 버퍼 오버플로우(Buffer Overflow): 버퍼가 넘치는 것
  • 버퍼는 제각기 크기를 가지고 있는데, int로 선언한 지역 변수는 4바이트의 크기를 갖고, 10개의 원소를 갖는 char 배열은 10바이트의 크기를 가짐
    • 10바이트 크기의 버퍼에 20바이트 크기의 데이터가 들어가려 하면 오버플로우 발생
  • 일반적으로 버퍼는 메모리상에 연속해서 할당되어 있으므로, 어떤 버퍼에서 오버플로우가 발생하면, 뒤에 있는 버퍼들의 값이 조작될 위험이 있음
  • Figure2. 버퍼 오버플로우로 인한 메모리 오염

 

2. 중요 데이터 변조

  • 버퍼 오버플로우가 발생하는 버퍼 뒤에 중요한 데이터가 있다면, 해당 데이터가 변조됨으로써 문제가 발생할 수 있음
    • 예를 들어, 입력 데이터에서 악성 데이터를 감지하여 경고해주는 프로그램이 있음
    • 악성의 조건이 변경되면 악성 데이터에도 알람이 울리지 않을 수 있음
    • 또한 "https://twitter.com"과 통신하는 프로그램이 있다면 주소를 "https://example.evil"로 조작하여 악성 서버와 데이터를 주고받게 할 수도 있음
  • Figure3. 스택 버퍼 오버플로우 예제
    • main함수는 argv[1]check_auth 함수의 인자로 전달한 후, 반환 값을 받아옴
    • 이때, 반환 값이 0이 아니라면 "Hello Admin"을, 0이라면 "Access Denied!"라는 문자열을 출력함
    • check_auth 함수에서는 16 바이트 크기의 temp 버퍼에 입력받은 패스워드를 복사한 후, 이를 "SECRET_PASSWORD" 문자열과 비교함 →문자열이 같다면 auth를 1로 설정하고 반환함
    • 그런데 check_auth에서 strncpy 함수를 통해 temp 버퍼를 복사할 때, temp의 크기인 16바이트가 아닌 인자로 전달된 password의 크기만큼 복사함
    • 그러므로 argv[1]에 16바이트가 넘는 문자열을 전달하면, 이들이 모두 복사되어 스택 버퍼 오버플로우가 발생하게 됨
    • auth는 temp 버퍼의 뒤에 존재하므로, temp 버퍼에 오버플로우를 발생시키면 auth의 값을 0이 아닌 임의의 값으로 바꿀 수 있음
    • 이 경우, 실제 인증 여부와는 상관없이 main 함수의 if(check_auth(argv[1]))는 항상 참이 됨
// Name: sbof_auth.c
// Compile: gcc -o sbof_auth sbof_auth.c -fno-stack-protector
#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");
}
  • overflow

다음과 같이 auth 값이 AAAA로 덮어씌워지면서 Hello Admin!이 출력되는 것을 확인 가능

 

3. 데이터 유출

  • C언어에서 정상적인 문자열은 널바이트로 종결되며, 표준 문자열 출력 함수들은 널바이트를 문자열의 끝으로 인식함
  • 만약 어떤 버퍼에 오버플로우를 발생시켜서 다른 버퍼와의 사이에 있는 널바이트를 모두 제거하면, 해당 버퍼를 출력시켜서 다른 버퍼의 데이터를 읽을 수 있음
  • 획득한 데이터는 각종 보호기법을 우회하는 데 사용될 수 있으며, 해당 데이터 자체가 중요한 정보일 수도 있음
  • Figure5. 스택 버퍼 오버플로우와 메모리릭
    • 8바이트 크기의 name버퍼에 12바이트의 입력을 받음
    • 읽고자 하는 데이터인 secret버퍼와의 사이에 barrier라는 4바이트의 널 배열이 존재하는데, 오버플로우를 이용하여 널 바이트를 모두 다른 값으로 변경하면 secret을 읽을 수 있음
// Name: sbof_leak.c
// Compile: gcc -o sbof_leak sbof_leak.c -fno-stack-protector
#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);
}
  • overflow: 인터랙티브 모듈을 활용해 "secret message" 출력하기

널 바이트를 a로 덮어 뒤의 secret message를 출력

 

4. 실행 흐름 조작

  • Background: Calling Convention에서 배운 내용
    • 함수를 호출할 때 반환 주소를 스택에 쌓고, 함수에서 반환될 때 이를 꺼내어 원래의 실행 흐름으로 돌아감
  • 함수의 반환 주소를 조작하면 프로세스의 실행 흐름을 바꿀 수 있음
  • Figure6. 스택 버퍼 오버플로우를 통한 반환 주소 덮어쓰기
// Name: sbof_ret_overwrite.c
// Compile: gcc -o sbof_ret_overwrite sbof_ret_overwrite.c -fno-stack-protector
#include <stdio.h>
#include <stdlib.h>
int main(void) {
    char buf[8];
    printf("Overwrite return address with 0x4141414141414141: ");
    gets(buf);
    return 0;
}
  • overflow: 인터렉티브 모듈을 활용하여 main 함수의 반환 주소를 0x4141414141414141로 변경하기
    • "Success!"라는 문자열이 출력되면 성공

buffer와 sfp를 B로 덮고 return address를 A로 덮어 실행흐름을 조작

 

 

# 마치며

  • 버퍼 오버플로우는 지정된 버퍼의 크기보다 많은 데이터가 입력되어 발생함
  • 모든 메모리 영역에서 발생할 수 있으며, 이를 통해 데이터 변조, 데이터 유출, 실행 흐름 조작 등이 가능함
profile

Fascination

@euna-319

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!