步步為營:如何通過USB方式攻破Android裝置
一、概述
最近,對智慧手機的物理攻擊話題引起了一定的關注,其中的一個主要關注點是:攻擊者是否能夠在裝置鎖定的狀態下,成功通過USB連線訪問儲存在裝置上的資料。本文主要描述了針對Android裝置如何成功實現此類攻擊,我們選用了Pixel 2裝置進行測試。
在Android手機啟動後,會要求使用者進行一次解鎖(在較新的裝置上,會顯示“解鎖所有功能和資料”頁面;在較老的裝置上,會顯示“要啟動Android,請輸入密碼”),裝置會儲存加密金鑰,用於解密核心記憶體中的檔案。即使螢幕在鎖定狀態,加密的檔案系統區域或分割槽仍然可以訪問。因此,如果在鎖定的裝置上成功提升許可權,那麼攻擊者不僅可以在裝置上執行任意程式碼並藉助後門訪問裝置,還可以直接訪問使用者的資料。
本文相關的漏洞報告及PoC程式碼,詳見以下連結:
https://bugs.chromium.org/p/project-zero/issues/detail?id=1583 (通過blkid輸出注入USB實現目錄遍歷)
https://bugs.chromium.org/p/project-zero/issues/detail?id=1590 (privesc zygote->init;USB攻擊鏈)
上述兩個漏洞編號為CVE-2018-9445(https://source.android.com/security/bulletin/2018-08-01 )和CVE-2018-9488(https://source.android.com/security/bulletin/2018-09-01#system ),分別在2018年8月補丁和9月補丁中實現修復。
二、攻擊面
許多Android手機都支援USB主機模式(通常需要使用OTG介面卡),該模式允許手機連線到多種型別的USB裝置(這個列表不一定完整):
1、USB儲存卡:當USB記憶棒插入Android手機時,使用者可以在手機和USB儲存卡之間複製檔案。即使裝置被鎖定,P版本之前的Android系統仍然會嘗試安裝USB儲存卡。(在報告漏洞後,新發布的Android 9中,裝置在鎖定狀態不會安裝USB儲存卡。)
2、USB鍵盤和滑鼠:Android支援使用外部輸入裝置,來替代原有的觸控式螢幕。此類裝置也會在鎖屏狀態下啟用,比如需要用這些裝置來輸入PIN碼。
3、USB乙太網介面卡:當USB乙太網介面卡連線到Android手機後,手機將會嘗試連線到有線網路,並使用DHCP獲取IP地址。此類裝置在手機鎖定狀態下同樣有效。
本文我們將關注的重點放在USB儲存卡上。如果在具有高許可權的系統元件中安裝不受信任的USB儲存卡,將會為攻擊者創造重要的攻擊面。核心必須使用包含SCSI子集的協議與USB大容量儲存裝置進行通訊,對其分割槽表進行解析,並使用核心的檔案系統實現來解釋其分割槽內容。使用者空間程式碼必須標識檔案系統型別,並指示核心將裝置安裝到某個位置。在Android上,使用者空間實現主要是在vold中(其中的一個程序與核心具有相同的許可權),它會在SELinux中使用單獨的程序,例如:確定USB儲存卡上分割槽的檔案系統型別。
三、漏洞分析:確定分割槽屬性
在插入USB儲存卡,且vold確定了裝置上的分割槽列表時,它會嘗試識別每個分割槽的三個屬性:標籤(描述分割槽的使用者可讀字串)、UUID(唯一識別符號,可用於確認USB儲存卡是否在此前已經連線過該裝置)和檔案系統型別。在最新的GPT分割槽方案中,這些屬性大多可以儲存在分割槽表之中,然而USB儲存卡傾向於使用MBR分割槽方案,該方案中不能儲存UUID和標籤。對於普通USB儲存卡,Android同時支援MBR分割槽方案和GPT分割槽方案。
為了提供標記分割槽併為其分配UUID的能力,一旦使用的是MBR分割槽方案,就會在檔案系統的頭部儲存一個包含這些屬性的欄位,允許在已經確定檔案系統、清除特定檔案系統的頭部結構的情況下,以特定的方式提取這一資訊。如果vold想要確定Label、UUID和檔案系統型別時,它會呼叫blkid_untrusted SELinux域中的/system/bin/blkid。首先,會嘗試使用Magic Number和一些啟發式識別檔案系統型別,隨後提取Label和UUID。
它會將結果以下列格式列印到stdout:
/dev/block/sda1: LABEL="<label>" UUID="<uuid>" TYPE="<type>"
然而,Android使用的blkid版本沒有對Label字串進行轉義,而負責解析blkid輸出的程式碼部分只會尋找第一次出現的UUID=”和TYPE=”。因此,如果建立一個具有特定Label的分割槽,就可以實現對UUID的控制,並得到返回到vold的字串。否則在正常情況下,它將始終是有效的UUID字串以及一組固定型別字串中的一個。
四、漏洞分析:掛載檔案系統
在使用MBR分割槽表的新USB儲存卡中,當vold確認包含核心的vfat檔案系統實現能夠掛載的vfat型別分割槽時,PublicVolume::doMount()會根據檔案系統UUID構造一個掛載路徑,然後通過嘗試確保mountpoint目錄存在並具有正確的許可權和模式,隨後嘗試掛載該目錄:
if (mFsType != "vfat") { LOG(ERROR) << getId() << " unsupported filesystem " << mFsType; return -EIO; } if (vfat::Check(mDevPath)) { LOG(ERROR) << getId() << " failed filesystem check"; return -EIO; } // Use UUID as stable name, if available std::string stableName = getId(); if (!mFsUuid.empty()) { stableName = mFsUuid; } mRawPath = StringPrintf("/mnt/media_rw/%s", stableName.c_str()); [...] if (fs_prepare_dir(mRawPath.c_str(), 0700, AID_ROOT, AID_ROOT)) { PLOG(ERROR) << getId() << " failed to create mount points"; return -errno; } if (vfat::Mount(mDevPath, mRawPath, false, false, false, AID_MEDIA_RW, AID_MEDIA_RW, 0007, true)) { PLOG(ERROR) << getId() << " failed to mount " << mDevPath; return -EIO; }
在此過程中,僅僅使用格式字串來確定裝載路徑,並不會對blkid提供的UUID字串進行任何檢查。因此,控制UUID字串的攻擊者可疑實現目錄遍歷攻擊,並使得FAT檔案系統安裝在/mnt/media_rw之外。
這意味著如果攻擊者將帶有標籤字串為UUID="../##的FAT檔案系統的USB儲存卡插入鎖定的手機中,手機會將該USB儲存卡掛載到/mnt/##。
然而,這種直接的攻擊方式有幾個嚴重的侷限性,其中的一些能夠解決,另一些則無法避免:
1、標籤字串長度問題:FAT檔案系統的標籤限制為11位元組,而攻擊者要實現注入,UUID =“字元就要佔用6個位元組,這樣一來,目錄遍歷就只剩下了5個字元,不足以到達掛載結構中的任何關鍵位置。我們將在下一節介紹如何避免這一問題。
2、SELinux對掛載點的限制:即使vold被認為是與核心具有同等許可權,但SELinux策略實際上也會對vold進行一些限制。具體而言,mounton許可權僅限於在一組經允許的標籤上使用。
3、可寫性要求:如果目標目錄不是0700許可權,並且chmod()失敗,那麼fs_prepare_dir()也將會失敗。
4、訪問vfat檔案系統的限制:安裝vfat檔案系統時,其所有檔案都被標記為u:object_r:vfat:s0。即使檔案系統安裝在載入重要程式碼或重要資料的位置,也不允許太多SELinux上下文與檔案系統進行實際的互動。例如,該限制不允許zygote和system_server進行此類操作。最重要的是,沒有足夠許可權繞過DAC檢查的程序也需要在media_rw組中。下文中的“處理SELinux:觸發兩次漏洞”一章將會詳細說明如何避免這些限制。
五、漏洞利用:USB大容量儲存
正如上一節中所說,FAT檔案系統的標籤限制為11個位元組。然而,blkid支援一系列具有更長標籤長度的其他檔案系統型別。如果使用了這些檔案系統型別,前提是必須要通過fsck對vfat檔案系統的檢查,並且還需要通過在vfat檔案系統裝載時由核心執行的檔案系統頭部檢查。Vfat核心檔案系統在分割槽開始時並不需要一個固定的Magic Value,所以理論上這種方式是可以實現的。但是,由於FAT檔案系統頭部中的幾個值對核心而言十分重要,同事blkid也會對一些超級塊(Superblocks)進行健全性檢查,因此PoC採用的是一種不同的方式。
在blkid讀取了部分檔案系統並確定檔案系統的型別、標籤和UUID之後,fsck_msdos和核心檔案系統實現將會重新讀取相同的資料,並且這些重複讀取的內容將會傳遞到儲存裝置上。當用戶控制元件直接與塊裝置進行互動時,Linux核心會快取塊裝置的頁,但__blkdev_put()會在引用自裝置的最後一個已開啟檔案關閉時,刪除與塊裝置關聯的所有快取資料。
由此,物理攻擊者可以通過附加假的儲存裝置來實現這一功能的濫用,該儲存裝置返回多次從同一位置讀取的不同資料。這樣一來,就允許我們將帶有長標籤字串的romfs頭部返回給blkid,同時向fsck_msdos和核心檔案系統實現呈現出完全正常的vfat檔案系統。
由於Linux內建支援裝置端USB,因此這一點實現起來相對簡單。Andrzej Pietrasiewicz曾發表過演講“製作你自己的USB實用工具”(https://events.static.linuxfound.org/sites/events/files/slides/LinuxConNA-Make-your-own-USB-gadget-Andrzej.Pietrasiewicz.pdf ),其演講內容可以作為我們的參考。基本上,核心都會附帶裝置端USB大容量儲存、HID裝置、乙太網介面卡等實現。我們利用相對簡單的基於偽檔案系統的配置介面,就可以定製出一個小工具,從而為連線的裝置提供一個或多個上述功能(可能會有多個例項)。我們需要一個執行Linux並支援裝置端USB的系統,在實際測試中,我們選用了Raspberry Pi Zero W。
f_mass_storage實用工具的作用在於,使用普通檔案作為後備儲存(Backing Storage)。為了能夠以互動方式響應來自Android手機的請求,我們使用FUSE檔案系統作為後備儲存,並使用direct_io選項 / FOPEN_DIRECT_IO標誌來確保核心中不會增加不需要的快取。
至此,我們已經可以實施檔案竊取攻擊,例如我們可以竊取儲存在外部儲存上的照片。針對攻擊者,在安裝USB儲存卡後,可以立即啟動com.android.externalstorage/.MountReceiver,這是SELinux域中一個允許訪問USB裝置的程序。因此,在惡意FAT分割槽通過標籤字串UUID="../../data成功在/data掛載之後,zygote程序會fork出一個具有適當SELinux上下文和組成員許可權的子程序,以允許訪問USB裝置。隨後,這個子程序從/data/dalvik-cache/載入位元組碼,最終允許我們控制具有洩露外部儲存內容的必要許可權的com.android.externalstorage。
但是,如果攻擊者不只是想要訪問照片那麼簡單,還想要訪問儲存在裝置上的聊天日誌或身份驗證憑據等內容,那這種訪問級別通常是不夠的。
六、處理SELinux:觸發兩次漏洞
在這裡,存在一個主要的限制因素。即使是可以掛載到/data下,但執行在裝置上的許多高許可權程式碼也不允許訪問已經掛載的檔案系統。然而,有一個高許可權的服務確實可以,那就是vold。
Vold實際上支援兩種型別的USB儲存卡,分別是PublicVolume和Private Volume。在這裡,我們主要關注PublicVolume。從這裡,PrivateVolume也開始變得重要。
PrivateVolume是必須使用GUID分割槽表格式化的USB儲存卡,其中必須包含型別為UUID kGptAndroidExpand(193D1EA4-B3CA-11E4-B075-10604B889DCF)的分割槽,其中包含著dm-crypt加密的ext4(或f2fs)檔案系統。相應的金鑰儲存在/data/misc/vold/expand_{partGuid}.key中,其中{partGuid}是GPT表中的分割槽GUID,作為規範化的小寫十六進位制字串。
作為攻擊者,一般是不應該以這種方式安裝ext4檔案系統的,因為手機通常不會設定這類的金鑰。即使設定了這類金鑰,你仍然需要知道正確的分割槽GUID以及正確的金鑰。然而,在這裡我們可以在/data/misc上安裝一個vfat檔案系統,並將我們自己的金鑰放在那裡,用於我們的GUID。然後,當第一個惡意USB大容量儲存裝置仍保持連線時,我們可以使用vold從第一個USB大容量儲存裝置中讀取的金鑰,連線第二個以PrivateVolume型別載入的儲存卡。
由於PrivateVolume例項使用的是ex4,所以我們可以控制檔案系統上DAC的所有權和許可權。又因為PrivateVolume是整合到系統中的,所以我們甚至可以控制該檔案系統上的SELinux標籤。
總而言之,至此為止我們可以在/data上裝載可以控制的檔案系統,並具有任意檔案許可權和任意SELinux上下文。由於此時已經能控制檔案許可權和SELinux上下文,所以就可以允許任何程序訪問檔案系統上的檔案,包括使用PROT_EXEC對它們進行對映。
七、注入Zygote
儘管沒有被列為TCB的一部分,但Zygote程序也確實足夠強大。根據設計,該程序以UID 0執行,可以任意改變其UID,並能夠執行動態SELinux轉換,轉換到system_server或是普通應用程式的SELinux上下文。
當64位zygote在系統啟動時執行時,它會從/data/dalvik-cache/arm64/system@framework@boot*.{art,oat,vdex} 載入程式碼。通常,oat檔案(包含將使用dlopen()載入的ELF庫)和vdex檔案是指向/system分割槽上檔案的符號連結,只有art檔案實際儲存在/data上。但是,我們可以將system@[email protected]和system@[email protected]符號連結指向/system(以便在不知道裝置Android版本的情況下進行一致性檢查),同時將我們的惡意ELF庫放在system@[email protected](使用合法oat檔案所具有的SELinux上下文)。
然後,在ELF庫中放置一個帶有__attribute__((constructor))的建構函式,我們可以在啟動時呼叫dlopen(),之後立即在zygote中執行程式碼。
在這裡,仍然存在一個問題。當執行攻擊時,zygote已經在執行,而這種攻擊只有在zygote啟動時才能有效。
八、產生系統崩潰
這一部分,比較令人頭疼。
當關鍵系統元件(特別是zygote或system_server)崩潰時,Android會嘗試通過重新啟動大多數使用者空間程序(包括zygote)的方式,嘗試自動恢復。在發生這種情況時,螢幕首先顯示啟動動畫,然後跳轉到鎖定螢幕。在這時,該螢幕上還會顯示“解鎖所有功能和資料”的提示,而這一提示通常僅在系統剛剛啟動後顯示。然而,此時仍然存在可用於訪問使用者資料的金鑰,我們可以通過在裝置上執行“ls /sdcard”來驗證ADB是否已開啟。
這意味著,如果我們可以以某種方式使system_server崩潰,就可以在使用者空間重啟期間,將程式碼注入到zygote中,並且能夠訪問裝置上的使用者資料。
當然,在/data上裝載我們自己的檔案系統是非常粗糙的方式,並且會產生各種各樣的問題。但令人驚訝的是,系統不會立即崩潰。儘管部分使用者介面無法使用,但大多數地方都有一些容錯機制,能保證在這種情況下不會產生太過嚴重的問題,以至於造成重啟。
經過一些嘗試,我們發現Android的網路頻寬用量跟蹤程式碼具有一個安全檢查機制:如果頻寬用量跟蹤程式碼無法寫入磁碟,並且自上次成功寫入以來已累計監測到>= 2MiB(mPersistThresholdBytes)的網路流量,就會丟擲一個致命的異常(Fatal Exception)。這意味著,如果我們可以建立某種型別的網路連線到裝置,然後傳送>= 2MiB的Ping泛洪,隨後通過等待定期回寫或更改網路介面的狀態觸發stats回寫,就可以造成裝置的重啟。
至於建立網路連線,我們有兩種方法:
1、連線到Wi-Fi網路。在Android 9之前,就算裝置被鎖定,通常也可以通過從螢幕頂部向下拖動來連線到新的Wi-Fi網路,點選Wi-Fi符號下方的下拉選單,然後選擇Wi-Fi網路的名稱。(這種方式對於受WPA保護的網路不起作用,但攻擊者其實可以將自己的Wi-Fi網路開啟。)許多裝置也只能自動連線到具有特定名稱的網路。
2、連線到乙太網網路。Android支援USB乙太網介面卡,並將自動連線到乙太網網路。
為了測試漏洞,我們手動建立了一個Wi-Fi,並用Android裝置與之連線。考慮到更加可靠的網路連線和更加輕鬆的漏洞利用,可能會有讀者更傾向於乙太網連線。
此時,我們可以在zygote上下文中執行任意原生代碼,並訪問使用者資料。但是,目前還不能讀取原始磁碟加密金鑰,直接訪問底層塊裝置或者進行RAM轉儲(由於剛剛的系統崩潰,可能有一半的轉儲內容已經丟失)。但在這裡,如果我們希望實現上述內容,就必須要升級我們的特權。
九、從Zygote到vold
即使zygote不是TCB的一部分,但它也可以訪問初始使用者名稱空間中的CAP_SYS_ADMIN功能,並且SELinux策略也允許使用此功能。Zygote將此功能用於mount()系統呼叫,並用於在不設定NO_NEW_PRIVS標誌的前提下安裝seccomp過濾器。實際上,有多種方法可以濫用CAP_SYS_ADMIN。特別是在Pixel 2上,以下幾種方法似乎是可行的:
方法1
我們可以在沒有NO_NEW_PRIVS標誌的情況下安裝seccomp過濾器,然後執行具有許可權轉換的execve()函式(SELinux exec轉換,setuid/setgid執行,或使用允許的檔案功能集執行)。Seccomp過濾器可以強制使特定的系統呼叫失敗,並返回錯誤值0。舉例來說,open()函式中的錯誤值0表明系統呼叫成功並分配了檔案描述符0。這種攻擊方式在這裡能夠成功實現,但顯得有點亂。
方法2
可以指示核心使用我們控制的檔案作為高優先順序交換裝置,然後產生儲存壓力(Memory Pressure)。一旦核心將棧或者堆頁從一個特權程序寫入交換檔案,我們就可以對換出的記憶體進行編輯,然後讓程序載入這部分記憶體。這種技術的缺點是非常難以預測,由於涉及到儲存壓力,所以可能會導致系統Kill掉我們想要保留的程序,並且有可能會破壞掉RAM中許多有用內容。同時,需要使用某種方法,來確定換出的頁是屬於哪個程序、是什麼作用。最後,此種方法需要核心支援交換(Swap)。
方法3
我們可以使用pivot_root()替換當前裝載的名稱空間或新建立的裝載名稱空間的根目錄,從而繞過本應該為mount()執行的SELinux檢查。如果我們希望影響之後提升許可權的子程序,那麼針對新的裝載名稱空間執行此操作會非常有效。如果跟檔案系統是rootfs檔案系統,則該方法不起作用。在我們的嘗試過程中,使用的是這一種方法。
在最近釋出的新版本Android中,用於建立崩潰程序轉儲的機制已經發生改變:以前是需要一個特權守護程序建立轉儲,而現在則是程序執行/system/bin/crash_dump64或/system/bin/crash_dump32中的一個助手,其中包含SELinux標籤u:object_r:crash_dump_exec:s0。目前,當任何SELinux域執行具有此類標籤的檔案時,都會觸發到crash_dump域的自動域轉換。這也意味著在輔助向量(Auxiliary Vector)中設定了AT_SECURE標誌,從而指示新程序的載入/連結器需要留意例如LD_PRELOAD這樣的環境變數。
https://android.googlesource.com/platform/system/sepolicy/+/master/private/domain.te#1 :
domain_auto_trans(domain, crash_dump_exec, crash_dump);
在報告此錯誤時,crash_dump域具有以下SELinux策略:
https://android.googlesource.com/platform/system/sepolicy/+/a3b3bdbb2fdbb4c540ef4e6c3ba77f5723ccf46d/public
/crash_dump.te: [...] allow crash_dump { domain -init -crash_dump -keystore -logd }:process { ptrace signal sigchld sigstop sigkill }; [...] r_dir_file(crash_dump, domain) [...]
該策略允許crash_dump通過ptrace()連線到幾乎任何域中的程序(如果DAC控制元件允許,則提供接管程序的能力),並允許它讀取procfs中任何程序的屬性。ptrace訪問的排除列表列出了一些TCB程序。但值得注意的是,vold不在名單上。因此,如果我們可以執行crash_dump64,並以某種方式將程式碼注入其中,那麼我們就可以接管vold。
請注意,實際ptrace()程序的能力仍由正常的Linux DAC檢查控制,而crash_dump不能使用CAP_SYS_PTRACE或CAP_SETUID。即使一個普通的應用程式成功將程式碼注入crash_dump64,但由於UID不匹配,它仍然無法利用它來攻擊系統元件。
如果你一直在仔細閱讀,你現在可能想知道我們是否可以在我們的假/data檔案系統上放置自己的二進位制檔案u:object_r:crash_dump_exec:s0並執行,從而在crash_dump域中獲得程式碼執行。實際上,並不起作用。因為在裝載USB儲存裝置時,vold會硬編碼MS_NOSUID標誌,這不僅會使得經典的setuid/setgid二進位制檔案執行被降級,還會使具有檔案功能的檔案執行被降級,而後者通常涉及自動SELinux域轉換(除非SELinux策略通過授予PROCESS2__NOSUID_TRANSITION明確地選擇退出此行為)。
要將程式碼注入crash_dump64,我們可以使用unshare()建立一個新的mount名稱空間(使用CAP_SYS_ADMIN功能),然後呼叫pivot_root()將程序的根目錄指向我們完全控制的目錄,然後執行crash_dump64。然後核心解析crash_dump64的ELF頭,讀取連結器的路徑(/system/bin/linker64),從該路徑將連結器載入到記憶體中(所以我們可以在這裡提供我們自己的連結器),並執行。
此時,就可以在crash_dump上下文中執行任意程式碼,並從那裡提升到vold。在這時,Android的安全策略會認為我們具有與核心同等的許可權。然而,如何又能獲得核心中的程式碼執行呢?我們來繼續分析。
十、從vold到init上下文
看起來,似乎沒有一種簡單的方法可以從vold進入到真正的init程序。但是,有一種進入init SELinux上下文的方法。我們檢視SELinux策略,試圖尋找一種允許轉換到init上下文的策略,最終找到了以下策略:
https://android.googlesource.com/platform/system/sepolicy/+/master/private/kernel.te:
domain_auto_trans(kernel, init_exec, init)
這意味著,如果我們能夠獲得核心環境的程式碼執行,執行一個檔案,控制該檔案的標籤為init_exec,並且在未使用MS_NOSUID裝載的檔案系統上執行,那麼該檔案就可以在init的上下文中執行。
在核心上下文中執行的唯一程式碼就是核心,因此,我們必須讓核心為我們執行檔案。在Linux中,有一種稱為“Usermode Helpers”的機制可以做到這一點:在某些情況下,核心會將操作(例如:建立coredump、將金鑰載入到核心、執行DNS查詢等)委託給使用者空間程式碼。特別是,當查詢不存在的金鑰時(例如通過request_key()函式),將會呼叫/sbin/request-key(硬編碼,只能在核心建立時使用CONFIG_STATIC_USERMODEHELPER_PATH將其更改為不同的靜態路徑)。
在vold中,我們可以在沒有MS_NOSUID的/sbin上安裝自己的ext4檔案系統,然後呼叫request_key(),核心將在init上下文中呼叫我們的請求金鑰。
漏洞利用到此就結束了。下面的章節中描述瞭如何構建以獲得核心中的程式碼執行。
十一、從init上下文到核心
從init上下文中,可以通過在顯式請求域轉換後執行適當標記的檔案來轉換到modprobe或vendor_modprobe上下文(需要注意的是,這裡是domain_trans(),而不是domain_auto_trans(),這裡是允許在exec上進行轉換的):
domain_trans(init, { rootfs toolbox_exec }, modprobe) domain_trans(init, vendor_toolbox_exec, vendor_modprobe)
modprobe和vendor_modprobe能夠從適當標記的檔案載入核心模組:
allow modprobe self:capability sys_module; allow modprobe { system_file }:system module_load; allow vendor_modprobe self:capability sys_module; allow vendor_modprobe { vendor_file }:system module_load;
Android現在就不需要核心模組的簽名了:
walleye:/ # zcat /proc/config.gz | grep MODULE CONFIG_MODULES_USE_ELF_RELA=y CONFIG_MODULES=y # CONFIG_MODULE_FORCE_LOAD is not set CONFIG_MODULE_UNLOAD=y CONFIG_MODULE_FORCE_UNLOAD=y CONFIG_MODULE_SRCVERSION_ALL=y # CONFIG_MODULE_SIG is not set # CONFIG_MODULE_COMPRESS is not set CONFIG_MODULES_TREE_LOOKUP=y CONFIG_ARM64_MODULE_CMODEL_LARGE=y CONFIG_ARM64_MODULE_PLTS=y CONFIG_RANDOMIZE_MODULE_REGION_FULL=y CONFIG_DEBUG_SET_MODULE_RONX=y
因此,我們可以執行帶有特定標籤的檔案,從而在modprobe上下文中執行程式碼,然後從那裡載入帶有特定標籤的惡意核心模組。
十二、總結
值得注意的是,這次漏洞利用跨越了兩個安全邊界:從blkid_untrusted到vold的邊界(由於vold使用blkid_untrusted提供的UUID而未檢查其是否合法),以及從zygote到TCB的邊界(通過濫用zygote的CAP_SYS_ADMIN功能)。作為開發者,必須意識到哪些位置是安全邊界,哪些位置不是。並且,在標出安全邊界後,必須要嚴格加強這些安全邊界。
最後,分析整個漏洞利用的過程,其實是vold和blkid_untrusted之間的安全邊界處產生了漏洞,並沒有起到任何緩解攻擊的作用。如果blkid程式碼在vold程序中執行,其實沒有必要序列化其輸出,這樣一來,注入假的UUID就不會再起作用。