studies/pwnable

[Format String Bug] 포맷스트링 버그 (fsb) - 1

$1m0hYa 2019. 10. 2. 10:49

이번 시리즈에서는 포맷스트링 버그를 이용한 공격방법을 배워볼건데요

 

그 전에 먼저 포맷스트링에 대해 알아봅시다.

 

 


1. 포맷스트링

포맷 스트링은 우리가 주로 사용하는 c언어에 있는 printf함수에서 사용되는 것입니다.

 

이 코드를 봅시다.

 

1
2
3
4
5
6
7
#include <stdio.h>
 
int main() {
    int a = 0;
    printf("%d" ,a);
    return 0;
}
cs

 

 

여기에서 사용되는 이 "%s"가 바로 포맷스트링입니다.

 

, printf의 인자에 대응해서 그 인자의 자료형에 맞게 포맷스트링을 지정해주어서 출력해줄수 있습니다.

 

포맷스트링은 대표적으로 이러한 것들이 있습니다.

 

인자

입력 타입

출력

%d

일반적인 10진수

%s

포인터

포인터에 위치하는 문자열

%x

16진수

%u

부호없는 10진수

%p

포인터

포인터가 가르키는 주소

%n

포인터

지금까지 출력한 바이트수를 포인터가 가르키는 주소에 넣어줌

 

여기서 많이 보지 못했던 포맷스트링이 있죠?

 

바로 %n일 것입니다

 

포맷스트링버그는 주로 이 %n에 의해 발생하게 됩니다.

 

그럼 이제 포맷스트링버그가 어떻게 일어나게 되는지 알아보겠습니당.

 


2. 포맷스트링 버그가 일어나는 곳

 

이런 코드를 짜보겠습니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
 
int main() {
   char str[100];
   scanf("%s", str);
 
   printf("올바른 출력 방법 : %s\n", str);
 
   printf("잘못된 출력 방법 : ");
   printf(str);
 
   return 0;
 
}
cs

 

한번 실행해 보겠습니다.

 

 

올바르게 출력이 됩니다

 위에서 이상한 점을 발견할수 있나요?

 

바로 10번 줄에서 printf(str) 부분일겁니다.

 

원래 우리가 printf함수를 사용할때 포맷스트링으로 지정해주어서 사용을 했습니다.

printf("%s", str) 이런식으로 말이죠.

 

그런데 printf(str)로 사용해도 문제가 없습니다.

왜일까요?

 

그 이유는 바로 printf의 원형을 보시면 알수 있습니다.

stdio.h 헤더에서 정의된 printf함수는 이렇게 되어있습니다.

 

printf(const char * a, ...);

 

, 상수 포인터를 넣으면 출력이 된다는 것이죠.

 

우리가 printf("aaa") aaa로 출력이 되는 이유는

"aaa" data영역에 저장된 aaa라는 문자열 리터럴의 주소이기에 출력이 가능한 것입니다.

 

결국 printf(str)는 결국 str이라는 포인터에 있는 문자열 주소이기에 출력이 가능합니다.

 

 

 

그런데, 저는 printf(str)을 잘못된 출력 방법이라고 했습니다.

 

그 이유는 여기서 확인할수 있습니다.

 

 

 

오오! 이럴수가!

이상한 값이 나오고 있어요!

 

 

올바르게 포맷스트링으로 형식을 지정해준 구문은 올바르게 출력이 되었지만,

올바르지 않게 사용한 곳에는 우리가 입력한 %x가 직접 그 역할을 해주고 있습니다!

 

즉, str이라는 변수는 사용자의 입력값이니 위의 상황을 직접 보여드리면

printf(str) = printf("Hello%x")

이렇게 되는것입니다.

 

따라서, printf(str)로 출력을 시켜버리면 버그가 발생하여 사용자의 입력값에 따라 메모리의 상태가 유출될 수 있습니다.


 3. 포맷스트링 버그를 이용한 메모리 출력

포맷스트링 버그를 이용해서 메모리값을 변조하는것이 가능합니다.

 

그 전에 알아야 할것은 함수호출 규약입니다.

 

32비트 기준으로 설명하자면 우리는 함수를 호출할때 스택이라는 메모리공간에서 이렇게 쌓이게 됩니다.

 

위에 보이는 사진은 제가 예전에 만든 ppt에서 따온겁니다 ㅎㅎ!

 

함수를 선언하게 되면 이 순서대로 스택에 쌓이게 됩니다.

  1. 인자
  2. 다시 돌아갈 주소
  3. 다시 돌아갈 ebp주소
  4. 그 함수안에있는 버퍼들

그렇다면 printf를 선언하면 메모리가 어떻게 될까요?

 

main함수에서 printf를 선언했을때, 먼저 인자인 "haha"가 먼저 쌓이게 되고

그 다음은 돌아갈 주소

그 다음은 그 함수안에있는 변수들이 공간을 차지하게 될겁니다.

 

그럼 printf에서 포맷스트링을 쓰면 어떻게 될까요?

 

메모리는 이렇게 되겠죠?

 

여기서 가장 눈여겨 봐야할것은 바로 포맷스트링 입니다.

 

여기서 사용된 포맷스트링인 %d는 바로 아래에 위치한 인자를 가리키고, 그것을 출력하게 되는 것입니다.

 

그렇다면 이렇게 되면 어떨까요?

 

 

이렇게 포맷스트링이 여러개 있다면, 순차적으로 아래에 있는 가리키게되어서 출력이 되겠죠?

 

그런데 인자 없이 %d를 넣으면 어떻게 될까요..?

 

 

 

이렇게 따로 인자로 정해두지 않은 곳을 가리키게 되어서 메모리를 출력하게 됩니다.

 

그럼 직접 해볼까요?

 

이런식으로 메모리 유출이 일어나게 됩니다!!


4. 포맷스트링 버그를 이용한 메모리값 변조

포맷스트링 버그로 메모리값을 변조하는 방법은 %n을 사용하는 것입니다.

 

%n은 지금까지 출력해준 바이트수를 인자에다 넣어주게 됩니다.

 

이 코드를 한번 봅시다.

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
 
int main() {
 
    int a;
    
    printf("aaabbbccc%n"&a);
    printf("\n a : %d", a);
    
    return 0;
 
}
cs

 

여기서 출력해주는 aaabbbccc는 총 9바이트 입니다.

 

그리고 그 9라는 숫자를 %n이 a라는 변수에 넣어주게 된다는 것이죠!

 

직접 실행시켜보겠습니다.

 

이렇게 9가 나오는 것을 알수 있습니다 ㅎㅎ!

 

이 기법을 이용해서 메모리값을 변조하는 것은 HackCTF basic_fsb를 풀어보면서 설명해보겠습니다.

 

[HackCTF] basic_FSB | write up   <- 링크

 

이상 포맷스트링에 대한 설명을 마치겠습니다!

 

(후.. 글쓰는데 엄청오래걸렸네 ㅎㅎ)

포맷스트링에서 사용가능한 예제는 2편에서 더 설명하도록 하겠습니다.

'studies > pwnable' 카테고리의 다른 글

[Format String Bug] 포맷스트링 버그 (fsb) - 2  (2) 2020.04.23
libc-database 사용해보기  (0) 2019.10.23