
Stack Overflow? VS Stack Buffer Overflow?
스택 오버플로우(Stack Overflow)는 스택 영역이 너무 많이 확장돼서 발생하는 버그를 뜻합니다.
스택 버퍼 오버플로우(Stack Buffer Overflow)는 스택에 위치한 버퍼에 버퍼의 크기보다 많은 데이터가 입력되어 발생하는 버그를 뜻합니다.
Buffer?
버퍼(Buffer) : 데이터가 목적지로 이동되기 전에 보관되는 임시 저장소
데이터의 처리속도가 다른 두 장치가 있을 때, 이 둘 사이에 오가는 데이터를 임시로 저장해 두는 것은 일종의 완충 작용을 합니다.
현대에는 이런 완충의 의미가 많이 희석되어 데이터가 저장될 수 있는 모든 단위를 버퍼라고 부르기도 합니다.
스택에 있는 지역 변수는 ‘스택 버퍼’, 힙에 할당된 메모리 영역은 '힙 버퍼'라고 불립니다.
Buffer Overflow?
버퍼 오버플로우(Buffer Overflow)는 문자 그대로 버퍼가 넘치는 것을 의미합니다. 버퍼는 제각기 크기를 가지고 있는데, int로 선언한 지역 변수는 4바이트의 크기를 갖고, 10개의 원소를 갖는 char배열은 10바이트의 크기를 갖습니다. 만약 10바이트 크기의 버퍼에 20바이트 크기의 데이터가 들어가려 하면 오버플로우가 발생합니다.
일반적으로 버퍼는 메모리상에 연속해서 할당되어 있으므로, 어떤 버퍼에서 오버플로우가 발생하면, 뒤에 있는 버퍼들의 값이 조작될 위험이 있습니다.
예시 1
#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");
}
main 함수는 argv[1]을 check_auth 함수의 인자로 전달한 후, 반환 값을 받아옵니다. 이 때, 반환 값이 0이 아니라면 "Hello Admin!"을, 0이라면 "Access Denied!"라는 문자열을 출력합니다.
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");
}
check_auth함수에서는 16 바이트 크기의 temp버퍼에 입력받은 패스워드를 복사한 후, 이를 "SECRET_PASSWORD" 문자열과 비교합니다. 문자열이 같다면 auth를 1로 설정하고 반환합니다.
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;
}
그런데 check_auth에서 strncpy 함수를 통해 temp버퍼를 복사할 때, temp의 크기인 16 바이트가 아닌 인자로 전달된 password의 크기만큼 복사합니다. 그러므로 argv[1]에 16 바이트가 넘는 문자열을 전달하면, 이들이 모두 복사되어 스택 버퍼 오버플로우가 발생하게 됩니다.
strncpy(temp, password, strlen(password));
auth는 temp버퍼의 뒤에 존재하므로, temp버퍼에 오버플로우를 발생시키면 auth의 값을 0이 아닌 임의의 값으로 바꿀 수 있습니다. 이 경우, 실제 인증 여부와는 상관없이 main함수의 if(check_auth(argv[1])) 는 항상 참이 됩니다.
예시 2
버퍼에 오버플로우를 발생시켜서 다른 버퍼와의 사이에 있는 널바이트를 모두 제거하면, 해당 버퍼를 출력시켜서 다른 버퍼의 데이터를 읽을 수 있습니다. 획득한 데이터는 각종 보호기법을 우회하는데 사용될 수 있으며, 해당 데이터 자체가 중요한 정보일 수도 있습니다.
8바이트 크기의 name 버퍼에 12바이트의 입력을 받습니다. 읽고자 하는 데이터인 secret버퍼와의 사이에 barrier라는 4바이트의 널 배열이 존재하는데, 오버플로우를 이용하여 널 바이트를 모두 다른 값으로 변경하면 secret을 읽을 수 있습니다.
#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);
}
예시 3
#include <stdio.h>
#include <unistd.h>
void init() {
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
}
void get_shell() {
char *cmd = "/bin/sh";
char *args[] = {cmd, NULL};
execve(cmd, args, NULL);
}
int main() {
char buf[0x28];
init();
printf("Input: ");
scanf("%s", buf);
return 0;
}
프로그램의 취약점은 scanf(“%s”, buf)에 있습니다.
입력의 길이를 제한하지 않으며, 공백 문자인 띄어쓰기, 탭, 개행 문자 등이 들어올 때까지 계속 입력을 받는다는 특징이 있습니다.
크기가 0x28인 버퍼에 scanf(“%s”, buf)로 입력을 받으므로, 입력을 길게 준다면 버퍼 오버플로우를 발생시켜서 main함수의 반환 주소를 덮을 수 있습니다.
int main() {
char buf[0x28];
init();
printf("Input: ");
scanf("%s", buf);
return 0;
}
셸을 실행해주는 get_shell() 함수가 있으므로, 이 함수의 주소로 main함수의 반환 주소를 덮어서 셸을 획득할 수 있습니다.
void get_shell(){
char *cmd = "/bin/sh";
char *args[] = {cmd, NULL};
execve(cmd, args, NULL);
}'임문주' 카테고리의 다른 글
| HTTP COOKIE (0) | 2025.02.09 |
|---|---|
| Lighttpd / CGI (0) | 2024.12.20 |
| CVE-2022-30780 (0) | 2024.11.18 |

stellarflare 님의 블로그 입니다.
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!