1. 버퍼 오버플로우
버퍼 오버플로우란?
C언어에서 버퍼란 지정된 크기의 메모리 공간이라는 뜻이다.
버퍼 오버플로우는 버퍼가 허용할 수 있는 양의 데이터보다 더 많은 값이 저장되어 버퍼가 넘치는 취약점이다.
발생하는 위치에 따라 스택 버퍼 오버플로우, 힙 오버플로우로 나뉜다. 그 이유는 인접한 메모리를 오염시키는 취약점이기 때문에 어떤 메모리를 오염시킬 수 있는지에 따라 공격 방법이 달라지기 때문이다.
스택 버퍼 오버플로우
스택 버퍼 오버플로우는 지역 변수가 할당되는 스택 메모리에서 오버플로우가 발생하는 경우다.
8 바이트의 버퍼 A와 8바이트 데이터 버퍼 B가 메모리에 선형적으로 할당되었다고 생각해보라. 버퍼 A에 16바이트의 데이터를 복사한다면 이 데이터의 뒷부분은 버퍼 A를 넘어 뒤에 있는 B에 쓰여진다. 이것을 버퍼 오버플로우가 발생했다고 하고, 이는 프로그램의 Undefined Behavior을 이끌어낸다. 만약 데이터 영역 B에 나중에 호출될 함수 포인터를 저장하고 있다면 이 값을 "AAAAAAAA"와 같은 데이터로 덮었을 때 Segmentation Fault를 발생시킬 것이다.
Undefined Behavior : 런타임중에 어떤 현상이 발생할 지 예측할 수 없다는 뜻
예상 공격
공격자는 어딘가에 기계어 코드를 삽입한 후 함수 포인터를 공격자의 코드의 주소로 덮어 코드를 실행할 수 있다.
예제 코드 (stack-1.c)
#include <stdio.h>
#include <stdlib.h>
int main(void) {
char buf[16];
gets(buf);
printf("%s", buf);
}
위 코드는 16바이트 버퍼를 스택에 할당한 후 gets함수를 통해 데이터를 입력받아 그대로 출력하는 코드다. gets함수는 사용자가 개행을 입력하기 전까지 입력했던 모든 내용을 첫 번째 인자로 전달된 버퍼에 저장하는 함수다. 그러나 별도의 길이 제한이 없어 16바이트가 넘는 데이터를 입력한다면 스택 버퍼 오버플로우가 발생한다.
이처럼 프로그래머가 버퍼의 길이에 대한 가정을 올바르지 않게 할 때 취약점이 발생한다. 보통 길이 제한이 없는 API함수들을 사용하거나 버퍼의 크기보다 입력받는 데이터의 길이가 더 크게 될 때 자주 일어나는 실수다.
예제 코드 (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");
}
strncpy 함수를 통해 temp 버퍼를 복사할 때, 16바이트가 아닌 password 문자열의 길이만큼을 복사하기 때문에 argv[1]에 16바이트가 넘는 문자열을 전달하면 스택 버퍼 오버플로우가 발생한다. temp 버퍼 뒤에 auth값이 존재하기 때문에, 공격자가 auth값을 바꾼다면 0이 아닌 다른 값이 될 수 있다. 인증 여부와 상관없이 if(check_auth(argv[1]))문이 항상 참을 반환하게 되는 것이다.
예제 코드 (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{-----------redacted---------}");
}
}
main 함수는 24 바이트 크기의 버퍼를 할당한다. scanf함수를 통해 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);
}
32바이트 buf를 초기화한 후 31바이트를 입력받고, sprintf함수를 통해 출력할 문자열을 저장한 뒤 출력한다. read 함수에서 받는 입력이 32바이트를 넘지 않더라도 sprintf함수를 통해 버퍼에 값을 쓸 때 "Your Input is: "문자열을 추가하기 때문에 buf에 31바이트를 꽉 채운다면 총 길이가 32바이트를 넘게 된다.
공격 벡터로부터 데이터를 입력받고 이를 버퍼에 저장하는 코드가 있다면 입력받은 데이터가 버퍼의 범위를 초과하지 않는지 항상 검사해야 한다. 입력받을 때 길이 제한이 없는 함수를 사용하는 것은 잠재적으로 취약하다. 입력받은 데이터가 버퍼에 저장되기까지의 흐름을 따라가 버퍼의 크기를 넘는 양을 저장할 수 있는지 가능성을 검토해야 한다.
'시스템 해킹 > dream hack' 카테고리의 다른 글
[dream hack] ASLR (0) | 2021.02.26 |
---|---|
[dream hack] NX bit (0) | 2021.02.20 |
[dream hack] NOP Sled (0) | 2021.02.18 |
[dream hack] Return address overwrite (0) | 2021.02.18 |
[dream hack] Linux Exploitation & Mitigation Part 1 - 1. ELF 동적 분석 (0) | 2021.02.08 |