Information Security ˗ˋˏ ♡ ˎˊ˗

Security/WebHacking

[웹취약점] 포맷스트링(Format String) 기본지식 및 공격방법

토오쓰 2023. 1. 4. 18:45

포맷스트링 취약점에 대한 기본적인 지식과 공격방법

정의

포맷스트링(Format String): 사용하는 함수에 대해 어떤 형식 또는 형태를 지정해주는 문자열 의미

<포맷스트링 예시 코드>

char str[10] = "World!";
printf("Hello, %s\n", str); //1번 코드
printf(str); //2번 코드

 <소스 결과>

Hello, World! //1번코드
World! //2번코드

 

포맷 인자

아래와 같은 포맷스트링 인자는 포맷 함수의 변환 형태를 말한다. 

인자 입력 타입 출력 타입
%d 정수형 10진수
%u 양의 정수 10진수
%x 16진수
%c 문자 값
%f 실수형 상수
%s 포인터 문자 스트링
%n 포인터 지금까지 출력한 바이트(byte) 수
지시자 앞까지의 길이를 저장

 

printf 함수 동작방식

printf()와 같은 포맷스트링을 사용하는 함수는 포맷 인자(형식 인자)를 함수에 인자로 넘겨 특정 동작을 수행한다.

 

C프로그램의 기본적인 코드에서는 main 함수가 실행되기 전에 인자들이 먼저 스택에 push가 된다.

RET(복귀주소)와 EBP(베이스포인터)가 스택에 push 되며 main 함수의 지역변수를 위한 공간이 확보된다.

printf 함수 호출하기 전에는 printf 인자와 RET, EBP값이 push 된다.

* 일반 문자의 경우, 일반 문자 그대로 출력

* 형식 지시자에 경우 해당 내용을 스택에서 4바이트만큼 pop 하여 출력한다.

⇒ pop 되는 것은 스택 상에서 포맷스트링 포인터 다음에 위치한 내용이다.

 

참고) https://eliez3r.github.io/post/2018/09/04/study-system-Format-String-Attack.html

 


포맷스트링 공격(Format String Attack)이란

포맷스트링과 이것을 사용하는 printf() 함수의 취약점을 이용하여 RET의 위치에 셸 코드의 주소를 읽어 셸을 획득하는 해킹 공격이다.

기존에 널리 사용되던 버퍼 오버플로우(Buffer Overflow) 공격 기법에 비교되는 강력한 해킹 기법이다.(출처:해시넷)

 

 

포맷스트링 공격 예시

 #include <stdio.h>
void main(int argc, char **argv) {
// 안전한 코드
printf("%s\n", argv[1]);

// 안전하지 않은 코드
printf(argv[1]);
}

// 안전한 코드

printf("%s\n", argv[1]);

실행: ./a.out Hello%p%p%p%p

⇒ 컴파일하고 실행하면 입력 문자열을 파싱 하지 않고 출력한다.

 

// 안전하지 않은 코드

printf(argv[1]);

실행: ./example 'Hello World %p%p%p%p'

⇒ 문자열 포인터에 대한 참조로 파싱 되므로 모든 %p 문자열에 대한 포인터로 해석한다. 스택에 있는 메모리 주소를 출력하여 취약하다.

 

결과: 입력한 문자열 이외의 값이 출력된다.

 

출력되는 이유: %p가 포함되어 있을 때는 printf("Hello World %p%p%p%p"); 와 같이 명령이 수행된다.

안전한 코드에서는 서식문자를 사용하여 printf("%s\n", argv[1]);와 같이 입력 값을 적어주었기 때문에 출력할 변수를 알려주고 있다.

하지만 안전하지 않은 코드에서는 참조할 인자 값이 존재하지 않기 때문에 스택에서 그 함수 인자가 있어야 할 곳에 있는 메모리를 참조하여 값을 출력하고 있다.

 

위험한 이유

위와 같은 코드처럼 메모리 값을 출력하는 포맷 스트링도 있지만 값을 쓸 수 있는 포맷스트링이 존재하기 때문이다.

 * %n: 값이 포인터로 지금까지 출력한 바이트(byte) 수, 지시자 앞까지의 길이를 저장한다.

aaaa를 입력하고 %x를 여러 번 입력했을 때 a의 주소 값이 출력될 수 있다. 여기서 %s로 입력했다면 문자열을 출력될 것이다.

 

aaaa 부분에 임의의 주소값을 넣고 a 결과가 나왔던 곳에 %s나 %n을 사용한다면 %s는 주소값에 있는 문자열을 출력하고 %n은 이전에 출력된 문자열의 개수를 세어 임의의 주소 값에 출력할 것이다.

 

임의의 주소에 원하는 값을 저장하여 프로그램 조작할 수 있다.

 

참고) 

https://bloofer.net/94

https://ajy1120.tistory.com/3


취약점 점검방법

1) 사용자 입력 값에 포맷 스트링 문자열 삽입

* printf(), snprintf() 등 포맷 문자열

 

 2) 입력 값의 형태와 다른 형태 (ex 양수가 전달되는 파라미터에 음수 전달 등) 입력 시도

* 특히, %n, %hn은 공격자가 이를 이용해 특정 메모리 위치에 특정값을 변경할 수 있으므로 포맷 스트링 매개변수로 사용하지 않는다.

* 사용자 입력값을 포맷 문자열을 사용하는 함수에 사용할 때는 가능하면 %s 포맷 문자열을 지정

 

보안대책

* 서식 문자열을 함수의 입력 파라미터로 직접 사용해서는 안된다.

* printf("%s", argv[1])과 같이 형태 지정해서 이용한다.

* 범위를 초과할 경우 문자열 유효성 검증을 통해 에러 페이지를 반환하지 않도록 한다.