[정보 보안] 버퍼 오버 플로우

Kookie·3일 전
0

정보보안

목록 보기
4/5
post-thumbnail

스택 버퍼 오버 플로우의 실습 코드에 대한 정리와 안전한 코드 예시에 대해 정리해봄




예시 코드

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>

shell_code(){
	setreuid(0,0); //ruid,euid를 root로 설정
	setregid(0,0); //rgid,egid를 root그룹으로 설정
	system("/bin/sh"); //root 및 root그롭 권한으로 쉘 실행
}

int main(int argc, char **argv){
	char buffer[12]; //크기가 12바이트인 버퍼생성
	memset(buffer, 0x00,sizeof(buffer));	// 버퍼를 0으로 초기화
	if(argc != 2){
		printf("USage : buf_over data \n");
		exit(-1);
	}
	strcpy(buffer, argv[1]);	//인자로 받은 문자열을 버퍼로 복사
	printf("sizeof %d \n", sizeof(argv[1]));
	printf("strlen %d \n", strlen(argv[1]));
	return 0;
}

코드 설명

  1. shell_code 함수:
    • 이 함수는 setreuid(0, 0)setregid(0, 0)을 사용해 현재 프로세스의 사용자 ID와 그룹 ID를 루트 사용자로 설정함
      즉, 현재 프로그램이 루트 권한으로 동작하게 만드는 것
    • 그 다음에 system("/bin/sh")을 호출해서 루트 권한으로 을 실행

  2. main 함수:
    • 크기가 12바이트인 buffer 배열을 생성, 이 배열은 사용자가 입력한 데이터를 저장할 공간
    • 프로그램이 실행될 때, 사용자가 입력한 문자열을 buffer로 복사, 이를 위해 strcpy 함수를 사용
    • 문제는 strcpy 함수는 입력한 문자열의 크기를 확인하지 않는다는 점
      입력이 너무 길면 buffer라는 작은 공간을 넘어 주변의 다른 메모리 영역까지 덮어쓰게 됨
      • 이것을 버퍼 오버플로우(Buffer Overflow)라고 함

코드의 위험성

  • 만약 사용자가 12바이트보다 더 큰 문자열을 입력하게 되면, buffer 뒤에 있는 다른 메모리 공간까지 덮어쓸 수 있음
    • 이 과정에서 프로그램의 동작을 조작하거나 악성 코드를 실행할 수 있음
  • 예를 들어, 프로그램은 main 함수의 마지막 부분에 도달하면 종료되는데, 버퍼 오버플로우를 통해 프로그램이 shell_code 함수를 실행하게 할 수도 있음. 그러면 루트 권한으로 쉘을 실행하게 됨

버퍼 오버플로우의 공격 예시

  1. 공격자는 strcpy가 입력 길이를 체크하지 않는 점을 이용해 긴 문자열을 입력
  2. 이 긴 문자열이 buffer를 넘쳐흘러서 프로그램의 제어 흐름을 변경
  3. 프로그램이 원래는 종료될 부분에서, 공격자가 조작한 문자열로 인해 shell_code 함수가 호출
  4. 루트 권한으로 쉘이 열리고, 공격자는 시스템을 마음대로 조작할 수 있게 됨




오버플로우 발생 시 스택 구조

1. 스택 프레임(Stack Frame)이란?

  • 함수가 호출될 때마다 그 함수만을 위한 메모리 공간(스택 프레임)이 생성됨

  • 이 스택 프레임은 스택 포인터(ESP)스택 프레임 포인터(EBP)로 관리

    • ESP는 스택의 현재 위치(포인터)를 가리키고,
    • EBP는 스택 프레임의 기준이 되는 포인터

    이를 통해 함수는 자기만의 메모리 영역에서 데이터를 관리하며, 스택의 주소를 사용해 메모리에 접근


2. 스택 프레임의 구성

  • 각 함수가 호출될 때는 다음 두 가지 정보가 스택에 저장됨:
    • SFP (Stack Frame Pointer): 이전 함수가 실행되던 스택 프레임 포인터. 이 값이 있어야 함수가 끝나고 이전 함수로 돌아갈 때, 그 함수의 스택 상태를 복원할 수 있음
    • RET (Return Address): 이전 함수가 끝난 뒤, 다시 어디서부터 실행할지에 대한 다음 명령어의 주소를 저장
      즉, 함수가 끝났을 때 RET가 가리키는 주소로 점프해 코드를 계속 실행

3. 레지스터의 역할

현재 함수는 EIP에 다음 명령어 주소를, ESP에 스택의 현재 위치를, EBP에 스택 프레임 시작 위치를 저장함. 함수 호출 시 스택에 이전 EBP와 RET(다음 명령어 주소)를 저장하며, 함수 종료 시 이 정보를 통해 원래 함수로 복귀함

  • EIP: 32비트 명령어 포인터 (Extended Instruction Pointer)
  • ESP: 32비트 스택 포인터 (Extended Stack Pointer)
  • EBP: 32비트 베이스 포인터 (Extended Base Pointer)

4. 정상적인 스택 실행 흐름

  • 함수를 호출하면, 현재 스택 프레임 포인터(EBP)다음 명령어의 주소(RET)가 스택에 저장됨. 이렇게 하면 함수가 끝난 후 정확한 명령어 위치로 돌아가서 이전 함수가 계속 실행됨

5. 버퍼 오버플로우 공격

  • 버퍼 오버플로우buffer의 크기를 초과하는 데이터를 입력해 메모리를 덮어쓰는 공격
  • 공격자는 이 덮어쓴 데이터를 통해, 스택에 저장된 중요한 값들(예: SFP와 RET)을 조작할 수 있음

6. 공격자의 전략

  • 공격자는 버퍼에 너무 긴 데이터를 입력해 SFPRET까지 덮어씀.
  • 특히, RET 주소가 덮어씌워지면, 함수가 끝날 때 원래 돌아가야 할 위치가 아닌 공격자가 지정한 악성 코드 위치로 돌아가서 코드를 실행하게 됨



그림 설명

1. 정상 실행

  • buf_over "AAAAAAAAAAAA"와 같이, 12바이트의 데이터를 입력한 경우:
    • buffer에는 "AAAAAAAAAAAA"가 저장
    • 그 뒤에 있는 SFPRET는 안전하게 유지
    • 함수가 끝나면 RET가 가리키는 주소로 정상적으로 돌아감

2. 버퍼 오버플로우 공격

  • 공격자가 길게 데이터를 입력(예: perl -e 'print "A"x16, "\x44\x85\x04\x08")하면:
    • buffer"AAAAAAAAAAAA"로 가득 차고,
    • 그 다음 SFPRET를 덮어씀. SFP"AAAA"(즉, 0x41414141)로 덮어씌워지고, RET는 공격자가 원하는 0x08048544로 변경됨
    • 0x08048544악성 코드가 위치한 주소로, 함수가 종료되면 이 주소로 점프하여 악성 코드(shell_code 함수)를 실행하게 됨




대응 방안

안전한 함수 사용 (strncpy)

기존에 사용된 strcpy 함수는 버퍼 오버플로우에 취약함. 이를 방지하기 위해 strncpy를 사용하여 입력 데이터의 크기를 제한하고, 마지막에 널 문자를 추가할 공간을 남김

코드:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>

shell_code(){
	setreuid(0,0); // ruid, euid를 root로 설정
	setregid(0,0); // rgid, egid를 root 그룹으로 설정
	system("/bin/sh"); // root 및 root 권한으로 쉘 실행
}

int main(int argc, char **argv){
	char buffer[12]; // 크기가 12바이트인 버퍼 생성
	memset(buffer, 0x00, sizeof(buffer));	// 버퍼를 널 값으로 초기화
	
	if(argc != 2){
		printf("Usage : buf_over data \n");
		exit(-1);
	}
	
	// 버퍼 오버플로우에 취약한 strcpy 대신 strncpy 사용
	strncpy(buffer, argv[1], sizeof(buffer) - 1); 
	printf("Buffer : %s \n", buffer);
	printf("sizeof : %d \n", sizeof(argv[1]));
	printf("strlen(argv[1]) : %d \n", strlen(argv[1]));
	printf("strlen(buffer) : %d \n", strlen(buffer));
	
	return 0;
}

실행 결과 (쉘):

[root@localhost LEC_BUF_OVER]# ./buf_over_safe01 `perl -e 'print "A"x16,"\x44\x85\x04\x08'`
Buffer : AAAAAAAAAAA
sizeof : 4
strlen(argv[1]) : 20
strlen(buffer) : 11
[root@localhost LEC_BUF_OVER]#
  • 설명:
    • 입력된 문자열은 20바이트이지만, strncpy는 버퍼 크기를 초과하지 않도록 11바이트만 복사하여 널 문자 포함을 보장함
    • 따라서, 버퍼 오버플로우 공격이 더 이상 발생하지 않음

입력값 검증을 통한 버퍼 오버플로우 방지

입력 값이 버퍼의 크기를 초과하는지 사전에 검증하고, 초과하면 프로그램을 종료하여 버퍼 오버플로우를 방지함

코드:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>

shell_code(){
	setreuid(0,0); // ruid, euid를 root로 설정
	setregid(0,0); // rgid, egid를 root 그룹으로 설정
	system("/bin/sh"); // root 및 root 권한으로 쉘 실행
}

int main(int argc, char **argv){
	char buffer[12]; // 크기가 12바이트인 버퍼 생성
	memset(buffer, 0x00, sizeof(buffer));	// 버퍼를 널 값으로 초기화
	
	if(argc != 2){
		printf("Usage : buf_over data \n");
		exit(-1);
	}
	
	// 입력 문자열이 버퍼 크기보다 크거나 같으면 프로그램 종료
	if(strlen(argv[1]) >= sizeof(buffer)){
		fprintf(stderr, "argument too long !!! \n");
		exit(1);
	}
	
	strcpy(buffer, argv[1]);	// 버퍼 오버플로우에 취약한 strcpy 사용
	printf("Buffer : %s \n", buffer);
	printf("sizeof : %d \n", sizeof(argv[1]));
	printf("strlen(argv[1]) : %d \n", strlen(argv[1]));
	printf("strlen(buffer) : %d \n", strlen(buffer));
	
	return 0;
}

실행 결과 (쉘):

[root@localhost LEC_BUF_OVER]# ./buf_over_safe01 `perl -e 'print "A"x16,"\x44\x85\x04\x08'`
argument too long !!!
[root@localhost LEC_BUF_OVER]#
  • 설명:
    • 입력된 문자열의 크기를 먼저 체크하여, 버퍼의 크기를 초과하는 경우 프로그램을 즉시 종료
    • 버퍼 오버플로우 공격이 사전에 차단됨




대응 기술

스택 버퍼 오버플로우 공격을 방어하기 위해 운영체제와 컴파일러 레벨에서 제공하는 주요 보안 기술에는 스택가드(StackGuard), 스택 쉴드(StackShield), 그리고 주소 공간 배치 난수화(ASLR, Address Space Layout Randomization)가 있음


스택가드 (StackGuard)

  • 개념: 함수 리턴 주소 덮어쓰기 방지용. 리턴 주소 앞에 카나리 값을 넣어둠. 함수 끝날 때 카나리 값 변조됐는지 확인
  • 작동 원리: 스택 오버플로우 발생 시 리턴 주소 덮어쓰면 카나리 값도 같이 덮어써짐. 카나리 값이 변하면 프로그램 바로 종료
  • 장점: 리턴 주소 보호
  • 단점: 카나리 값이 노출되면 우회 가능

스택 쉴드 (Stack Shield)

  • 개념: 리턴 주소를 스택에 저장하기 전에 다른 안전한 공간에 복사해 둠
  • 작동 원리: 함수 끝날 때 스택에 있는 리턴 주소와 저장해둔 리턴 주소 비교해서 변조됐는지 확인. 변조되면 프로그램 종료
  • 장점: 리턴 주소 직접 보호
  • 단점: 메모리 더 쓰고 성능에 영향 줄 수 있음

ASLR (Address Space Layout Randomization)

  • 개념: 메모리 주소를 무작위로 배치해서 공격자가 특정 주소를 예측하지 못하게 만듦
  • 작동 원리: 스택, 힙, 코드 등의 메모리 주소가 프로그램 실행할 때마다 바뀜. 공격자가 메모리 위치를 추정하기 어렵게 만듦
  • 장점: 주소 예측 공격 방어
  • 단점: 무작위 배치가 완벽하지 않으면 우회될 수 있음

요약

메커니즘작동 방식방어 대상장점단점
스택가드스택 프레임에 카나리 값 삽입리턴 주소 변조간단한 보호 방식카나리 값을 우회할 가능성
스택쉴드리턴 주소를 별도로 백업리턴 주소 변조리턴 주소 무결성 강화추가 메모리 사용 및 성능 저하
ASLR메모리 영역의 주소 무작위화메모리 공격 (예: ROP)주소 예측 방지완벽하지 않은 무작위화는 우회 가능

0개의 댓글