作者:wup and suezi of IceSword Lab , Qihoo 360
作者博客:https://www.iceswordlab.com/2018/07/25/kdhack/
今年6月,微軟聯合一線筆記本廠商正式發布了搭載高通驍龍處理器的Windows 10筆記本產品。作為主角的Win10 ARM64,自然亮點無數,對PC設備廠商也是各種利好。實際上,為了與廠商同步發布安全防護產品,IceswordLab的小伙伴早已將底層驅動程序集移植到了Win10 ARM64平臺上,筆者也因此積累了一些有趣的內核調試方法。在x86平臺使用vmware等虛擬機軟件搭建遠程內核調試環境是非常方便有效的辦法,但目前Win10 ARM64平臺沒有這樣的虛擬機軟件,于是筆者利用qemu模擬器DIY一個。
0x0 準備試驗環境
物理機系統環境 :Windows10 RS4 x64
虛擬化軟件qemu : qemu-w64-setup-20180519.exe
虛擬機系統環境 :Windows10 RS4 ARM64
UEFI 模塊 : Linaro 17.08 QEMU_EFI.fd
WINDBG :WDK10 (amd64fre-rs3-16299)附帶的WinDBG
0x1 qemu遠程內核調試開啟失敗
在qemu環境下,我們使用Linaro.org網站提供的針對QEMU(AARCH64)的1708版的UEFI文件QEMU_EFI.fd啟動Win10ARM64的系統,并使用bcdedit修改qemu模擬器里的Win10ARM64的啟動配置以實現遠程內核調試。配置如下圖,

我們遇到了兩個問題:
(1) 以“-serial pipe:com_1”參數啟動qemu模擬器,qemu會被卡住,導致虛擬機系統無法啟動;
(2)無論是否開啟了基于串口的遠程內核調試,系統內核加載的都是kd.dll而非預期的kdcom.dll;
對于問題(1),我們利用qemu串口轉發功能,開發一個代理程序:建立一個namedpipe等待windbg的連接,并建立與qemu串口socket服務器的連接,從而實現將pipe上讀取(ReadFile)的數據寫入(send)到socket、將socket上讀取(recv)的數據寫入(WriteFile)到pipe。如此我們解決了問題(1)。
至于問題(2),對比VMWare里用UEFI方式部署的Win10RS4x64,不開啟內核調試時系統加載的是kd.dll,開啟內核調試時系統加載的是kdcom.dll,下面對其進一步分析。
0x2 系統提供的kdcom.dll存在問題
在Win10RS4ARM64安裝鏡像的預置驅動里,無法找到serial.sys這個經典的串口驅動;而Win10ARM64筆記本的串口設備是存在的,且串口驅動是高通官方提供的。實際上通過串口遠程調試windows,系統正常的啟動過程中,調試子系統的初始化是早先于串口驅動程序,調試子系統調用kdcom.dll提供的功能,并不需要串口驅動程序的支持。因此微軟沒有為Win10RS4ARM64提供串口驅動serial.sys,對我們最終的目標沒有影響。
那么問題究竟出在哪里呢?是因為Loader所使用的Qemu中的UEFI有問題嗎?
對照qemu的源碼可知,qemu為aarch64模擬器環境提供了串口設備PL011。我們研究了Linaro UEFI的源碼EDK2并編譯了對應的UEFI文件,確保使用的UEFI文件確實提供了串口功能。再用與Win10ARM64模擬器同樣的配置安裝了Ubuntu for ARM,在這個模擬器里PL011串口通信正常,串口采用MMIO,其映射的基址為0x09000000。但安裝Win10后問題依舊:以基于串口的遠程內核調試的啟動配置來啟動Win10RS4ARM64,系統加載的是kd.dll而非期望的kdcom.dll,故而推測是winload 沒有識別PL011串口設備、沒能去加載kdcom.dll。由此,我們決定直接將kdcom.dll替換kd.dll來使用。不過使用kdcom.dll替換kd.dll后出現了新的問題——系統引導異常,下面進一步分析其原因。
kdcom!KdCompInitialize是串口初始化的關鍵函數,分析它是如何初始化并使用串口設備的。系統第一次調用kdcom!KdInitialize初始化串口時,傳遞給KdCompInitialize的第二個參數LoaderBlock是nt!KeLoaderBlock,非NULL,此時kdcom!KdCompInitialize里的關鍵流程如下:
(1) HalPrivateDispatchTable->KdEnumerateDebuggingDevices已被賦值為hal!HalpKdEnumerateDebuggingDevices,調用返回0xC0000001;
(2) 串口處理器UartHardwareDriver為NULL,沒有被賦值;
(3) HalPrivateDispatchTable->KdGetAcpiTablePhase0已被賦值為hal!HalAcpiGetTable,
調用HalAcpiGetTable(loaderBlock, ‘2GBD’)返回NULL,
調用HalAcpiGetTable(loaderBlock, ‘PGBD’)返回NULL,
因此gDebugPortTable為NULL;
(4) 參數LoaderBlocker非NULL且gDebugPortTable為NULL,調用GetDebugAddressFromComPort來配置串口地址;
GetDebugAddressFromComPort調用nt!KeFindConfigurationEntry失敗,按照既定策略,基于DebugPortId的值指派串口地址(DebugPort.Address)為0x3F8/0x2F8/0x3E8/0x2E8/0x00五者之一;
(5) 由于gDebugPortTable為NULL,串口處理器UartHardwareDriver賦值為Uart16550HardwareDriver; 由于串口地址(DebugPort.Address)非NULL,調用串口初始化函數UartHardwareDriver->InitializePort初始化串口; 模擬器提供的串口設備為PL011, 串口處理器應被賦值為是PL011HardwareDriver 而非Uart16550HardwareDriver;
至此,我們發現導致異常的原因: 模擬器提供的是PL011串口設備, kdcom.dll雖提供了支持PL011的代碼,但未能正確識別適配,依然把它當成了PC的isa-serial串口設備。這應屬于kdcom.dll的bug。
0x3 開啟qemu遠程內核調試
現在看來,我們需要解決的問題有兩個:系統Loader僅加載不支持遠程內核調試的kd.dll,系統模塊kdcom.dll沒能完全支持PL011串口設備。
對于第一個問題,我們簡單采取文件替換的辦法繞過它。
對于第二個問題,預期可以使用這樣的辦法解決:開發一個boot類型的驅動,讓它能夠加載kdcom.dll并主動修正kdcom.dll中所有相關數據,對內核映像Ntoskrnl.exe執行IATHook——把導入地址表中的kd.dll函數地址全部替換成kdcom.dll對應函數地址,最后執行nt!KdInitSystem來初始化調試子系統。這種方案篡改內核數據后,會很快觸發PatchGuard藍屏,因此我們需要設計出一個更可用的方案。
我們可以開發一個能夠實現遠程內核調試所需的串口通信功能的dll(即沒有BUG的kdcom.dll)來替換系統目錄下kd.dll,在“禁用驅動程序強制簽名”的場景下實現對操作系統初始化流程的劫持。
微軟給WINDBG的安裝包捆入了一個名為KdSerial的示例項目。這個項目缺少了一些代碼,但是關鍵的部分都在。通過筆者的改造,成功編譯得到一個kdserial.dll,它擁有遠程內核調試所需的串口通信功能和正確的PL011串口配置,能夠替代Win10ARM64RS4系統里的kdcom.dll。將這個kdserial.dll替換系統里的kd.dll,開機時選擇“啟動設置”菜單里的“禁止驅動程序強制簽名”,達成遠程內核調試Win10RS4ARM64的目標。

參考文獻
- Windows Internals 6th
- https://docs.microsoft.com/en-us/windows-hardware/drivers/devtest/bcdedit--dbgsettings
- https://docs.microsoft.com/en-us/windows-hardware/drivers/devtest/bcd-boot-options-reference
- https://wiki.linaro.org/LEG/UEFIforQEMU
- https://blog.csdn.net/iiprogram/article/details/2298550
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/651/
暫無評論