『壹』 如何提取BIOS固件中的代碼
2 逆向系統管理中斷(SMI)句柄
或許有人認為應該動用「硬體分析器」來參與我們的宏偉計劃,其實這是一種誤解。SMI處理程序是BIOS固件的一部分,和普通的BIOS代碼一樣,也可以類似的從中將其反匯編出來。Pinczakko的《BIOS Disassembly Ninjutsu Uncovered 》和《Guide to Award BIOS Reverse Engineering》二書,講述了Award and AMI BIOS逆向方面的詳細情況,有興趣的讀者可以到此參考。在這里,從BIOS中轉儲SMI處理函數,我們有兩種方法可供選擇。
1. 找一個漏洞,從保護模式進入SMRAM,並將SMRAM中的所有內容,尤其是TSEG,High SMRAM和0xA0000-0xBFFFF等區域轉儲出來。如果BIOS沒有鎖定D_LCK位,可以通過Duflot和BSDaemon介紹的修改SMRAMC PCI配置寄存器的方式轉儲。萬一BIOS鎖定了SMRAM,BIOS固件看上去余簡也無懈可擊,那就只有修改BIOS,令其不會設置D_LCK位這一條路了。將修改的程序重刷回BIOS的ROM,這樣在啟動時,SMRAM就不會被鎖定了。不過這樣做首先要確定BIOS不需要數字簽名(digitally signed),而且目前幾乎沒有主板會使用帶數字簽名的非EFIBIOS固件。
我想在這里有必要提一下BIOS設置D_LCK位的方法。通常情況下,BIOS都傾向於使用0xCF8/0xCFC埠,通過合法的I/O訪問,設置相關的PCI配置寄存器。BIOS首先將0x8000009C寫入到0xCF8的地址埠,然後再將0x1A的數值寫入到0xCFC的數據埠,設洞毀純置SMRAMC寄存器就可以鎖定SMRAM了。
2. 還有另外一種方法,相對前者來說要簡單一些,不需要訪問運行時的SMRAM數據。
2.1 從BIOS開發商的網站下載最新的,或者使用快閃記憶體編程器件(Flash Programmer)從BIOS的ROM中提取固件的二進制代碼。我們針對的ASUS P5Q主板就是要下載P5Q-ASUS-PRO-1613.ROM文件。
2.2 大多數的BIOS固件都包含了主BIOS模塊,壓縮的SMI處理句柄就位於其中,利用開發商提供的提取/解壓工具打開BIOS主模塊。由於ASUS BIOS是基於AMI BIOS的,我們使用AMIBIOS BIOS Mole Manipulation Utility 和MMTool.exe從中抽取主模塊。在MMTool中打開下載的.ROM文件,單擊「Single Link Arch BIOS」抽取模塊(ID=1Bh),然後檢查「In uncompressed form」的選項,最後保存,就得到了包含SMI處理句柄的主BIOS模塊。
2.3 主BIOS模塊抽取完成,就可以用HIEW或IDA Pro等工具開始我們的SMI反匯編之旅了。
反匯編SMI句柄
我們注意到在ASUS/AMI BIOS中使用的是一個結構體數組來描述SMI的處理函數。數組中的每一個入口項都有「$SMIxx」的簽名,其中「xx」字元指明了具體的SMI處理函數。圖1顯示的是基於P45晶元組ASUS P5Q SE主板的AMIBIOS 8所使用的SMI分配表(SMI dispatch table)數據。
反匯編SMI分配函數
BIOS中有一個特殊的SMI分配函數,我們將其命名為「dispatch_smi」,將遍歷分配表中的所有入口,並調用handle_smi_ptr指向的處理函數。如果沒有任何處理函數能夠響應當前的SMI中斷信號,它將調用最後的$DEF常式。下面的代碼就是我們從ASUS P5Q主板反匯編得到納咐的Handle_SMI BIOS函數。
鉤掛SMI處理函數
基於上述的討論,鉤掛SMI處理函數的方法也有很多,可以添加一個新的SMI處理句柄,也可以給已有的句柄打個補丁,加上新的功能。兩種方法在本質上並沒有多大區別,所以兩種情況我們都將做必要的介紹。
1.在SMI分配表中添加我們自己的SMI處理函數。
要加入新的函數,必須先在分配表中建立一個新的入口表項,我們將其取名為「$SMIaa」,如圖3所示。
該入口包含了指向默認SMI處理句柄的指針,待會兒我們將修改這個默認的處理函數。或者我們也可以在SMRAM中找一塊空閑區域,放上一段shellcode,將指向默認句柄的指針替換為指向shellcode。最後還要注意,當有新的處理函數加入到分配表中之後,保存在SMRAM數據段中的SMI代碼計數值也要相應的加1,保持SMI處理函數數目的一致性。
上述這個「調試」處理函數只做了一件事情,就是將SMI分配表從0x0B428:[si]拷貝到0x07000:[di]的位置,看來我們可以放心大膽的用自己的SMI代碼鉤掛它了。隨後,我們將實現一個鍵盤記錄程序,將其注入到這個處理函數內部。不過在我們開始新的章節之前,還是有必要先來回顧一下可以用於在用戶擊鍵時,調用記錄程序的相關技術。
1. 使用I/O APIC將鍵盤的硬體中斷(IRQ #01)導向SMI。Shawn Embleton和Sherri Sparks採用的就是I/O高級可編程中斷控制器,將鍵盤的IRQ #01號中斷導向SMI,並在SMI處理程序中捕獲擊鍵事件。
2. 採用鍵盤控制器數據埠訪問時的I/O陷阱機制。
我們在本文中使用了不同於前者的I/O陷阱技術,該項技術最初是BIOS模擬PS/2鍵盤的用途,在下一章節中我們將詳細的解釋其工作原理。
3 SMM鍵盤記錄程序
3.1 硬體I/O陷阱機制
實現一個內核級的鍵盤記錄程序,方法之一是鉤掛中斷描述符表(IDT)中的調試陷阱#DB處理函數,並設置調試寄存器DR0-DR3,用數據埠0x60捕獲系統的擊鍵事件。類似的,我們也可以採用通過鍵盤I/O埠60/64陷入SMI的方法。我們參考了AMI BIOS的設計白皮書《USB Support for AMIBIOS8》,裡面有這樣的一段敘述。
「2.5.4 60/64埠模擬(emulation)
該選項可以開啟或者關閉60h/64h埠的陷阱功能。60h/64h埠陷阱允許BIOS為USB鍵盤和滑鼠提供基於PS/2的完全支持,在Microsoft Windows NT操作系統和支持多語言鍵盤上尤為有用。該選項還為USB鍵盤提供了諸如鍵盤鎖定,密碼設置和掃描碼選擇等各項PS/2鍵盤的功能。」
該機制由硬體系統來完成,所以我們還要查看一下具體的硬體配置情況。好在Intel和AMD的CPU中都有I/O陷阱的相關機制。AMD開發手冊《BIOS and Kernel's Developer's Guide for AMD Athlon 64 and AMD Opteron Processors》中的「SMM I/O Trap and I/O Restart」一節,和Intel手冊《Intel IA-32 Architecture Software Developer's Manual》中的「I/O State Implementation and I/O INSTRUCTION RESTART」一節對該機制都有詳細的介紹。
I/O陷阱機制允許陷入SMI後,在SMI處理程序內部使用IN和OUT指令來訪問系統的任意I/O埠。之所以設計該機制的目的是為了在斷電時,通過I/O埠來開啟(power on)某些設備。除此之外,I/O陷阱當然也可以用於在SMI句柄中模擬60h/64h的鍵盤埠。在某種程度上說,它和上述的調試陷阱機制有些類似,用陷阱捕獲對I/O埠的訪問,但是並非調用OS內核的調試陷阱處理句柄,而是產生一個SMI中斷,讓CPU進入SMM模式,執行I/O陷阱的SMI處理函數。
當處理器陷入I/O指令,進入SMM模式時,它會將I/O指令陷入時的所有信息保存在SMM存儲狀態映射區(Saved State Map)的I/O狀態域(I/O State Field)中,位於SMBASE+0x8000+0x7FA4的位置。圖4是該區域的數據分布情況,待會兒我們的記錄程序將會用到。
-設置了IO_SMI (bit 0),表示當前是一個I/O陷阱SMI。
- I/O 長度標志(bits [1:3])表示 I/O訪問是byte(001b)、word(010b)或dword(100b)三者之一。
- I/O Type標志(bits [4:7])表示I/O指令的類型,IN imm(1001b),IN DX(0001b)等。
- I/O 埠(bits [16:31]),包含了當前訪問的I/O埠號。
如果當前是通過IN DX指令,位元組寬度來訪問0x60埠, IO_SMI置位,SMM keylogger首先需要檢測和更新SMM存儲狀態映射區中的EAX域,然後還要檢測0x7FA4處的I/O狀態域的值是否為0x00600013。
mov esi, SMBASE
mov ecx, dword ptr fs:[esi + 0xFFA4]
cmp ecx, 0x00600013
jnz _not_io_smi
上述是檢測的簡化形式,SMM keylogger還需要檢測I/O狀態域中I/OType和I/O Length等其他標志。因為我們是記錄鍵盤的目的,所以只關心I/O陷阱,並不用理會I/O重啟(I/O Restart)的相關設置。I/O重啟和I/O陷阱構成了完整的SMI I/O處理方式,當SMM中的SMI執行完畢時,I/O重啟允許IN或OUT指令從SMI中斷處恢復並繼續執行。
I/O陷阱機制允許我們在任意I/O埠的軟硬體交互讀寫操作時陷入SMI的處理常式,現在關心的只是0x60數據埠,實現鍵盤擊鍵時的I/O陷入的具體步驟如下:
1. 擊鍵事件發生時,鍵盤控制器產生一個硬體中斷,用I/O APIC將IRQ 1中斷信號導向SMI的處理句柄。
2. 收到鍵盤中斷之後,APIC調用IDT中的鍵盤中斷處理程序,對PS/2鍵盤是0x93號中斷向量。
3. 鍵盤中斷處理程序通過埠0x60從鍵盤控制器的緩沖區中讀取按鍵掃描碼。正常情況下將清空掃描碼,並將其顯示在屏幕上。
4. 此時晶元組引起埠0x60的讀取陷阱,產生信號通知I/O陷阱SMI。
5. 在SMM模式下,keylogger的SMI處理句柄響應SMI中斷,處理I/O陷阱SMI。
6. 退出SMM時,keylogger的SMI處理句柄將結果(當前掃描碼)返回給0x60埠的讀取指令,交由內核的中斷句柄作進一步處理。
上述的第6步操作,將掃描碼返回到OS的鍵盤中斷處理程序,在I/O陷阱和I/O APIC下的實現是有區別的。如果使用了APIC來觸發SMI,SMI keylogger必須再次向鍵盤控制器的緩沖區中填充掃描碼,以待操作系統再次讀取,做進一步處理。
I/O陷阱下則是採用另外的方法。OS鍵盤中斷處理程序使用的「IN al, 0x60」指令會引起SMM keylogger的I/O陷入,由於該IN指令產生了無窮的SMI陷入循環,將永遠無法從SMM中恢復到原來的狀態繼續執行。此時,SMI句柄只要將IN指令的讀取結果保存到AL/AX/EAX寄存器,表現得就像IN指令從來沒有陷入過一樣。
IA32體系,EAX寄存器位於SMRAM存儲狀態區偏移為0x7FD0的位置,即 SMBASE +0x8000+0x7FD0,在IA64下為SMBASE + 0x8000+0x7F5C。因此當上述的IO_SMI置位時,SMM keylogger需要將從0x60埠讀取的掃描碼更新至EAX域,下面就是更新EAX域的代碼片段:
; 1.驗證讀取0x60埠時設置的IO_SMI位
; 2.更新SMM存儲狀態區的EAX域(SMBASE + 0x8000 + 0x7FD0)
mov esi, SMBASE
mov ecx, dword ptr fs:[esi + 0xFFA4]
cmp ecx, 0x00600013
jnz _not_io_smi
mov byte ptr fs:[esi + 0xFFD0], al
3.5 多處理器下的keylogger說明
我們的keylogger已經更新了系統SMRAM存儲狀態映射區中的EAX(RAX)寄存器,要是碰到了多處理器系統那又該怎麼辦呢?當多個邏輯處理器同時進入SMM模式時,它們在SMRAM中都要有自己的SMM存儲狀態映射區,這將由BIOS為每個處理器分配不同的SMRAM 基地址(SMBASE)來妥善解決,因此該項技術也被稱為「SMBASE重定向」。
例如在雙處理器系統中,兩個邏輯處理器分別具有不同的SMBASE,SMBASE0和SMBASE0+0x300;第一個處理器的SMI處理句柄將從EIP = SMBASE0+0x8000處開始執行,而第二個則從EIP = SMBASE0+0x8000+0x300的地方開始;同理,它們各自的存儲狀態映射區也就分別位於(SMBASE0+0x8000+0x7F00)和(SMBASE0+0x8000+0x7F00+0x300)。
不只是0x300,BIOS也會為額外的處理器設置其他的SMBASE增量偏移。增量偏移雖然可變,但是其計算過程也不算復雜。在SMM存儲狀態映射區內部0x7EFC偏移處包含了一個SMM修正ID(Revision ID),對每個處理器來說都是同樣的數值。例如SMM的修正ID可能為0x30100,在SMRAM中找到各處理器的修正ID,計算它們之間的差值也就得到了各SMBASE間的相對位移。
下面我們展示的是SMM keylogger在雙處理器系統上的EAX更新代碼。它將順次檢查I/O狀態域是否和某個處理器的I/O陷阱匹配,確定的話則更新其SMM存儲狀態映射區中的EAX值。
; 在雙處理器系統上更新EAX
mov esi, SMBASE
lea ecx, dword ptr [esi + SMM_MAP_IO_STATE_INFO]
cmp ecx, IOSMI_IN_60_BYTE
jne _skip_proc0:
mov byte ptr [esi + SMM_MAP_EAX], al
_skip_proc0:
lea ecx, dword ptr [esi + SMM_MAP_IO_STATE_INFO + 0x300]
cmp ecx, IOSMI_IN_60_BYTE
jne _skip_proc1:
mov byte ptr [esi + SMM_MAP_EAX + 0x300], al
_skip_proc1:
…
4 建議的檢測方法
4.1 I/O陷阱機制檢測
4.2 計時(timing)檢測