본문 바로가기

Let's Study/Basic System Hacking Technique

5. Format String Attack (FSB)


system_hacking5.pdf







Shayete 입니다. 5번째 강의는 포맷스트링 버그에 대해 알아보도록 하겠습니다. 포맷스트링버그는 개발자가 printf()함수 구현 시, 해킹 공격을 고려하지 않고 개발하여 생긴 보안 취약점입니다.

 printf()를 "printf(buf)" 와 같은 형태로 사용했을 때 출력할 buf의 값이 포맷스트링이라면 buf 값을 문자로 취급하는게 아닌, 서식문자로 취급하여 취약점이 일어날 수 있습니다.




위의 그림에서 보이듯이 fgets() 로 buf에 서식문자를 집어넣고 그걸 printf() 로 출력했을 때 출력될 buf 배열에 서식문자가 있을 경우, buf배열을 가리키는 주소의 다음 4바이트 위치를 참조하여 그 서식문자의 기능대로 출력합니다. 이게 무슨말일까요.




printf나 scanf에 쓰이는 서식문자는 위와 같습니다. 서식문자의 기능대로 출력한다는 뜻은, %d를 입력했을 때, 메모리에 있는 값을 10진수로 출력하고 %s는 문자열로, %x는 16진수로 출력합니다. 




현재 스택이 이렇게 쌓여있다고 가정합시다. 이 상태에서 printf(buf)를 실행했을 경우 buf 배열을 가리키고 있는 parameter1(buf) 다음 4바이트 주소(parameter2)부터 출력이 됩니다. 


"AAAA %d %p %x" 를 넣었을 때 AAAA, parameter2(sizeof(buf))->1024(%d), parameter3(stdin)->0xb773cc20(%p), AAAA-> 41414141(%x) 이 출력되겠지요. 여기까지가 포맷스트링버그의 기본적인 설명이었습니다.




이 취약점을 이용해 공격하는 방법은, %n directive를 이용합니다. %n은 현재까지 출력된 값들을 count해서 그 다음 메모리주소값에 그대로 대입하는 서식문자입니다. 예를 들어 위의 스택에서 "AAAA %p %1234x %n" 을 입력했을 경우, %n이 위치한 곳에 41414141 값이 들어있었으므로


"AAAA(4) + 공백(1) + %p(1) + %1234x(1234) + 공백(1) = 1242", 출력된 1242개의 문자들이 count되어 0x41414141 주소에 들어갑니다. 1242는 16진수로 0x4da 이므로 0x41414141 = 0x4da (1242) 가 됩니다.




제가 강의하고 포스팅할 코드에서는 일반적인 fgets(buf, sizeof(buf), stdin), printf(buf)가 아닌 snprintf()를 이용해 취약점공격을 하는 것을 보여드리겠습니다. printf에서는 3번째 서식문자에 입력값이 존재하여 메모리 값을 원하는대로 변경할 수 있었습니다. snprintf()를 쓰면 어떻게 될까요?




취약점 테스트에 쓸 코드는 이렇습니다. name 배열 1024바이트, name2 배열 1024바이트. Format String Attack 을 이용해 secret함수가 실행되도록 하겠습니다. snprintf() 함수는 name2 배열에 있는 값을 name2 사이즈만큼 name에 출력하는 함수입니다. 




이걸 실행해서 "AAAA %x %x %x %x"를 쓰면 "AAAA 41414141 20782520 25207825 78252078"이 출력됩니다.

?????????????????????????? 뭘까요. printf()를 썼을때와 다르게 출력되네요.




일반적으로 알고있는 포맷스트링 형태입니다. 위에서도 설명드렸듯 printf(buf)를 할 때에 buf가 가리키는 메모리의 다음 4바이트부터 (0x400부터) 입력한 서식문자의 기능대로 출력하는게 포맷스트링 버그라고 설명드렸습니다.




snprintf()도 똑같습니다. snprintf도 결국에는 buf에 있는걸 buf2에 "출력"하는 기능이 있습니다. printf(buf) 로 buf2에 출력하는 것과 똑같다는 말이지요. (쉽게 설명드리자면 이렇다는 말이에요..) 그럼 buf배열을 가리키고 있는 다음 4바이트부터 (0xbfffecc8 주소부터 출력) 입력한 서식문자의 기능대로 출력할 수 있습니다. 결국 포맷스트링버그를 이용하는 건 스택의 상태에 따라 특정 주소에 원하는 값을 넣을 수 있습니다.




공격할 payload 를 구성해봅니다. puts_got를 secret 함수의 주소로 바꿔 puts 대신 secret 함수를 실행시키는 페이로드를 구성해보았습니다. dummy값을 넣은 이유는 서식문자 2개를 이용해야되기 때문에 (secret의 10진수주소값만큼 출력할 서식문자와, 그 주소값에 넣을 서식문자 %n) dummy 4바이트와 puts_got 4바이트를 넣고 secret 주소값을 10진수로 계산, %n을 넣어주면 공격할 수 있습니다. 볼까요



쉘을 얻었습니다. 이걸로 포맷스트링 버그 포스팅을 마칩니다.




ps. 배열을 전역변수로 선언했을 때는 경우가 약간 다릅니다. 전역변수로 배열을 선언했다면 스택이 아닌 전역변수 영역(예를 들어 0x8048060)에 입력한 값들이 들어가기 때문에 공격할 때 기존의 포맷스트링버그와 다르게 접근해야 합니다. 스택에 어느 주소값들이 있는지 확인하고 그걸 어떻게 쓸지 고민해야합니다.




fsb 과제