• <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>

            興海北路

            ---男兒仗劍自橫行
            <2008年3月>
            2425262728291
            2345678
            9101112131415
            16171819202122
            23242526272829
            303112345

            統(tǒng)計

            • 隨筆 - 85
            • 文章 - 0
            • 評論 - 17
            • 引用 - 0

            常用鏈接

            留言簿(6)

            隨筆分類

            隨筆檔案

            收藏夾

            全是知識啊

            搜索

            •  

            最新評論

            閱讀排行榜

            評論排行榜

            C語言程序緩沖區(qū)注入的分析(第一部分:進程的內存映像)
            by falcon <zhangjinw@gmail.com>
            2008-2-13

          1. 閑言戲語

                最近在寫《Shell編程范例之進程操作》,到現(xiàn)在也沒完。本打算介紹進程的相關操作,后面竟寫到Linux下的C語言開發(fā)過程,想把文件是怎么變成進程 的整個過程給全部搗弄一遍。雖然到程序加載以及動態(tài)符號連接都已經(jīng)很理解了,但是這伙卻被進程的內存映像給”糾纏“住。看著看著就一發(fā)不可收拾——很有 趣。
                下面一起來分享人家研究的”緩沖區(qū)溢出和注入“問題(主要是關心程序的內存映像)吧,更多內容見最后的參考資料(第二部分最后有列出)。

          2. 轉入正題

            1、Hello World

            永遠的Hello World,太熟悉了吧,



            Code:

            [Ctrl+A Select All]



            如果要用內聯(lián)匯編(inline assembly)來寫呢?



            Code:

            [Ctrl+A Select All]



            看起來很復雜,實際上就做了一個事情,往終端上寫了個Hello World。不過這個非常有意思。先簡單分析一下流程:

            1) 第4行指令的作用是跳轉到第15行(即forward標記處),接著執(zhí)行第16行。
            2) 第16行調用backward,跳轉到第5行,接著執(zhí)行6到14行。
            3) 第6行到第11行負責在終端打印出Hello World字符串(等一下詳細介紹)。
            4) 第12行到第14行退出程序(等一下詳細介紹)。

            為了更好的理解上面的代碼和后續(xù)的分析,先來介紹幾個比較重要的東西,

            1.1 三個比較重要的寄存器

            EIP: 程序指令指針,通常指向下一條指令的位置
            ESP:程序堆棧指針,通常指向當前堆棧的當前位置
            EBP:程序基指針,通常指向函數(shù)使用的堆棧頂端

            當然,上面都是擴展的寄存器,用于32位系統(tǒng),對應的16系統(tǒng)為ip,sp,bp。

            1.2 call,ret指令的作用分析

            call指令

            跳轉到某個位置,并在之前把下一條指令的地址(EIP)入棧(為了方便”程序“返回以后能夠接著執(zhí)行)。這樣的話就有:
            Quote:

            call backward   ==>   push eip
                                              jmp backward



            通常call指令和ret是配合使用的,前者壓入跳轉前的下一條指令地址,后者彈出call指令壓入的那條指令,從而可以在函數(shù)調用結束以后接著執(zhí)行后面的指令。
            Quote:

            ret                    ==>   pop eip



            通 常在函數(shù)調用過后,還需要恢復esp和ebp,恢復esp即恢復當前棧指針,以便釋放調用函數(shù)時為存儲函數(shù)的局部變量而自動分配的空間;恢復ebp是從棧 中彈出一個數(shù)據(jù)項(通常函數(shù)調用過后的第一條語句就是push ebp),從而恢復當前的函數(shù)指針為函數(shù)調用者本身。這兩個動作可以通過一條leave指令完成。

            這三個指令對我們后續(xù)的解釋會很有幫 助。更多關于Intel的指令集,請參考本節(jié)參考手冊,關于AT&T的,看本文最后的參考資料[4]和本節(jié)參考資料[2],更多關于X86的匯 編,請參考本節(jié)資料[3],關于其他平臺的,可以考慮看看"See Mips Run"和一些專門平臺自己的手冊。

            本節(jié)參考資料

            [1] Intel 386 Manual
            http://www.x86.org/intel.doc/386manuals.htm
            [2] Linux_Assembly_Language_Programming
            http://mirror.lzu.edu.cn/doc/incoming/ebooks/linux-unix/Linux_EN_Original_Books/
            http://mirror.lzu.edu.cn/doc/incoming/ebooks/linux-unix/Programming/Assembly/
            [3] x86 Assembly Language FAQ
            http://www.faqs.org/faqs/assembly-language/x86/general/part1/
            http://www.faqs.org/faqs/assembly-language/x86/general/part2/
            http://www.faqs.org/faqs/assembly-language/x86/general/part3/

            1.3 什么是系統(tǒng)調用(以2.6.21版本和x86平臺為例)

            系統(tǒng)調用是用戶和內核之間的接口,用戶如果想寫程序,很多時候直接調用了C庫,并沒有關心系統(tǒng)調用,而實際上C庫也是基于系統(tǒng)調用的。這樣應用程序和內核之間就可以通過系統(tǒng)調用聯(lián)系起來了。它們分別處于操作系統(tǒng)的用戶空間和內核空間(主要是內存地址空間的隔離)。

            Quote:

            用戶空間         應用程序(Applications)
                                  |      |
                                  |     C庫(如glibc)
                                  |      |
                                 系統(tǒng)調用(System Calls,如sys_read, sys_write, sys_exit)
                                      |
            內核空間            內核(Kernel)



            系統(tǒng)調用實際上也是一些函數(shù),它們被定義在arch/i386/kernel/sys_i386.c
            (老 的在arch/i386/kernel/sys.c)文件中,并且通過一張系統(tǒng)調用表組織,該表在內核啟動的時候就已經(jīng)加載了,這個表的入口在內核源代碼 的arch/i386/kernel/syscall_table.S里頭(老的在arch/i386/kernel/entry.S)。這樣,如果想添 加一個新的系統(tǒng)調用,修改上面兩個內核中的文件,并重新編譯內核就可以了。當然,如果要在應用程序中使用它們,還得把它寫到 include/asm/unistd.h中。

            如果要在C語言中使用某個系統(tǒng)調用,需要包含頭文件 /usr/include/asm/unistd.h,里頭有各個系統(tǒng)調用的聲明以及系統(tǒng)調用號(對應于調用表的入口,即在調用表中的索引,為方便查找調 用表而設立的)。如果是自己定義的新系統(tǒng)調用,可能還要在開頭用宏_syscall(type, name, type1, name1...)來聲明好參數(shù)。具體用法見參考資料[3]里的實例。

            如果要在匯編語言中使用,需要用到int 0x80調用,這個是系統(tǒng)調用的中斷入口。涉及到傳送參數(shù)的寄存器有這么幾個,eax是系統(tǒng)調用號(可以到/usr/include/asm- i386/unistd.h或者直接到arch/i386/kernel/syscall_table.S查到),其他寄存器如ebx,ecx,edx, esi,edi一次存放系統(tǒng)調用的參數(shù)。而系統(tǒng)調用的返回值存放在eax寄存器中。

            下面我們就很容易解釋前面的shellcode.c程序流程的2),3)兩部分了。因為都用了int 0x80中斷,所以都用到了系統(tǒng)調用。

            第3) 部分很簡單,用到的系統(tǒng)調用號是1,通過查表(查/usr/include/asm-i386/unistd.h或 arch/i386/kernel/syscall_table.S)可以發(fā)現(xiàn)這里是sys_exit調用,再從 /usr/include/unistd.h文件看這個系統(tǒng)調用的聲明,發(fā)現(xiàn)參數(shù)ebx是程序退出狀態(tài)。

            第2)部分比較有趣,而且復雜一 點。我們依次來看各個寄存器,首先根據(jù)eax為4確定(同樣查表)系統(tǒng)調用為sys_write,而查看它的聲明(從 /usr/include/unistd.h),我們找到了參數(shù)依次為文件描述符、字符串指針和字符串長度。第一個參數(shù)是ebx,正好是2,即標準錯誤輸 出,默認為終端,第二個參數(shù)是什么呢?ecx,而ecx的內容來自esi,esi來自剛彈出棧的值(見第6行popl   %esi;),而之前剛好有call指令引起了最近一次壓棧操作,入棧的內容剛好是call指令的下一條指令的地址,即.string所在行的地址,這樣 ecx剛好引用了"Hello World\n"字符串的地址。第三個參數(shù)呢,是edx,剛好是12,即"Hello World\n"字符串的長度(包括一個空字符)。這樣,shellcode.c的執(zhí)行流程就很清楚了,第4,5,15,16行指令的巧妙之處也就容易理 解了(把.string存放在call指令之后,并用popl指令把eip彈出當作字符串的入口)。

            本節(jié)推薦資料;

            [1] 深入理解linux系統(tǒng)調用
            http://www.linuxdiyf.com/bbs/redirect.php?tid=2270&goto=lastpost&highlight=
            [2] 在Linux操作系統(tǒng)下如何截獲系統(tǒng)調用(如果無法訪問,可以看linux module programing how to)
            http://www.xker.com/html/czxt/linux/2006_03_10_10_925.html

            1.4 什么是ELF文件

            這里的ELF不是“精靈”,而是Executable and Linking Format文件,是Linux下用來做目標文件、可執(zhí)行文件和共享庫的一種文件格式,它有專門的標準(見本節(jié)參考資料)。這里簡單描述ELF的格式。

            ELF文件主要有三種,分別是:

            1、可重定位的目標文件,在編譯時用gcc的-c參數(shù)時產(chǎn)生。
            2、可執(zhí)行文件,這類文件就是我們后面要討論的可以執(zhí)行的文件。
            3、共享庫,這里主要是動態(tài)共享庫,而靜態(tài)共享庫則是可重定位的目標文件通過ar命令組織的。

            ELF文件的大體結構:

            Quote:

            ELF Header                      #程序頭,有該文件的Magic number(參考man magic),類型等
            Program Header Table     #對可執(zhí)行文件和共享庫有效,它描述下面各個節(jié)(section)組成的段
            Section1
            Section2
            Section3
            .....
            Program Section Table   #僅對可重定位目標文件和靜態(tài)庫有效,用于描述各個Section的重定位信息等。



            對 于可執(zhí)行文件,文件最后的Program Section Table(節(jié)區(qū)表)和一些非重定位的Section,比如.comment,.note.XXX.debug等信息都可以刪除掉,不過如果用 strip,objcopy等工具刪除掉以后,就不可恢復了。因為這些信息對程序的運行一般沒有任何用處。

            ELF文件的主要節(jié)區(qū)(section)有.data,.text,.bss,.interp等,而主要段(segment)有LOAD,INTERP等。它們之間(節(jié)區(qū)和段)的主要對應關系如下:

            .data 初始化的數(shù)據(jù),比如int a=10
            .bss 未初始化的數(shù)據(jù),不如char sum[100];這個在程序執(zhí)行之前,內核將初始化為0
            .text 程序正文,即可執(zhí)行指令
            .interp 這里描述了程序需要的解釋器(動態(tài)連接和裝載程序),存有解釋器的全路徑,如/lib/ld-linux.so

            而程序在執(zhí)行以后,.data, .bss,.text等一些節(jié)區(qū)會被Program header table映射到LOAD段,.interp則被映射到了INTERP段。

            對于ELF文件的分析,建議使用file, size, readelf,objdump,strip,objcopy,gdb,nm等工具,如果有興趣,可以考慮閱讀一下本節(jié)參考資料[2]。

            這里簡單地演示這幾個工具:

            Quote:

            $ gcc -g -o shellcode shellcode.c       #如果要用gdb調試,編譯時加上-g是必須的
            shellcode.c: In function ‘main’:
            shellcode.c:3: warning: return type of ‘main’ is not ‘int’
            f$ file shellcode              #file命令查看文件類型,想了解工作原理,可man magic,man file
            shellcode: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), not stripped
            $ readelf -l shellcode  #列出ELF文件前面的program head table,后面是它描
                                               #述了各個段(segment)和節(jié)區(qū)(section)的關系,即各個段包含哪些節(jié)區(qū)。
            Elf file type is EXEC (Executable file)
            Entry point 0x8048280
            There are 7 program headers, starting at offset 52

            Program Headers:
              Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
              PHDR           0x000034 0x08048034 0x08048034 0x000e0 0x000e0 R E 0x4
              INTERP         0x000114 0x08048114 0x08048114 0x00013 0x00013 R   0x1
                  [Requesting program interpreter: /lib/ld-linux.so.2]
              LOAD           0x000000 0x08048000 0x08048000 0x0044c 0x0044c R E 0x1000
              LOAD           0x00044c 0x0804944c 0x0804944c 0x00100 0x00104 RW  0x1000
              DYNAMIC        0x000460 0x08049460 0x08049460 0x000c8 0x000c8 RW  0x4
              NOTE           0x000128 0x08048128 0x08048128 0x00020 0x00020 R   0x4
              GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x4

             Section to Segment mapping:
              Segment Sections...
               00
               01     .interp
               02     .interp .note.ABI-tag .hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata .eh_frame
               03     .ctors .dtors .jcr .dynamic .got .got.plt .data .bss
               04     .dynamic
               05     .note.ABI-tag
               06
            $ size shellcode   #可用size命令查看各個段(對應后面將分析的進程內存映像)的大小
               text    data     bss     dec     hex filename
                815     256       4    1075     433 shellcode
            $ strip -R .note.ABI-tag shellcode    #可用strip來給可執(zhí)行文件“減肥”,刪除無用信息
            $ size shellcode                               #“減肥”后效果“明顯”,對于嵌入式系統(tǒng)應該有很大的作用
               text    data     bss     dec     hex filename
                783     256       4    1043     413 shellcode
            $ objdump -s -j .interp shellcode   #這個主要工作是反編譯,不過用來查看各個節(jié)區(qū)也很厲害

            shellcode:     file format elf32-i386

            Contents of section .interp:
             8048114 2f6c6962 2f6c642d 6c696e75 782e736f  /lib/ld-linux.so
             8048124 2e3200                               .2.



            補充:如果要刪除可執(zhí)行文件的program section table,可以用參考資料[2]一文的作者寫的elf kicker[3]工具鏈中的sstrip工具。

            本節(jié)參考資料:
            [1] ELF format and ABI
            http://www.x86.org/ftp/manuals/tools/elf.pdf
            http://162.105.203.48/web/gaikuang/submission/TN05.ELF.Format.Summary.pdf
            http://www.xfocus.net/articles/200105/174.html
            [2] A Whirlwind Tutorial on Creating Really Teensy ELF Executables for Linux
            http://www.muppetlabs.com/~breadbox/software/tiny/teensy.html
            [3] Kickers of ELF
            http://www.muppetlabs.com/~breadbox/software/elfkickers.html

            1.5 程序執(zhí)行基本過程

            在命令行下,敲入程序的名字或者是全路徑,然后按下回車就可以啟動程序,這個具體是怎么工作的呢?

            首 先要再認識一下我們的命令行,命令行是內核和用戶之間的接口,它本身也是一個程序。在Linux系統(tǒng)啟動以后會為每個終端用戶建立一個進程執(zhí)行一個 Shell解釋程序,這個程序解釋并執(zhí)行用戶輸入的命令,以實現(xiàn)用戶和內核之間的接口。這類解釋程序有哪些呢?目前Linux下比較常用的有 /bin/bash。那么該程序接收并執(zhí)行命令的過程是怎么樣的呢?

            先簡單描述一下這個過程[4]:
            1)讀取用戶由鍵盤輸入的命令行。
            2)分析命令,以命令名作為文件名,并將其它參數(shù)改為系統(tǒng)調用execve( )內部處理所要求的形式。
            3)終端進程調用fork( )建立一個子進程。
            4)終端進程本身用系統(tǒng)調用wait4( )來等待子進程完成(如果是后臺命令,則不等待)。當子進程運行
            時調用execve( ),子進程根據(jù)文件名(即命令名)到目錄中查找有關文件(這是命令解釋程序構成的
            文件),將它調入內存,執(zhí)行這個程序(解釋這條命令)。
            5)如果命令末尾有&號(后臺命令符號),則終端進程不用系統(tǒng)調用wait4( )等待,立即發(fā)提示符,讓
            用戶輸入下一個命令,轉1)。如果命令末尾沒有&號,則終端進程要一直等待,當子進程(即運行命令
            的進程)完成處理后終止,向父進程(終端進程)報告,此時終端進程醒來,在做必要的判別等工作后,
            終端進程發(fā)提示符,讓用戶輸入新的命令,重復上述處理過程。

            現(xiàn)在用strace來跟蹤一下程序執(zhí)行過程中用到的系統(tǒng)調用。
            Quote:

            $ strace -f -o strace.out test
            $ cat strace.out | grep \(.*\) | sed -e "s#[0-9]* \([a-zA-Z0-9_]*\)(.*).*#\1#g"
            execve
            brk
            access
            open
            fstat64
            mmap2
            close
            open
            read
            fstat64
            mmap2
            mmap2
            mmap2
            mmap2
            close
            mmap2
            set_thread_area
            mprotect
            munmap
            brk
            brk
            open
            fstat64
            mmap2
            close
            close
            close
            exit_group



            相關的系統(tǒng)調用基本體現(xiàn)了上面的執(zhí)行過程,需要注意的是,里頭還涉及到內存映射(mmap2)等。

            下面再羅嗦一些比較有意思的內容,參考〈深入理解Linux內核〉的"程序的執(zhí)行", P681和本節(jié)參考資料。

            Linux 支持很多不同的可執(zhí)行文件格式,這些不同的格式是如何解釋的呢?平時我們在命令行下敲入一個命令就完了,也沒有去管這些細節(jié)。實際上Linux下有一個 struct linux_binfmt結構來管理不同的可執(zhí)行文件類型,這個結構中有對應的可執(zhí)行文件的處理函數(shù)。大概的過程(詳細的過程見本節(jié)參考資料)如下:

            1) 在用戶態(tài)執(zhí)行了execve后,引發(fā)int 0x80中斷,進入內核態(tài),執(zhí)行內核態(tài)的相應函數(shù)do_sys_execve,該函數(shù)又調用do_execve函數(shù)。do_execve函數(shù)讀入可執(zhí)行文 件,檢查權限,如果沒問題,繼續(xù)讀入可執(zhí)行文件需要的相關信息(struct linux_binprm描述的)。

            2)接著執(zhí)行 search_binary_handler,根據(jù)可執(zhí)行文件的類型(由上一步的最后確定),在linux_binfmt結構鏈表(formats,這個 鏈表可以通過register_binfmt和unregister_binfmt注冊和刪除某些可執(zhí)行文件的信息,因此注冊新的可執(zhí)行文件成為可能,后 面再介紹)上查找,找到相應的結構,然后執(zhí)行相應的load_binary()函數(shù)開始加載可執(zhí)行文件。在該鏈表的最后一個元素總是對解釋腳本 (interpreted script)的可執(zhí)行文件格式進行描述的一個對象。這種格式只定義了load_binary方法,其相應的load_script()函數(shù)檢查這種可執(zhí) 行文件是否以兩個#!字符開始,如果是,這個函數(shù)就以另一個可執(zhí)行文件的路徑名作為參數(shù)解釋第一行的其余部分,并把腳本文件名作為參數(shù)傳遞以執(zhí)行這個腳本 (實際上腳本程序把自身的內容當作一個參數(shù)傳遞給了解釋程序(如/bin/bash),而這個解釋程序通常在腳本文件的開頭用#!標記,如果沒有標記,那 么默認解釋程序為當前SHELL)。

            3)對于ELF類型文件,其處理函數(shù)是load_elf_binary,它先讀入ELF文件的頭部, 根據(jù)頭部信息讀入各種數(shù)據(jù),再次掃描程序段描述表(program header table),找到類型為PT_LOAD的段(即.text,.data,.bss等節(jié)區(qū)),將其映射(elf_map)到內存的固定地址上,如果沒有動 態(tài)連接器的描述段,把返回的入口地址設置成應用程序入口。完成這個功能的是start_thread,它不啟動一個線程,而只是用來修改了pt_regs 中保存的PC等寄存器的值,使其指向加載的應用程序的入口。當內核操作結束,返回用戶態(tài)時接著就執(zhí)行應用程序本身了。

            4)如果應用程序使 用了動態(tài)連接庫,內核除了加載指定的可執(zhí)行文件外,還要把控制權交給動態(tài)連接器(ld-linux.so)以便處理動態(tài)連接的程序。內核搜尋段表 (Program Header Table),找到標記為PT_INTERP段中所對應的動態(tài)連接器的名稱,并使用load_elf_interp()加載其映像,并把返回的入口地址設 置成load_elf_interp()的返回值,即動態(tài)鏈接器的入口。當execve系統(tǒng)調用退出時,動態(tài)連接器接著運行,它檢查應用程序對共享鏈接庫 的依賴性,并在需要時對其加載,對程序的外部引用進行重定位(具體過程見〈shell編程范例之進程操作〉)。然后把控制權交給應用程序,從ELF文件頭 部中定義的程序進入點(用readelf -h可以出看到,Entry point address即是)開始執(zhí)行。(不過對于非LIB_BIND_NOW的共享庫裝載是在有外部引用請求時才執(zhí)行的)。

            對于內核態(tài)的函數(shù)調 用過程,沒有辦法通過strace(它只能跟蹤到系統(tǒng)調用層)來做的,因此要想跟蹤內核中各個系統(tǒng)調用的執(zhí)行細節(jié),需要用其他工具。比如可以通過給內核打 一個KFT(Kernel Function Tracing)的補丁來跟蹤內核具體調用了哪些函數(shù)(見本節(jié)參考資料[2])。當然,也可以通過ctags/cscope/LXR等工具分析內核的源代 碼。

            Linux允許自己注冊我們自己定義的可執(zhí)行格式,主要接口是/procy/sys/fs/binfmt_misc/register,可以往里頭寫入特定格式的字符串來實現(xiàn)。該字符串格式如下:
            Quote:

            :name:type:offset:string:mask:interpreter:


            name 新格式的標示符
            type 識別類型(M表示魔數(shù),E表示擴展)
            offset 魔數(shù)(magic number,請參考man magic和man file)在文件中的啟始偏移量
            string 以魔數(shù)或者以擴展名匹配的字節(jié)序列
            mask 用來屏蔽掉string的一些位
            interpreter 程序解釋器的完整路徑名

            本節(jié)參考資料;

            [1] 應用程序在Linux上是如何執(zhí)行的
            http://javadino.blog.sohu.com/74639896.html
            [2] KFT基本使用
            http://dslab.lzu.edu.cn/members/zhangwei/doc/KFT-HOWTO
            [3] Cscope基本使用
            http://dslab.lzu.edu.cn/members/zhangwei/doc/cscope-HOWTO
            [4] Shell基本工作原理
            http://www.gbunix.com/htmldata/2006_08/14/18/article_1381_1.html

            1.6 Linux下程序的內存映像

            Linux下是如何給進程分配內存(這里僅討論虛擬內存的分配)的呢?可以從/proc/<pid>/maps文件中看到個大概。這里的pid是進程號。

            /proc下有一個文件比較特殊,是self,它鏈接到當前進程的進程號,例如:
            Quote:

            $ ls /proc/self -l
            lrwxrwxrwx 1 root root 64 2000-01-10 18:26 /proc/self -> 11291/
            $ ls /proc/self -l
            lrwxrwxrwx 1 root root 64 2000-01-10 18:26 /proc/self -> 11292/



            看到?jīng)]?每次都不一樣,這樣我們通過cat /proc/self/maps就可以看到cat程序執(zhí)行時的內存映像了。
            Quote:

            $ cat -n /proc/self/maps                                                        
                 1  08048000-0804c000 r-xp 00000000 03:01 273716     /bin/cat
                 2  0804c000-0804d000 rw-p 00003000 03:01 273716     /bin/cat
                 3  0804d000-0806e000 rw-p 0804d000 00:00 0          [heap]
                 4  b7b90000-b7d90000 r--p 00000000 03:01 87528      /usr/lib/locale/locale-archive
                 5  b7d90000-b7d91000 rw-p b7d90000 00:00 0
                 6  b7d91000-b7ecd000 r-xp 00000000 03:01 466875     /lib/libc-2.5.so
                 7  b7ecd000-b7ece000 r--p 0013c000 03:01 466875     /lib/libc-2.5.so
                 8  b7ece000-b7ed0000 rw-p 0013d000 03:01 466875     /lib/libc-2.5.so
                 9  b7ed0000-b7ed4000 rw-p b7ed0000 00:00 0
                10  b7eeb000-b7f06000 r-xp 00000000 03:01 402817     /lib/ld-2.5.so
                11  b7f06000-b7f08000 rw-p 0001b000 03:01 402817     /lib/ld-2.5.so
                12  bfbe3000-bfbf8000 rw-p bfbe3000 00:00 0          [stack]
                13  ffffe000-fffff000 r-xp 00000000 00:00 0          [vdso]



            編號是原文件里頭沒有的,為了說明方便,用-n參數(shù)加上去的。我們從中可以得到如下信息:
            1)  第1,2行對應的內存區(qū)是我們的程序(包括指令,數(shù)據(jù)等)
            2)  第3到12行對應的內存區(qū)是堆棧段,里頭也映像了程序引用的動態(tài)連接庫
            3)  第13行是內核空間

            總結一下:

            1) 前兩部分是用戶空間,可以從0x00000000到0xbfffffff(在測試的2.6.21.5-smp上只到bfbf8000),而內核空間從0xC0000000到0xffffffff,分別是3G和1G,所以對于每一個進程來說,共占用4G的虛擬內存空間
            2) 從程序本身占用的內存,到堆棧段(動態(tài)獲取內存或者是函數(shù)運行過程中用來存儲局部變量、參數(shù)的空間,前者是heap,后者是stack),再到內核空間,地址是從低到高的
            3) 棧頂并非0xC0000000下的一個固定數(shù)值

            結合參考資料[2]以及本小節(jié)列出的資料,可以得到這么一個比較詳細的進程內存映像表(以2.6.21.5-smp為例):
            Quote:

                                        內核空間
            0xC0000000       
                                        (program flie)程序名          #execve的第一個參數(shù)
                                        (environment)環(huán)境變量      #execve的第三個參數(shù),main的第三個參數(shù)
                                        (arguments)參數(shù)               #execve的第二個參數(shù),main的形參
                                        (stack)棧                           #自動變量以及每次函數(shù)調用時所需保存的信息都
                                               |                                #存放在此,包括函數(shù)返回地址、調用者的
                                              \|/                               #環(huán)境信息等,函數(shù)的參數(shù),局部變量都存放在此
                                               ...
                                               ^
                                                |
                                        (heap)堆                            #主要在這里進行動態(tài)存儲分配,
                                                                                  #比如malloc,new等。
                                        .bss(uninitilized data)       #沒有初始化的數(shù)據(jù)(全局變量哦)
                                        .data(initilized global data) #已經(jīng)初始化的全局數(shù)據(jù)(全局變量)   
                                        .text(Executable Instructions) #通常是可執(zhí)行指令
            0x08048000
                                                    
            0x00000000



            光看沒有任何概念,我們用gdb來看看剛才那個簡單的程序。

            Quote:

            $ gcc -g -o shellcode shellcode.c #要用gdb調試,在編譯時需要加-g參數(shù)
            $ gdb ./shellcode
            (gdb) set args arg1 arg2 arg3 arg4     #為了測試,設置幾個參數(shù)
            (gdb) l                                                   #瀏覽代碼
            1 /* shellcode.c */
            2 void main()
            3 {
            4     __asm__ __volatile__("jmp forward;"
            5     "backward:"
            6        "popl   %esi;"
            7        "movl   $4, %eax;"
            8        "movl   $2, %ebx;"
            9        "movl   %esi, %ecx;"
            10               "movl   $12, %edx;"
            (gdb) break 4                                       #在匯編入口設置一個斷點,讓程序運行后停到這里
            Breakpoint 1 at 0x8048332: file shellcode.c, line 4.
            (gdb) r                                                   #運行程序
            Starting program: /mnt/hda8/Temp/c/program/shellcode arg1 arg2 arg3 arg4

            Breakpoint 1, main () at shellcode.c:4
            4     __asm__ __volatile__("jmp forward;"
            (gdb) print $esp                                    #打印當前堆棧指針值,用于查找整個棧的棧頂
            $1 = (void *) 0xbffe1584
            (gdb) x/100s $esp+4000                     #改變后面的4000,不斷往更大的空間找
            (gdb) x/1s 0xbffe1fd9            #在 0xbffe1fd9 找到了程序名,這里是該次運行時的棧頂
            0xbffe1fd9:      "/mnt/hda8/Temp/c/program/shellcode"
            (gdb) x/10s 0xbffe17b7         #其他環(huán)境變量信息
            0xbffe17b7:      "CPLUS_INCLUDE_PATH=/usr/lib/qt/include"
            0xbffe17de:      "MANPATH=/usr/local/man:/usr/man:/usr/X11R6/man:/usr/lib/java/man:/usr/share/texmf/man"
            0xbffe1834:      "HOSTNAME=falcon.lzu.edu.cn"
            0xbffe184f:      "TERM=xterm"
            0xbffe185a:      "SSH_CLIENT=219.246.50.235 3099 22"
            0xbffe187c:      "QTDIR=/usr/lib/qt"
            0xbffe188e:      "SSH_TTY=/dev/pts/0"
            0xbffe18a1:      "USER=falcon"
            ...
            (gdb) x/5s 0xbffe1780    #一些傳遞給main函數(shù)的參數(shù),包括文件名和其他參數(shù)
            0xbffe1780:      "/mnt/hda8/Temp/c/program/shellcode"
            0xbffe17a3:      "arg1"
            0xbffe17a8:      "arg2"
            0xbffe17ad:      "arg3"
            0xbffe17b2:      "arg4"
            (gdb) print init    #打印init函數(shù)的地址,這個是/usr/lib/crti.o里頭的函數(shù),做一些初始化操作
            $2 = {<text variable, no debug info>} 0xb7e73d00 <init>
            (gdb) print fini     #也在/usr/lib/crti.o中定義,在程序結束時做一些處理工作
            $3 = {<text variable, no debug info>} 0xb7f4a380 <fini>
            (gdb) print _start  #在/usr/lib/crt1.o,這個才是程序的入口,必須的,ld會檢查這個
            $4 = {<text variable, no debug info>} 0x8048280 <__libc_start_main@plt+20>
            (gdb) print main   #這里是我們的main函數(shù)
            $5 = {void ()} 0x8048324 <main>



            補 充:在進程的內存映像中你可能看到諸如init,fini,_start等函數(shù)(或者是入口),這些東西并不是我們自己寫的啊?為什么會跑到我們的代碼里 頭呢?實際上這些東西是鏈接的時候gcc默認給連接進去的,主要用來做一些進程的初始化和終止的動作。更多相關的細節(jié)可以看看本節(jié)參考資料[1][2], 如果想了解鏈接(ld)的具體過程,可以看看本節(jié)參考資料[3]。

            上面的操作對堆棧的操作比較少,下面我們用一個例子來演示棧在內存中的情況。

            本節(jié)參考資料:

            [1] 如何獲取當前進程之靜態(tài)影像文件
            http://edu.stuccess.com/KnowCenter/Unix/13/hellguard_unix_faq/00000089.htm
            [2] "The Linux Kernel Primer", P234, Figure 4.11
            [3] 《Unix環(huán)境高級編程編程》第7章 "UnIx進程的環(huán)境", P127和P133
            [4] C/C++程序編譯步驟詳解
            http://www.xxlinux.com/linux/article/development/soft/20070424/8267.html
            [5]  ELF: From The Programmer's Perspective
            http://linux.jinr.ru/usoft/WWW/www_debian.org/Documentation/elf/elf.html
            [6] GNU-ld連接腳本 Linker Scripts
            http://womking.bokee.com/5967668.html

            1.7 棧在內存中的組織

            這 一節(jié)主要介紹一個函數(shù)被調用時,參數(shù)是如何傳遞的,局部變量是如何存儲的,它們對應的棧的位置和變化情況,從而加深對棧的理解。在操作時發(fā)現(xiàn)和參考資料的 結果不太一樣(參考資料中沒有edi和esi相關信息,再第二部分的一個小程序里頭也沒有),可能是gcc版本的問題或者是它對不同源代碼的處理不同。我 的版本是4.1.2(可以通過gcc --version查看)。

            先來一段簡單的程序,這個程序除了做一個加法操作外,還復制了一些字符串。



            Code:

            [Ctrl+A Select All]



            上面這個代碼沒有什么問題,編譯執(zhí)行一下:

            Quote:

            $ make testshellcode
            cc     testshellcode.c   -o testshellcode
            $ ./testshellcode
            sum = 6



            下面調試一下,看看在調用func后的棧的內容。

            Quote:

            $ gcc -g -o testshellcode testshellcode.c          #為了調試,需要在編譯時加-g選項
            $ gdb ./testshellcode              #啟動gdb調試
            ...
            (gdb) set logging on               #如果要記錄調試過程中的信息,可以把日志記錄功能打開
            Copying output to gdb.txt.
            (gdb) l main                             #列出源代碼
            20
            21              return sum;
            22      }
            23
            24      int main()
            25      {
            26              int sum;
            27
            28              sum = func(1, 2, 3);
            29
            (gdb) break 28       #在調用func函數(shù)之前讓程序停一下,以便記錄當時的ebp(基指針)
            Breakpoint 1 at 0x80483ac: file testshellcode.c, line 28.
            (gdb) break func     #設置斷點在函數(shù)入口,以便逐步記錄棧信息
            Breakpoint 2 at 0x804835c: file testshellcode.c, line 13.
            (gdb) disassemble main   #反編譯main函數(shù),以便記錄調用func后的下一條指令地址
            Dump of assembler code for function main:
            0x0804839b <main+0>:    lea    0x4(%esp),%ecx
            0x0804839f <main+4>:    and    $0xfffffff0,%esp
            0x080483a2 <main+7>:    pushl  0xfffffffc(%ecx)
            0x080483a5 <main+10>:   push   %ebp
            0x080483a6 <main+11>:   mov    %esp,%ebp
            0x080483a8 <main+13>:   push   %ecx
            0x080483a9 <main+14>:   sub    $0x14,%esp
            0x080483ac <main+17>:   push   $0x3
            0x080483ae <main+19>:   push   $0x2
            0x080483b0 <main+21>:   push   $0x1
            0x080483b2 <main+23>:   call   0x8048354 <func>
            0x080483b7 <main+28>:   add    $0xc,%esp
            0x080483ba <main+31>:   mov    %eax,0xfffffff8(%ebp)
            0x080483bd <main+34>:   sub    $0x8,%esp
            0x080483c0 <main+37>:   pushl  0xfffffff8(%ebp)
            0x080483c3 <main+40>:   push   $0x80484c0
            0x080483c8 <main+45>:   call   0x80482a0 <printf@plt>
            0x080483cd <main+50>:   add    $0x10,%esp
            0x080483d0 <main+53>:   mov    $0x0,%eax
            0x080483d5 <main+58>:   mov    0xfffffffc(%ebp),%ecx
            0x080483d8 <main+61>:   leave
            0x080483d9 <main+62>:   lea    0xfffffffc(%ecx),%esp
            0x080483dc <main+65>:   ret
            End of assembler dump.
            (gdb) r        #運行程序
            Starting program: /mnt/hda8/Temp/c/program/testshellcode

            Breakpoint 1, main () at testshellcode.c:28
            28              sum = func(1, 2, 3);
            (gdb) print $ebp     #打印調用func函數(shù)之前的基地址,即Previous frame pointer。
            $1 = (void *) 0xbf84fdd8
            (gdb) n                   #執(zhí)行call指令并跳轉到func函數(shù)的入口

            Breakpoint 2, func (a=1, b=2, c=3) at testshellcode.c:13
            13              int sum = 0;
            (gdb) n      
            16              sum = a + b + c;
            (gdb) x/11x $esp  #打印當前棧的內容,可以看出,地址從低到高,注意標記有藍色和紅色的值
                                         #它們分別是前一個棧基地址(ebp)和call調用之后的下一條指令的指針(eip)
            0xbf84fd94:     0x00000000      0x00000000      0x080482e0      0x00000000
            0xbf84fda4:     0xb7f2bce0      0x00000000      0xbf84fdd8      0x080483b7
            0xbf84fdb4:     0x00000001      0x00000002      0x00000003
            (gdb) n       #執(zhí)行sum = a + b + c,后,比較棧內容第一行,第4列,由0變?yōu)?
            18              memset(buffer, '\0', BUF_SIZE);
            (gdb) x/11x $esp
            0xbf84fd94:     0x00000000      0x00000000      0x080482e0      0x00000006
            0xbf84fda4:     0xb7f2bce0      0x00000000      0xbf84fdd8      0x080483b7
            0xbf84fdb4:     0x00000001      0x00000002      0x00000003
            (gdb) n      
            19              memcpy(buffer, STR_SRC, sizeof(STR_SRC)-1);
            (gdb) x/11x $esp #緩沖區(qū)初始化以后變成了0
            0xbf84fd94:     0x00000000      0x00000000      0x00000000      0x00000006
            0xbf84fda4:     0xb7f2bce0      0x00000000      0xbf84fdd8      0x080483b7
            0xbf84fdb4:     0x00000001      0x00000002      0x00000003
            (gdb) n
            21              return sum;
            (gdb) x/11x $esp      #進行copy以后,這兩列的值變了,大小剛好是7個字節(jié),最后一個字節(jié)為'\0'
            0xbf84fd94:     0x00000000      0x41414141      0x00414141      0x00000006
            0xbf84fda4:     0xb7f2bce0      0x00000000      0xbf84fdd8      0x080483b7
            0xbf84fdb4:     0x00000001      0x00000002      0x00000003
            (gdb) c
            Continuing.
            sum = 6

            Program exited normally.
            (gdb) quit



            從上面的操作過程,我們可以得出大概的棧分布(func函數(shù)結束之前)如下:

            Quote:

            低地址(棧頂方向)
            地址               值(hex)             符號或者寄存器
            ---------------------------------------------------------------------------------------
            0xbf84fd98   0x41414141      buf[0]   #可以看出little endian(小端,重要的數(shù)據(jù)在前面)
            0xbf84fd9c   0x00414141      buf[1]
            0xbf84fda0   0x00000006      sum     #可見這上面都是func函數(shù)里頭的局部變量
            ----------------------------------------------------------------------------------------
            0xbf84fda4   0xb7f2bce0       esi, 源索引指針,可以通過產(chǎn)生中間代碼查看,貌似沒什么作用
            0xbf84fda8   0x00000000      edi, 目的索引指針
            0xbf84fdac    0xbf84fdd8       ebp,調用func之前的棧的基地址,以便調用函數(shù)結束之后恢復
            0xbf84fdb0   0x080483b7      eip,調用func之前的指令指針,以便調用函數(shù)結束之后繼續(xù)執(zhí)行
            -----------------------------------------------------------------------------------------
            0xbf84fdb4   0x00000001       a,第一個參數(shù)
            0xbf84fdb8   0x00000002       b,第二個參數(shù)
            0xbf84fdbc   0x00000003        c,第三個參數(shù),可見參數(shù)是從最后一個開始壓棧的
            高地址(棧底方向)



            先說明一下edi和esi的由來(在上面的調試過程中我們并沒有看到),是通過產(chǎn)生中間匯編代碼分析得出的。
            Quote:

            $ gcc -S testshellcode.c



            在產(chǎn)生的testshellcode.s代碼里頭的func部分看到push ebp之后就push了edi和esi。但是搜索了一下代碼,發(fā)現(xiàn)就這個函數(shù)里頭引用了這兩個寄存器,所以保存它們沒什么用,刪除以后編譯產(chǎn)生目標代碼后證明是沒用的。

            Quote:

            $ cat testshellcode.s
            ...
            func:
                    pushl   %ebp
                    movl    %esp, %ebp
                    pushl   %edi
                    pushl   %esi
            ...
                     popl    %esi
                    popl    %edi
                    popl    %ebp
            ...



            下面就不管這兩部分(edi和esi)了,主要來分析和函數(shù)相關的這幾部分在棧內的分布:

            1、函數(shù)局部變量,在靠近棧頂一端
            2、調用函數(shù)之前的棧的基地址(ebp, Previous Frame Pointer),在中間靠近棧頂方向
            3、調用函數(shù)指令的下一條指令地址 (eip),在中間靠近棧底的方向
            4、函數(shù)參數(shù),在靠近棧底的一端,最后一個參數(shù)最先入棧

            到這里,函數(shù)調用時的相關內容在棧內的分布就比較清楚了,在具體分析緩沖區(qū)溢出問題之前,我們再來看一個和函數(shù)關系很大的問題,即函數(shù)返回值的存儲問題:函數(shù)的返回值存放在寄存器eax中[2]。

            先來看這段代碼:



            Code:

            [Ctrl+A Select All]



            編 譯運行后,可以看到返回值為1,剛好是我們在func函數(shù)中mov到eax中的“立即數(shù)”1,因此很容易理解返回值存儲在eax中的事實,如果還有疑慮, 可以再看看匯編代碼。在函數(shù)返回之后,eax中的值當作了printf的參數(shù)壓入了棧中,而在源代碼中我們正是把func的結果作為printf的第二個 參數(shù)的。

            Quote:

            $ make test_return
            cc     test_return.c   -o test_return
            $ ./test_return
            the return of func: 1
            $ gcc -S test_return.c
            $ cat test_return.s
            ...
                    call    func
                    subl    $8, %esp
                    pushl   %eax      #這個是printf的第二個參數(shù),把func的返回值壓入了棧底
                    pushl   $.LC0     #這個是printf的第一個參數(shù)the return of func: %d\n
                    call    printf
            ...



            對于系統(tǒng)調用,返回值也存儲在eax寄存器中。

            (由于BLOG空間限制,其他內容見下一部分,主要包括緩沖區(qū)溢出和緩沖區(qū)注入實例)
          3. posted on 2008-03-14 15:29 隨意門 閱讀(2019) 評論(0)  編輯 收藏 引用

            AV无码久久久久不卡蜜桃| 精品国产一区二区三区久久久狼| 久久久精品人妻一区二区三区蜜桃 | 久久er国产精品免费观看2| 国产成人精品久久亚洲| 91精品婷婷国产综合久久| 久久精品草草草| 久久久久久极精品久久久| 91精品国产91热久久久久福利 | 亚洲性久久久影院| 久久一区二区三区免费| 久久精品免费全国观看国产| 欧美大战日韩91综合一区婷婷久久青草| 久久久精品久久久久久| 一极黄色视频久久网站| 亚洲日本久久久午夜精品| 中文成人无码精品久久久不卡 | 久久婷婷五月综合国产尤物app| 午夜精品久久久久9999高清| 中文字幕无码久久久| 久久午夜无码鲁丝片秋霞 | 亚洲精品乱码久久久久久久久久久久| 一本久道久久综合狠狠躁AV| 亚洲欧美精品一区久久中文字幕 | 亚洲级αV无码毛片久久精品| 久久99精品久久久久婷婷| 97久久国产亚洲精品超碰热 | 亚洲天堂久久久| 国产成人久久精品一区二区三区 | 大香伊人久久精品一区二区| 亚洲午夜久久久久久久久久| 丁香狠狠色婷婷久久综合| 久久中文字幕视频、最近更新| 一本一道久久综合狠狠老| 99久久99久久| 久久精品人妻中文系列| 国产精品久久久久久久| 一本久久a久久精品综合香蕉| 国产AⅤ精品一区二区三区久久| 久久午夜夜伦鲁鲁片免费无码影视| 俺来也俺去啦久久综合网|