검수요청.png검수요청.png

포맷스트링 공격

해시넷
이동: 둘러보기, 검색

포맷스트링 공격(Format String Attack)이란 포맷스트링과 이것을 사용하는 printf() 함수의 취약점을 이용하여 RET의 위치에 셸 코드의 주소를 읽어 셸을 획득하는 해킹 공격이다. 기존에 널리 사용되던 버퍼 오버플로우(Buffer Overflow) 공격 기법에 비교되는 강력한 해킹 기법이다.

개요[편집]

포맷스트링 버그가 발표된 것은 1999년 tf8이라는 닉네임의 해커에 의해 보고되었다. tf8은 a.k.a x90c라고 알려진 해커의 다른 이름이다. 포맷스트링 공격은 기존에 널리 사용되고 있던 버퍼 오버플로(buffer overflow) 공격 기법에 견줄 만한 강력한 해킹 기법이다. 이 해킹 기법이 발표되고 나서 그동안 별문제 없었던 각종 프로그램들에 대한 취약점이 속속 발표되고 해당 프로그램을 제작했던 회사들은 이 취약점을 해결하기 위해 분주해졌다. 포맷스트링 공격 기법에 대해 이해하기 위해서는 포맷스트링이라는 것과, C 프로그램에서 어떻게 처리되는지를 이해해야 한다.

특징[편집]

포맷스트링[편집]

포맷스트링(format string)이란 일반적으로 사용로부터 입력을 받아들이거나 결과를 출력하기 위하여 사용하는 형식이다. 포맷스트링을 사용하는 함수에 대해 형식이나 형태를 지정해주는 문자열을 의미한다. C 언어에서는 일반적으로 사용하는 기호로 다음과 같은 것들이 있다.

  •  %d: 정수형 10진수 상수
  •  %f: 실수형 상수
  •  %lf: 실수형 상수
  •  %c: 문자값
  •  %s: 문자 스트링
  •  %u: 양의 정수(10진수)
  •  %o: 양의 정수(8진수)
  •  %x: 양의 정수(16진수)
  •  %n: 쓰인 총 바이트 수

%n은 이전까지 입력되었던 문자열의 길이(byte) 수만큼 해당 변수에 저장시키기 때문에 메모리의 내용도 변조 가능하다. 이를 이용해 문자열의 길이를 내가 변조시키고 싶은 값의 길이만큼 만든 후 %n을 써주게 되면 메모리상에 내가 원하는 값을 넣을 수 있게 된다.[1]

포맷스트링의 문제점[편집]

printf() 함수

포맷스트링공격은 다른 공격 기법처럼 취약점이나 버그들처럼 프로그래머의 작은 실수를 이용해서 공격을 하는 기법이다. 포맷스트링으로 인한 취약점을 이용해서 공격하는 것을 포맷스트링 공격이라고 한다.[2] 예를 들어 두 명의 프로그래머가 같은 프로그램을 짠다고 가정해보면 첫번째 프로그래머는

printf("%s", str);

이렇게 작성할 수 있다. 두 번째 프로그래머의 경우

printf(str); 

이렇게 작성할 수 있다. 두 가지 모두 틀린 것은 아니다. 오히려 두 번째 코드의 경우 소스코드를 더 적게 사용하여 현명하다고 판단할 수도 있다. 그렇지만 해커에게는 이렇게 작성된 코드에서 프로그램의 흐름을 바꿀 수 있는 방법을 찾게 만든다. 우선 아래와 같이 코드를 작성한다.

#include<stdio.h>
int main(){
      char str[15] = "Hello, World!\n;
      printf("%s", str);       //1번 예시
      printf(str);             //2번 예시
      return 0;

작성한 코드를 실행시킨다.

Hello, World!
Hello, World!

위와 같이 실행되는 것을 확인할 수 있다. 문제없이 실행된다. 그러나 2번 예시의 경우 printf 함수에 의해서 해석되는 문자열"str"은 문자열이 아니라 형식 지시자를 포함한 포맷스트링으로 인식되게 된다. 이 부분에서 포맷스트링의 취약점이 드러나게 된다. 이처럼 소스코드의 양을 줄이려고 하다가 되어 해커에게 공격의 기회를 줄 수 있다.

출력하려는 문자열 내에 "%d"와 같은 지시자가 들어있으면 형식지시자의 개수만큼 인자들이 스택에서 추출된다. printf 함수에 전달된 인자의 개수와는 상관없이 지시자의 개수만큼 포맷스트링 문자열 다음에 저장되어 있는 스택의 내용을 추출하게 되는 것이다. 두 번째 예시로 코드를 작성할 경우 입력에 따라 지시자의 수가 달라질 수 있게 되어서 악의적으로 스택의 내용을 확인할 수 있다.[3]

버퍼 오버플로 공격과의 비교[편집]

포맷스트링 공격은 기존에 널리 사용되던 버퍼 오버플로(Buffer Overflow) 공격과 비교된다. 버퍼 오버플로는 쉽게 말하자면 입력받는 값이 버퍼를 채우다 못해 흘러넘쳐 발생하는 현상이다. 이는 프로그래머가 데이터를 다루는 데에 오류가 발생해서 오동작하게 되는 취약점이다.[4] 프로그램을 작성할 때 문자열에 할당된 메모리 용량보다 큰 공간을 할당했을 때를 예시로 들 수 있다. 8바이트 문자열을 넣을 수 있는 버퍼가 존재할 때, 이 메모리 공간 뒤에는 중요한 코드가 존재한다고 가정하자. 이 8바이트 공간 안에 8바이트를 넘는 문자열을 입력할 경우 중요한 코드가 손상되어 오동작을 유발할 수 있다.

버퍼 오버플로는 C언어 데이터 무결성 문제로 처음 등장했다. 단순 프로그램상 문제로 인식되었다. 후에 악성코드의 원조 격인 모리스 웜이 버퍼 오버플로를 사용했다는 것이 알려지면서 공격 기법으로써 인식되기 시작했다.[4]

활용[편집]

포맷스트링 공격으로 할 수 있는 일에는 여러 가지가 있다.

프로그램 다운[편집]

첫 번째로 프로그램을 다운시킬 수 있다.

char buf[512]
read(0, buf, 512);
printf(buf);

이와 같은 코드가 있을 경우 해커가 "%s%s%s%s%s%s"라는 입력을 넣게 되면 프로그램은 다운된다. printf 함수가 %s를 만나면 스택에 있는 메모리 주소로 인식하고 주소에 접근한다. 이 때 %s%s%s%s%s%s를 입력하게 되면 스택에서 위로 24바이트를 읽어서 주소로 인식하게 된다. 하지만 원래 스택에 있는 값은 문자열의 주소값이 아니기 때문에 잘못된 주소에 접근하게 되는데, 이때 세그멘테이션 폴트(Segmentation fault)가 일어나고 프로그램이 다운된다.

프로세스 스택 확인[편집]

스택 보기

두 번째로 프로세스 스택을 볼 수 있다. %d나 %x는 스택에서 4바이트 값을 읽어와서 출력한다. 즉 스택의 4바이트를 보고 싶다면 %x를 입력하면 된다. 이렇게 메모리 스택을 볼 수 있으면 공격자는 프로세스의 메모리 구조를 파악할 수 있게 된다.

메모리 임의의 위치 보기[편집]

포맷스트링 공격으로 공격자는 메모리의 원하는 부분을 볼 수 있다.

char buf[512]
int a;
int b;
read(0, buf, 512); //표준입력에서 읽어들임
printf(buf);

위와 같은 코드가 있을 경우 buf에 자신의 데이터를 입력한다. 만약 buf의 처음 4바이트에 보기를 원하는 주소를 넣는다면 해커는 8바이트만큼 스택을 거슬러 올라가서 그 주소에 접근할 수 있다. 예시를 든다면 해커가 0x0a0b0c0d를 보고자 한다면 "\x0d\x0c\x0b\x0a\ %08x %08x %s" 를 입력해야 한다. %08x가 두개 있기 때문에 8바이트를 거슬러 올라가서 문자열이 있는 버퍼까지 간다. 그 다음에는 %s가 있기 때문에 스택에서 4바이트를 읽고 주소로 인식한다. 마지막으로 printf 함수는 0x0a0b0c0d번지에서 NULL값이 나올 때까지 읽는다. 이처럼 해커는 원하는 부분을 볼 수 있다.

대안[편집]

포맷스트링 공격은 아주 강력한 공격 기법이라고 볼 수 있다. 따라서 대응 방안 역시 꼭 필요하다. 포맷스트링 함수를 사용할 때는 입력에 대한 철저한 검사가 필수이다. 대표적으로 철저한 검사가 요구시 되는 위험한 함수들이 있다. 다음과 같다.

* Fprintf
* printf
* sprintf
* snprintf
* vfprintf
* vprintf
* vsprintf
* syslog

또한 사용자가 입력한 것을 포맷스트링 문자열의 출력으로 바로 보내면 안된다. 예를 들어 다음과 같은 함수가 있다.

void func(char *user)
{
printf(user);
}

이 같은 함수의 경우 입력한 user라는 값이 바로 출력으로 가기 때문에 공격자가 입력 문자열을 조작하여 공격할 수 있다.

void func(char *user)
{
printf("%s",user);
}

이렇게 고치게 될 경우 user 안에 포맷 문자열이 들어 있어도 전체를 %s문자열이 받기 때문에 안전하다.

결론[편집]

포맷스트링 취약점을 이용한 공격 기법은 이렇게 이루어진다. 위의 대안에서도 살펴보았듯이 프로그램을 작성할 때 프로그래머는 항상 포맷스트링에 대한 위험성을 인식하고 있어야 하며, 정확하고 안전하게 포맷스트링 함수를 사용해야 한다. 만약 코드 작성자가 정확하고 안전하게 작성하지 않은 경우 공격자는 코드를 입력시켜 프로그램의 UID를 변경하거나, 원하는 명령을 수행하도록 만들 수 있다. 또한 쉘 코드를 포함하고 있는 위치로 주소를 복귀 시킬 수 있다. 이것이 포맷스트링 공격의 개념이자 핵심이다.

하지만 포맷스트링 공격 기법의 경우는 난이도가 높은 공격 기법이다. 심지어는 보안성이 가장 뛰어나다고 알려져 있는 프리비에스디(Free-BSD) 계열에서도 포맷스트링에 대한 문제점이 발견되었다.

각주[편집]

  1. myPPT, 〈포맷 스트링〉, 《티스토리》, 2017-07-28
  2. haks2198, 〈[Format String Attack 포맷 스트링 공격이란?]〉, 《네이버 블로그》, 2016-10-19
  3. pRoneeR, 〈[FS 포맷스트링(Format String) Attack]〉, 《티스토리》, 2007-02-02
  4. 4.0 4.1 ChocoPeanut, 〈버퍼 오버플로&포맷 스트링 공격〉, 《티스토리》, 2017-04-29

참고자료[편집]

같이 보기[편집]


  검수요청.png검수요청.png 이 포맷스트링 공격 문서는 보안에 관한 글로서 검토가 필요합니다. 위키 문서는 누구든지 자유롭게 편집할 수 있습니다. [편집]을 눌러 문서 내용을 검토·수정해 주세요.