對於這幾個月除了 OpenCore Legacy Patcher 之外,我還在忙些什麼感到好奇的人,我獲得了一個絕佳的機會,在一家當地顧問公司擔任 Mac 管理實習生。

我投入了相當多時間研究的領域之一是 macOS 虛擬機,特別是基於 Apple 的 Virtualization framework 的 Apple Silicon 虛擬機。然而,在使用 Apple 的 VM 堆疊(透過絕佳的 UTM 專案)進行大量開發和測試後不久,我發現了一個非常令人沮喪的限制:Apple Silicon 主機一次最多只能啟用 2 台 macOS 客戶端虛擬機。

這最常見的情況是出現由 Virtualization.framework 產生的這個錯誤:

虛擬機數量超過限制。已達到支援的最大作用中虛擬機數量。

造成此錯誤的主要原因來自 macOS 的 SLA(軟體授權協議),第 2.B.iii 節:

(iii) 針對您擁有或控制的、已運行 Apple 軟體的每一台 Apple 品牌電腦,安裝、使用和運行最多兩 (2) 份額外的 Apple 軟體副本或實例,或任何先前版本的 macOS 或 OS X 作業系統軟體或後續發行的 Apple 軟體,在虛擬作業系統環境中,用於:(a) 軟體開發;(b) 軟體開發期間的測試;(c) 使用 macOS Server;或 (d) 個人、非商業用途。

雖然我無法正式在單一機器上同時虛擬化超過 2 份 macOS 副本用於工作,但我仍然對找出 Apple 在 macOS 中嵌入這些檢查的位置,以及愛好者和研究人員是否能啟用支援超過 2 台作用中 macOS 虛擬機的能力感興趣。

一開始,我最初認為這個限制是基於使用者空間的,因此會嵌入在 `/System/Library/Frameworks/Virtualization.framework` 的某處。由於 macOS Big Sur 的 dyld 合併框架,我們需要手動提取框架或使用 Hopper Disassembler 等工具來載入嵌入在 dyld 共用快取中的特定二進位檔。

透過這個方法,我能夠更仔細地檢查框架。然而,經過數小時的研究,我未能找到 Apple 設定 VM 限制的地方。最多只能確定錯誤訊息是由框架產生的,但在使用者空間本身,Apple 並沒有定義硬編碼的 2 VM 限制……

在 Hack Different Discord 伺服器上的 jevinskie 提供提示後,我得知 Apple 的客戶端限制是實作在 XNU(macOS 核心)的閉源部分。雖然我沒有任何字串可以參考,但我知道 Intel 核心不會有相同的程式碼。因此,透過對 Intel 和 Apple Silicon 核心的函式和字串進行比較(這並非易事),我發現主要的 VM 堆疊位於 `hv_vm_*` 下。

將 macOS Sonoma Beta 4 (23A5301h) 的開發核心載入 IDA 中,我找到了 VM 堆疊的初始化程式碼:`hv_init()`:

這裡我們可以看到 Apple 如何處理 VM 限制:使用 `int hv_apple_isa_vm_quota` 變數,核心會在啟動/停止新的虛擬機時遞減/遞增該變數:

還有一些有趣的地方,兩個新的啟動參數:`hypervisor=` 和 `hv_apple_isa_vm_quota=`。

前者是一個簡單的閘道檢查,用於後者,後者更有趣:`hv_apple_isa_vm_quota=` 可以覆寫核心中的 VM 限制!

然而,經過一些進一步的研究,我發現這個邏輯在發行版核心中並不相同。相反地,Apple 將 `hypervisor` 啟動參數替換為透過系統完整性保護 (System Integrity Protection) 進行的 `AppleInternal` 檢查:

為了保留我僅存的理智,我們將採用第一種方法。所以現在我的下一個挑戰是:在我的 MacBook Pro 上啟動一個開發核心。

要建置一個開發核心集合,我們需要從 Apple 開發者網站下載適當的 Kernel Debug Kit (KDK)。請注意,KDK 必須與主機匹配,否則在核心和 kext 連結以及啟動過程中都可能出現問題。

下載 KDK 磁碟映像並安裝嵌入的套件後,接下來檢查您的 Mac 使用的核心類型:

在 M2 Pro MacBook Pro (Mac14,9) 上,這將返回 T6020。在其他 CPU 型號上,特別是不同世代,例如 M1 與 M2,核心變體將會不同:

現在我們可以開始建置我們的核心了!

請確保調整您下方的呼叫以分別符合您的主機。

這將在您的主目錄中建立一個 `VirtualMachine.kc` 檔案。請記住這個路徑,因為我們需要從 recoveryOS 存取它。

最後關閉您的 Mac,然後透過按住電源按鈕並選擇「Option」來啟動進入恢復模式:

接下來,授權使用者,然後從選單列選擇「Utilities」->「Terminal」。在這裡我們將為我們的機器設定一些策略:

重新啟動後,您可以在終端機中驗證此設定是否已套用:

一切準備就緒後,您現在需要取得任何使用 Virtualization.framework 的虛擬化解決方案。一些例子包括:

現在我們可以啟動我們的虛擬機了!以下是我在我的 M2 Pro MacBook Pro 上同時運行 9 台 macOS 虛擬機,並且仍然可用於測試!

(這也是我第一次聽到這台機器的風扇啟動,所以我們知道物有所值;p)

看來 macOS 12 Monterey 隨同 Virtualization 堆疊一起加入了這個啟動參數。正如我們在 Sonoma 核心中所見,即使在 Monterey 中,`AppleInternal` 檢查仍然存在。看來 Apple 在 XNU 中仍然隱藏著許多秘密。

使用自訂核心集合搭配 Apple Silicon 時,有一些不幸的缺點。最大的缺點是無法再進行無縫的作業系統更新。您仍然可以安裝更新,但在更新完成後,您的機器將會出錯。

要修復此問題,您需要將機器恢復到預設的核心集合。

要重設,您只需在 recoveryOS 中使用 `bputil` 建立一個新的啟動策略。這可以是完全安全(`--full-security`)或任何其他組合(例如 `--disable-boot-args-restriction`)。一旦您在 recoveryOS 中運行此命令並重新啟動,預設核心應該就會生效。

總體而言,這是一次非常有趣的研究之旅,我很高興能夠找出 Apple 如何實施這個限制。此外,我非常感謝,儘管這是一個不受支援的使用案例,CoreOS 的 Virtualization 團隊仍然為愛好者提供了繞過此限制的選項(即使沒有文件說明或操作不便)。

我未來考慮的一些改進(雖然不太可能實現):

希望這個部落格文章能讓社群覺得有趣,我的下一個旅程可能會是看看是否可能實現 Apple Silicon 虛擬機的 DEP Enrollment/Serial Number 覆寫。不過可能不會像這篇文章這麼幸運;p

部落格以前致力於我的漫談,包括逆向工程 macOS 內部、OS 修改以及記錄我糟糕的工作。