研究”加固Windows 10的0day利用緩解措施”
0x00 前言
在兩周前,來自Windows攻擊安全研究團隊(OSR)發布的關于加固Windows 10對抗內核利用:https://blogs.technet.microsoft.com/mmpc/2017/01/13/hardening-windows-10-with-zero-day-exploit-mitigations/https://blogs.technet.microsoft.com/mmpc/2017/01/13/hardening-windows-10-with-zero-day-exploit-mitigations/
Windows上的內核利用幾乎總是需要原始內核讀或寫。因此OSR報告了Windows 10周年版更新如何加固來緩解原始操作的使用。問題來自與tagWND對象,在內核中表示一個窗口。讀了這個博文,我想起了我去年10月份做的一些研究。大約在Black Hat Europe 2016之前兩周,我在Windows10周年版更新上面查找利用tagWND對象來原始讀寫。但是在我準備寫些關于我發現的東西之前,在Black Hat Europe上通過窗口攻擊窗口的討論就發表了:https://www.blackhat.com/docs/eu-16/materials/eu-16-Liang-Attacking-Windows-By-Windows.pdf
因此我停止了寫作的想法,因為我的發現基本上和他們相同。然而在讀了OSR發布的博文后,來自Yin Liang 和 Zhou Li的演講只能在1511版本上面演示,這個版本不存在新的緩解措施。然而我做我的研究時發現了一些煩人的指針驗證,但是發現了一個繞過他們的方式,在當時并沒有想到它。現在我確認了這個指針驗證就是OSR發布的加固措施,并且他們非常容易繞過,重新帶回原始讀寫功能。
本文將瀏覽加固的過程,和它的問題,且如何繞過它。下面的分析是在2016年更新的Windows 10版本研究的。
0x01 原始PoC
我將復用來自Black Hat Europe的演講內容,因此如果你還沒有閱讀,我建議你現在看一下它。這個演講的本質是一個write-what-where漏洞的情況,結構體tagWND的cbwndExtra字段可能增長并且允許覆蓋內存。因此如果兩個tagWND對象緊挨著存放,覆蓋第一個tagWND對象的cbwndExtra字段可能允許利用來修改下一個tagWND對象的字段。這些當中有strName,包含了一個窗口標題位置的指針,修改這個能被利用來在內核內存中讀寫。下面的代碼片段展示了如何使用SetWindowLongPtr和NtUserDefSetText來做到這點:
這個創建了一個新的LargeUnicodeString對象和試圖在任意地址寫入內容。調用SetWindowLongPtr被用來改變窗口名字的指針,并且然后再次恢復它。這個在周年版之前的所有版本可以有效,現在會引起相面的bugcheck:
這個確實在OSR發布的博文中有描述。
0x02 深入挖掘
為了理解為什么會引起bugcheck,我開始調試函數DefSetText的流程。當進入這個函數,我們已經有了RCX存放的tagWND對象的地址和RDX存放的指向新的LargeUnicodeString對象的指針。第一部分的驗證如下:
這個僅僅確保tagWND對象和新的LargeUnicodeString對象格式正確。一點點深入函數:
DesktopVerifyHeapLargeUnicodeString是新的加固函數。它使用LargeUnicodeString地址為參數,這個包含了我們通過調用SetWindowLongPtr改變的指針。并且針對Desktop的tagWND對象的一個指向tagDESKTOP結構體的指針被使用。新函數的第一部分是驗證字符串的長度是不是正確的格式,并且不還有原始長度,因為他們應該是Unicode字符串:
然后校驗確保LargeUnicodeString的長度不是負數:
然后以指向tagDESKTOP的指針為參數調用DesktopVerifyHeapPointer,記住RDX已經包含了緩沖區地址。接下來發生的是觸發bugcheck。在結構體tagDESKTOP對象的偏移0x78和0x80處解引用了,這是桌面的堆和大小,比較地址我們試圖寫操作LargeUnicodeString。如果那個地址不在桌面堆中,則會引起bugcheck。OSR說的加固措施如下:
非常清晰,原始寫不再有效,除非在桌面堆中使用,但是被限制了。
0x03 新的希望
不是所有的都丟了,桌面堆的地址和它的大小都來自tagDESKTOP對象。然而沒有驗證指向tagDESKTOP對象的指針是否正確。因此如果我們創建一個假的tagDESKTOP對象,并且替換原始的,然后我們能控制0x78和0x80偏移。因為指向tagDESKTOP的指針被tagWND使用,我們也能使用SetWindowLongPtr修改它。下面是更新的函數:
g_fakeDesktop被分配的用戶層地址是0x2a000000。因為Windows10不采用SMAP所以這是可能的,然而如果這么做,我們能將它放到桌面堆上,因為仍然允許在哪里寫。運行更新的PoC來確保校驗通過了,并且回到下面的代碼片段:
因此另一個調用來做相同的校驗,還是tagDESKTOP作為第一個參數,現在緩沖區指針加上最大字符串的長度減1是第二個參數,而不是字符串緩沖區的開始。校驗將通過并執行到DefSetText。
當我們繼續執行,我們還會引起一個新的bugcheck:
這是因為下面的指令:
因為R9包含了0x1111111111111111,非常清楚是由假的tagDESKTOP對象填充的。使用IDA我們發現:
證實了R9的內容確實來自tagDESKTOP指針,并且是第二個QWORD。因此我們能更新代碼來設置這個值。如果設置為0,解引用被繞過。運行新的代碼不會導致崩潰,任意覆蓋如下:
0x04 總結
總結下OSR確實做了加固措施,但是還不夠。相同的方式也能通過使用InternalGetWindowText來作為原始讀。
代碼如下:https://github.com/MortenSchenk/tagWnd-Hardening-Bypass