跳至主要内容

Microdroid:Android 為什麼要在手機裡再開一台 Android

讀者定位:知道 Android 開機流程、SELinux、AVB 大概在做什麼,但沒碰過 AVF / pKVM。本文從「為什麼需要」講到「開機鏈每一步在驗什麼」,最後收在實務邊界。

姊妹篇:AVB 深入解析 講的是「整台裝置的 Verified Boot」;本文講的是「一台 VM 內部自己的 Verified Boot」——會發現兩者是同一套機制在不同尺度上重演。


一、問題的起點:Android 的隔離已經不夠用了

Android 原本的隔離模型是 UID sandbox + SELinux:每個 App 一個 UID,policy 規定誰能碰什麼。這套東西擋得住 App 之間互相偷看,但有一個前提假設:

Linux kernel 是可信的。

因為所有隔離都是 kernel 執行的。kernel 一被打下來,所有 sandbox 同時失效——UID 檢查、SELinux hook、權限模型全都是 kernel 裡的一段 if

這在 2010 年代還算可以接受,因為當時「機密資料」大多是 App 自己的資料,且真正高價值的東西(金鑰、指紋樣板)已經被丟進 TrustZone 了。但接下來兩件事同時發生:

  1. 需要保護的計算變重了。 早期 TrustZone 只要存金鑰、做簽章;現在要跑的是 on-device 機器學習推論、DRM 影像解碼、ART 的 AOT 編譯。這些東西動輒上百 MB 記憶體、要跑幾十秒,把它們塞進 TEE 是災難——TEE 的 TCB 本來要「小到可以審計」,塞進一個 ML runtime 就沒有這個性質了。
  2. kernel 攻擊面持續擴大。 vendor driver、DSP/NPU driver、各種 SoC 專屬 IP 的 kernel 介面愈來愈多。Pixel 8 GXP DSP 那個案例 就是很典型的展示:主流防護(MTE、KASLR、SELinux)全開,攻擊者換走一顆沒人看過的 DSP driver 就整條打穿到 root。

於是需求變成:我要一個「比 App sandbox 強、但比 TEE 便宜」的隔離層級,而且它的安全性不能建立在「Linux kernel 沒有洞」這個假設上。

這就是 AVF(Android Virtualization Framework)與 Microdroid 存在的理由。


二、先把這疊東西的名字分清楚

初學這塊最大的障礙是名詞太多且層次混亂。一次講清楚:

名稱它是什麼跑在哪
AVFAndroid Virtualization Framework,整套框架的統稱(不是某個程式)
pKVMprotected KVM,做隔離的 hypervisor 本體EL2
crosvmVMM(Virtual Machine Monitor),用 Rust 寫的,負責模擬 virtio 裝置、組裝磁碟映像Android host 的 userspace
VirtualizationService / virtmgrAndroid 這側的管理服務,開關 VM、配 CID、生成磁碟Android host 的 userspace
pVMprotected VM,被 pKVM 保護起來的那個 guest(一個容器概念,裡面裝什麼都行)guest
Microdroid一個極簡的 Android based guest OS,是 pVM 裡最常裝的東西guest

一句話理清關係:

pKVM 提供隔離,crosvm 提供虛擬硬體,Microdroid 是裝進去的作業系統,AVF 是這一整套的名字。

Microdroid 官方定義就是「a mini-Android OS that runs in a pVM」。它跟 pVM 不是同一件事——你可以在 pVM 裡跑別的東西(Android 15+ 的 Linux Terminal 就是在 AVF 上跑 Debian,那不是 Microdroid)。


三、關鍵設計:host kernel 被「降權」了

這是整套機制最重要的一件事,也是最容易被輕描淡寫帶過的一件事。

ARM 定義四個 exception level:

EL誰住在這
EL0userspace
EL1Linux kernel
EL2pKVM hypervisor
EL3secure firmware(TrustZone)

一般的 KVM,hypervisor 是 kernel 的一部分,所以「host kernel」和「hypervisor」是同一個信任等級。pKVM 把它們拆開:hypervisor 那一小塊留在 EL2,Android 的 Linux kernel 被留在 EL1。

結果是:Android kernel 從此不是 guest 的 TCB 的一部分。 它還是能開關 VM、還是能調度 CPU,但它讀不到 guest 的記憶體

實現手法是記憶體所有權轉移。文件的說法是:guest 的記憶體一開始屬於 host,建立 pVM 時「host donates memory pages」,然後「hypervisor transitions the ownership of those pages from the host to the pVM」。技術上是透過 stage 2 page table——pKVM 為 host 也開了一份 stage 2 轉譯,把捐出去的頁面從 host 的 stage 2 拿掉。host kernel 拿著實體位址也解不開,因為它的 stage 2 沒有那個 mapping。

要通訊怎麼辦?guest 必須主動用 hypercall 把特定頁面分享回去。所以 virtio 的資料 buffer 是走一個明確的共享視窗(doc: 「data buffers bounced through shared memory windows」),而不是 host 想讀哪就讀哪。

幾個配套機制也值得記下來:

  • MMIO guard:guest 對 MMIO 的存取會被攔下來 trap 給 hypervisor,而不是任意穿透。
  • DMA 保護必須靠 IOMMU。文件明白要求所有具備 DMA 能力的裝置都要有 IOMMU(推薦 Arm SMMU)。理由很直接:如果某顆周邊可以繞過 MMU 直接寫實體記憶體,pKVM 的 stage 2 保護就形同虛設——這正是 GXP 那個漏洞 的攻擊路徑,只是換了個尺度重演。
  • SMC 走 pKVM proxy,並實作 FF-A(Firmware Framework for Arm)。這是為了防 confused deputy:host 不能叫 TrustZone 去讀一塊它自己讀不到的 buffer。

一句話:pKVM 的價值不在「能開 VM」,而在它讓「Android kernel 被打下來」不再等於「機密資料洩漏」。這是威脅模型的改變,不是效能或功能的改變。

目前 protected VM 只支援 ARM64;x86_64 只能跑 non-protected VM(給開發測試用)。


四、Microdroid 有什麼、沒有什麼

理解 Microdroid 的最快方式是看它砍掉了什麼。官方列的不支援清單:

  • android.* 的 Java API
  • ❌ SystemServer 與 Zygote
  • ❌ 圖形 / UI
  • ❌ HAL

支援的:

  • ✅ NDK API 的子集(Android 的 libc / Bionic 的 API 全部提供)
  • ✅ Verified Boot 與 SELinux
  • ✅ 從 APK 裡載入並執行一個 binary + 它的 shared library
  • ✅ Binder RPC over vsock,以及帶隱含完整性檢查的檔案交換
  • ✅ 載入 APEX
  • ✅ 除錯功能:adb、logcat、tombstone、gdb

從這張清單可以直接推出 Microdroid 的定位:

它不是「一台小 Android」,而是「一個帶 Bionic 和 Verified Boot 的原生執行環境」。

沒有 Zygote 和 SystemServer,意思是不要想在裡面跑 Activity、拿 Context、用 Java framework。你的 payload 是一顆 native .sojava.* 的核心 API 在載入 ART APEX 之後可以用,但 android.* 那一整套不存在。

沒有 HAL,意思是碰不到硬體。這不是還沒做,是設計意圖——一個 pVM 的價值就在於它的攻擊面小,把 HAL 接進去就把攻擊面接回來了。

有 SELinux 這點滿有趣:VM 內部又跑了一次完整的 Verified Boot + SELinux。這是縱深防禦——就算 payload 本身被打下來,它在 VM 裡還是被關著。


五、磁碟映像:一台 VM 的分割表

crosvm 會組出一個 composite disk image。分割區組成(官方明述):

分割區內容
bootloader驗證並啟動 kernel
boot.imgkernel 與 init ramdisk
vendor_boot.imgVM 專屬的 kernel module,例如 virtio
super.imgsystem 與 vendor 兩個 logical partition
vbmeta.imgverified boot metadata

加上 VirtualizationService 額外生成的兩塊:

分割區內容
payload由 Android 的 APEX 與 APK 撐起來的一組分割區
instance加密分割區,持久保存 per-instance 的 verified boot 資料

看到這張表應該會有既視感:boot / vendor_boot / super / vbmeta——這就是一台 Android 手機的分割表。Microdroid 完整複製了 Android 的 Verified Boot 結構,只是尺度縮到一台 VM 裡面。

instance 那塊是整套安全模型的樞紐,第七節再談。


六、開機鏈:每一步在驗什麼

這是本文的核心。Microdroid 的開機鏈(官方順序):

逐步看重點:

6.1 pvmfw:VM 自己的 Boot ROM

pvmfw(protected VM firmware)是 guest 記憶體裡執行的第一段程式,扮演的角色等同於實體裝置上燒死在晶片裡的 Boot ROM——信任鏈的起點。

它做兩件事:驗證下一階段(bootloader)的完整性,以及透過 instance image 維持「這台 VM 的身分一致性」。後者是精髓:pvmfw 要能回答「這次開機的這台 VM,跟上次那台是不是同一台、跑的是不是同一份程式」。

6.2 Bootloader:驗 vbmeta,推導 secrets

這一步和實體手機的 ABL 做的事高度平行(可對照 ABL / AVB 逆向那篇):驗 vbmeta 的簽章、走 descriptor、決定要不要放行。

差別在多了一件事:推導 pVM secrets。金鑰不是「存」在某處給 VM 讀,而是從開機鏈的量測值一路推導出來的。這代表:

程式碼改了 → 量測值變了 → 推導出來的金鑰就變了 → 舊資料解不開。

這是 DICE 家族的核心手法。它的實務意義是:攻擊者換掉 payload 之後,那台 VM 的 sealing key 自然就不一樣了,根本讀不到原本封存的資料——不需要任何額外的「檢查有沒有被改」的邏輯,改了就自動解不開。

6.3 microdroid_manager:VM 內的 init 之後那一棒

init.rc 官方說是「similar to that of full Android but tailored to the needs of Microdroid」——結構一樣,內容砍掉大半。

microdroid_manager 是這裡最關鍵的 service,它負責:

  • 上一階段傳來的金鑰解密(instance 映像)
  • 讀出公鑰與 rollback counter
  • 透過 Binder RPC 與 Android 側的 VirtualizationService 通訊
  • 從 APK 的 config 讀出 main binary 位置,然後執行它

它是「VM 內部的驗證決策者」——公鑰比對和 rollback 檢查都在這裡收斂。

6.4 zipfuse:不解壓縮就掛載 APK

zipfuse 是 Microdroid 自己的 FUSE 檔案系統,作用是把 client 的 APK(本質上就是個 Zip)直接當成檔案系統掛起來

為什麼不解壓縮?兩個理由:

  1. 省時間省空間。APK 可能幾十上百 MB,開機時解壓縮是純浪費。
  2. 保留驗證能力。APK 有自己的簽章與 Merkle 樹(APK Signature Scheme v4 的 idsig),按需讀取時可以逐塊驗;解壓縮到某個可寫目錄之後,這個保證就斷了。

這個設計和 adb remount 用 overlayfs、Android 用 EROFS 的思路一致:能不搬資料就不搬,驗證要能跟著資料一路帶到讀取那一刻。


七、身分、金鑰與防回滾

pVM 拿到兩把不同用途的金鑰(官方明述):

金鑰用途
sealing key穩定的封存金鑰,用來加密持久化資料
attestation key簽章用,向外證明「我是誰、我跑的是什麼」

「穩定」(stable)這個詞是關鍵:同一台 VM 實例、跑同一份程式,每次開機推導出來的 sealing key 都一樣,所以資料能跨開機存活。反之,任何一項改變都會讓它變成另一把金鑰。

instance 分割區就是撐起這個「同一台」概念的東西。它是加密的,裡面存 per-instance 的 verified boot 資料——公鑰、rollback counter 等。開機流程裡 pvmfw 用它「maintain consistency」、microdroid_manager 從裡面讀 rollback counter,指的都是這塊。

rollback counter 的作用和實體裝置上的 rollback index 完全一樣:擋住「刷回一份過去合法簽署、但含已知漏洞的舊 payload」。差別只在儲存位置——實體裝置用 RPMB,Microdroid 用這塊加密分割區。

實務上的推論:這也意味著「合法更新 payload」需要被當成一件事來設計。 你的 payload 升版了,量測值就變了,sealing key 就變了,上一版封存的資料原地失效。任何要跨版本保留狀態的設計都得先想清楚遷移路徑(此段為作者依機制推論,非文件明述)。


八、通訊:vsock、Binder RPC、AuthFS

pVM 沒有網路概念,只有 vsock

vsock 用 32-bit 的 CID(context identifier)定位,官方的類比是「analogous to IP addresses」,由 VirtualizationServiceInternal 分配,再由 virtmgr 裡的 VirtualizationService 帶著這個 CID 去啟動 crosvm 子行程。

在 vsock 之上跑的是 Binder RPC。這點很值得注意:Android 把 Binder 從「kernel driver 上的 IPC」擴展成「socket 上的 RPC」,於是同一套 AIDL 介面可以跨 VM 邊界。Client 端用 RpcSession 連,Server 端用 RpcServer 提供服務。對開發者的意義是:你不用自己設計序列化協定,寫 AIDL 就好。

VM 生命週期是引用計數的:只要 client 還持有 IVirtualMachine 物件,VM 就繼續跑;所有引用消失時服務會自動關掉 VM。這個設計是為了防止「client 行程被殺掉之後留下孤兒 VM 吃資源」。

檔案交換用 AuthFS。它解決的問題是:兩端互不信任,但要交換檔案。做法是在每一次存取操作上做透明的完整性檢查(官方類比是 fs-verity)。

重點在「每一次存取」而不是「開檔時驗一次」。開檔時驗一次的模型在跨信任邊界時是無效的——對方可以在你驗完之後才改內容(TOCTOU)。逐次存取驗證才擋得住。


九、實務:怎麼跑、怎麼看

AVF 相關的執行檔都在 com.android.virt 這個 APEX 裡,vm 是主要的除錯 CLI:

# 大致長相;子命令與旗標依 Android 版本有差,先跑 --help 為準
adb shell /apex/com.android.virt/bin/vm --help
adb shell /apex/com.android.virt/bin/vm list

vm 能做的事官方描述是:從 shell 啟動 VM、看 log、關掉 VM。

Debug level 是最需要記住的一個概念。 VM 可以用 debuggable(FULL)non-debuggable(NONE) 啟動:

Debug level能做什麼用在哪
FULLadb、logcat、tombstone、gdb 都能用開發
NONE上述全部關閉出貨

這兩者的差別不是「方便程度」,是安全等級。 一台 debuggable 的 pVM 等於把 guest 內部攤開給 host 看——那正好是 pKVM 存在的目的所要防止的事。文件明確說 production deployment 應該用 non-debuggable。

由此推出一條實務原則(作者觀點):

任何在 debuggable VM 上做出的機密性驗證結果都不算數。 要驗機密性,必須跑在 NONE 上;而 NONE 上你看不到 log——所以除錯策略必須從一開始就設計,不能等到出問題才想。

Client App 這側,官方說法是「透過 AIDL API 存取 VirtualizationService,方式是直接執行 virtmgr,或引入 javalib / rustlib」。實際的 framework 類別名稱、需要的權限、vm_config.json 的完整欄位,官方這幾頁沒有列出來,請以你所用 Android 版本的 packages/modules/Virtualization/ 原始碼與 SDK 文件為準


十、邊界與常見誤解

10.1 「Microdroid 是輕量 Android,所以我的 App 邏輯搬進去就能跑」

不能。沒有 Zygote、沒有 SystemServer、沒有 android.*。你要準備的是一顆 native library,不是一個 Activity。這是移植成本的主要來源,而且是架構性的,沒有 workaround。

10.2 「開了 pVM 就安全了」

pKVM 保護的是記憶體隔離。它不保護:

  • payload 自己的邏輯漏洞(VM 裡照樣有 SELinux 關著它,就是因為預期它可能被打下來)
  • 沒有 IOMMU 的 DMA 路徑(這是硬體前提,不是軟體能補的)
  • 你自己選擇分享出去的頁面
  • host 對 VM 的可用性攻擊——host 還是能把 VM 關掉。pKVM 提供的是機密性與完整性,不是可用性

10.3 「pVM 可以取代 TEE」

不能,也不是設計目的。TEE(EL3/TrustZone)在信任等級上仍然比 pVM 高,而且有硬體綁定的 root of trust。pVM 是中間層:比 App sandbox 強、比 TEE 便宜、可以承載大型計算。這三者是階梯而不是替代關係。

10.4 「debuggable 跟 non-debuggable 只差在能不能看 log」

見第九節。差在攻擊者能不能看你的 guest。

10.5 硬體支援不是普遍的

protected VM 目前只有 ARM64,且需要平台的 pKVM 支援與 IOMMU。在寫任何依賴 pVM 的功能之前,先確認目標裝置上真的支援,並且設計好不支援時的退路。具體的偵測 property 名稱依版本而異,請查你手上那版的原始碼。


十一、一句話的心智模型

Microdroid 把「一台 Android 裝置的 Verified Boot 信任鏈」整套縮小,塞進一個 Android kernel 讀不到的記憶體區域裡。

抓住這個模型,前面所有細節都變成推論:

  • 因為是完整的信任鏈 → 才會有 bootloader / vbmeta / super 這組分割區、才會有 pvmfw 當 Boot ROM、才會有 rollback counter。
  • 因為 Android kernel 讀不到 → 才需要記憶體捐贈與 stage 2、才需要 vsock 而非共享記憶體亂讀、才需要 IOMMU 補上 DMA 這個漏洞。
  • 因為金鑰從量測值推導 → 改了程式碼就自動解不開舊資料,不需要額外的防竄改檢查。
  • 因為要縮小 → 砍掉 Zygote、UI、HAL;payload 只能是 native .so

反過來說,這個模型也直接標出它的極限:它保護的是「跑什麼」和「資料被誰看見」,不保護「跑不跑得起來」。


驗證說明

本文以下內容取自 AOSP 官方文件(2026-07-30 抓取):

以下為作者依機制所作的推論或觀點,非文件明述,引用前請自行驗證:

  • 「金鑰從量測值推導 → 改程式碼就解不開舊資料」是依 sealing key 的 stable 語意與 DICE 的一般設計推得;本文引用的三頁文件未展開 DICE / CDI 的推導細節。
  • payload 升版導致封存資料失效、因此需要設計遷移路徑(第七節末)。
  • 「debuggable VM 上的機密性驗證不算數」這條實務原則。
  • 第一節把 GXP 案例、TEE 成本、kernel 攻擊面串成 AVF 的動機,是作者的敘事框架。
  • zipfuse 不解壓縮的兩個理由(省資源、保留逐塊驗證能力)中,第二點關於 APK Signature Scheme v4 idsig 的部分是依機制推論。

本文未涵蓋(想深入的話這幾塊都要另外查你所用版本的原始碼):

  • DICE / BCC 憑證鏈的實際結構與 CDI 推導步驟
  • vm_config.json 的完整欄位、payload 的 C API 名稱、framework 類別與所需權限
  • Gunyah(部分平台使用的另一套 hypervisor)與 pKVM 的差異
  • 偵測 pVM 支援度的 property 名稱
  • 效能數據與開機時間

參考資料