在之前的一篇文章中,我談到了Lenovo ThinkPad的系統(tǒng)管理模式(SMM)代碼中的漏洞。那時(shí),我很好奇如何處理UEFI密碼(尤其是用于保護(hù)BIOS接口的密碼)。密碼的處理特定于每個(gè)構(gòu)造函數(shù),這意味著此處說明的代碼特定于Lenovo,更確切地說特定于某些ThinkPad(這對于三個(gè)不同的ThinkPad版本通常是通用的,因此,大多數(shù)內(nèi)容可能保持不變)。
在此文章中,我先回顧一下之前的漏洞,然后解釋我如何查看Lenovo密碼。首先,我們將逆向啟動(dòng)方式以及固件中的各種密碼,然后再更深入地研究其中兩個(gè):開機(jī)密碼和BIOS密碼。在這些密碼的管理中,尚未發(fā)現(xiàn)任何漏洞,但事不宜遲,讓我們開始吧。
0x01 SMM和UEFI
UEFI是描述用于開發(fā)固件(尤其是BIOS)的標(biāo)準(zhǔn)接口集的規(guī)范。該固件是啟動(dòng)時(shí)在CPU上執(zhí)行的第一批操作之一。負(fù)責(zé)初始化硬件并進(jìn)行設(shè)置,以便操作系統(tǒng)可以啟動(dòng)。該固件存儲在計(jì)算機(jī)中存在的SPI閃存中,攻擊者破壞此固件的主要優(yōu)點(diǎn)是,可以在硬盤以外的其他地方實(shí)現(xiàn)持久性。
系統(tǒng)管理模式(SMM)是Intel CPU模式。它通常被稱為ring -2, 因?yàn)樗葍?nèi)核或系統(tǒng)管理程序更具特權(quán)。SMM擁有自己的稱為SMRAM的存儲空間,該存儲空間受到保護(hù),無法通過其他模式訪問。SMM可以被視為與ARM上的Trust Zone相似的“安全世界”。但是,其最初目標(biāo)不是提供安全函數(shù),而是處理計(jì)算機(jī)的特定要求,例如高級電源管理(APM,已由ACPI代替)。如今,它還用于保護(hù)對包含UEFI代碼的SPI Flash的寫訪問。
《英特爾手冊》中的“處理器工作模式之間的轉(zhuǎn)換”
從前面的示意圖中可以看出,可以從任何“正常”模式訪問SMM。SMM還支持16位,32位和64位,這使其成為所有其他模式的副本。當(dāng)觸發(fā)系統(tǒng)管理中斷(SMI)時(shí),將在正常模式和SMM之間進(jìn)行轉(zhuǎn)換。發(fā)生這種情況時(shí),處理器將切換到SMM:它將首先將CPU的當(dāng)前狀態(tài)保存到一個(gè)稱為“保存狀態(tài)”的存儲區(qū)(以后才能恢復(fù)該狀態(tài)是必需的),然后更改包括指令指針的上下文用于執(zhí)行SMRAM中的代碼。
基本SMRAM映射,SMBASE可能不會(huì)隨SMRAM的開始而被使用。
SMRAM是由UEFI固件保留以供SMM使用的物理RAM區(qū)域。SMRR可以保護(hù)它免受“常規(guī)”訪問,也可以保護(hù)它不受DMA訪問等等。SMBASE是一個(gè)必須在此范圍內(nèi)的地址,將用于確定在切換到SMM時(shí)必須將保存狀態(tài)存儲在哪里以及將指令指針設(shè)置在哪個(gè)位置。每個(gè)內(nèi)核只有一個(gè)SMBASE(為避免兩個(gè)內(nèi)核同時(shí)切換而彼此重寫保存的狀態(tài)),對SMRAM內(nèi)部的位置沒有限制。
存在幾種SMI,但特別是一種軟件SMI(SWSMI)對于攻擊者來說很有趣。在ioport上寫入0xb2值時(shí)將觸發(fā)SWSMI 。進(jìn)行切換后,代碼通常將搜索與ioport上寫入的值相對應(yīng)的SWSMI處理程序,這些處理程序通常以64位編寫。
最后,由UEFI固件初始化在SMM中運(yùn)行的代碼(SMRAM中的設(shè)置)。特別是,通常在UEFI引導(dǎo)的驅(qū)動(dòng)程序執(zhí)行環(huán)境(DXE)階段設(shè)置SWSMI處理程序。DXE階段由數(shù)百個(gè)驅(qū)動(dòng)程序組成,這些驅(qū)動(dòng)程序用于從硬件初始化到網(wǎng)絡(luò)堆棧實(shí)施的所有過程。
這些驅(qū)動(dòng)程序提供了位于正常模式下的一組服務(wù)(尤其是 EFI_BOOT_SERVICES和EFI_RUNTIME_SERVICES),這些服務(wù)提供了一組基本函數(shù),例如分配和對非易失性變量的訪問。
該EFI_BOOT_SERVICES還允許注冊和訪問協(xié)議。協(xié)議允許驅(qū)動(dòng)程序共享函數(shù),并由GUID標(biāo)識。實(shí)際上,由于在UEFI引導(dǎo)過程中所有內(nèi)存訪問均在物理內(nèi)存中進(jìn)行,因此協(xié)議僅將GUID與指針相關(guān)聯(lián)。這些協(xié)議中有一些是公開的并有文檔記錄(某些在UEFI規(guī)范中,有些在edk2中),而另一些則針對每個(gè)構(gòu)造函數(shù)。在DXE階段結(jié)束時(shí),固件將鎖定SMRAM,阻止對其進(jìn)行訪問,然后嘗試啟動(dòng)引導(dǎo)加載程序以過渡到OS。
0x02 漏洞分析
逆向分析
當(dāng)我開始逆向固件時(shí),我首先確定驅(qū)動(dòng)程序使用了哪個(gè)協(xié)議來注冊SWSMI處理程序。在這種情況下,他們使用EFI_SMM_SW_DISPATCH2_PROTOCOLedk2(MdePkg/Include/Protocol/SmmSwDispatch2.h)中定義的文檔并進(jìn)行了記錄。一旦確定了該協(xié)議,我便對使用該協(xié)議的所有驅(qū)動(dòng)程序進(jìn)行了簡單的二進(jìn)制搜索,然后開始逆向。
其中一個(gè)驅(qū)動(dòng)程序被命名為SmmOEMInt15,它是一個(gè)非常小的驅(qū)動(dòng)程序,僅具有21個(gè)函數(shù),其中包括一個(gè)注冊SWSMI的函數(shù):
上面的代碼片段執(zhí)行以下操作:
1.使用()包含unk_protocolGUID ff052503-1af9-4aeb-83c4-c2d4ceb10ca3(UnkProtocolGuid) 檢索未記錄的協(xié)議(), 其中包含一些SMM服務(wù)。
2.使用新的未知GUID eee19e05-079a-4d17-8f46-cf811260db26(&swsmi_oemint15_guid)調(diào)用第一個(gè)函數(shù), 并將其用于檢索數(shù)字(swsmi_number)。
3.在swsmi_number在前面的步驟中檢索隨后處于后來為了注冊SWSMI處理程序使用的上下文的設(shè)置,這是0xb2必須的IOPORT被寫入的值。
4.最后,EFI_SMM_SW_DISPATCH2_PROTOCOL用于將函數(shù)注冊swsmi_handler_unk_func為SWSMI處理程序。
此代碼的第一個(gè)問題是使用未知協(xié)議來獲取SWSMI編號。幾個(gè)(但不是全部)注冊SWSMI的驅(qū)動(dòng)程序使用了該協(xié)議,因此在執(zhí)行任何測試之前必須將其逆向。
SystemSwSmiAllocatorSmm
通過搜索未公開協(xié)議(ff052503-1af9-4aeb-83c4-c2d4ceb10ca3)的GUID,很容易找到實(shí)現(xiàn)該協(xié)議的驅(qū)動(dòng)程序:SystemSwSmiAllocatorSmm。該驅(qū)動(dòng)程序也非常簡單,函數(shù)更少。
該驅(qū)動(dòng)程序的第一步是在正常情況下分配多個(gè)緩沖區(qū),其中一個(gè)特別有趣,因?yàn)樗言贕UID中注冊為 配置表7E791691-5752-4392-B888-EFF9C74F5D77。所有驅(qū)動(dòng)程序都可以訪問配置表,并將配置表關(guān)聯(lián)到GUID,它們通常用于將數(shù)據(jù)從一個(gè)驅(qū)動(dòng)程序傳遞到另一驅(qū)動(dòng)程序,而協(xié)議則用于傳遞函數(shù),實(shí)際上它們都將GUID與指針關(guān)聯(lián)。
一旦完成了這些初始步驟和一些初始化,驅(qū)動(dòng)程序就會(huì)注冊我們感興趣的協(xié)議,我SystemSwsmiAllocatorProtocol 從驅(qū)動(dòng)程序的名稱中對其進(jìn)行命名。該協(xié)議包含3個(gè)函數(shù): get_swsmi_num_and_add2list,get_swsmi_num_from_guid和 add_swsmi_to_list_no_check。
基本上,此驅(qū)動(dòng)程序允許將SWSMI編號與GUID相關(guān)聯(lián)??梢砸篁?qū)動(dòng)程序找到下一個(gè)可用的SWSMI編號(使用第一個(gè)函數(shù))或提供它(使用第三個(gè)函數(shù))。第二個(gè)函數(shù)僅允許從GUID獲取SWSMI編號。
這些關(guān)聯(lián)存儲在通常情況下的鏈表中,該鏈表由開始時(shí)注冊的配置表引用。這允許SMM外部的應(yīng)用程序獲得其希望使用的函數(shù)的正確SWSMI號。這樣做可能是為了避免在其他驅(qū)動(dòng)程序與其他注冊SWSMI處理程序的組件之間發(fā)生SWSMI號沖突。
利用所有這些信息,可以很容易地動(dòng)態(tài)檢索SWSMI號。使用UEFI Shell中的chipsec 4,我可以匹配SWSMI號和GUID:
1.ct_swsmi_allocator從GUID 檢索配置表7E791691-5752-4392-B888-EFF9C74F5D77。
2.ct_swsmi_allocator + 0x38是雙鏈表頂部的指針(這是一個(gè)保護(hù),在此元素后面沒有實(shí)際數(shù)據(jù))??梢栽诖肆斜砩线M(jìn)行迭代,直到再次達(dá)到頭部為止。
3.對于elt列表的每個(gè)元素,都有一些有趣的數(shù)據(jù):
· 在elt-0x8是一種magic 0x4E415353。
· SWSMI號在elt+0x10qword上。
· GUID位于elt+0x18。
一旦檢索了GUID和SWSMI編號之間的相關(guān)性,就可以觸發(fā)SWSMI處理程序的代碼。
漏洞分析
SWSMI處理程序的第一個(gè)操作SmmOEMInt15是RSI從保存的狀態(tài)中檢索寄存器的值。這是通過使用EFI_MM_CPU_PROTOCOL(以前稱為EFI_SMM_CPU_PROTOCOL)完成的, 該文件也已記錄在案,并且是edk2(MdePkg/Include/Protocol/MmCpu.h)的一部分。該協(xié)議將在保存狀態(tài)下搜索CPU保存的值以獲取寄存器并返回。對于SWSMI處理程序而言,這是一個(gè)非常有趣的開始,因?yàn)榇酥凳菍?shí)際的用戶輸入。
甚至更有趣的是,此值用作結(jié)構(gòu)上的指針,并且此結(jié)構(gòu)的前兩個(gè)字節(jié)用作調(diào)用不同處理程序的開關(guān)的枚舉。我開始快速逆向處理程序,但是由于查看處理程序時(shí)發(fā)現(xiàn)了一段非常有趣的代碼,所以從未真正逆向完成0x3E00。
該處理程序要做的第一件事是從結(jié)構(gòu)中的兩個(gè)字段計(jì)算值,并在controlled調(diào)用內(nèi)部函數(shù)之前將其設(shè)置為全局變量():
該handler_internal_3E00函數(shù)本身以兩個(gè)非常有趣的基本塊開始:
handler_internal_3E00函數(shù)開始
要做的第一件事是檢查at的值*(controlled+2)是否為0,如果是這種情況,它將經(jīng)過一些奇怪的事情(這的確是0xFFFEFFFE在該地址的寫操作,0x4因?yàn)槲覀冊谖锢韮?nèi)存中沒有任何保護(hù))這不會(huì)造成崩潰,請調(diào)用該 EFI_BOOT_SERVICES.LocateHandleBuffer函數(shù)。
從SMM調(diào)用此函數(shù)的問題在于,EFI_BOOT_SERVICES 是位于正常環(huán)境中的服務(wù)表。攻擊者可以簡單地更改EFI_BOOT_SERVICES表中的地址并獲得任意調(diào)用。這種類型的漏洞通常稱為SMRAM調(diào)用,它們基本上等效于從內(nèi)核調(diào)用用戶級代碼。
0x03 漏洞利用
大約2017年至2018年,SMRAM的調(diào)用非常容易利用:在觸發(fā)SWSMI之前足以更改代碼(在這種情況下為函數(shù)指針)。但是,SMM_CODE_CHK_EN從那時(shí)起,緩解措施已開始普遍使用,并且確實(shí)已在我的Lenovo P51s上激活。
SMM_CODE_CHK_EN是SMM的類似于SMEP的函數(shù):如果在SMM中執(zhí)行了SMRAM外部代碼(由SMRR定義),則計(jì)算機(jī)基本上只會(huì)崩潰。實(shí)際上,SMM_CODE_CHK_EN是在引導(dǎo)過程中由固件初始化的MSR。它可以被鎖定,一旦被鎖定,就不能被禁用。由于它是一種類似于SMEP的函數(shù),因此通常的內(nèi)核繞過將起作用,但是使用它們有一些缺點(diǎn):
· 固件不如內(nèi)核標(biāo)準(zhǔn):技巧可能無法移植,
· 從正常的世界來看,SMM是一個(gè)很大的黑盒,并且數(shù)據(jù)通信受到限制,
· 沒有ASLR,但地址將取決于計(jì)算機(jī)和固件版本。
由于所有這些原因,利用漏洞可能無法在另一個(gè)易受攻擊的固件上正常工作。
此時(shí),如果我們嘗試0x3E00使用SMRAM調(diào)用觸發(fā)處理程序的代碼,則會(huì)發(fā)生以下情況:
觸發(fā)標(biāo)注
1.我們用正確的編號,RSI和內(nèi)存中的正確值觸發(fā)SWSMI,以到達(dá)標(biāo)注。
2.CPU會(huì)將當(dāng)前狀態(tài)保存在SMRAM中的某個(gè)位置。
3.將執(zhí)行一些代碼(包括切換到64位),并將調(diào)用我們的SWSMI處理程序。
4.該0x3E00處理器將搜索EFI_BOOT_SERVICES.LocateHandleBuffer 函數(shù)指針在內(nèi)存中。
5.并調(diào)用該函數(shù)。
6.然后它將崩潰。由于SMM_CODE_CHK_EN激活了正常世界中的代碼調(diào)用,因此將永遠(yuǎn)不會(huì)執(zhí)行,因此未經(jīng)任何修改的原始代碼甚至無法工作。
現(xiàn)在我們知道了,目標(biāo)是能夠以穩(wěn)定的方式在SMM中執(zhí)行我們的代碼,并希望能夠輕松地在具有相同漏洞的兩個(gè)不同固件之間移植。為此,我使用了我先前在另一篇博客文章中詳細(xì)解釋過的技術(shù): SMM中的Code Check(mate)。
基本思想是受益于保存狀態(tài),該狀態(tài)由CPU在切換到SMM時(shí)設(shè)置。保存狀態(tài)始終位于SMBASE + 0xFC00并且包含許多通用寄存器,使我們能夠控制(在最佳情況下)0x80內(nèi)存字節(jié):
由于所有內(nèi)容都使用物理地址,并且未啟用任何內(nèi)存保護(hù),因此保存狀態(tài)的內(nèi)容將是可執(zhí)行的,而0x80字節(jié)數(shù)對于放置Shellcode而言已綽綽有余,這將使我們獲得完全控制權(quán)。
當(dāng)時(shí)的想法是:
繞過CodeChk的想法
1.首先,我們改寫的地址LocateHandleBuffer在 EFI_BOOT_SERVICES與在我們的寄存器位于shellcode的地址結(jié)構(gòu)。
2.然后,我們使用存儲在寄存器中的shellcode觸發(fā)SWSMI。我們?nèi)匀槐仨氉袷卣{(diào)用處理程序所必需的所有條件,但這將為我們的Shellcode留出足夠的空間。
3.然后,CPU將把我們的狀態(tài)保存在SMRAM中,為我們映射shellcode。
4.我們的SWSMI處理程序?qū)⒈徽{(diào)用,他自己將調(diào)用該0x3E00處理程序。
5.EFI_BOOT_SERVICES.LocateHandleBuffer將獲取用于的函數(shù)指針,但是將檢索處于保存狀態(tài)的地址。
6.我們的shellcode將被調(diào)用,并且由于保存狀態(tài)位于SMRAM內(nèi)部,SMM_CODE_CHK_EN不會(huì)被觸發(fā)。
這個(gè)想法非常簡單,它使我們可以在SMRAM內(nèi)映射shellcode,而不必依賴于固件的代碼??杀氖?,它存在一個(gè)小問題:我們不知道SMBASE哪個(gè)用于計(jì)算已保存狀態(tài)的基址。
SMBASE從很長一段時(shí)間以來,獲得的價(jià)值一直是利用SMM漏洞的經(jīng)典問題。通常,檢索它的主要方法有三種:可以猜測,可以強(qiáng)行使用或讀取IA32_SMBASE包含其值的MSR 。前兩種技術(shù)極有可能使計(jì)算機(jī)崩潰,遺憾的是IA32_SMBASE 只能從SMM讀取寄存器,從而造成雞和蛋的問題。因此,我開始尋找一種更好的技術(shù),該技術(shù)將允許SMBASE可靠地獲得硬件控制。
將SMBASE在初始化PiSmmCpuDxeSMM驅(qū)動(dòng)程序,該驅(qū)動(dòng)程序是開源和edk2可用。初始化SMBASE第一件事情時(shí),它計(jì)算要保留的大小。因?yàn)镾MBASE每個(gè)CPU 需要一個(gè)內(nèi)存,因此不足以保留0x10000,但是為了優(yōu)化RAM空間,驅(qū)動(dòng)程序避免了每個(gè)CPU保留那么多內(nèi)存。TileSize在驅(qū)動(dòng)程序中計(jì)算A 來確定應(yīng)移動(dòng)SMBASE多少,而實(shí)際上在驅(qū)動(dòng)程序中進(jìn)行動(dòng)態(tài)計(jì)算時(shí),總是將其偏移0x2000字節(jié)。現(xiàn)在我們知道了SMBASE相互比較的位置,并且 0x10000 + TileSize * (number_of_cpu - 1)將保留內(nèi)存字節(jié)。
為了保留內(nèi)存,驅(qū)動(dòng)程序在SmmAllocatePages 函數(shù)上使用包裝器,并且未指定將內(nèi)存映射到的特定地址。默認(rèn)情況下,SmmAllocatePages將首先嘗試查找空閑列表,但沒有結(jié)果將采用最高的可用地址。在啟動(dòng)時(shí),沒有理由釋放這么大的內(nèi)存,這意味著我們可以放心地忽略freelist。關(guān)于最后一個(gè)有趣的一點(diǎn) SmmAllocatePages是,它也用于映射SMM驅(qū)動(dòng)程序,并且當(dāng)完成SMBASE分配時(shí),我們知道最后分配的PiSmmCpuDxeSMM驅(qū)動(dòng)程序是驅(qū)動(dòng)程序。
內(nèi)存布局如下:
SMBASE周圍的內(nèi)存布局
我們?nèi)匀粵]有SMBASE,但是我們開始對周圍的事物有了一個(gè)很好的了解,并且碰巧PiSmmCpuDxeSMM注冊了一個(gè)普通的協(xié)議:
在gSmmCpuPrivate->SmmConfiguration位于內(nèi)部 PiSmmCpuDxeSMM驅(qū)動(dòng)器,由于它與注冊EFI_BOOT_SERVICES,該指針及其相關(guān)聯(lián)的GUID( gEfiSmmConfigurationProtocolGuid)將被保存在正常的環(huán)境中。使用, EFI_BOOT_SERVICES.LocateProtocol我們可以檢索此指針。看起來很奇怪,這實(shí)際上是“故意”制作的:普通世界的驅(qū)動(dòng)程序在引導(dǎo)階段會(huì)使用此協(xié)議,而當(dāng)他們確實(shí)使用它時(shí),SMRAM尚未鎖定。但是,可以通過在鎖定SMRAM的同時(shí)卸載此協(xié)議來避免此類泄漏。由于該驅(qū)動(dòng)程序是edk2的一部分,因此大多數(shù)固件都將其集成在一起,并且該技術(shù)基本上可以在不同的構(gòu)造函數(shù)之間移植。如果你希望對泄漏進(jìn)行更詳細(xì)的描述,可以在我以前的博客文章中找到。
利用該泄漏,我們可以計(jì)算PiSmmCpuDxeSMM (base = leak - off)的基地址,用它來推導(dǎo)SMBASE 地址(base - 0x10000 - tilesize * (numcpu - 1)),然后從該計(jì)算中獲取已保存的狀態(tài)地址。我在使用此技術(shù)時(shí)遇到的一個(gè)問題是cpu(numcpu)的實(shí)際數(shù)量與實(shí)際情況不符,因此我花了一些時(shí)間來弄清該錯(cuò)誤。實(shí)際上,可以使用EfiPiMpServicesProtocol可從正常世界訪問的來獲得用于計(jì)算的實(shí)際數(shù)字。
至此,我們具備了漏洞利用所需的一切:
全面開發(fā)
首先,我們需要獲取保存狀態(tài)的地址:
1.使用該EFI_BOOT_SERVICES.LocateProtocol函數(shù)檢索EfiSmmConfigurationProtocol。
2.從協(xié)議中我們可以得出PiSmmCpuDxeSMM驅(qū)動(dòng)程序中的漏洞。
3.它允許計(jì)算SMBASE并推斷出我們的shellcode所在的保存狀態(tài)的地址。
然后我們需要觸發(fā)漏洞利用:
1.我們首先用EFI_BOOT_SERVICES.LocateHandleBuffer剛計(jì)算出的值重寫函數(shù)的地址。
2.我們使用存儲在寄存器中的shellcode觸發(fā)SWSMI。
3.CPU將把我們的shellcode映射到我們之前計(jì)算出的地址。
4.SmmOEMInt15的SWSMI 被調(diào)用,尤其是0x3E00處理程序。
5.嘗試獲取LocateHandleBuffer地址時(shí),它將檢索已映射我們的shellcode的地址。
6.最后,將調(diào)用我們的shellcode,使我們在SMM中執(zhí)行代碼。
0x04 逆向固件密碼
聯(lián)想ThinkPad固件設(shè)置了幾種密碼,最初我感興趣的是一種保護(hù)BIOS設(shè)置界面的密碼。固件中的一些驅(qū)動(dòng)程序包含對字符串“ passwords”的引用。我開始看的是LenovoSetupSecurityDxe因?yàn)樵擈?qū)動(dòng)程序似乎集中了大部分代碼,這些代碼允許使用用戶界面設(shè)置和刪除密碼。
逆向HII
在UEFI中,使用人機(jī)界面基礎(chǔ)架構(gòu)(HII)來與用戶建立接口:一組允許打印并從用戶那里獲取value的接口。特別是EFI_HII_STRING_PROTOCOL 允許使用“ a” StringId(簡單數(shù)字)從“數(shù)據(jù)庫”中檢索字符串?;旧?,這意味著在逆向時(shí)不可能直接在字符串上使用外部參照。在數(shù)據(jù)庫中找到的字符串很容易識別:它們是UTF-16字符串,后跟一個(gè)字節(jié),值0x14。此值指示HII數(shù)據(jù)庫的此元素是字符串。在所有這些字符串之前,都有一個(gè)header。header遵循的 EFI_HII_STRING_PACKAGE_HDR結(jié)構(gòu)包含StringInfoOffset指示字符串開頭的字段。
一旦找到header,就可以找到該數(shù)據(jù)庫的初始化。特別是將調(diào)用gEfiHiiDatabaseProtocolInterface->NewPackageList允許使用句柄注冊數(shù)據(jù)庫字符串的函數(shù)。然后,其余代碼將使用此句柄和StringId來檢索字符串,通常是通過調(diào)用gEfiHiiStringProtocolInterface->GetString。
跟蹤字符串使用情況使我們能夠確定代碼的哪一部分用于哪些活動(dòng),并且經(jīng)過一些逆向操作之后,可以識別出一些有趣的全局變量。
聯(lián)想密碼協(xié)議
實(shí)際上,用于操作和檢查密碼的全局變量是協(xié)議。這些協(xié)議通過以下偽代碼獲取:
這段代碼不搜索一個(gè)協(xié)議,而是搜索其中的幾個(gè)協(xié)議,所有協(xié)議都具有相同的GUID LenovoPwdGuid(2846b2a8-77c8-4432-86ec-199f205d37ca)(1)。它正在檢索每個(gè)接口的接口(2),并將接口的開頭與硬編碼的GUID(Guid1在這種情況下)進(jìn)行比較(3)。根據(jù)GUID,使用相應(yīng)的全局變量來存儲該接口(4)。這樣設(shè)置了四個(gè)不同的全局變量。
這意味著幾個(gè)不同的協(xié)議都安裝了相同的GUID,然后通過比較接口結(jié)構(gòu)開始處存在的另一個(gè)GUID來區(qū)分這些協(xié)議。這些接口中的每一個(gè)都代表不同類型的密碼,并且在該初始GUID之后,提供了用于操縱它們的函數(shù)。
密碼類型
通過搜索用于比較結(jié)構(gòu)的硬編碼GUID,僅發(fā)現(xiàn)了一些二進(jìn)制文件。四個(gè)驅(qū)動(dòng)程序都有一個(gè)有趣的名字:LenovoPopManagerDxe, LenovoSvpManagerDxe,LenovoHdpManagerDxe和LenovoSmpManagerDxe。查看調(diào)試字符串,很容易猜出縮寫的含義:
· POP:開機(jī)密碼
· SVP:SuperVisor密碼
· HDP:硬盤密碼
· SMP:系統(tǒng)管理密碼
還有趣的是,SVP和HDP都具有SMM驅(qū)動(dòng)程序。
通過逆向一個(gè)驅(qū)動(dòng)程序的用法和代碼,很容易理解GUID之后的第一個(gè)函數(shù)的用法:
除get_status之外,此接口的所有函數(shù)都執(zhí)行一些操作并更改全局共享位字段以指示結(jié)果狀態(tài):status。該get_status函數(shù)允許檢索該位字段的值,從而確定用戶是否提供了密碼。
一旦理解了這一點(diǎn),我就可以開始研究所有這些如何工作。SMP和SVP密碼的工作方式幾乎相同,稍后將對其 進(jìn)行詳細(xì)說明。HDP已記錄在博客文章3中,我對此并不感興趣。最后,還有開機(jī)密碼。
0x05 開機(jī)密碼
POP是用戶可以設(shè)置的密碼,每次計(jì)算機(jī)啟動(dòng)時(shí)都會(huì)詢問。它由LenovoPopManagerDxe驅(qū)動(dòng)程序處理,該驅(qū)動(dòng)程序公開了前面描述的接口。
密碼哈希和PCD
為了查看密碼的存儲方式,這兩個(gè)函數(shù)set_pwd和 check_pwd是最好的選擇。該函數(shù)set_pwd首先從0xC參數(shù)中給定的指針中獲取字節(jié),然后計(jì)算散列密碼。通過使用驅(qū)動(dòng)程序中73e47354-b0c5-4e00-a714-9d0d5a4fdbfd實(shí)現(xiàn)的另一個(gè)協(xié)議() 計(jì)算LenovoCryptService哈希值。該協(xié)議的第一個(gè)函數(shù)允許計(jì)算SHA256,并且是用于哈希密碼的函數(shù)。哈希被加鹽,鹽通過平臺配置數(shù)據(jù)庫(PCD)獲取。
PCD是在UEFI PEI和DXE階段之間傳輸并在驅(qū)動(dòng)程序之間共享的通用存儲系統(tǒng),PCD協(xié)議的實(shí)現(xiàn)在edk2中是開源的。PCD允許通過令牌ID定義共享內(nèi)存緩沖區(qū),該令牌ID在固件編譯時(shí)自動(dòng)確定。靜態(tài)存儲由驅(qū)動(dòng)程序加載(一個(gè)用于PEI,一個(gè)用于DXE),并存在于固件文件系統(tǒng)(FFS)中??梢酝ㄟ^搜索GUID輕松找到該存儲,PCD_DATA_BASE_SIGNATURE_GUID但通常與驅(qū)動(dòng)程序位于同一“文件”中。該協(xié)議還提供了動(dòng)態(tài)存儲,可用于在驅(qū)動(dòng)程序之間共享數(shù)據(jù)。
如果是鹽,則使用動(dòng)態(tài)存儲。鹽在最近的固件中具有0x20字節(jié)大小, 而較舊的固件具有較短的大?。?xA)。只需向PCD協(xié)議詢問正確的令牌ID,就可以很容易地從UEFI Shell中檢索鹽。由于令牌ID是在編譯時(shí)生成的,因此攻擊者必須能夠自動(dòng)確定該令牌,或者簡單地逆向該特定固件的驅(qū)動(dòng)程序以找到該ID。
需要注意的有趣一點(diǎn)是,0x00在釋放該驅(qū)動(dòng)程序中用于存儲密碼和鹽的所有緩沖區(qū)之前,都要在其中重置。檢索密碼的哈希值不像在啟動(dòng)后簡單地轉(zhuǎn)儲內(nèi)存那樣簡單。現(xiàn)在讓我們看一下它的存儲方式。
存儲函數(shù)
密碼通過允許在存儲區(qū)中寫入字節(jié)的函數(shù)存儲,該函數(shù)的代碼幾乎可以自我說明:
一個(gè)IOPort用于指示讀取或?qū)懭氲钠屏?,另一個(gè)IOPort用于寫入值。讀取的工作方式相同,只不過將out值的write()替換為read(in)。四個(gè)IOPorts 0x70 到0x73是已知的,他們用于與實(shí)時(shí)時(shí)鐘(RTC)設(shè)備進(jìn)行交互。該設(shè)備的主要目標(biāo)是允許訪問時(shí)間,但它也提供了一些通常稱為CMOS的存儲空間。這些IOPorts已記錄在PCH數(shù)據(jù)表中, 但是osdev Wiki上也有不錯(cuò)的資源。
關(guān)于RTC設(shè)備的一個(gè)有趣的事實(shí)是,必須始終打開它的電源,以免丟失存儲中的數(shù)據(jù)。通常,計(jì)算機(jī)中裝有一個(gè)小的電池(與主要電池不同),以確保該設(shè)備始終處于開機(jī)狀態(tài)。這意味著具有物理訪問權(quán)限的攻擊者只需刪除各種電源訪問權(quán)限,就可以繞過此密碼。聯(lián)想確實(shí)意識到了這一點(diǎn),甚至對此進(jìn)行了 記錄。
快速查看開機(jī)密碼后,我也有興趣查看其他密碼。
0x06 BIOS密碼
我真正感興趣的一個(gè)密碼是保護(hù)BIOS配置的密碼,實(shí)際上SMP和SVP密碼的工作方式幾乎相同。這兩個(gè)驅(qū)動(dòng)程序公開了前面介紹的密碼界面,并使用相同的存儲。
對于POP來說,了解密碼存儲方式的最簡單方法就是查看set_pwd函數(shù)。它首先使用SHA256像POP一樣對輸入的哈希執(zhí)行計(jì)算。有趣的是,此哈希使用與POP相同的鹽,但真正有趣的部分是密碼的存儲方式。
模擬Eeprom
該存儲使用帶有GUID的協(xié)議進(jìn)行, 82b244dc-8503-454b-a96a-d0d2e00bf86a該協(xié)議由驅(qū)動(dòng)程序注冊EmulatedEepromDxe。憑借其顯式名稱,我們可以推斷出這可能是存儲API,有趣的是,聯(lián)想過去似乎在計(jì)算機(jī)中嵌入了eeprom。該協(xié)議注冊了三個(gè)函數(shù),但只有前兩個(gè)用于密碼管理,這意味著我們可能具有讀取和寫入函數(shù)。第一個(gè)函數(shù)同時(shí)用于測試和設(shè)置密碼,而第二個(gè)函數(shù)僅用于設(shè)置密碼的函數(shù):這似乎很有力地表明第一個(gè)函數(shù)允許讀取,而第二個(gè)函數(shù)則允許寫作?,F(xiàn)在,真正有趣的問題是EmulatedEepromDxe 驅(qū)動(dòng)程序?qū)嶋H上在哪里存儲該數(shù)據(jù)?
該協(xié)議的第一個(gè)函數(shù)具有以下原型:
EFI_STATUS EmulEeprom_Read(void *this, UINT64 unk_enum, UINT64 index, UINT8 *pRes)
第一個(gè)參數(shù)(this)只是協(xié)議接口上的指針,最后一個(gè)參數(shù)(pRes)顯然用于檢索讀取的值,另外兩個(gè)參數(shù)清楚地指示要使用的存儲空間。在index這個(gè)存儲空間的偏移,但unk_enum還不清楚。與NOR或NAND閃存相反,Eeprom在擦除大小上可以具有良好的粒度。但是,由于用于處理小尺寸擦除的電路占用了空間,可用于更多存儲,因此擦除通常是在“存儲體”中重新分組的幾個(gè)字節(jié)上進(jìn)行的。實(shí)際上,這意味著編程接口實(shí)際上與NOR或NAND閃存非常相似。這是大多數(shù)eeprom被更便宜的NOR或NAND閃存取代的原因之一。在我們的情況下unk_enum實(shí)際上是在模擬的eeprom中的一個(gè)庫號,在代碼中將該庫號翻譯并添加到該index編號中,以計(jì)算讀取或?qū)懭霑r(shí)的偏移量。
EmulEeprom_Read函數(shù)上提供的值進(jìn)行一些檢查,并調(diào)用另一個(gè)函數(shù),perform_read與bank_num,該index和pRes。實(shí)際上是執(zhí)行實(shí)際讀取的函數(shù)。此函數(shù)調(diào)用在IOPort上讀寫的其他幾個(gè)函數(shù)。這是逆向固件的要點(diǎn),如果未記錄IOPort,固件通常會(huì)很痛苦。使用了三個(gè)不同的IOPort,第一個(gè)是IOPort 0x1808,它僅在in循環(huán)讀?。ǎr(shí)使用,后跟pausex86指令。如果那還不是很明顯,那就是計(jì)時(shí)器,尤其是PM計(jì)時(shí)器。在Linux上,簡單的dmesg提示會(huì)給你很大的提示:ACPI: PM-Timer IO Port: 0x1808。但是,另外兩個(gè)IOPort 0x1630和0x1634,并不那么容易理解。
IOPort逆向
這兩個(gè)IOPort顯然是用于讀取和寫入數(shù)據(jù)的IOPort,每個(gè)都用于讀?。╫ut)和寫入(in)。IOPort 0x1634通常用常量寫入,而不依賴于偏移量,讀取時(shí)通常將結(jié)果檢查為位字段。另一方面,IOPort 0x1630既用于寫入先前計(jì)算的偏移量,又用于讀取實(shí)際結(jié)果。在至少一項(xiàng)函數(shù)中,對此IOPort進(jìn)行讀取并將結(jié)果丟棄。這是與其他硬件設(shè)備連接的典型模式:一個(gè)IOPort是“配置” IOPort,用于檢查另一設(shè)備的狀態(tài),指示執(zhí)行的操作的類型,依此類推,在本例中為IOPort 0x1634。第二個(gè)IOPort(0x1630)是用于傳輸數(shù)據(jù)(無論是讀取還是寫入)的一種。在IOPort上進(jìn)行讀取可能會(huì)對設(shè)備產(chǎn)生副作用,因此在丟棄結(jié)果的同時(shí)執(zhí)行了一次讀取。這是與使用IOPort與外部設(shè)備進(jìn)行討論的經(jīng)典模式,基本上與以SPI閃存進(jìn)行討論或與PCI設(shè)備進(jìn)行接口的方式相同。
因此,在這一點(diǎn)上,我們知道這些密碼的哈希值未存儲在SPI閃存中,而是存儲在計(jì)算機(jī)中的另一臺設(shè)備上(再次),所以現(xiàn)在的問題是哪個(gè)?使用的兩個(gè)IOPort是可變的(與固定端口相反,例如用于PCI的IOPort),這意味著這些端口號取決于系統(tǒng)的配置。搜索使用那些IOPort的設(shè)備通常很復(fù)雜,在那種情況下,我首先搜索了PCI設(shè)備聲明的IOPorts(使用lspci),但沒有找到可用信息。下一步是查看在CPU和Platorm Controller Hub(PCH)數(shù)據(jù)表中定義的變量IOPort。列舉了一些時(shí)間之后,我終于在低引腳數(shù)(LPC)控制器中找到了這些IOPort的注冊為LPC通用IO范圍1(LGIR1)。
低引腳數(shù)總線用于與計(jì)算機(jī)內(nèi)部的多個(gè)設(shè)備進(jìn)行通信。特別是,它用于與稱為嵌入式控制器(EC)的設(shè)備進(jìn)行通信,該設(shè)備在PCH數(shù)據(jù)表中被簡稱為“微控制器1”。EC是一種微控制器,以負(fù)責(zé)為筆記本電腦供電而著稱。那時(shí),我還記得閱讀 過Alex Matrosov和Alexandre Gazet 的演講 Breaking Through Another Side,他們在演講中談到了EC及其安全影響?;仡櫵麄兊恼勗?,我注意到其中還引用了這兩個(gè)IOPort,因此密碼的哈希存儲在EC中。
EC有自己的固件,因此查看它不是該項(xiàng)目的一部分。但是,我可以做的一件事就是嘗試以與驅(qū)動(dòng)程序相同的方式讀取密碼。我使用chipsec實(shí)現(xiàn)了與EC交互的小腳本 ,但是當(dāng)嘗試讀取密碼的哈希值時(shí),只有空字節(jié)。由于我能夠讀取其他模擬“存儲區(qū)”的內(nèi)容,因此這似乎是一種保護(hù)機(jī)制:固件可能在引導(dǎo)階段完成后鎖定對哈希的訪問。
最后一件事引起了我的興趣:我之前提到,LenovoSvpManagerSmm存在用于SVP密碼的SMM驅(qū)動(dòng)程序。由于SMM與OS并行運(yùn)行,因此我對查看SMM如何檢索密碼的哈希值很感興趣。進(jìn)行一些逆向之后,似乎該驅(qū)動(dòng)程序使用了SMM替代EmulatedEepromDxe驅(qū)動(dòng)程序:EmulatedEepromSmm。該驅(qū)動(dòng)程序與的工作方式相同,EmulatedEepromDxe對相同的IOPort執(zhí)行相同的操作。但是,LenovoSvpManagerSmm實(shí)際上是在初始化過程中檢索哈希并將其存儲在SMRAM中的緩沖區(qū)中。這似乎表明,如我之前的博文中所述,SMM漏洞 應(yīng)允許檢索這些哈希。
實(shí)際上,BIOS固件密碼的哈希值存儲在嵌入式控制器中,并且在引導(dǎo)結(jié)束后似乎已鎖定。攻擊者應(yīng)該能夠利用UEFI或SMM漏洞來獲取攻擊者,但這已經(jīng)是一項(xiàng)更加復(fù)雜的任務(wù)。它們的安全性仍基于EC的安全性,但這將是另一次研究。
0x07 分析總結(jié)
總而言之,這里研究的Lenovo密碼的處理非常好,擁有硬件訪問計(jì)算機(jī)權(quán)限的攻擊者應(yīng)該能夠繞過那些密碼,但這并不像我最初預(yù)期的那樣容易。
開機(jī)密碼可以很容易地重置,這是最有問題的事情,但是仍然需要通過硬件訪問(當(dāng)然,或者是固件中的漏洞)。BIOS密碼未存儲在SPI閃存中,而是存儲在EC閃存中,并且引導(dǎo)后似乎已鎖定了讀/寫訪問權(quán)限。這意味著計(jì)算機(jī)用戶在不物理打開計(jì)算機(jī)的情況下應(yīng)該無法輕松刪除或更改BIOS密碼。
還可以看到一個(gè)有趣的趨勢:被認(rèn)為是整個(gè)系統(tǒng)信任根的UEFI固件越來越多地被其他固件取代。Lenovo似乎將EC用于其安全性的某些部分,而且管理引擎(ME)和驗(yàn)證碼模塊(ACM)現(xiàn)在已成為UEFI固件的信任根。在實(shí)踐中,這使攻擊者的生活更加困難,但同時(shí)也提供了潛在的更廣闊的攻擊面,而改變信任根可能只是在改變問題。