0x00 摘要
2018年8月27日名為sandboxescaper的網友上傳了一份win10本地提權的0day利用代碼(後被微軟修複并配置設定CVE編号CVE-2018-8440),我通過對曆史漏洞進行研究,對Windows系統中RPC(Remote Procedure Call,遠端過程調用)漏洞挖掘進行了簡單的探索,和大家分享一下探索的過程。了解這種攻擊的工作方式将極大地幫助其他研究人員發現類似于sandboxescaper在Windows任務計劃程式中發現的漏洞,這篇文章中首先回顧sandboxescaper和google project zero發現的曆史漏洞原理,接着介紹對類似漏洞挖掘的嘗試和一些成果,最後提出一些繼續挖掘類似漏洞的方法。
0x01 曆史漏洞回顧
簡單回顧一下CVE-2018-8440的原理:SchRpcSetSecurity函數在win10中會檢測C:WindowsTasks目錄下是否存在字尾為.job的檔案,如果存在則會寫入DACL(Discretionary Access Control List,自主通路控制清單)資料。如果将job檔案硬連結到特定的dll那麼特定的dll就會被寫入DACL資料,本來普通使用者對特定的dll隻具有讀權限,這樣就具有了寫權限,接下來向dll寫入漏洞利用代碼并啟動相應的程式就可以提權了。詳細的分析請閱讀參考資料中此前釋出的預警通告。
那麼首先可以想到的是RPC中是否還有類似的函數存在同樣的問題呢?無獨有偶,在2018年4月google project zero披露過SvcMoveFileInheritSecurity函數中的漏洞。
void SvcMoveFileInheritSecurity(LPCWSTR lpExistingFileName,
LPCWSTR lpNewFileName,
DWORD dwFlags) {
PACL pAcl;
if (!RpcImpersonateClient()) {
// Move file while impersonating.
if (MoveFileEx(lpExistingFileName, lpNewFileName, dwFlags)) {
RpcRevertToSelf();
// Copy inherited DACL while not.
InitializeAcl(&pAcl, 8, ACL_REVISION);
DWORD status = SetNamedSecurityInfo(lpNewFileName, SE_FILE_OBJECT,
UNPROTECTED_DACL_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
nullptr, nullptr, &pAcl, nullptr);
if (status != ERROR_SUCCESS)
MoveFileEx(lpNewFileName, lpExistingFileName, dwFlags);
}
else {
// Copy file instead...
RpcRevertToSelf();
}
}
}
在繼續深入之前先簡單介紹一下Windows中的(Access Control Model,通路控制模型)。如果一個Windows對象沒有DACL,則系統允許每個人完全通路。如果對象具有DACL,則系統僅允許DACL中的ACE(Access Control Entry,通路控制條目)明确允許的通路。如果DACL中沒有ACE,則系統不允許任何人通路。下圖是一個拒絕使用者Andrew通路,允許A組成員寫入,允許所有人讀取和執行的DACL的例子。
回到SvcMoveFileInheritSecurity函數。這個函數的功能應該是移動檔案到一個新的位置,然後調用SetNamedSecurityInfo函數将所有繼承的ACE應用于新目錄中的DACL。為了確定這個函數不會以服務的使用者身份運作時(這裡是Local System)允許任意使用者移動任意檔案,需要模拟一個RPC調用者(caller)。模拟是線程使用與擁有線程的程序不同的安全資訊執行的能力。通常伺服器應用程式中的線程模拟用戶端,這允許伺服器線程代表該用戶端操作以通路伺服器上的對象或驗證對用戶端自己對象的通路。Windows的RPC服務應用程式可以調用RpcImpersonateClient函數來模拟一個用戶端。對于大多數的模拟,這個模拟線程可以調用RevertToSelf函數恢複到原來的安全描述符。
問題就在于此,當第一次調用MoveFileEx函數後會調用RpcRevertToSelf函數,然後調用SetNamedSecurityInfo函數。如果SetNamedSecurityInfo函數調用失敗會再次調用MoveFileEx函數,嘗試恢複原來的檔案移動操作。第一個漏洞是有可能原來檔案名所處的位置通過符号連結指向了别的地方,是以可以建立任意檔案(CVE-2018-0826)。第二個漏洞是如果先硬連結像SYSTEM32目錄中那些使用者隻有讀取權限的檔案,在移動後的硬連結檔案上調用SetNamedSecurityInfo函數,SetNamedSecurityInfo函數會從新目錄位置中提取繼承的ACE,然後将ACE應用到被硬連結的檔案上。由于這是作為SYSTEM執行的,這意味着任何檔案都可以被賦予任意安全描述符,這将允許使用者修改它(CVE-2018-0983)。
修複也經曆了一些波折,微軟在2018年2月和3月分别釋出了兩個更新檔之後才徹底解決該問題。修補後和修補前的SvcMoveFileInheritSecurity函數如圖所示,解決方法是第一次調用MoveFileEx函數後不再調用RpcRevertToSelf函數恢複到原來的安全描述符。
0x02 嘗試挖掘類似漏洞
要試圖尋找類似的漏洞,首先需要導出所有的RPC函數。在A view into ALPC-RPC這個talk中提到了RPCview這個工具,這個工具可以用來反編譯并檢視RPC interface,界面是用QT寫的。然而當下載下傳下來運作時會出現下面這樣的錯誤。
仔細閱讀README之後發現需要自己添加rpcrt4.dll的版本。對于win10來說,修改RpcCoreRpcCore4_32bitsRpcInternals.h和RpcCoreRpcCore4_64bits RpcInternals.h,如下所示。
寫一個bat編譯。
set CMAKE_PREFIX_PATH=C:QtQt5.9.15.9.1msvc2017_64
cmake .... -G"Visual Studio 15 2017 Win64"
cmake --build . --config release
cd D:ALPC-fuzzRpcViewBuildx64binRelease
mkdir RpcView64
copy *.dll RpcView64
copy *.exe RpcView64
C:QtQt5.9.15.9.1msvc2017_64binwindeployqt.exe --release RpcView64
以管理者身份運作編譯好的RpcView.exe(普通使用者權限反編譯出來的結果較少)。
Decompilation視窗是對指定interface反編譯的結果,其中函數名都是ProcX的形式,為了得到函數名需要正确設定符号路徑。該工具似乎并不支援微軟的符号伺服器,是以把c:windowssystem32目錄下所有的dll的符号下載下傳到本地。
symchk /s srv*c:symbol*https://msdl.microsoft.com/download/symbols c:windowssystem32*.dll
設定_NT_SYMBOL_PATH環境變量。
現在能夠反編譯出來函數名了,需要對源代碼做适當的修改讓它一次性導出所有反編譯結果,而不用一個一個去點。主要修改了源代碼中下面幾處地方。
在EndpointsWidget.cpp的EndpointsWidget_C::AddEndpoint函數開始時添加了一些代碼導出反編譯的Endpoints。
在InterfacesWidget.cpp的InterfacesWidget_C::AddInterfaces函數傳回前增加了調用InterfaceSelected函數的循環。
在InterfacesWidget_C::InterfaceSelected函數中首先檢查uuid是否重複避免陷入死循環。
原來反編譯需要右鍵點選Decompile,是以注釋掉了這部分代碼使得InterfacesWidget_C::InterfaceSelected函數能夠直接調用SigDecompileInterface函數。
修改了IdlInterface.cpp的IdlInterface::dump函數,把反編譯結果寫到檔案中。
此外還有其它一些因為編譯語言環境不同的修改。運作修改版的RPCview效果如下。
之前出過問題的SvcMoveFileInheritSecurity函數和SchRpcSetSecurity函數的函數名都帶有Security,來看看還有沒有函數名中含有Security的函數。
除了SvcMoveFileInheritSecurity函數和SchRpcSetSecurity函數,果然還有一些函數名中含有Security的函數。比如這裡的NetrpSetFileSecurity函數,看起來真的很有可能存在類似的問題。在IDA中看看反編譯出的代碼。
如果RtlValidRelativeSecurityDescriptor函數和SetFileSecurityW函數之間調用了RpcRevertToSelf函數,就像之前存在漏洞的SvcMoveFileInheritSecurity函數一樣那麼很有可能也能用這個函數提權,不過這個函數中是不存在這種漏洞的。
雖然在初次嘗試挖掘過程中沒有能夠找到類似的問題,但是猜測sandboxescaper所披露的CVE-2018-8440應該是用和文中類似的方法發現的。之後fortinet也對RPCview進行了類似的改造。
0x03 RPC Fuzzing
回到之前的talk:A view into ALPC-RPC,研究人員開源了一個RPC fuzz工具RPCForge,但是這個工具并不能直接使用,因為對方沒有開源如何生成待fuzz的interface的這部分代碼,而隻給出了5個示例的interface。
RPCForge的作者之一也是PythonForWindows這個庫的作者,這個庫中提供了一些友善的封裝函數,可以節省開發RPC用戶端的時間。RPCForge也用到了這個庫。其實RPCForge的原理特别簡單,就是不斷去調用這些RPC函數,觀察是否有崩潰或者異常。函數的參數是通過用sulley中提供的原始資料生成的。
雖然現在已經導出了所有interface反編譯的結果,但是在此前修改的RPCview反編譯的格式和RPCForge用的格式并不相同,于是嘗試編寫了簡易的python腳本,通過正則對兩個工具的格式進行轉換。
由于有一些interface并沒有能夠下載下傳到對應的符号,加之一些結構體中資料過于複雜未進行處理,在RPCview反編譯出來的兩百多個interface中能夠fuzz的隻有一百多個。
在運作RPCForge一段時間後,就在win10最新版上跑出了兩個BSOD,第一個直到現在仍然能在最新的win10正式版和預覽版上複現,第二個可以在win10 1803上複現,不能在win10 1809上複現(更多的版本沒有測試)。
首先是第一個漏洞:
原理非常簡單,BfeRpcEngineClose函數沒有檢查傳進來的參數,直接通路了非法的位址。
接着是第二個漏洞:
當一些%s被作為Srv_CreateResourcePolicy函數的參數傳入時Srv_CreateResourcePolicy函數最終調用到vsnwprintf函數,使用棧上的值作為字元串的位址,由于沒有檢查%s個數導緻非法位址通路,多一個%s産生了BSOD。
将這兩個問題報告給MSRC之後,MSRC以”Beyond causing a crash this doesn’t appear to leak any data or escalate privileges in any way”為由拒絕修複。
0x04 一些調試技巧
在确定漏洞的具體成因時需要調試,但此處的調試與正常稍有差別,因為含有漏洞函數的dll是被加載到system的svchost.exe中運作的,不能直接用核心态調試sys的方法,使用者态調試也因為權限問題不太好操作。在此采用了下面的方法進行調試。
搭建好雙機調試環境之後首先确定含有漏洞函數的dll的svchost.exe的程序号,例如30c,首先檢視程序資訊。
1: kd> !process 30c 0
Searching for Process with Cid == 30c
PROCESS ffffb9010a30b540
SessionId: 0 Cid: 030c Peb: a1e044f000 ParentCid: 024c
DirBase: 21b00002 ObjectTable: ffffd98b2160cb00 HandleCount: 1145.
Image: svchost.exe
使用得到的位址切換到該程序上下文,重新加載使用者态符号之後再次侵入式切換。
1: kd> .process /p ffffb9010a30b540
Implicit process is now ffffb901`0a30b540
.cache forcedecodeuser done
1: kd> .reload /f /user
Loading User Symbols
1: kd> .process /i /p ffffb9010a30b540
You need to continue execution (press 'g' <enter>) for the context
to be switched. When the debugger breaks in again, you will be in
the new process context.
在存在漏洞函數上下斷點,g之後運作poc即可斷下。
1: kd> bp resourcepolicyserver!Srv_CreateResourcePolicy
1: kd> g
Break instruction exception - code 80000003 (first chance)
nt!DbgBreakPointWithStatus:
fffff802`085d4980 cc int 3
1: kd> g
Breakpoint 0 hit
resourcepolicyserver!Srv_CreateResourcePolicy:
0033:00007ffa`0d91afb0 4883ec38 sub rsp,38h
如果存在漏洞函數所在的dll預設并沒有被加載可以在poc代碼中先調用該dll中的其它函數并且設定斷點,待該dll被加載之後再在想要下斷點的函數處斷下,繼續運作poc代碼即可。為了友善調試複現,還可以用pyinstaller将python代碼打包成可以運作的exe。
pyinstaller.exe -F D:20181009pocpoc.py --hidden-import interfaces.test
0x05 繼續挖掘的方向
時至今日,除了CVE-2018-8440這個能夠直接提權的漏洞外,sandboxescaper還公布了兩個越權删除任意檔案的EXP(CVE-2018-8584)和一個越權讀取任意檔案的EXP。12月份公布的後兩個漏洞雖然也是Windows中的邏輯問題,但是與RPC無關。已經被修複的CVE-2018-8584是RpcDSSMoveFromSharedFile函數中的漏洞,同樣也是一個邏輯問題,當任意權限的使用者調用該函數時,該函數會将第三個參數所代表的檔案删除。在此之前,通過CreateMountPoint函數建立兩個檔案夾之間的軟連結,達到删除目錄下的特定檔案進而将所連結目錄下的檔案一并删除的效果。
為了能夠繼續發現RPC中的漏洞,通過研究發現還有以下嘗試方向:
- 靜态審計函數名中含有Move,Copy,Security等詞的高危RPC函數。
- 通過Process Monitor之類的工具動态監控其它的RPC函數在運作時有無調用到1中所說的高危函數。
-
RPC Forge這個工具原理還比較簡單,作者也坦言道:
This is more a PoC than a real fuzzer. Its aim was to be able to forge a valid serialized stream reaching RPC methods code without being rejected by the Windows RPC Runtime (because of bad arguments type leading to error: RPC_X_BAD_STUB_DATA).Thus, it doesn't contain any instrumentation in the server side to improve code coverage.
通過改進原始資料或者增強代碼覆寫率進行發現更多的漏洞的嘗試時發現,基于代碼覆寫率統計做驅動回報的效果不佳。Pin或DynamoRIO類似的工具多用于使用者态,Qemu或Bochs等虛拟化技術多用于核心态,對于system權限的svchost.exe程序似乎都不太友善。另外就是要fuzz的dll太多了,被加載到幾十個程序,每個dll又隻有那麼幾個函數,輸入資料覆寫的代碼有限而且分散,效果不會太好。
另外無論是靜态審計還是動态監控也與預期有一定差異。在了解CVE-2018-8440後可能認為發現它的過程比較簡單,但是在調試後會發現設定檔案安全描述符的函數代碼是一處虛函數調用,靜态審計無法看到,動态調試才能确定最終調用了taskcomp!SetSDNotification函數,按照傳遞的任務名稱參數和SDDL安全描述字串設定%systemdir%Tasks任務名稱.job的安全屬性。
0x06 總結
通過改進完善開源fuzz工具和研究已經公布的漏洞來尋找類似的漏洞仍然是找到漏洞的一種高效的方式。RPC中仍然存在非常有趣的邏輯漏洞等待人們發現,快速找到這些邏輯漏洞可能需要對系統深入的了解以及一些不同于查找記憶體破壞漏洞的手段。
本文用到的所有代碼開源在https://github.com/houjingyi233/ALPC-fuzz-study,文中提到的兩個BSOD的代碼和打包好的可執行檔案在https://github.com/houjingyi233/windows-BSOD。
0x07 參考連結
-
https://blog.0patch.com/
[https://blog.0patch.com/]
-
http://sandboxescaper.blogspot.com
[http://sandboxescaper.blogspot.com]
-
https://github.com/silverf0x/RpcView
[https://github.com/silverf0x/RpcView]
-
Windows全版本提權之Win10系列解析
[https://www.freebuf.com/vuls/184090.html]
-
Windows: StorSvc SvcMoveFileInheritSecurity Arbitrary File Creation EoP
[https://bugs.chromium.org/p/project-zero/issues/detail?id=1427]
-
Windows: StorSvc SvcMoveFileInheritSecurity Arbitrary File Security Descriptor Overwrite EoP
[https://bugs.chromium.org/p/project-zero/issues/detail?id=1428]
-
A view into ALPC-RPC
[https://pacsec.jp/psj17/PSJ2017_Rouault_Imbert_alpc_rpc_pacsec.pdf]
-
win10本地提權0Day預警
[https://cert.360.cn/warning/detail?id=208606780533f78f0731544f1935f8b2]
-
Windows Exploitation Tricks: Exploiting Arbitrary File Writes for Local Elevation of Privilege
[https://googleprojectzero.blogspot.com/2018/04/windows-exploitation-tricks-exploiting.html]
-
RPC Bug Hunting Case Studies – Part 1
[https://www.fortinet.com/blog/threat-research/the-case-studies-of-microsoft-windows-remote-procedure-call-serv.html]
-
Introduction to Logical Privilege Escalation on Windows
[ https://conference.hitb.org/hitbsecconf2017ams/materials/D2T3%20-%20James%20Forshaw%20-%20Introduction%20to%20Logical%20Privilege%20Escalation%20on%20Windows.pdf]