버퍼 오버플로우(Buffer Overflow Attack)란?
- 버퍼란 데이터가 저장되는 메모리 공간이다. 이 프로그램이 실행될 때 이 메모리 공간에 버퍼의 크기보다 더 많은 입력을 받는다면 버퍼 오버플로우가 발생한다. 쉽게 말해서 크기가 정해져 있는 컵에 물을 넘치게 받았다고 생각하면 된다. 이 경우 해커가 특정 메모리 값을 임의로 변조할 수 있기 때문에 취약점이 된다.
위 그림과 같이 buf1의 크기가 5byte이고 buf2의 크기도 5byte이라고 가정해보자.
여기서 buf1 주소에 입력을 받게 되었을 때, 입력 크기를 제한해주지 않는다면, 5byte를 넘어서는 입력 값을 줄 수 있다.
이 경우 buf1의 입력을 통해 buf2의 공간의 값을 임의로 바꿀 수 있다.
buf1에 'abcdefgh'라는 문자열을 입력한 경우이다. buf1의 크기로는 이 문자열을 모두 저장할 수 없으므로 buf2의 저장공간까지 사용하게 된다. 이 취약점을 모르고 있다는 것은 상당한 위험을 초래한다. 이 원리를 이용해서 특정 주소의 값을 바꾸어 프로그램의 실행 흐름을 바꾸거나 해킹을 당할 수도 있다.
다음 예제 코드로 실습 해보자.
//name : bof.c
//compile : gcc -o bof bof.c -fno-stack-protector
#include<stdio.h>
int main(){
char chk = '0';
char passwd[10];
scanf("%s",passwd);
if (*passwd == *"clear") chk = '1';
if (chk == '1'){
printf("success!");
} else{
printf("fail!");
}
return 0;
}
코드의 흐름을 간단히 설명하면, passwd를 입력받고 그 값이 "clear"라면 chk의 값을 '1'로 변경한다.
이후 chk 값으로 패스워드가 맞았는지 틀렸는지 확인해서 "success!" 또는 "fail!" 문자열을 출력하는 코드이다.
여기서 버퍼 오버플로우라는 취약점을 이용해서 비밀번호를 "clear"로 입력하지 않고 "success!" 문자열을 출력할 수 있다.
먼저, 선언된 지역변수 chk와 passwd가 어떻게 스택 프레임에 쌓이는지 확인해보자.
위 그림이 이해되지 않는다면, 스택 프레임에 대해서 공부를 하고 오는 것을 추천한다.
다음 그림이 이해가 됐다면, 저 passwd[10] 공간에 입력을 받을 때 "clear" 값이 들어가게 되면 '0'이었던 chk의 값이 '1'로 설정되고 "success!" 문자열이 출력되게 된다.
하지만 passwd의 주소부터 입력을 받고, 그 크기를 제한하지 않기 때문에 chk의 값을 내가 넣고 싶은 만큼 넣을 수 있게 된다.
passwd의 크기는 10byte이므로, 10byte를 아무런 문자열로 채워넣고 11번째 문자값에 '1'을 넣어주면 passwd값에 상관없이 "success!"를 출력할 수 있다.
코드를 컴파일하여 직접 정상적인 흐름과 버퍼오버플로우를 이용한 우회를 비교해보자.
* 이해를 하기 위한 코드 이므로 컴파일 시 스택 보호 기법을 해제한다.
ex) compile : gcc -o bof bof.c -fno-stack-protector
<정상적인 흐름>
첫번째 실행에서는 "clear"라는 문자열를 입력해서 "success!"가 출력되었고, 두번째 실행에서는 "hello"라는 문자열을 입력해서 "fail!" 문자열이 출력되었다. 이것이 코드의 정상적인 흐름이다.
<비정상적인 흐름>
분명 "clear"라는 문자열은 입력하지 않았지만, "success!" 문자열이 출력된다.
버퍼 오버플로우의 취약점으로 chk의 값이 변조되었기 때문이다.
스택 그림을 통해서 알아보자.
비정상적인 흐름에서 10byte의 값을 아무것이나 넣어주고 11번째 문자열 값을 '1'로 주면 스택에서 chk값에 '1'이 들어가게 된다. 이후, 코드의 if(chk=='1')의 구문에서 true가 나오게 되고 "success!" 문자열이 출력되는 것이다.
스택 버퍼오버플로우에 대해서 이해했다면, 다시 한번 생각해보자.
예제 실습은 chk의 값을 변조하여 프로그램의 흐름을 바꾸었지만, chk를 넘어서 더 많은 값을 넣어주게 되면 '이전 스택 프레임의 ebp(sfp)'와 '함수가 끝나고 복귀할 주소(ret)' 공간에도 값을 변조해줄 수 있지 않는가?
이것을 통해 공격자가 악의적으로 특정 함수로 프로그램 흐름을 변경시켜서 시스템의 권한을 탈취할 수 있다.
이러한 공격을 방지하기 위해서 여러 메모리 보호기법이 있다. 하지만 프로그래머가 프로그래밍을 할 때 이 취약점을 이해하고 유의하여 scanf_s와 같은 함수로 입력받을 문자열의 길이를 지정해주는 것이 중요하다.