본 포스팅은 DreamHack 사이트의 Memory Corruption - C(1) 강의 내용을 요약한 것입니다.
이 글은 저의 취향에 맞춘 정리글이므로 Dreamhack 사이트의 내용을 보시기 바랍니다.
버퍼 오버플로우(Buffer Overflow)
C언어에서 버퍼 : 지정된 크기의 메모리 공간
버퍼 오버플로우 취약점: 버퍼가 허용할 수 있는 양의 데이터보다 더 많은 값이 저장되어 버퍼가 넘치는 취약점
종류
-Stack Buffer Overflow
-Hip Overflow
Stack Overflow : 지역 변수가 할당되는 스택 메모리에서 오버플로우 발생
위와 같이 선형적으로 할당되었다고 했을 때
버퍼 A에 8Byte가 아닌 16Byte 의 데이터를 복사한다면 데이터 영역 B에도 쓰여지게 된다.
이때 버퍼 오버플로우가 발생했다고 한다.(정의되지 않은 행동을 이끌어냄)
만약 데이터 영역 B에 나중에 호출될 함수 포인터가 저장되어 있다면 이 값을 "AAAAAAAA"와 같은 데이터로 덮었을 때 Segmentation Fault(접근 권한이 없는 메모리 영역을 읽거나 쓰려고 할 때 발생하는 예외)를 발생시킴.
이를 약용한다면 함수 포인터를 공격자의 코드주소로 덮어서 의도한 코드를 실행할 수 있다.
//stack-1.c
#include <stdio.h>
#include <stdlib.h>
int main(void) {
char buf[16];
gets(buf);
printf("%s", buf);
}
}
16Byte 버퍼를 스택에 할당하고, gets 함수로 데이터를 입력받아 그대로 출력하는 코드
gets함수: 사용자가 개행을 입력하기 전까지 입력했던 모든 내용을 첫 번째 인자로 전달된 버퍼에 저장
But 길이제한 x -> 16Byte가 넘는 데이터를 입력한다면 스택 버퍼 오버플로우 발생
// stack-2.c
#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: ./stack-1 ADMIN_PASSWORD\n");
exit(-1);
}
if (check_auth(argv[1]))
printf("Hello Admin!\n");
else
printf("Access Denied!\n");
}
main 함수에서 argv[1]을 check_auth 함수의 인자로 전달하고 리턴을 받아온 뒤 리턴 값에 따라 출력 문자가 달라짐. check_auth 함수에서는 16Byte 크기의 temp 버퍼에 입력받은 패스워드를 복사하고 "SECRET_PASSWORD" 문자열과 비교한 뒤 문자열이 같으면 auth를 1로 설정하고 리턴.
But, strncpy 함수로 temp를 복사할 때 인자로 전달된 password문자열의 길이만큼을 복사함. 따라서 argv[1]에 16Byte가 넘는 문자열을 전달한다면 길이 제한없이 문자열이 복사되어서 스택 버퍼 오버플로우 발생.
temp 버퍼 뒤에 auth값이 존재하므로 오버플로우로 auth값을 바꾼다면 auth가 0이 아닌 다른 값이 될 수 있다.
따라서 실제 인증 여부와는 상관없이 if(check(auth[1])문은 항상 참을 반환함.
(temp 버퍼 뒤에 auth 값이 존재하는 이유는 stack이기 때문이다. 메모리가 밑에서부터 쌓이기 때문에 temp 다음이 auth가 될 수 있는 것이다.)
// stack-3.c
#include <stdio.h>
#include <unistd.h>
int main(void) {
char win[4];
int size;
char buf[24];
scanf("%d", &size);
read(0, buf, size);
if (strncmp(win, "ABCD", 4)){
printf("Theori{-----------redeacted---------}");
}
}
24Byte의 버퍼를 할당받고, size변수를 입력받고, size만큼 buf에 데이터를 입력받음. 고정된 크기의 버퍼보다 더 긴 데이터를 입력받아 스택 버퍼 오버플로우가 발생함.
// stack-4.c
#include <stdio.h>
int main(void) {
char buf[32] = {0, };
read(0, buf, 31);
sprintf(buf, "Your Input is: %s\n", buf);
puts(buf);
}
buf를 초기화하고 데이터를 31Byte 입력받고, sprintf로 출력할 문자열을 저장한 뒤 출력하는 코드.
read에서는 괜찮지만 sprintf 함수를 통해 버퍼에 값을 쓸 때 Your Input is: 문자열 또한 포함된다는 사실을 생각해야 함. buf를 31Byte를 꽉 채우게 된다면 총 길이가 32Byte를 넘기게 됨.
Heap Overflow
// heap-1.c
#include <stdio.h>
#include <stdlib.h>
int main(void) {
char *input = malloc(40);
char *hello = malloc(40);
memset(input, 0, 40);
memset(hello, 0, 40);
strcpy(hello, "HI!");
read(0, input, 100);
printf("Input: %s\n", input);
printf("hello: %s\n", hello);
}
이 강의에서는 heap overflow에 대해 간단히 설명한다.
heap 버퍼 input과 hello를 할당, hello에 HI! 문자열을 복사하고 read로 input에 입력받는 코드
But, read 함수를 통해 입력받는 길이인 100Byte가 input버퍼의 크기(40Byte)보다 크기 때문에 발생한다.
input에서 버퍼 오버플로우가 발생해 hello까지 침범할 경우 hello에 공격자에게 오염된 데이터가 출력된다.
Out Of Boundary: 버퍼의 길이 범위를 벗어나는 인덱스에 접근할 때 발생
// oob-1.c
#include <stdio.h>
int main(void) {
int win;
int idx;
int buf[10];
printf("Which index? ");
scanf("%d", &idx);
printf("Value: ");
scanf("%d", &buf[idx]);
printf("idx: %d, value: %d\n", idx, buf[idx]);
if(win == 31337){
printf("Theori{-----------redeacted---------}");
}
}
int형 배열 buf를 선언하고 idx를 입력받음. 그다음 buf[idx]에 정수를 입력받고 idx와 buf[idx]값을 출력한다.
buf의 길이는 10이므로 인덱스는 0<=x<10 의 정수이다. 그러나 코드에서는 입력받은 idx값을 인덱스로 사용할 때 올바른 범위에 속해 있는지 검사하지 않음. 따라서 올바르지 않은 값을 사용한다면 buf 영역 밖에 있는 값에 접근할 수 있다.
근데... 문제를 어떻게풀지..?
풀었다.
// oob-2.c
#include <stdio.h>
int main(void) {
int idx;
int buf[10];
int win;
printf("Which index? ");
scanf("%d", &idx);
idx = idx % 10;
printf("Value: ");
scanf("%d", &buf[idx]);
printf("idx: %d, value: %d\n", idx, buf[idx]);
if(win == 31337){
printf("Theori{-----------redeacted---------}");
}
}
이번 코드에는 idx = idx%10이라는 코드가 추가됨. 하지만 0<=x<10까지의 값 뿐만 아니라 -(음수) 값도 나올 수 있다 따라서 이를 이용한다면 OOB를 발생시킬 수 있음.
//oob-3.c
#include <stdio.h>
int main(void) {
int idx;
int buf[10];
int dummy[7];
int win;
printf("Which index? ");
scanf("%d", &idx);
if(idx < 0)
idx = -idx;
idx = idx % 10; // No more OOB!@!#!
printf("Value: ");
scanf("%d", &buf[idx]);
printf("idx: %d, value: %d\n", idx, buf[idx]);
if(win == 31337){
printf("Theori{-----------redeacted---------}");
}
}
이번에는 idx가 음수일 경우 양수로 바꿔주는 코드가 더 추가됨. 여기서 문제가 될 경우는 -2의 31승 뿐이다. 왜냐하면 int 자료형의 범위가 -2의 31승부터 +2의 31승-1까지이기 때문이다.(양수는 0때문에 한가지가 빠지게 됨)
이 때문에 값이 이상해져서 OOB가 발생할 수 있음.
Off By One: 경계 검사에서 하나의 오차가 있을 때 발생하는 취약점.
버퍼의 경계 계산 혹은 반복문의 회수 계산 시 <대신 <=를 쓰거나 0부터 시작하는 인덱스를 고려하지 못할 때 발생.
// off-by-one-1.c
#include <stdio.h>
void copy_buf(char *buf, int sz) {
char temp[16];
for(i = 0; i <= sz; i++)
temp[i] = buf[i];
}
int main(void) {
char buf[16];
read(0, buf, 16);
copy_buf(buf, sizeof(buf));
}
buf에 16Byte 문자열을 입력받고 buf와 buf의 크기를 copy_buf함수의 인자로 전달. copy_buf 함수에서는 d임시로 temp를 할당하고 반복문을 통해 buf의 데이터를 복사함. 그러나 반복문은 i가 0일때부터 sz일 때까지 sz+1번 반복하게 됨 따라서 sz +1만큼 데이터가 복사되고, off by one 취약점이 발생하게 됨.
정리
스택 버퍼 오버플로우: 지역 변수가 할당되는 스택 메모리에서 발생하는 취약점. 데이터를 입력받거나 복사하는 부분에 대한 길이 검증이 존재하지 않거나 미흡할 경우 발생
힙 오버플로우: 동적으로 할당된 힘 메모리 영역에서 발생. 데이터를 입력받거나 복사하는 부분에 대한 길이 검증이 존재하지 않거나 미흡할 경우 발생
Out Of Boundary: 버퍼의 길이 범위를 벗어나는 인덱스에 접근할 때 발생하는 취약점. 이는 올바르지 않은 값이 버퍼의 인덱스로 사용될 경우 발생
Off By One: 버퍼의 경계 계산 혹은 잘못된 반복문의 연산자를 사용하는 등의 인덱스를 고려하지 않을 때 발생
'Hacking-기초 > Pwnable' 카테고리의 다른 글
Linux Exploitation & Mitigation Part 1 (1) (1) | 2020.04.13 |
---|---|
Memory Corruption - C (2) FSB 문제해설 (1) | 2020.04.11 |
Buffer Overflow Attack 기초(1) (0) | 2020.04.11 |
Memory Corruption - C (2) (1) | 2020.04.11 |
시스템 해킹 기초 (0) | 2020.04.11 |