취약한 함수란

취약한 함수란 컴파일되기 이전에 프로그래머로부터 작성된 코드 중 버퍼 오버 플로우나 포맷 스트링 공격 등에 노출될 수 있는 함수를 뜻한다. 이러한 함수의 사용은 오류를 발생시키거나 심할 경우 상위 권한까지 탈취될 수 있기에 주의하여야 한다. 따라서 이러한 함수의 어떠한 부분이 취약한지 등을 알고 제작할 때 해당 함수들의 사용을 자제하므로 추가적인 피해를 방지할 수 있다. 


취약한 함수에는 대표적으로 gets, scanf 등과 같은 함수로 입력받는 문자열의 크기와 주어진 변수의 크기를 고려하지 않는다는 점이다. 이렇게 변수의 크기를 고려하지 않는 함수들은 입력받은 문자열 등이 변수 공간보다 클 경우, 결국 스택의 다른 곳까지 침범하게 된다. 스택의 다른 요소들이 침범될 경우 BOF 공격 등에 쉽게 노출될 수가 있으며 이는 공격자가 해당 프로그램을 조작하거나 심지어 상위 권한을 획득할 수 있는 여건을 줄 수가 있다. 이제 이러한 함수들에 대하여 알아보자.


Gets


사용자로부터 문자열을 입력받는 gets 함수는 가장 대표적으로 취약한 함수 중 하나이다. 해당 함수는 문자열을 입력받지만 문자열을 담을 공간의 길이와 입력받은 문자열의 길이를 확인하지 않기 때문에 버퍼오버플로우에 취약하다. 우선 아래의 코드를 확인해보자.

#include <stdio.h>
int main()
{
    char buf[10];
    gets(buf);
    printf("%s\n",buf);
}

char형 배열 buf를 10만큼 선언한 뒤 여기에 사용자로부터 입력을 받고 해당 내용을 출력한다. 이를 어셈블리로 나타내면 위 그림과 같은데, 컴파일 과정에 있어서 CheckEsp와 CheckStackVars와 같이 별도의 함수가 추가되어 있는 것을 확인할 수가 있다. 이 두개는 모두 취약한 함수가 공격당했는가를 확인하는 부분이라 생각하면 된다.


어셈블리의 형태로 보면 사용자로부터 입력을 받을 공간 [ebp+buf]를 gets의 인자로 주기 위해 스택에 넣고 gets 함수를 호출한다. 호출된 함수를 통해 [ebp+buf]에는 사용자가 입력한 문자열이 위치하게 되며, 이는 다시 printf의 인자로 출력된다. 여기서 자세히 보아야 할 부분은 바로 gets를 호출하기 바로 전 변수 buf의 주소 [ebp+buf]이다. 해당 부분을 스택에서 확인하면 주어진 공간은 10이기 때문에 그 이후의 공간은 다른 내용(여기서 다른 내용이란 스택의 변조를 체크하기 위한 \xCCCCCCCC이다.)으로 채워져 있다. =


사용자로부터 입력받을 문자열의 길이를 확인하지 않기 때문에 만약 10보다 큰 내용의 문자열이 입력된다면 이러한 내용은 변조된다. 아래의 그림의 첫 번째 표와 같이 스택에 아홉 개의 "A"와 \x00이 채워지면 그 뒤의 내용은 변화되지 않는다. 하지만 만약 10 보다 큰 내용을 입력할 경우 두 번째 표와 같이 기존에 존재하던 내용들이 덮어 써지는 걸 확인할 수 있다.

이러한 경우 스택 변화를 방지하기 위한 \xCC까지 침범하여 결국 CheckStackVars를 호출하는 과정에 있어 오류가 나타나며 프로그램이 종료된다. 만약 CheckStackVars와 같이 스택의 변화를 방지하는 요소가 없다면 "A"라는 문자열은 스택에 저장되어 있는 다른 부분까지 침범이 가능해지고 이를 통해 RETN 값을 수정하는 등을 통해 BOF 공격을 취할 수가 있다.


Scanf


scanf 또한 사용자로부터 문자열을 입력받아 변수에 저장하는 용도로 사용된다. 하지만 역시 입력받은 문자열의 길이를 체크하지 않기 때문에 스택의 값이 변조될 수가 있다. 아래의 코드를 보자.

#include <stdio.h>
int main()
{
    char buf[10];
    scanf("%s",buf);
    printf("%s",buf);
}

전체적인 내용은 위 gets와 유사하며, 변수 buf의 주소 [ebp+buf]에 scanf를 통해 입력을 받지만 그 길이의 제한이 없기 때문에 이전에 스택에 push 된 다른 값들이 변조될 수 있다. 주어진 길이보다 클 경우 스택 체크 함수로 인해 오류를 나타내며 프로그램은 종료된다.


Strcat


이번엔 strcat 함수에 대하여 알아보자. strcat은 변수 a에 변수 b의 내용을 덧붙여주는 함수로, 이 또한 변수의 길이를 체크하지 않기 때문에 BOF 공격에 취약성을 가지고 있다. 아래의 코드와 어셈블리어를 보자.

#include <stdio.h>
int main()
{
    char buf[10]="AAAAAAAAA";
    char str[10]="BBBBBBBBB";
    strcat(buf,str);
    printf("%s\n",buf);
}

우선 [ebp+buf], [ebp+buf+4], [ebp+buf+8] 등을 이용해 해당 문자열을 저장하는 것을 확인할 수가 있다. 이렇게 저장된 문자열의 시작 주소 [ebp+buf], [ebp+str]은 스택에 넣어지는데 여기서 [ebp+str]이 변수 스택에 들어가고 그다음 [ebp+buf]가 스택에 들어가게 된다.


그 후 "BBB..."를 "AAA..."에 덧붙이게 되는데, 이 과정에서 변수 buf의 공간은 초기에 10만큼만 할당되었기에 변수 str의 크기를 확인한다면 이를 덧붙일 수 없는 것이 정상적이다. 하지만 strcat 함수의 경우 이러한 길이를 고려하지 않기 때문에 결국 [ebp+buf]의 뒷부분에 [ebp+str]의 내용이 덧붙여진다. 그러므로 결국 아래의 그림과 같이 뒷부분에 존재하고 있던 내용 "\x00\xCC..."가 사라지게 되고 변수 str의 내용인 "BBBB..."가 자리 잡게 된다. 이 또한 gets와 마찬가지로 스택의 내용을 변조시키므로  CheckStackVars 함수에서 오류를 나타내게 된다. 만약 이러한 스택 변화를 확인하는 함수가 없을 경우 쉽게 스택의 내용이 변조되어 공격자에게 이용당할 수 있다.


Strcpy


strcat이 버퍼에 있는 내용을 덧붙이는 것이라면, strcpy 함수는 해당 내용을 통째로 옮기는 용도로 사용된다. 전반적인 내용은 strcat과 유사하며, 우선 아래의 코드를 확인하자.

#include <stdio.h>
int main()
{
    char buf[10] = "AAAAAAAAA";
    char str[] = "BBBBBBBBBBBB";
    strcpy(buf,str);
    printf("%s\n", buf);
}

선언된 변수가 스택에 저장되는 과정은 위와 동일하며, strcpy를 호출하기 전에 [ebp+str]을 먼저 스택에 넣은 다음 [ebp+buf]를 인자로 넣어준다. 이러한 과정을 거쳐 결국 변수 buf에 있던 내용은 str의 내용으로 바뀌게 된다. 하지만 strcpy 함수 역시 크기를 체크하지 않기 때문에 변수 buf의 크기가 10 임에도 불구하고 10보다 큰 내용이 오게 되었다. 이를 표현하면 아래의 표와 같다.


Sprintf


sprintf는 printf와 비슷하게 출력 함수로 사용되지만, 다른 점이 있다면 printf가 모니터 화면에 출력되는 것이라면 sprintf는 버퍼로 사용될 변수로 출력이 된다는 점이다. 아래의 코드를 확인해보자.

#include <stdio.h>
int main()
{
    char buf[10];
    char str[] = "BBBBBBBBBBBB";
    sprintf(buf, "%s", str);
    printf("%s\n", buf);
}

[ebp+str]부터 [ebp+str+0xC]까지 해당 문자열을 넣은 다음, 해당 문자열의 시작 주소인 [ebp+str]을 인자로 넣어준 뒤 포맷 스트링, 그리고 해당 버퍼가 저장될 변수 [ebp+buf]가 차례대로 스택에 쌓이게 된다. 아래의 표와 같이 원래 해당 buf의 내용이 존재하지 않았으며 buf 뒤에는 다른 내용의 값들이 존재하고 있다. 하지만 sprintf 계열의 함수를 사용할 경우 하단의 표와 같이 다른 부분의 값을 덮어씌울 수가 있다.


Reference


http://j3nasis.tistory.com/entry/버퍼오버플로우-취약-함수별-대책

http://ljsk139.blog.me/30129428446

http://itguru.tistory.com/66

http://www.hackerschool.org/HS_Boards/data/Lib_system/sprintf.txt



'Reversing > Theory' 카테고리의 다른 글

윈도우 후킹 원리 (1) - User Mode  (3) 2016.04.23
System Call & SSDT Hooking  (0) 2016.04.10
윈도우 메모리구조와 메모리분석 기초  (3) 2016.03.29
CPU 레지스터  (0) 2016.03.26
C기본 문법 어셈블리 변환  (5) 2016.03.20