버퍼 오버플로란 무엇인가요?
버퍼 오버플로
버퍼는 메모리에 데이터가 저장되는 곳입니다. 버퍼 오버플로는 프로그램이 컴퓨터 메모리의 한 위치에 데이터를 저장하려고 시도하지만 공간이 충분하지 않을 때 발생합니다.
버퍼는 프로그램이 실행되는 동안 특정 유형의 데이터를 보관하는 연속적인 컴퓨터 메모리 블록입니다. 변수의 동적 할당에 문제가 발생합니다. 메모리를 덜 사용하기 위해 동적으로 할당된 변수가 있는 프로그램은 프로그램이 실행될 때만 변수에 할당할 메모리 양을 결정합니다. 프로그램이 동적으로 할당된 버퍼에 너무 많은 데이터를 넣으면 어떻게 될까요? 넘쳐흘러 다른 곳으로 흘러내렸습니다. 버퍼 오버플로 응용 프로그램은 오버플로 데이터를 사용하여 어셈블리 언어 코드를 일반적으로 루트 권한이 발생하는 컴퓨터 메모리에 배치합니다. 버퍼 오버플로만으로는 보안 문제가 발생하지 않습니다. 이는 루트 권한으로 명령을 실행할 수 있는 영역으로 오버플로가 전송되는 경우에만 작동합니다. 이러한 방식으로 버퍼 익스플로잇은 실행 가능한 명령을 루트 권한으로 메모리에 배치하므로 이러한 명령이 실행되면 컴퓨터가 루트 권한으로 제어됩니다. 위의 설명을 요약하자면. 버퍼 오버플로(Buffer Overflow)란 프로그램의 버퍼 길이를 넘어서 내용을 쓰면 버퍼가 오버플로되어 프로그램의 스택이 파괴되고 프로그램이 목적을 달성하기 위해 다른 명령을 실행하게 만드는 시스템 공격 방법을 말한다. 공격. 통계에 따르면, 버퍼 오버플로를 통한 공격은 전체 시스템에 대한 전체 공격 횟수의 80% 이상을 차지합니다. 버퍼 오버플로의 원인은 사용자가 입력한 매개변수를 프로그램에서 주의 깊게 확인하지 않기 때문입니다. 예를 들어 다음 프로그램은 다음과 같습니다.
example0.c
------------- ---------------------
void function(char *str) {
char 버퍼[16];
strcpy(buffer, str);
}
--------- ---- --------------------------------- ----
위의 strcpy()는 str의 내용을 버퍼에 직접 복사합니다. 이런 식으로 str의 길이가 16보다 크면 버퍼가 오버플로되고 프로그램이 잘못 실행됩니다. strcpy와 같은 문제가 있는 표준 함수로는 루프 내에서 strcat(), sprintf(), vsprintf(), gets(), scanf() 및 getc(), fgetc(), getchar() 등이 있습니다. C 언어에서는 정적 변수가 데이터 세그먼트에 할당되고, 동적 변수가 스택 세그먼트에 할당됩니다. 버퍼 오버플로는 스택 세그먼트의 오버플로를 사용합니다. 프로그램은 일반적으로 메모리에서 프로그램 세그먼트, 데이터 끝 및 스택의 세 부분으로 나뉩니다. 프로그램 세그먼트에는 프로그램의 기계어 코드와 읽기 전용 데이터가 포함되어 있습니다. 이 세그먼트는 일반적으로 읽기 전용이므로 쓰기가 불법입니다. 데이터 세그먼트에는 프로그램의 정적 데이터가 포함됩니다. 동적 데이터는 스택을 통해 저장됩니다. 메모리 내에서의 위치는 다음과 같습니다:
/――――――――\ 메모리 하한
|프로그램 섹션|
|― ― ――――――――|
|데이터 세그먼트|
|―――――――――|
|스택|< /p >
\―――――――――――/메모리의 상한
스택은 메모리의 연속 블록입니다. 스택 포인터(SP)라는 레지스터가 스택의 맨 위를 가리킵니다. 스택의 맨 아래는 고정된 주소입니다. 스택의 특징 중 하나는 후입선출(Last In First Out)이라는 점입니다. 즉, 나중에 입력된 데이터를 먼저 꺼냅니다. PUSH와 POP의 두 가지 작업을 지원합니다. PUSH는 스택의 맨 위에 데이터를 넣는 것이고, POP는 스택의 맨 위에서 데이터를 가져오는 것입니다.
고급 언어에서는 프로그램 함수 호출과 함수의 임시 변수가 스택을 사용합니다. 스택은 매개변수 전송 및 반환 값에도 사용됩니다. 일반적으로 지역 변수에 대한 참조는 해당 오프셋을 SP에 제공하여 이루어집니다. 많은 컴파일러가 실제로 로컬 변수와 매개변수를 참조하는 데 사용하는 기본 포인터(인텔 칩의 FP, BP)도 있습니다. 일반적으로 FP를 기준으로 한 오프셋은 매개변수의 경우 양수이고 지역 변수의 경우 음수입니다. 프로그램에서 함수 호출이 발생하면 컴퓨터는 다음을 수행합니다. 먼저 매개변수를 스택에 푸시한 다음 명령어 레지스터(IP)의 내용을 반환 주소(RET)로 저장합니다. 기본 주소 레지스터(FP). 그런 다음 현재 스택 포인터(SP)를 새 기본 주소로 FP에 복사합니다. 마지막으로 로컬 변수를 위한 특정 공간을 남겨두고 SP에서 적절한 값을 뺍니다.
예는 다음과 같습니다.
example1.c:
------- ------ ---------------------------
무효 함수( int a, int b, int c) {
char buffer1[5]
char buffer2[10]
}
void main() {
함수(1, 2, 3)
}
------- ------ ------------------ ------
프로그램이 function()을 호출하는 방법을 이해하려면 -S 옵션을 사용하십시오. Linux에서는 gcc로 컴파일하여 어셈블리 코드 출력을 생성합니다.
$ gcc -S -o example1.s example1.c
함수를 호출하는 출력 파일 부분을 살펴보세요:
pushl $3
pushl $2
pushl $1 p>
호출 함수
이는 세 개의 매개변수를 스택에 푸시하고 function()을 호출합니다. 명령어 호출은 명령어 포인터 IP를 스택에 푸시합니다. 돌아올 때 RET는 저장된 이 IP를 사용합니다. 함수에서 가장 먼저 해야 할 일은 몇 가지 필요한 처리를 수행하는 것입니다. 각 함수에는 다음 절차가 있어야 합니다:
pushl ebp
movl esp, ebp
subl $20, esp
다음은 항목입니다. 명령어는 기본 포인터인 EBP를 스택에 넣습니다. 그런 다음 현재 SP를 EBP에 복사합니다. 그런 다음 지역 변수에 공간을 할당하고 해당 크기를 SP에서 뺍니다. 메모리 할당은 워드 단위이므로 여기서 buffer1은 8바이트(2워드, 워드당 4바이트)를 사용합니다. Buffer2는 12바이트(3워드)를 사용합니다. 따라서 여기서 ESP는 20만큼 감소합니다. 이제 스택은 다음과 같아야 합니다.
로우엔드 메모리 하이엔드 메모리
buffer2 buffer1 sfp ret a b c
lt; [ ][ ][ ]
스택 맨 위 맨 아래
버퍼 오버플로로 인해 버퍼에 너무 많은 데이터가 기록됩니다.
그럼 어떻게 사용하나요?
다음 프로그램을 보세요:
example2.c
-------------- -- -------------------
void function(char *str) {
char 버퍼[16]
strcpy(buffer, str)
}
void main() {
char Large_string[256];
int i
for( i = 0; i lt; 255; i ) p>
Large_string[i] = 'A';
function(large_string)
}
----- ---- --------------------------------- ---- -
이 프로그램은 전형적인 버퍼 오버플로 코딩 오류입니다. 이 함수는 경계 검사 없이 문자열을 다른 메모리 영역에 복사합니다. function() 함수가 호출되면 스택은 다음과 같습니다:
낮은 메모리 끝 버퍼 sfp ret *str 높은 메모리 끝
------ [ ] [ ][ ] [ ]
스택 맨 위 스택 맨 아래
분명히 프로그램 실행의 결과는 "세그먼트 오류(코어 덤프)" 또는 이와 유사한 오류 메시지입니다. 버퍼에서 시작하는 256바이트는 sfp, ret 및 *str을 포함하여 *str의 'A' 내용으로 덮어쓰이기 때문입니다. 'A'의 16진수 값이 0x41이므로 함수의 반환 주소가 0x41414141이 되어 프로그램의 주소 공간을 초과하므로 세그폴트가 발생한다. 보시다시피 버퍼 오버플로를 통해 함수의 반환 주소를 변경할 수 있습니다. 이런 방법으로 프로그램의 실행 순서를 변경할 수 있습니다.