포맷스트링 공격(Format String Attack)이란?
포맷스트링 공격은 프로그램에 입력된 문자열 데이터가 명령으로 해석될 때 발생합니다. 이러한 방식으로 공격자는 코드를 실행하거나 스택 메모리 일부를 읽거나 실행중인 프로그램에 Segmentation Fault를 발생시켜 시스템에 의도되지 않은 동작을 일으킬 수 있습니다.
포맷스트링의 3가지 개념
포맷 함수(Format Function)
프로그램 언어의 변수를 사람이 읽을 수 있는 문자열 형식으로 변환하는 printf, fprintf와 같은 ANSI C 함수입니다.
표1. 포맷 함수의 예시
Format function | Description |
fprint | Writes the printf to a file |
printf | Output a formatted string |
sprintf | Prints into a string |
snprintf | Prints into a string checking the length |
vfprintf | Prints the a va_arg structure to a file |
vprintf | Prints the va_arg structure to stdout |
vsprintf | Prints the va_arg to a string |
vsnprintf | Prints the va_arg to a string checking the length |
포맷 스트링(Format String)
포맷 함수의 인자이며 다음과 같은 텍스트 및 포맷 인자를 포함하는 ASCII 문자열입니다.
printf("The magic number is : %d\n", 1911);
포맷스트링 인자(Format String Parameter)
%x %s와 같은 포맷스트링 인자는 포맷 함수의 변환 형태를 정의합니다. 프로그램이 전달된 스트링 입력의 유효성을 제대로 확인하지 않으면 공격을 받을 수 있습니다. 이 경우 %x와 같은 포맷스트링 인자가 데이터에 삽입되면 해당 문자열은 포맷 함수에 의해 파싱되고 인자에 지정된 변환이 일어납니다. 그러나 포맷 함수는 더 많은 인자 입력에 대한 예외 처리를 하지 않으면 함수가 스택을 읽는 등의 위험성을 내포합니다.
표2. 포맷스트링 공격에 사용되는 파라미터들
Parameters | Output | Passed as |
%% | % character (literal) | Reference |
%p | External representation of a pointer to void | Reference |
%d | Decimal | Value |
%c | Character | |
%u | Unsigned decimal | Value |
%x | Hexadecimal | Value |
%s | String | Reference |
%n | Writes the number of characters into a pointer | Reference |
포맷스트링 공격 예시
안전한 코드
printf('%s', argv[1]);
예제에서 라인 5의 프로그램을 컴파일하고 실행하면 안전합니다.
./example 'Hello World %s%s%s%s%s%s'
첫 번째 줄의 printf는 입력 문자열의 "%s%s%s%s%s%s"를 파싱하지 않으며 출력은 다음과 같습니다.
"Hello World %s%s%s%s%s%s”
위험한 코드
printf(argv[1]);
예제에서 라인 8의 프로그램을 컴파일하고 실행하면 위험합니다.
./example 'Hello World %s%s%s%s%s%s''
두 번째 줄의 printf는 입력 문자열의 %s%s%s%s%s%s를 문자열 포인터에 대한 참조로 파싱되므로 모든 %s를 문자열에 대한 포인터로 해석합니다. 특정 시점에서 잘못된 주소에 도달하여 액세스를 시도하면 프로그램이 중단됩니다.
다른 포맷스트링 인자 사용
공격자는 이 정보를 사용하여 스택 메모리에 관한 정보도 얻을 수 있습니다. 예를 들어 다음을 실행할 경우,
./example 'Hello World %p%p%p%p%p%p'
다음과 같은 결과가 나타날 수 있습니다.
Hello World %p%p%p%p%p%p
Hello World 000E133E 000E133E 0057F000 CCCCCCCC CCCCCCCC CCCCCCCC
첫 번째 줄은 안전한 방식의 printf 버전에서 출력되고 두 번째 줄은 위험한 방식에서 출력됩니다. "Hello World" 텍스트 뒤에 출력된 값은 이 예제를 실행하는 순간의 스택에 있는 메모리 주소입니다.
또한 일부 조건에서는 메모리 위치에 대한 읽기 및 쓰기가 가능하며 코드 실행도 가능합니다.
다른 포맷함수 예시
printf 계열의 포맷 함수들은 모두 위험성을 내포합니다. 다음은 snprintf의 예제입니다.
이 프로그램을 다음과 같이 실행하면 앱 크래시가 발생합니다.
./example 'Hello World %s%s%s%s%s%s'
출처: owasp.org