青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

興海北路

---男兒仗劍自橫行
<2008年4月>
303112345
6789101112
13141516171819
20212223242526
27282930123
45678910

統計

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

常用鏈接

留言簿(6)

隨筆分類

隨筆檔案

收藏夾

全是知識啊

搜索

  •  

最新評論

閱讀排行榜

評論排行榜

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

  • 閑言戲語

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

  • 轉入正題

    1、Hello World

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



    Code:

    [Ctrl+A Select All]



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



    Code:

    [Ctrl+A Select All]



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

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

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

    1.1 三個比較重要的寄存器

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

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

    1.2 call,ret指令的作用分析

    call指令

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

    call backward   ==>   push eip
                                      jmp backward



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

    ret                    ==>   pop eip



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

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

    本節參考資料

    [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 什么是系統調用(以2.6.21版本和x86平臺為例)

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

    Quote:

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



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

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

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

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

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

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

    本節推薦資料;

    [1] 深入理解linux系統調用
    http://www.linuxdiyf.com/bbs/redirect.php?tid=2270&goto=lastpost&highlight=
    [2] 在Linux操作系統下如何截獲系統調用(如果無法訪問,可以看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下用來做目標文件、可執行文件和共享庫的一種文件格式,它有專門的標準(見本節參考資料)。這里簡單描述ELF的格式。

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

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

    ELF文件的大體結構:

    Quote:

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



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

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

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

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

    對于ELF文件的分析,建議使用file, size, readelf,objdump,strip,objcopy,gdb,nm等工具,如果有興趣,可以考慮閱讀一下本節參考資料[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)和節區(section)的關系,即各個段包含哪些節區。
    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來給可執行文件“減肥”,刪除無用信息
    $ size shellcode                               #“減肥”后效果“明顯”,對于嵌入式系統應該有很大的作用
       text    data     bss     dec     hex filename
        783     256       4    1043     413 shellcode
    $ objdump -s -j .interp shellcode   #這個主要工作是反編譯,不過用來查看各個節區也很厲害

    shellcode:     file format elf32-i386

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



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

    本節參考資料:
    [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 程序執行基本過程

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

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

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

    現在用strace來跟蹤一下程序執行過程中用到的系統調用。
    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



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

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

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

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

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

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

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

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

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

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


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

    本節參考資料;

    [1] 應用程序在Linux上是如何執行的
    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/



    看到沒?每次都不一樣,這樣我們通過cat /proc/self/maps就可以看到cat程序執行時的內存映像了。
    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參數加上去的。我們從中可以得到如下信息:
    1)  第1,2行對應的內存區是我們的程序(包括指令,數據等)
    2)  第3到12行對應的內存區是堆棧段,里頭也映像了程序引用的動態連接庫
    3)  第13行是內核空間

    總結一下:

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

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

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



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

    Quote:

    $ gcc -g -o shellcode shellcode.c #要用gdb調試,在編譯時需要加-g參數
    $ gdb ./shellcode
    (gdb) set args arg1 arg2 arg3 arg4     #為了測試,設置幾個參數
    (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         #其他環境變量信息
    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函數的參數,包括文件名和其他參數
    0xbffe1780:      "/mnt/hda8/Temp/c/program/shellcode"
    0xbffe17a3:      "arg1"
    0xbffe17a8:      "arg2"
    0xbffe17ad:      "arg3"
    0xbffe17b2:      "arg4"
    (gdb) print init    #打印init函數的地址,這個是/usr/lib/crti.o里頭的函數,做一些初始化操作
    $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函數
    $5 = {void ()} 0x8048324 <main>



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

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

    本節參考資料:

    [1] 如何獲取當前進程之靜態影像文件
    http://edu.stuccess.com/KnowCenter/Unix/13/hellguard_unix_faq/00000089.htm
    [2] "The Linux Kernel Primer", P234, Figure 4.11
    [3] 《Unix環境高級編程編程》第7章 "UnIx進程的環境", 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 棧在內存中的組織

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

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



    Code:

    [Ctrl+A Select All]



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

    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函數之前讓程序停一下,以便記錄當時的ebp(基指針)
    Breakpoint 1 at 0x80483ac: file testshellcode.c, line 28.
    (gdb) break func     #設置斷點在函數入口,以便逐步記錄棧信息
    Breakpoint 2 at 0x804835c: file testshellcode.c, line 13.
    (gdb) disassemble main   #反編譯main函數,以便記錄調用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函數之前的基地址,即Previous frame pointer。
    $1 = (void *) 0xbf84fdd8
    (gdb) n                   #執行call指令并跳轉到func函數的入口

    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       #執行sum = a + b + c,后,比較棧內容第一行,第4列,由0變為6
    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 #緩沖區初始化以后變成了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個字節,最后一個字節為'\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函數結束之前)如下:

    Quote:

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



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

    $ gcc -S testshellcode.c



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

    Quote:

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



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

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

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

    先來看這段代碼:



    Code:

    [Ctrl+A Select All]



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

    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的第二個參數,把func的返回值壓入了棧底
            pushl   $.LC0     #這個是printf的第一個參數the return of func: %d\n
            call    printf
    ...



    對于系統調用,返回值也存儲在eax寄存器中。

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

    青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <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>
            国产精品第一区| 欧美经典一区二区| 午夜激情综合网| 欧美激情中文不卡| 国产午夜亚洲精品理论片色戒| 伊人成人在线| 欧美一区2区三区4区公司二百| 亚洲二区在线观看| 在线午夜精品自拍| 欧美亚洲网站| 亚洲区一区二区三区| 欧美一区二区三区视频免费播放| 亚洲久色影视| 久久综合国产精品| 国产又爽又黄的激情精品视频| 亚洲在线观看| 亚洲精品1区2区| 免费国产一区二区| 在线欧美日韩| 久久深夜福利免费观看| 亚洲综合丁香| 国产精品久久久久久久午夜片| 亚洲人成网在线播放| 欧美国产日韩视频| 久久综合给合| 99国产精品久久久久久久久久| 亚洲第一页中文字幕| 久久精品欧美日韩| 激情综合在线| 欧美成人国产一区二区| 久久综合中文色婷婷| 久久永久免费| 亚洲国产精品免费| 亚洲国产精品成人精品| 欧美黄免费看| 亚洲视频免费观看| 在线亚洲激情| 国产麻豆视频精品| 久久久99爱| 久久亚洲精品网站| 亚洲成色777777在线观看影院| 久久精品国产精品亚洲| 亚洲女女女同性video| 国产精品一区二区视频 | 国产精品国色综合久久| 亚洲一区二区精品在线| 夜夜嗨av一区二区三区四季av | 久久精品一区二区国产| 精品51国产黑色丝袜高跟鞋| 欧美国产丝袜视频| 欧美视频一区二区三区…| 欧美在现视频| 久久综合久久88| 一本色道久久综合一区| 亚洲午夜在线| 在线观看日韩| 亚洲日本理论电影| 欧美精品亚洲一区二区在线播放| 中文在线不卡| 欧美亚洲在线视频| 亚洲人成网站999久久久综合| 一区二区三区四区五区在线 | 亚洲欧洲一区二区三区久久| 这里是久久伊人| 亚洲国产精品成人精品 | 国产美女精品| 欧美国产欧美亚洲国产日韩mv天天看完整 | 亚洲电影专区| 国产精品色婷婷久久58| 欧美黄色免费网站| 国产日本欧洲亚洲| 亚洲国产影院| 一区二区精品在线观看| 好看的日韩视频| 99ri日韩精品视频| 国内视频一区| 一区二区三区欧美日韩| 亚洲黄色视屏| 久久精品欧美日韩精品| 性色一区二区三区| 欧美亚一区二区| 亚洲精品免费看| 蜜臀av在线播放一区二区三区| 欧美中文字幕在线| 国产精品福利在线观看网址| 亚洲国产精品福利| 精品不卡一区二区三区| 亚洲免费在线看| 亚洲免费影视| 国产精品a久久久久| 牛人盗摄一区二区三区视频| 国产乱理伦片在线观看夜一区| 亚洲欧洲一区二区在线播放| 国产亚洲精品久久久久动| 洋洋av久久久久久久一区| 妖精成人www高清在线观看| 另类综合日韩欧美亚洲| 久久免费高清| 国产一区二区黄| 欧美一区二视频| 久久久.com| 精品成人a区在线观看| 欧美在线观看一区二区| 久久高清一区| 国产一二三精品| 亚洲欧美日韩综合国产aⅴ| 欧美一区二区大片| 国产欧美成人| 欧美在线观看一区二区| 久久久久88色偷偷免费| 狠狠久久五月精品中文字幕| 久久精品道一区二区三区| 久久琪琪电影院| 影音先锋亚洲精品| 久久漫画官网| 亚洲精品国产精品国自产在线 | 中文欧美字幕免费| 国产伦精品一区二区三区免费 | 91久久精品久久国产性色也91 | 日韩一级片网址| 亚洲欧美三级伦理| 伊人色综合久久天天| 欧美精品激情在线| 午夜久久影院| 欧美国产日韩一区二区| 亚洲综合视频在线| 国产精品系列在线播放| 午夜精品一区二区三区在线播放| 欧美激情亚洲视频| 亚洲图片你懂的| 影音先锋欧美精品| 国产精品高清在线观看| 性欧美1819性猛交| 亚洲国产精品成人| 亚洲人成在线免费观看| 浪潮色综合久久天堂| 久久久蜜桃精品| 久久久午夜视频| 免费日韩av片| 亚洲综合大片69999| 欧美国产三区| 亚洲国产99| 午夜精品视频在线观看一区二区| 欧美大片免费久久精品三p| 国产一区二区三区久久| 欧美亚洲视频在线观看| 99在线热播精品免费| 中文精品在线| 国产乱码精品一区二区三区五月婷| 亚洲视频 欧洲视频| 国产视频欧美| 午夜一区二区三视频在线观看| 亚洲欧美日韩专区| 999亚洲国产精| 欧美日韩在线高清| 亚洲天堂视频在线观看| 欧美日韩精品在线播放| 欧美激情一区二区三区在线视频| 欧美亚洲免费电影| 国产一区二区三区黄| 亚洲人体影院| 亚洲影院免费| 国产日韩在线一区| 久久精品一区二区三区不卡牛牛 | 久久国产高清| 欧美电影免费观看| 99re66热这里只有精品3直播| 亚洲观看高清完整版在线观看| 欧美理论电影网| 亚洲欧美日韩国产成人| 最新国产成人av网站网址麻豆| 国产一区视频网站| 久久视频在线免费观看| 欧美成人国产一区二区| 国产日韩成人精品| 欧美成人免费va影院高清| 99国产精品久久久久久久久久| 亚洲免费一在线| 性欧美精品高清| 亚洲精品视频在线| 国产欧美一区二区三区另类精品| 国产精品一区二区在线观看| 免费成人黄色| 亚洲欧美国产高清| 一本色道久久综合精品竹菊| 中文国产亚洲喷潮| 亚洲高清在线精品| 国产精品一区二区欧美| 国模精品娜娜一二三区| 亚洲专区免费| 亚洲国产精品久久| 亚洲精品日韩综合观看成人91| 久久久精品国产免大香伊| aⅴ色国产欧美| 欧美风情在线| 国产精品福利久久久| 欧美成人国产一区二区| 校园春色国产精品| 男人的天堂成人在线| 欧美一区二区三区免费在线看|