Firecracker VM 逃逸:PwnEscape 第2部
Firecracker VM Escape: PwnEscape Part 2
Firecracker VM 逃逸:作者在微型 VM 中利用暫存器篡改,成功在宿主機獲得 root 許可權。
- 完整攻擊鏈:先定位宿主物理頁,逆向對映 Firecracker 程序,找到 vCPU 執行緒的暫存器幀,改寫返回地址執行自定義寫入,最終在宿主機以 root 執行。
AI 伴隨問答
針對這篇報導有疑問?可直接點選快捷問題或向 AI 追問背景脈絡與技術細節:
本文揭示 Firecracker 微型 VM 逃逸的完整鏈路,展示如何利用暫存器篡改在宿主機獲得 root 許可權,提醒雲環境安全團隊關注 VMM 逃逸風險。
Firecracker 本應將不受信任的程式碼限制在 microVM 內。 我們逃逸了它,並在主機上以 root 許可權執行程式碼。
我們發現它如何儲存暫存器、改變恢復位置,並在從 KVM 返回時執行我們選擇的呼叫。 隨後來賓恢復了臨時堆疊變更。 Firecracker 繼續執行,VM 仍可透過 SSH 存取。
PwnEscape Part 1 介紹了此工作背後的主機記憶體原語。此處我們接續鏈條的下一步:利用該存取方式在記憶體中定位 Firecracker,並從虛擬機器內部接管一個正在執行的執行緒。這是為了展示 CVE-2026-99999 在 Firecracker 環境中的例項。
00 / 選擇控制點
Firecracker 微型虛擬機器在主機上有一個程序負責管理。每個虛擬 CPU(vCPU)都有一個執行緒在該程序中。該執行緒進入 KVM 執行客機,並在需要在使用者空間處理事件時返回 Firecracker。Firecracker 在其 architecture overview 文件中說明瞭此結構。

執行虛擬 CPU 的呼叫如下:
在執行緒處於該系統呼叫時,主機核心將其儲存的使用者空間暫存器存放於名為 pt_regs 的結構中。 這些暫存器描述了 Firecracker 執行緒:其指令指標說明執行應該恢復的位置,堆疊指標說明應使用哪個使用者空間堆疊。
這使得從 KVM_RUN 返回成為一個有吸引力的目標。 若能定位並修改儲存的框架,我們就能選擇 Firecracker 在執行緒返回使用者空間時執行的程式碼。 觸發此返回的來賓操作將提供觸發條件。
我們已經透過來賓對映存取到部分主機物理記憶體。為了使用它,我們需要識別 Firecracker 程序,找到可在該程序中呼叫的程式碼,並定位其活動 vCPU 執行緒的暫存器框架。起點是一頁我們在來賓內部控制的記憶體。

圖 1。來賓識別其 Firecracker 程序,找到 vCPU 執行緒的儲存暫存器,並在返回 KVM 時重新導向執行緒。
01 / 在主機上尋找我們的來賓頁面
來賓和主機使用不同的物理位址空間。 一頁在 VM 內有一個來賓物理位址(GPA),在機器的支援中則有一個主機物理位址(HPA)。 在目標上,擴充頁表(EPT)提供兩者之間的轉譯。
我們在來賓頁面放置了一個可辨識的位元組模式,稱為 nonce。第一個解析器以該頁的已知 GPA 及其內容作為輸入。兩者共同給我們一個要轉譯的位址,以及一種檢查轉譯是否抵達我們頁面的方式。
解析器搜尋由記憶體原語覆蓋的主機物理範圍,尋找候選的 EPT 根。EPT 根是轉譯表的起點。對於每個候選,解析器跟隨來賓位址所選的條目,直到到達一個物理頁面,然後檢查該頁面是否包含 nonce。
以下片段為說明性偽程式碼,使用描述性輔助名稱。它們顯示了各階段之間傳遞的資料以及用於連結它們的檢查。
nonce 檢查將建議的轉譯與我們控制的記憶體連結。更廣泛的搜尋產生兩個候選;對巢狀頁表進行驗證後將其縮減為一個 EPT 根。遍歷該根給我們來賓頁面的主機物理位址。
我們將此結果稱為 guest_page_hpa 並將其傳遞給下一個解析器。此時我們在主機記憶體中有一個可辨識的物件。下一個任務是找出對映它的主機程序。
02 / 找尋 Firecracker 程序
Firecracker 將客戶端使用的記憶體對映到它自己的位址空間。這個對映讓我們能從客戶端頁面回推到對應的程序。
主機程序使用頁表將它們的虛擬位址轉換為實體記憶體。最高層稱為頁全域目錄(PGD)。恢復適當的 PGD 可以讓我們在 Firecracker 內部轉換位址,並透過記憶體原語讀取其程式碼與堆疊。
我們的反向對映器搜尋本機主機頁表中 guest_page_hpa 的對映。正常的頁表遍歷從虛擬位址開始,產生實體位址。在此,我們尋找已知的實體頁面的對映,然後恢復與之相關的虛擬位址與頁表根。
檢查同時在兩個方向執行:
這將主機程序的虛擬對映與透過 EPT 找到的實體頁面連結起來。反向對映器回傳一個已驗證的位址空間。我們將其根保留為 host_pgd,並用它來檢查該程序。
下一個解析器在這些對映中尋找 Firecracker 可執行檔。它識別出一個 ELF 影像,並檢查 Firecracker 的 write(2) 包裝器中的已知位元序列。這將恢復的位址空間與我們目標的二進位檔連結。
ASLR 會改變可執行檔載入的位置。ELF 影像中的位址與其對應執行時位址之間的差值即為載入偏移。對映器一旦識別出影像,就能恢復該偏移,並利用它定位攻擊所需的程式碼:
對映器將載入偏移回傳為 firecracker_base。以此值及 host_pgd 為基礎,我們能在執行中的程序中定位程式碼,將其虛擬位址轉換為主機實體記憶體,並將那裡的位元與預期二進位進行比對。
我們檢查了三個位置:將產生我們證明的 write(2) 包裝器、KVM ioctl 後的延續,以及為了順利返回所需的短 xor eax,eax; ret 序列。它們的目的在於下文構造返回路徑時會變得重要。
此時,客戶端已能檢查 Firecracker 影像及其私有對映。我們仍需要目前執行我們虛擬機器的執行緒的儲存註冊值。

圖 2. nonce 識別主機記憶體中的客戶端頁面。其對映導致一個主機頁表根,允許下一個解析器定位並驗證 Firecracker 影像。
03 / 定位實際 vCPU 框架
框架搜尋器從前置階段取得 host_pgd 和 firecracker_base,並用它們識別主機執行緒的儲存狀態。
最有用的線索是回傳位址。在 KVM_RUN 中的 vCPU 執行緒應在 Firecracker 對應的 ioctl 延續處擁有已儲存的指令指標。我們可從已驗證的二進位與載入偏移計算出該位址。
接著我們搜尋主機記憶體,尋找與該呼叫一致的 pt_regs 框架。僅靠位址相符也會捕捉到過期的堆疊內容,因此候選者必須滿足多項相關檢查:
| 已儲存狀態 | 我們檢查的專案 |
|---|---|
| 系統呼叫 | 系統呼叫的身份與進行中的狀態與一個活動的 ioctl 匹配。 |
| 請求引數 | ioctl 請求為 KVM_RUN。 |
| 檔案描述符 | 該描述符落在協助程式預期範圍內。 |
| 指令指標 | 它與已驗證的 Firecracker ioctl 延續相符。 |
| 堆疊指標 | 它透過恢復的主機 PGD 進行轉換。 |
| 段選擇子與旗標 | 他們描述了一個有效的返回到使用者空間。 |
| 呼叫者返回地址 | 它落在已驗證的 Firecracker 文本對映內。 |
堆疊檢查將框架與我們從來賓頁面恢復的相同地址空間繫結。指令和呼叫者地址將其與 Firecracker 映像繫結。系統呼叫欄位將其與執行 vCPU 的操作繫結。
搜尋剩下一個候選。其位置存為 frame_hpa,即儲存的暫存器框架的宿主實體地址。我們將其、PGD 以及載入偏移傳給最終輔助程式。
在改變記憶體之前,輔助程式重複了程式碼簽名、框架和堆疊檢查。這確保了活動階段使用與解析器識別相同的程序狀態。後續的執行與恢復檢查將確定所選框架仍在運作。

圖 3。候選暫存器框架必須與 KVM ioctl、恢復的程序頁表以及已驗證的 Firecracker 程式碼保持一致。
04 / 從 KVM 重定向返回
我們選擇了一個簡單的有效負載:讓 Firecracker 程式將可辨識的字串寫入其自身的 stderr。這將為宿主執行者提供一個可觀測的結果,來自我們已接管的程序。
字串為 N8V23M2\n,七個字元後接一個換行符。它佔用八個位元組,且可放入一個堆疊字。我們想讓 Firecracker 執行的呼叫是:
此處 2 為 Firecracker 的 stderr 檔案描述符。緩衝區指標必須指向 Firecracker 地址空間中的字串,因此我們將字串存於其私有使用者空間堆疊上。
我們在修改之前先儲存了三個堆疊字。其一存放字串;其餘兩個存放臨時呼叫序列的返回位址。接著我們改變儲存的暫存器框架,以提供包裝器地址、其引數,以及該序列的堆疊指標:
從 write 返回也需要小心。成功寫入八個位元組會回傳 8,而成功的 KVM_RUN 呼叫則回傳零。我們將 xor eax,eax; ret 的位址先放入返回序列。它在第二個位址將執行流程帶回 Firecracker 原本的 ioctl 繼續之前,先清除返回值。
結果的控制流程為:
在堆疊與儲存的暫存器準備好後,來賓寫入一個位元組到其序列埠。該操作使 vCPU 執行緒從 KVM_RUN 返回。核心還原了修改後的暫存器,Firecracker 以我們的引數進入 write 包裝器。
包裝器發出標記,返回序列清除 eax,執行緒重新加入其原始執行路徑。控制返回來賓後,輔助程式恢復了三個堆疊字。

圖 4。儲存的暫存器框架進入寫入包裝器。兩個返回位址經由返回值調整後回到原始 Firecracker 路徑。
05 / 檢查執行與恢復
宿主執行者從來賓分別捕獲了 Firecracker 的 stderr。完整輸出為:
捕獲包含恰好八個位元組,包括最後的換行符。Firecracker 仍在執行,來賓 SSH 可回應,輔助程式已恢復三個臨時堆疊字。
我們以一次不改變記憶體的全新通過檢查恢復。它使用相同的框架位址,並在觸發後重複執行暫存器、程式碼與堆疊檢查。檢查通過,並未在臨時堆疊位置發現過期標記。
這些觀察回答了三個不同的問題。來賓日誌記錄了觸發與恢復。主機擷取顯示 Firecracker 執行了選定的操作。最終驗證檢查了執行後的狀態。
| 檢查 | 已記錄結果 |
|---|---|
| Firecracker stderr | 一份標記,總共八個位元組 |
| Firecracker 程式 | PID 2936,觸發後仍存活 |
| 來賓健康 | SSH 可回應;正常運作時間 1615.72 秒 |
| 臨時堆疊狀態 | 三個原始單字皆已恢復 |
| 可執行文本與共享函式庫 | 未修改 |
| 觸發後主機核心錯誤 | 未觀察到 |
| 恢復後驗證 | 所提供的框架地址及其周邊不變式均通過 |

圖 5。來賓日誌、Firecracker stderr 擷取以及最終驗證記錄了觸發、主機執行與恢復狀態。
06 / 主機上的 Root 執行
當標記出現時,來賓已選取主機執行緒的指令指標、函式引數、堆疊資料與返回路徑。Firecracker 使用其自身許可權執行該呼叫。
目標為 Firecracker v1.16.1 在 x86-64 上,主機核心 7.0.0-1011-aws 與來賓核心 6.18.44+。我們直接以 root 身份在 systemd-run 下啟動 Firecracker,並以 taskset 固定。結果,受控呼叫以 root 身份在主機上執行。
現有的寫入包裝器為我們提供了該控制的簡潔證明。將其擴充套件至其他呼叫需要選擇與程式對映與系統呼叫政策相容的程式碼與引數。
此處使用的設定沒有 jailer。Firecracker 的 production containment 包含降權與透過名稱空間與 seccomp 限制程式。使用這些控制的部署需要對在妥協其 VMM 後可用的許可權與資源進行單獨評估。
位址發現使惡意程式能追蹤執行中的例項。EPT 解析器將來賓頁面連結到主機記憶體;反向對映器恢復主機位址空間;影像對映器定位 ASLR 下的 Firecracker;框架搜尋器辨識 vCPU 的儲存狀態。將最終階段調整至其他版本需驗證該二進位檔的程式碼簽章與返回路徑。
來賓利用對主機記憶體的存取來接管執行 VM 的執行緒。序列寫入提供了觸發,接下來的使用者空間操作屬於我們。
擷取的解析器值與產物
以下值來自保留的執行記錄。上述方法使用符號名稱以便清楚顯示每個值的角色;此表將這些名稱與記錄的位址連結。
| 值 | 記錄位址 | 進入鏈結的位置 |
|---|---|---|
guest_gpa | 0x08f03000 | 供給第一個解析器的 nonce 頁面的來賓物理位址 |
guest_page_hpa | 0x165610000 | EPT 解析器找到的該頁面的主機位址 |
host_pgd | 0x14afc2000 | 由反向對映器恢復的主機頁表根 |
firecracker_base | 0x7298c2400000 | 由影像對映器恢復的 Firecracker 載入偏移 |
frame_hpa | 0x13a5bff48 | 為活動階段選取的已儲存暫存器框架 |
記錄使用執行 ID eventpath-v23-0907self02 與啟動 ID 2b3cd5ac-2311-427f-a50a-6a79ecb8daa7。這些識別碼連結瞭解析器記錄、主機擷取與恢復後驗證。
早期的記憶體供應者也在構建記憶體通道後收到主機選定的任務與 canary 頁框編號,作為獨立驗證覆蓋。這些位址未被 EPT 解析器、反向對映器、Firecracker 對映器、框架搜尋器或最終標記輔助程式消耗。
發行套件包含最終的 helper 原始碼、JSONL 解決器紀錄、主機擷取、恢復清單、四個保留的唯讀解決器二進位檔(含來源追蹤)以及 SHA-256 清單。最終的 helper 接收解決器產生的 PGD、載入偏移量與 frame HPA。它的檢查專案涵蓋執行識別碼、地址範圍、程式碼簽章、儲存的暫存器與堆疊結構;其 dry‑run 模式會在不寫入記憶體的情況下進行驗證。
概念驗證
實體雜湊值為:
使用以下指令驗證套件與其清單:
shasum -a 256 -c SHA256SUMS