먼저 스택 오버플로우와 스택 버퍼 오버플로우의 차이점.
스택 오버 플로우는 스택 영역이 너무 많이 확장돼서 발생하는 버그를 뜻하며, 스택 버퍼 오버플로우는 스택에 위치하 버퍼에 버퍼의 크기보다 많은 데이터가 입력되어 발생하는 버그를 뜻한다.
스택 버퍼 오버플로우
스택의 버퍼에서 발생하는 오버플로우를 뜻한다.
버퍼(Buffer)란?
데이터가 목적지로 이동되기 전에 보관되는 임시 저장소이다.
데이터의 처리속도가 다른 두 장치가 있을 때, 둘 사이에 오가는 데이터를 임시로 저장해 두는 것은 일종의 완충 작용을 한다. 예시로 키보드 입력보다 데이터 처리 속도가 더 느릴경우 데이터가 유실 될 수 있수 있어, 수신 송신 측 사이에 버퍼라는 임시 저장소를 두고 간접적으로 데이터를 전달한다.
빠른 속도로 이동하던 데이터가 안정적으로 목적지에 도달할 수 있도록 완충작용을 하는 것이 버퍼의 역할이다.
현대에는 의미가 희석되어 데이터가 저장될 수 있는 모든 단위를 버퍼라고 부르기도 한다.
스택에 있는 지역 변수는 '스택 버퍼', 힙에 할당된 메모리 영역은 '힙 버퍼'라고 불린다.
+ 버퍼링(Buffering) : 송신 측의 전송 속도가 느려서 수신 측의 버퍼가 채워질 때까지 대기하는 것을 의미.
버퍼 오버플로우: 버퍼가 넘침.
일반적으로 버퍼는 메모리상에 연속해서 할당되어 있으므로, 어떤 버퍼에서 오버플로우가 발생하면, 뒤에 있는 버퍼들의 값이 조작될 위험이 있다.
보안위협
중요 데이터 변조
버퍼 오버플로우가 발생하는 버퍼 뒤에 중요한 데이터가 있다면, 데이터가 변조됨으로써 문제가 발생할 수 있다.
#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");
}
데이터 유출
C언어에서 정상적인 문자열은 널바이트로 종결되며, 표준 문자열 출력 함수들은 널바이트를 문자열의 끝으로 인식한다.
만약에 어떤 버퍼에 오버플로우를 발생시켜서 다른 버퍼와의 사이에 있는 널바이트를 모두 제거하면, 해당 버퍼를 출력시켜서 다른 버퍼의 데이터를 읽을 수 있다. 획득한 데이터는 보호기법 우회로 사용되거나, 이미 중요한 정보일 수 있다.
#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);
}
실행 흐름 조작
함수를 호출할 때 반환 주소를 스택에 쌓고, 함수에서 반환될 때 이를 꺼내어 원래의 실행 흐름으로 돌아간다. 스택 버퍼 오버플로우로 반환 주소를 조작하면 프로세스의 실행 흐름을 바꿀 수 있다.
#include <stdio.h>
#include <stdlib.h>
int main(void) {
char buf[8];
printf("Overwrite return address with 0x4141414141414141: ");
gets(buf);
return 0;
}
0X41은 65로 아스키 A이다.