[6] Format String Bug [FSB]

2017. 6. 8. 14:24SystemHacking/System

 

 

 

원재아빠님의 Format String 강좌를 보면서 개인공부를 위해 작성하였습니다.


포맷 스트링이란 변수의 형태를 지정해주는 것입니다

종류에는

%d : 정수형 상수 int

%c : 문자형 char

%f : 실수형 상수 float

%x : 16진수

%o : 10진수

%s : 문자 스트링


등이 있으며, 중요한 %n 도 있습니다




[ 포맷스트링 예제 ]


포맷스트링의 빈틈은 다음과 같은 소스코드에서 발생합니다

#vi test2.c

#gcc -o test2 test2.c

[ 그림1 ]


printf()함수에서 받아들인 변수 put을 스트링의 여과 없이 그대로 출력해주고 있습니다

여기서 문제가 발생합니다. 특정 스트링을 임의로 사용자가 넣으면 특이한 현상이 발생합니다


[ 그림2 ]


fgets()를 통해서 put에 문자열을 입력할 때 " %x " 스트링을 추가하였더니 해당프로그램의 메모리 구조가 출력됩니다

포맷스트링을 통해 이렇게 주소값을 보여주는 것에서부터 특정값을 변경시킬 수도 있게 됩니다 ( %n 을 사용해서 )

6번째 %8x 에서 우리가 입력한 AAAA 가 저장되있는 곳을 확인할 수 있었습니다


FSB(Format String Bug)공격은 " %n " 스트링을 이용합니다. 사용법에 대해서 알아봅시다


#vi test3.c

[ 그림3 ]


아래그림은 실행결과입니다


[ 그림4 ]


" %n "스트링은 %n 앞의 글자수(byte)의 값을 할당된 주소에 덮어씌운다. ( 다른 스트링들과는 작동방향이 반대라고 생각하면 된다 )

즉, 특정 주소번지에 있는 값을 " %n "스트링을 통해서 원하는 값으로 변경시킬 수 있다 !

포맷스트링 공격법의 관점으로 보면 " RET "에 있는 값을 %n을 통해서 원하는 다른 주소로 변질시키는 것이다


그럼 특정 주소에 주소값을 넣는 방법에 대해 알아보겠습니다.


#vi test4.c

[ 그림5 ]


[ 프로그램 실행과정 ]

먼저 변수 " i "를 0x00000014 로 초기화시킵니다

변수 " k " 는 변수 " i "의 주소값입니다

65281을 " i의 시작주소 " 에 덮어씌운다.

65281+49406 = 114687을 " i의 시작주소 + 2 " 지점에 덮어씌운다.

그리고 " i "의 값을 출력해보자 ( %x : 16진수로 )


#gcc -o test4 test4.c

#./test4

[ 그림6 ]


" i "의 값은 " bfffff01 "

어떻게 해서 i 의 값이 변하였을까? i 의 값이 변화되는 과정을 살펴보도록 합시다


0x02e139a8 : 14 00 00 00 => 0x00000014


그리고 i의 시작주소(k)에 %n 스트링을 이용해서 65281을 입력시켰습니다. (= 0xff01 )

( *참고 : 리틀 엔디안 방식으로 인하여 1byte씩 역순으로 저장됩니다 )


0x02e139a8 : 01 ff 00 00


그리고 " i "의 시작주소+2byte한 주소(k+2)에 두번째 %n 스트링을 이용해서 65281+49406 = 114687을 입력시켰습니다. (= 0x1bfff )


0x02e139a8 : 01 ff ff bf      => " 0xbfffff01 "

0x02e139ab : 10 00 00 00    : 쓰레기값


위처럼 메모리 주소를 값으로 넣기위해서는 매우 큰 수가 적용되므로 나누어서 집어넣는 방법을 사용합니다


이해가 잘 안될 수 있으니 실전으로 예제를 들어보겠습니다




[ 포맷스트링 실전 ]

# 임의의주소(0xbffffa6b)에 우리가 원하는값(0xbffffb30)을 넣는 방법을 알아보자


$vi test6.c

[ 그림7 ]


dumpcode.h는 ohhara님 께서 만드신 헤더라고 합니다. 해당 주소에 들어있는 값을 출력해주는 함수가 있어서 매우 편리합니다

( 구글링하면 나와요 )



1. 입력한 값의 위치 찾기 ㅡ 그림8


[ 그림8 ]


네번째의 " %8x " 스트링에서 AAAA가 저장되 있는 것을 확인 할 수 있다

즉 문자열을 입력하면 메모리 공간의 4번째 위치에 해당 문자열이 저장됨을 알 수 있다

그럼 " AAAA " 대신 메모리값을 넣어보도록 합시다



2. 메모리값 넣기 ㅡ 그림9

메모리값을 넣으려면 16진수형태로 넣어주어야 하기때문에 python 이나 perl 또는 printf 함수를 이용해야합니다

( printf함수에서 %%8x 는 파이프 연결로 인해 %8x 와같은 역할을 합니다 )


[ 그림9 ]


4번째 " %%8x " 에서 입력한 메모리 값이 저장되어 있음이 확인됩니다

임의의주소 " 0xbffffa6b " 를 메모리공간에 저장시켰습니다. 이를 응용해서 +2byte한 주소 또한 저장시킬 수 있습니다


[ 그림10 ]


그림에서는 0xbffffb30을 넣엇지만 실제 우리가 사용할 주소는 0xbffffb6d 입니다 ( 임의의주소에 +2byte한 주소 )

$(printf"\x41\x41\x41\x41\x6b\xfa\xff\xbf\x41\x41\x41\x41\x6d\xfb\xff\xbf%%8x%%8x%%8x%%c%%8x%%c%%8x";cat)|./test6 


%%8x 스트링에 의해 메모리공간에 있는 쓰레기값들이 출력되어지고

%%c 스트링에 의해 " \x41\x41\x41\x41 " 문자열이 "A"라는 문자로 출력되어지고

%%8x 스트링에 의해서 " 0xbfffab6b " 가 출력되고 

%%c 스트링에 의해 " \x41\x41\x41\x41 " 문자열이 "A"라는 문자로 출력되어지고

%%8x 스트링에 의해서 " 0xbfffab6d " 가 출력되어 졌습니다


메모리 구조를 표로 작성해보겠습니다 ( 이해를 돕기위해 임의적으로 작성 )

 첫번째 %%8x

 두번째 %%8x

 세번째 %%8x

 %c

 %%8x

 %c

 %%8x

 쓰레기

쓰레기값 

쓰레기값 

AAAA

0xbffffa6b 

 AAAA

0xbffffa6d 


각각의 칸은 메모리의 공간을 차지하고 있고 스트링을 사용하여 해당 공간안의 값을 출력해준 것

즉, 메모리 공간안의 5번째 칸에는 " 0xbffffa6b " 가 위치하고 있다는 뜻 => 해당 값을 %%8x로 출력하여 보여준것


그럼 %%8x 스트링을 " %%n " 스트링으로 바꿔서 작성하면 어떤 변화가 생길것인가 ?


[ 그림 11 ]


0xbffffa6b의 값이 29 00 2a 00 으로 변했습니다 ! why ??

우리가 지금 %%n 스트링을 사용한 지점이 어디일까? 바로 " 0xbffffa6b " 와 " 0xbffffa6d " 가 위치해있는 메모리 공간이다

따라서 %%n 스트링은 " 0xbffffa6b " 와 " 0xbffffa6d " 지점에 값을 덮어 씌워서 변질시킨 것이다 ★★★

먼저 처음에 위치한 " %%n "을 봐보겠습니다. %%n 스트링 이전에 문자의개수는 4+4+4+4+8+8+8+1 = 41 = 0x29

그리고 두번째 위치한 " %%n "을 보시면 41개의 문자에 " %%n과 %%c " 2문자를 더한 43 = 0x2a 입니다



즉, 문자의 개수를 늘려주면 %%n의 값이 변하겟죠? 그럼 우리가 원하는 16진수값을 해당 0xbffffa6b지점에 덮어씌울 수 있습니다 !

넣고자 하는값 : " 0xbffffb30 "

첫번째 %%n 스트링에 " fb30 " 을 집어넣어주고 두번째 %%n 스트링에 " bfff " 를 넣어주어야 합니다.

그런데 " bfff "가 작은 수인데 어떻게 뒤에 나오는 문자의 개수를 맞춰줄까 생각이 들겁니다

" bfff " 대신 " 1bfff " 를 얻으면됩니다. 스택의 특성을 이용하는 것입니다


 0xbffffa6b : 30 fb ff bf => " 0xbffffb30 "

 0xbffffa6c : ff bf 01 00

 0xbfffa6d : fa ff bf 01 


실제로 해당 값을 해당 메모리 공간에 넣어보도록 하겠습니다


0xfb30 = 64304byte입니다. 이때 " %%c "스트링 앞에 있는 문자의 개수는 40(4+4+4+4+8+8+8)개입니다 ( 64304-40 = 64264 )

따라서 첫번째" %%c "스트링을 " %%64264c "으로 바꿔서 입력해보도록 하겠습니다.


$(printf"\x41\x41\x41\x41\x6b\xfa\xff\xbf\x41\x41\x41\x41\x6d\xfb\xff\xbf%%8x%%8x%%8x%%64264c%%n%%c%%n";cat)|./test6 


[ 그림 12 ]


해당 지점(0xbffffa6b)에 원하는 값이 들어가 있음을 확인 할 수 있습니다.

다음으로 두번째 %%n 스트링에 들어갈 문자개수를 조절하여 " 0xbffffa6d "주소에 값을 덮어씌우면 성공입니다


0x1bfff = 114687byte입니다. 114687 - 64264- 40 = 50383byte개의 문자가 필요합니다


[ 그림 13 ]


원하는 메모리 공간에 원하는 값을 넣었습니다.


# 임의의 메모리 공간을 A 라고 했을 때 왜 A주소에 +2byte한 지점에 값을 집어넣는지 이해가 안될 수도 있다

# 16진수의 메모리 주소는 총 4byte이다


 주소

1 byte

1 byte

1 byte

1 byte

 값

 0xbffffa6b 

 30

fb 

ff 

bf 

 0xbffffb30 

 0xbffffa6c 

  fb 

ff 

bf 

01 

 0x01bffffb

 0xbffffa6d 

 ff

 bf

01

00 

 0x0001bfff


# 처음 %%n에 의해서 " 0xbffffa6b "지점은 " 0x0000fb30 " 값을 가지고 있다

# " 0xbffffa6d "지점에 " 0x0001bfff "값으로 덮어씌움으로써 " 0xbffffa6b "지점의 처음 2byte부분을 변질시킨 것

# 메모리 주소값을 대입하기에 너무 큰 수를 사용해야 하므로 위처럼 2byte씩 끈어서 대입시키는 방법을 사용한다

 ( 정수가 표현할 수 있는 범위를 벗어나버린다 )

FTZ 11번문제에서 해당개념을 응용하여 사용해보도록 하겠고 이상으로 포맷스트링의 개념에 대한 설명을 끝마치도록 하겠습니다.