-====-====-====-====-====-====-====-====-====-====-====-====-====-====-====-====- 제목 : 포맷스트링 버그 활용 정리 작 성 자 : ttongfly@realskulls.org2003. 11. 17 홈페이지 : http://ttongfly.realskulls.org 참고문서 : 포멧스트링 버그 100% 활용하기 vol.1 -naska21 -====-====-====-====-====-====-====-====-====-====-====-====-====-====-====-====- 포맷스트링 버그에 관한 개념은 다른 문서를 참고하길 바란다. 이 문서는 개인적인 스터디 정리를 목적으로 작성되었다. 테스트 환경은 다음과 같다.
포맷 스트링 버그의 장점은 그 특징을 이용하여 스택을 덤프해서 볼 수 있다는데 커다란 장점이 있다.
func(char *str) { printf(str); } main(int argc, char **argv) { func(argv[1]); }
다음과 같은 명령으로 스택의 내용을 덤프 받을수 있다. 아래는 8개의 값을 덤프 받는다.
fsb1: file format elf32-i386 Disassembly of section .init:
804835e가 func의 리턴값임을 알 수 있다. 위에서 제시한 소스는 printf(str); 로 스트링을 출력함으로써 문자열 길이에 대한 제한이 없었다. 그래서 모든 스택 및 환경변수까지 모두 덤프해 볼 수 있지만, 대부분의 프로그램은 스트링 길이에 제한을 두고 있으므로 위와같은 방법을 적용하는데는 한계가 있다. 그래서 등장한것이 $-flag를 써서 스택을 덤프하는 방법이다. 사용법은 아래와 같다.
사용방법은 %(숫자:아규먼트)$(%를 뺀문자열) 이다. 그리고 $의 경우는 쉘상에서 특수문자이기 때문에 앞에 \\를 붙여준다 그리하여 $숫자\\$8x가 되는것이다.
위의 두가지 덤프내용을 보면 7번째 인자는 모두 804835e를 가르키고 있다. 이것은 func()의 ret값으로 모두 일치한다. 여기서 804835e라는 func의 ret값의 바로 전값이 func()의 sfp이며 main의 ebp이기도 하다. main의 ebp 에 4를 더한값이 바로 main()의 ret값이기도 하다. 그런데 func의 sfp이며 main의 ebp가 되어야 할 값이 위의 두가지 방법의 덤프를 살펴보면 서로 다른값을 가지고 있음을 알 수 있다. 그리고 804835e의 바로전값은 6번째 인자값이 첫번째 덤프의 경우 bffffac8을 가르키고 있고 두번째의 경우 bfffa98을 가르키고 있다. 그렇다면 메모리를 덤프해서 804835e값의 전값이 bffffac8인지 bffffa98인지 확인해 보도록 하겠다. 일단 func함수만 덤프해서 보면..
앞서 살펴보았던 포맷스트링 덤프값의 경우는 두가지 방법은 전자의 방법으로 하여야지만 ret값을 찾을 수 있다는 걸 알 수 있었다.
func(char *str) { printf(str); } main() { char str[40]; fgets(str, 39, stdin); func(str); }
ret값을 찾았다. 참조문서에 나와있는데로 func()의 ret도 바꿔보도록 하자. 우선 main()의 ret값이 몇번째 인자에해당하는지 알아야 한다. 좀전 덤프결과를 보면 12번째부터 str[]이 시작되며 버퍼가 40바이트 이므로 +10 main의 sfp가 있으므로 +1, ebp와의 더미값이 8바이트므로 +2 12+10+1+2=25 이므로 25번째 + a에 main()의 ret이 있을것으로 사료된다.
자 그럼 27번째에 main의 ret값이 있음을 알았다. func의 ret은 7번째에 있었기 때문에 20번 만큼 차이가 있다. 하나의 주소값은 4바이트만큼씩 차지함으로 80바이트만큼의 차이가 있음을 알 수 있다.
저 주소로 공격을 해보자. 세그멘테이션 오류가 나면 성공이다.
성공했다. ^^; 이로써 특정함수를 호출했을경우 각각의 sfp(ebp)와 ret값을 찾는 방법에 대해서 공부해 보았다. 허접하지만 여기서 일단 정리 끝.