EXE, COM, DLL 등과 같은 실행 파일의 형태로 작동하는 프로그램의 코드 보안
하드웨어, 어셈블리어, 코드 중 가장 취약한 곳 = 소스 코드
'데이터 길이와 형태에 대한 불명확한 정의'
보통 C/C++ 등과 같은 low-level language에서 많이 발생한다.
동적 prototyping 필요
레지스터는 CPU의 임시 메모리
CPU연산과 어셈블리어 동작에 필요하다.
인텔의 80x86 CPU기준으로 다음과 같은 레지스터를 사용한다.
본 셸(bourne shell), 콘 셸(korn shell), C 셸(C shell)
본 셸은 유닉스 시스템에서 사용하는 기본 셸
/bin/sh
명령으로 셸 실행, exit
명령으로 셸 빠져나오기SetUID = 유닉스 시스템을 해킹하는 데 매우 중요한 요소
유닉스 파일에 rws r-x r-x
로 권한이 설정된 경우(사용자 권한에서 x가 아닌 s가 설정되어있다)
SetUID 파일은 누가 실행하든 상관없이 해당 파일이 실행될 때 파일 소유자의 권한을 가짐
test 파일이 root 소유이고, SetUID 비트가 설정되어 있다면 다음과 같이 실행된다.
- 데이터 '길이'에 대한 불명확한 정의
int main(int argc, char *argv[]) { // argv[0]: 실행 파일의 이름, argv[1]: 첫번째 인자, argv[2]: 두번째 인자 char buffer[10]; // 10바이트 크기의 버퍼 할당(12byte의 주소할당) strcpy(buffer, argv[1]); // 버퍼 오버플로는 여기서 발생 printf("%s\n", &buffer); // 버퍼에 저장된 내용 출력 }
10바이트 길이를 넘지 않아야 하는데,(실제 메모리는 12byte만 할당받긴 함) strcpy는 입력받은 인수의 경계를 체크하지 않아 10바이트보다 큰 인수를 받아도 스택에 쌓인다.(세그멘테이션 오류 발생)
즉, 기존의 EBP 값을 지나 변조된 RET값을 참조할 수 있다.
strcpy(char *dest, const char *src);
→strcpy_s()
,strncpy()
함수 사용(인수 경계 체크하는 함수)strcat(char *dest, const char *src);
getwd(char *buf);
gets(char *s);
//사용자가 입력받는 문자열 그대로 입력받기
→fgets()
함수 사용fscanf(FILE *stream, const char *format, ...);
scanf(const char *format, ...);
realpath(char *path, char resloved_path[]);
sprintf(char *str, const char *format);
non-executable stack, 스택 가드, 스택 실드와 같이 운영체제 내에서 공격 코드가 실행되지 않도록 하는 여러 장치가 존재한다.
#include <stdio.h>
main() {
char *buffer="wishfree";
printf("%s\n", buffer); //'%s'는 문자열 포맷 스트링 문자
}
데이터의 포맷 스트링이 명확하게 정해져 있으면 포맷 스트링 공격이 적용되지 않는다.
#include <stdio.h>
main() {
char *buffer="wishfree";
printf(buffer); // 포맷 스트링 공격이 일어날 수 있는 곳
}
#include <stdio.h>
main() {
char *buffer="wishfree\n%x";
// %x는 양의 정수(16진수), 메모리값 참조 가능
printf(buffer); // 포맷 스트링 공격이 일어날 수 있는 곳
}
#include <stdio.h>
main() {
long i = 0x00000064, j = 1;
printf("i의 주소 : %x\n", &i);
printf("i의 값: %x\n", i);
printf("%64d%n\n", j, &i); //포맷 스트링 문제
//%64d 부분에 공격 셸(egg shell)의 주소 값을 계산하여 넣는다.
printf("변경된 i의 값 : %x\n", i);
}