만족
[정보보안] 코드 보안: 포맷 스트링 공격 본문
[정보보안] 코드 보안: 포맷 스트링 공격
기타 CS 이론/IT Security Satisfaction 2021. 12. 13. 06:51포맷 스트링 공격은 버퍼 오버플로우 공격과 매우 유사하다.
포맷 스트링
int main(){
char* buf= "str";
printf("%s", buf);
}
위와 같이 %s 와 같이 출력 서식을 지정하는 문자를 포맷 스트링이라고 한다.
조금 특이한 %n에 대해서 알아볼 것이다.
int main(){
int n;
printf("hahaha%n", &n);
}
%n은 현재까지 출력된 바이트 수를 지정한 변수에 넣는다.
여기에서 %n 전에 출력된 문자열은 hahaha로 6byte이므로, n에 6이 들어간다.
%n은 값을 4byte크기로 쓰기작업을 하는데,
만약 2byte만 쓰기에 사용하고 싶을 경우 %hn을 사용한다.
포맷 스트링 공격 원리
만약 printf에서 포맷 스트링을 사용하고 추가 인자를 넘겨주지 않는다면 어떻게 될까?
함수를 호출할 때 어셈블리를 살펴보면,
인자에 해당하는 값을 %eax에 넣는다.
이후 호출된 함수에서는 %eax에 오프셋을 더하면서 매개변수가 있다고 가정하고
해당 메모리 영역을 매개변수로써 참조한다.
int main(){
char* buf= "str";
printf("%s");
}
이렇게 하면 "%s"의 주소값이 스택에 쌓이고,
%s에 대응되는 값을 찾으려 할 때, "%s"이전에 스택에 쌓인 값을 참조한다.
//test.c
#include <stdio.h>
int main(){
char buf[64];
fgets(buffer, 63, stdin);
printf(buffer);
return 0;
}
이 코드를 실행하고 stdin에 "\x41\x41\x41\x41\x78\xfb\xff\xbf%%d%%n" 을 넣어주면 어떤 일이 발생할까?
함수 호출 시점에서 스택은 다음과 같은 모습이 된다.
우선 fgets로 \x41... 의 값이 buf 주소값에 들어간다.
그리고 printf를 호출하기 전 printf의 매개변수인 buf를 스택에 쌓고,
call printf해서 printf함수를 호출하게 된다.
printf함수는 문자열을 출력하다가 포맷스트링 %d를 만나면
매개변수로 받은 주소부터 4byte씩 줄여가며 매개변수로 취급한다.
첫 포맷 스트링인 %d를 만나면 buf의 주소의 +0위치에 있는 \x41\x41\x41\x41을 출력한다.
(출력값은 1094795585; 출력 크기는 10byte크기)
두 번째 포맷 스트링인 %n을 만나면 다음 위치(이전에 참조한 주소에서 4byte만큼 이동)의 \x78\xfb\xff\xbf 를 참조하는데
%n은 현재까지 출력한 바이트의 갯수를 전달받은 주소에 넣어버린다.
현재까지 출력된 문자열은 \x41\x41\x41\x41\x78\xfb\xff\xbf과 1094795585이므로
18byte를 출력하였고, 18이라는 값을 \x78\xfb\xff\xbf, 즉 0xbffffb78 위치에 18을 쓰게된다.
따라서 개발자가 의도하지 않은 변수 덮어쓰기가 발생한다는 것을 알 수 있다.
포맷 스트링 공격
%n을 이용해 원하는 위치에 원하는 값을 넣을 수 있음을 알았다.
이것은 함수의 return address를 알고 있다면 그 위치의 값을 변조해 원하는 위치로 점프할 수 있다는 것을 의미한다.
return address는 0xbffffba8처럼 매우 큰 수인데, 이 엄청나게 긴 문자열을 모두 작성해야 할까?
그렇지 않다.
//test.c
#include <stdio.h>
int main(){
char buf[64];
fgets(buffer, 63, stdin);
printf(buffer);
return 0;
}
마찬가지로 위 코드에서, \x41\x41\x41\x98\xfb\xff\xbf%%3215719092d%%n을 입력하면 어떻게 될까?
\x41\x41\x41\x98\xfb\xff\xbf에 해당하는 값이 출력되고(8byte)
%%3215719092d로 인해 3215719092만큼의 길이를 갖는 문자열에서 \x41\x41\x41\x41에 해당하는 정수가 출력된다.
그리고 %%n에서 \x98\xfb\xff\xbf에 해당하는 주소인 0xbffffb98에
3215719092+8= 3215719100이 덮어씌워진다
는 아니고 안된다.
3215719100은 signed int로 표현할 수 없는 큰 수이기 때문에 값이 입력되지 않는다.
%hn을 이용해 2byte씩 나누어 저장한다면 가능하다.
예를 들어 \x41\x41\x41\x98\xfb\xff\xbf%%64180%%hn 을 입력한다면
%%64180d에 의해 64180+8= 64188만큼 출력되고
0xbffffb98에서 2byte만큼의 크기에 64188(0xfabc)가 출력된다.
이 성질을 이용해서
\x41\x41\x41\x41\x98\xfb\xff\xbf
\x41\x41\x41\x41\x9a\xfb\xff\xbf
%%64172d%%hn%%50415d%%hn 을 입력하게 되면
%%64172d에서 64172+16= 64188만큼 출력되고
%%hn에서 0xbffffb98위치에 64188(0xfabc)가 쓰여진다.
그리고 %%50415d에서 64188+ 50415= 0x1bfab 만큼이 출력되고
%%hn에서 0xbffffb9a위치에 0x1bfab에서 2바이트 만큼(0xbfab) 쓰여진다.
따라서 0xbffffb98~0xbffffb9c에는 \xbc\xfa\xab\xbf가 쓰여지게 된다.
여기에서 0xbffffb98이 만약 함수의 return address라고 하고
\xbc\xfa\xab\xbf가 원하는 함수의 위치라고 한다면 main 함수가 종료된 후 그 함수(심지어는 쉘)로 점프한다.
'기타 CS 이론 > IT Security' 카테고리의 다른 글
[정보보안] 전자 서명과 공개키 인증 (0) | 2021.12.13 |
---|---|
[정보보안] 암호의 이해 (0) | 2021.12.13 |
[정보보안] 코드 보안: 계정과 권한, 프로그램 실행 구조 (0) | 2021.10.17 |
[정보보안] 인터넷 정보보안: HTTP 기본 개념과 웹 해킹 공격 (0) | 2021.10.16 |
[정보보안] 네트워크 보안 시스템: 방화벽, 침입 탐지와 침입 감지 시스템 (0) | 2021.10.16 |