본문 바로가기

정보보안/시스템 보안

[UNIX/Linux 기본학습] 시스템 해킹

버퍼 오버플로우 공격
Buffer Over Flow
① 개념 : 연속된 메모리 공간을 사용하는 프로그램에 할당된 메모리의 범위를 넘어선 위치에 자료를 읽거나 쓰려고 할 때 발생
- 스택 버퍼 오버플로우 : 스택은 함수 처리를 위해 지역변수 및 매개변수가 위치하는 메모리 영역을 말한다. 스택에 할당된 버퍼들이 문자열 계산 등에 의해 정의된 버퍼의 한계치를 넘는 경우 발생하며, 복귀주소(Return Address)를 변경해 공격자가 원하는 임의 코드를 실행한다.
- 힙 버퍼 오버플로우 : 힙은 사용자가 동적으로 할당하는 메모리 영역(malloc()과 같은 메모리 할당 함수)이다. 힙에 할당된 버퍼들에 문자열 등이 저장될 때, 최초 정의된 힙의 메모리 사이즈를 초과하여 문자열 등이 저장되는 경우 버퍼 오버플로우가 발생하여 데이터와 함수 주소 등을 변경하여 공격자가 원하는 임의 코드를 실행한다.

② 관련 주요 C언어 함수
- strcpy(char *dst, const char *src) : (취약) src 문자열을 문자열 길이를 체크하지 않고 dst 버퍼에 저장
- strncpy(char *dst, const char *src, size_t len) : (권장) len 만큼의 src를 dst 버퍼에 저장
- strlen(const char *str) : 문자열(str)의 null 문자를 제외한 바이트수 반환.
- sizeof(피연산자) : 피연산자 크기 반환
- c언어 문자열 처리방식 : null 문자(0x00)로 문자열 끝을 표현

③ 스택오버플로우 실습
스택 구조
buffer
(지정된 버퍼 크기)
SFP
(이전 함수의 SFP 저장)
RET
(이전함수의 다음 실행
명령주소)
- 모든 함수는 호출이 되면 메모리에 자신만의 스택 공간이 할당되며 이를 스택 프레임(Stack Frame)이라 한다. 스텍 프레임 공간 내에서 스택 프레임 포인터(SFP:Stack Frame Pointer)를 기준점으로 스택 포인터(Stack Pointer)에 상대주소(offset)를 저장하여 메모리 접근을 하게된다.
- 현재 실행중인 함수의 SFP는 EBP 레지스터에 저장되고, SP는 ESP 레지스터에 저장되며, 다음 실행할 명령어의 주소는 EIP 레지스터에 저장된다.
- 함수가 호출되면 이전 함수의 다음 실행할 명령어의 주소정보와 SFP를 먼저 스택에 저장한다. 이는 현재 호출된 함수가 종료하고 행이 이전 호출한 함수로 돌아갔을 때 EIP, EBP 레지스터의 값을 이전 함수로 되돌리기 위한 일종의 백업이다.
- RET 영역(Return Address)이 이전 함수의 다음 실행 명령어의 주소를 저장하는 영역이고 SFP 영역이 이전 함수의 SFP를 저장하는 영역이다. 만약 RET영역이 악성코드가 위치한 주소로 변조가 된다면ㅇ 함수가 종료된 후 악성코드가 실행될 수 있다.
- 공격자는 RET영역을 변조하기 위해 버퍼 오버플로우를 이용하여 SFP 영역까지 오버플로우될 값으로 덮어쓴 후 악성코드가 위치한 주소값으로 RET 영역을 덮어쓴다. 결과적으로 main 함수가 종료하게 되면 RET주소값을 참조하여 악성코드(vulnerable_function)가 실행된다.
[취약한 코드]
#include <stdio.h>
#include <string.h>

void vulnerable_function(char *input) {
    char buffer[10];  // 작은 크기의 버퍼
    strcpy(buffer, input);  // 입력을 버퍼에 복사
    printf("Buffer: %s\n", buffer);
}

int main() {
    char *long_input = "This input is too long and will overflow the buffer!";
    vulnerable_function(long_input);  // 버퍼 크기보다 큰 입력 전달
    return 0;
}


[권장 코드]
- strcpy -> strncpy 로 변경해 버퍼를 오버하지 않도록 지정된 문자열 크기를 인수로 넣는다.
- 인수 입력값에 대한 사전 점검을 한다. (if(strlen(argv[1]) >= sizeof(buffer)) -> 실행 안되게 처리

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

void safe_function(char *input) {
    char buffer[10];  // 작은 크기의 버퍼
    strncpy(buffer, input, sizeof(buffer) - 1);  // 안전하게 복사
    buffer[sizeof(buffer) - 1] = '\0';  // 널 문자로 종료 보장
    printf("Buffer: %s\n", buffer);
}

int main() {
    char *long_input = "This input is too long and will overflow the buffer!";
    safe_function(long_input);  // 버퍼 크기보다 큰 입력 전달
    return 0;
}

 

레이스 컨디션 공격
Race Condition Attack

구조

① 개념 : 둘 이상의 프로세스나 스레드가 공유자원에 동시에 접근할 때 접근하는 순서에 따라 비정상적인 결과가 발생하는 상황
- 만약 프로세스가 setuid 설정이 되어 root권한으로 실행된다면 권한 상승을 통한 중요 자원에 접근하는 심각한 문제 발생
- fp=fopen(arv[1],'w') 코드가 임시파일 오픈해서 쓰게하는것.

② 실습
#취약한 코드
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>

void vulnerable_function(char *filename) {
    struct stat statbuf;

    // 파일이 일반 파일인지 검사
    if (stat(filename, &statbuf) == 0 && S_ISREG(statbuf.st_mode)) {
        printf("'%s'는 일반 파일입니다. 파일을 엽니다.\n", filename);

        // 파일을 열고 작업 수행
        FILE *file = fopen(filename, "r");
        if (file != NULL) {
            char buffer[100];
            fread(buffer, sizeof(char), 100, file);
            printf("파일 내용: %s\n", buffer);
            fclose(file);
        } else {
            perror("파일을 열 수 없습니다");
        }
    } else {
        printf("'%s'는 일반 파일이 아닙니다.\n", filename);
    }
}

int main() {
    vulnerable_function("/tmp/testfile");
    return 0;
}

#권장 코드 fstat으로 파일 속성 확인
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

void safe_function(char *filename) {
    int fd;
    struct stat statbuf;

    // 파일을 열고 파일 디스크립터를 얻음
    fd = open(filename, O_RDONLY);
    if (fd == -1) {
        perror("파일을 열 수 없습니다");
        return;
    }

    // 파일 디스크립터로 파일이 일반 파일인지 확인
    if (fstat(fd, &statbuf) == 0 && S_ISREG(statbuf.st_mode)) {
        printf("'%s'는 일반 파일입니다. 파일을 읽습니다.\n", filename);

        // 파일 읽기
        char buffer[100];
        read(fd, buffer, sizeof(buffer) - 1);
        buffer[sizeof(buffer) - 1] = '\0';  // null-terminate
        printf("파일 내용: %s\n", buffer);
    } else {
        printf("'%s'는 일반 파일이 아닙니다.\n", filename);
    }

    // 파일 디스크립터 닫기
    close(fd);
}

int main() {
    safe_function("/tmp/testfile");
    return 0;
}

③ 대응방안

- 가능하면 임시파일을 생성하지 않는다.
- 파일 생성 시 이미 동일한 파일이 존재하는 경우 파일 생성 또는 쓰기를 금지한다. (if 임시파일존재? 하는 코드 작성,fstat)
- 사용하고자 하는 파일에 링크가 걸려있으면 실행을 중단한다.
- umask를 최하 022정도로 유지하여 임시로 생성한 파일이 공격자에 의해 악의적으로 삭제되지 않도록 한다.(소유자 제외 w권한 없애)

 

포맷 스트링 공격
Format String Attack
① 개념 : 외부로부터 입력된 값을 검증하지 않고 입출력 함수의 포맷 스트링을 그대로 사용하는 경우 발생할 수 있는 취약점
- 포맷스트링 : c언어의 printf()등의 함수에서 사용되는 문자열의 입,출력 형태를 정의하는 문자열
- 포맷 스트링을 인자로 하는 함수를 사용하여 포맷 스트링을 지정하지 않고 사용자 입력을 통해서 포맷 스트링이 결정된다면 공격자는 이를 조작하여 메모리 내용을 참조하고 특정 영역의 값을 변경할 수 있다.

② 주요 함수 및 포맷 스트링 식별자
- printf(서식문자열, 인자1, ...인자 N) : 인자값을 포맷 스트링으로 출력한다.
- fprintf(fp, 서식문자열, 인자1,. ..인자 N) : 인자값을 포맷 스트링으로 지정한 파일(fp)에 출력한다.
- sprintf(buf, 서식문자열, 인자1, ... 인자N) : 인자값을 포맷 스트링으로 지정한 버퍼(buf)에 출력한다.
식별자 출력형식
%d 10진수 정수 출력
%f 실수(float) 출력
%c 문자 출력
%s 문자열 출력
%x 16진수 출력
- 메모리 정보 출력함
%n 이전까지 출력한 총 바이트 수를 지정한 변수(4byte 단위)에 저장
- 메모리에 값을 설정함. RET에 악성코드 주소값 설정 가능

③ 대응방안
- 포맷스트링을 함수의 입력 파라미터로 직접사용하지 않는다. 즉 ,반드시 포맷스트링을 지정해준다. 
- printf(argv[1] -> printf(%s,argv[1])