스택 버퍼 오버 플로우의 실습 코드에 대한 정리와 안전한 코드 예시에 대해 정리해봄
#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;
}
setreuid(0, 0)
와 setregid(0, 0)
을 사용해 현재 프로세스의 사용자 ID와 그룹 ID를 루트 사용자로 설정함system("/bin/sh")
을 호출해서 루트 권한으로 쉘을 실행buffer
배열을 생성, 이 배열은 사용자가 입력한 데이터를 저장할 공간buffer
로 복사, 이를 위해 strcpy
함수를 사용strcpy
함수는 입력한 문자열의 크기를 확인하지 않는다는 점buffer
라는 작은 공간을 넘어 주변의 다른 메모리 영역까지 덮어쓰게 됨buffer
뒤에 있는 다른 메모리 공간까지 덮어쓸 수 있음main
함수의 마지막 부분에 도달하면 종료되는데, 버퍼 오버플로우를 통해 프로그램이 shell_code
함수를 실행하게 할 수도 있음. 그러면 루트 권한으로 쉘을 실행하게 됨strcpy
가 입력 길이를 체크하지 않는 점을 이용해 긴 문자열을 입력buffer
를 넘쳐흘러서 프로그램의 제어 흐름을 변경shell_code
함수가 호출함수가 호출될 때마다 그 함수만을 위한 메모리 공간(스택 프레임)이 생성됨
이 스택 프레임은 스택 포인터(ESP)와 스택 프레임 포인터(EBP)로 관리
이를 통해 함수는 자기만의 메모리 영역에서 데이터를 관리하며, 스택의 주소를 사용해 메모리에 접근
현재 함수는 EIP에 다음 명령어 주소를, ESP에 스택의 현재 위치를, EBP에 스택 프레임 시작 위치를 저장함. 함수 호출 시 스택에 이전 EBP와 RET(다음 명령어 주소)를 저장하며, 함수 종료 시 이 정보를 통해 원래 함수로 복귀함
buffer
의 크기를 초과하는 데이터를 입력해 메모리를 덮어쓰는 공격buf_over "AAAAAAAAAAAA"
와 같이, 12바이트의 데이터를 입력한 경우:buffer
에는 "AAAAAAAAAAAA"
가 저장perl -e 'print "A"x16, "\x44\x85\x04\x08"
)하면:"AAAAAAAAAAAA"
로 가득 차고,"AAAA"
(즉, 0x41414141)로 덮어씌워지고, RET는 공격자가 원하는 0x08048544로 변경됨기존에 사용된 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]#
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)가 있음
메커니즘 | 작동 방식 | 방어 대상 | 장점 | 단점 |
---|---|---|---|---|
스택가드 | 스택 프레임에 카나리 값 삽입 | 리턴 주소 변조 | 간단한 보호 방식 | 카나리 값을 우회할 가능성 |
스택쉴드 | 리턴 주소를 별도로 백업 | 리턴 주소 변조 | 리턴 주소 무결성 강화 | 추가 메모리 사용 및 성능 저하 |
ASLR | 메모리 영역의 주소 무작위화 | 메모리 공격 (예: ROP) | 주소 예측 방지 | 완벽하지 않은 무작위화는 우회 가능 |