먼저 문제는 printf(%64%n\n:,j, &i)다. 먼저 i의 주소 값에, j에 64진수 값을 입력한다. Test4.c를 컴파일하여 실행하면 64가 16진수인 0x40로 출력된다. 아래 그림에서 확인할수 있듯이 소스 코드에는 변수에 데이터를 입력하는 동작이 없어 보이지만, 실행 결과 우리는 변수(long i)에 0x40 값을 집어넣었다는 것을 알 수 있다. Printf(“%64d%n\n”,j,&i)행에서 64(0x40)값을 i값이 있는 주소에 저장한 것이다. 포맷 스트링 공격은 이러한 원리를 이용한다.
포맷
스트링
차례
●
포맷
스트링의
정의
●
포맷
스트링의
종류
●
포맷스트링버그를이용해할수있는일
●
대응책
●
실제
포맷
스트링
버그
실습
●
Q&A
포맷
스트링의
정의
●
2000년도
여름에
세상에
나온
새로운
버그
●
포맷
스트링은
printf 나
sprintf 같은
함수에
쓰이는
%기호를
포함한
문자열을
의미
●
포맷
문자열의
포맷
문자
개수와
스택
데이터의
개수가
불일치
하는
상황에서는
해커가
교묘하게
데이터를
조작할
수
있고, 전혀
예상치
못했던
일이
일어날
수
있고
이것을
바로
포맷
스트링
취약점이라고
함
●
%n 포맷
문자를
이용하면
스택에
특정
값을
쓸
수
있음
포맷
스트링의
종류
●
포맷 스트링의 종류에는 여러가지가 있는데 그중 C언어에서 일반적으로
Data( 변수)를 입출력문에서 일정한 형태로 받아들이거나 출력하기 위하여
사용하는 기호는 다음과 같은 것들이 있다. Ex) %d, %f, %c, %s, %x, %p …
●
%d : 정수형 10진수 상수
●
%f : 실수형 상수
●
%lf : 실수형 상수
●
%c : 문자값
●
%s : 문자 스트링
●
%u : 양의 정수(10진수)
●
%o : 양의정수 (8진수)
●
%x : 양의 정수 (16진수)
●
%n : 쓰인 총 바이트수
●
%n 은 포맷 스트링 버그에서 핵심이다. %n 은 이전까지 입력되었던 문자열의
길이(Byte)수 만큼 해당변수에 저장시키기 때문에 메모리의 내용도 변조 가능하
기 때문이다.
●
이를 이용해 문자열의 길이를 내가 변조시키고싶은 값의 길이만큼 만든후 %n을
써주게 되면 메모리상에 내가 원하는 값을 넣을수 있게 되는 것이다.
포맷스트스트링
버그를
이용해
할수있는
일
●
프로그램을
다운시킴
●
EX)
●
공격자는
프로그램을
다운시킬
수
있음. 예를
들어
해커가
다음과
같은
코드에
대해
입력을
“%s%s%s%s%s%s”와
같이
주었는
가정하에
●
char buf[512];
●
read(0, buf, 512);
●
print(buf);
●
Printf함수가
%s인자를
만나면
스택에
있는
메모리
주소로
인식하고
그
주소에
접근. 이럴경우
위와
같이
%s를
6개
써
주면
printf 함수는
스택에서
위로
24byte 를
읽어서
그걸
주소로
인식하게됨. 그런데
원래
스텍에
있는
값들은
문
자열의
주소값이
아니기
때문에
printf 함수는
잘못된
주소에
접근하게
된다. 그
러면
세그멘테이션
폴트가
일어나고
프로그램은
다운됨.
포맷스트스트링
버그를
이용해
할수있는
일
●
프로세스
스택
보기
●
공격자는
포맷
스트링
버그를
이용해
프로세스의
스택을
볼
수도
있음.
●
포맷문자
%d나
%x는
스택에서
4byte 의
값을
읽어와서
그
값을
출력. 즉
포맷
문자
%x 하나는
스택의
4byte 를
보고
싶다면
단순히
%x를
하나만
입력하면
됨.
그리고
우리가
스택의
여러
byte 를
보고
싶으면
그
만큼
%x를
늘려주면
됨.
●
EX)
●
printf(“%08x.%08x.%08x.%08x.%08x”);
●
위와
같은
함수는
스택으로부터
20byte 를
읽어와서
4byte 단위
16진수로
출력해
준다. 이
기능을
이용해서
공격자는
프로세스의
메모리
구조를
자세히
파악
할
수
있다. 이와
같이
포맷
스트링
버그를
이용하면
원격지
프로세스의
스택을
볼
수
있기
때문에
해커가
주소를
추정하기가
훨씬
쉬워진다. 즉
포맷
스트링
버그는
버퍼
오버플로우
공격을
쉽게
할
수
있도록
도와주는
역할을
하기도
한다.
포맷스트스트링
버그를
이용해
할수있는
일
●
메모리의
임의의
위치
보기
●
공격자는
포맷
스트링
버그를
이용하여
메모리의
어떤
부분도
볼
수
있다.
●
EX)
●
char buf[512]
●
int a;
●
int b;
●
read(0, buf, 512); // 표준입력에서
읽어
들인다.
●
printf(buf);
●
위
코드에서
사용자는
buf 에
자신의
데이터를
입력한다. 만약
사용자가
buf 의
처음
4byte 에
보기를
원하는
주소를
넣는다면
해커는
8byte 만큼
스택을
거슬러
올라가야
그
주소에
접근할
수
있다. 예를
들어
해커가
0x0a0b0c0d 를
보고자
한다면
다음과
같이
입력해야
한다.
●
\x0d\x0c\x0b\x0a\ %08x %08x %s
●
위
포맷
문자열은
%08x 가
2개
있기
때문에
8byte 를
거슬러
올라간다. 그리고
스택을
거슬러
올라가서
문자열이
있는
버퍼까지
간다. 그
다음
%s가
있기
때문에
해커는
스택에서
4byte 를
읽고
그것을
주소로
인식한다. 이제
printf
함수는
0x0a0b0c0d 번지에서
NULL 이
나올
때까지
읽는다.
대응책
●
첫째, 포맷 함수를 사용할때는 사용자의 입력에 대해 철저한 검사를 해야한다.
위험한 포맷 함수들은 다음과 같다.
●
Fprintf, printf, sprintf, snprifnt, vfpirntf, vprintf, vsprintf, syslog, …
●
둘째, 사용자의 입력에 대해 미리 검사를 하는 것도 중요하지만 사용자가 입력한
것을 포맷 문자열의 출력으로 바로 보내면 안된다. 예를 들어 다음과 같은 func()
함수가 있다는 가정하에
●
void func(char *user)
●
{
●
printf(user);
●
}
●
위와 같은 함수는 사용자가 입력한 user이라는 값이 바로 출력으로 가기 때문에
공격자가 입력문자열을 조작하여 포맷 스트링 버그를 시도할 가능성이 많다.
그래서 위 함수는 다음과 같이 바꾸면 안전한다.
●
Void func(char *user)
●
{
●
printf(“%s”,user);
●
}
●
새로 고친 함수는 user 안에 포맷 문자열이 들어 있다고 해도 그것
전체를 %s문자열이 받게 되기 때문에 안전하다.
포맷
스트링
실습
정상적인
포맷
스트링
취약점이
있는
포맷
스트링
주소값
확인하기
Char *buffer 에
문자열을
입력할
때
%x라는
포맷
스트링
문자를
추
가하면
wish문자열
외에
4000c660
이
출력되는데
wishfree 문자열이
저장됙
다음
메모리에
존재하는
값
이다.
포맷
스트링의
변조