Windows/TestHarnesses/T1127.001_MSBuild/InvokeMSBuild.ps1
function Invoke-ATHMSBuild { <# .SYNOPSIS MSBuild execution harness for the purposes of validating detection coverage. Technique ID: T1127.001 (Trusted Developer Utilities Proxy Execution: MSBuild) .DESCRIPTION Invoke-ATHMSBuild automates the execution of .NET code using MSBuild for the purposes of validating detection coverage. .PARAMETER ProjectFilePath Specifies the full path of the MSBuild project file that is written to disk. If not specified, "test.proj" will be written to the current directory. With the exception of supplying -NoCLIProjectFile, the project file can have any file extension or no extension. .PARAMETER MSBuildFilePath Specifies an alternate path to MSBuild. MSBuild can execute from any directory with any filename and extension. If not specified, the default MSBuild path is used based on the .NET framework runtime used by the running PowerShell process. .PARAMETER Language Specifies the language of the embedded .NET code in the MSbuild project file. By default, MSbuild supports inline C#, VB.Net, and JScript.NET. The following language specifiations are supported in the project XML: * cs, c#, csharp (All of which imply C# code) * vb, vbs, visualbasic, vbscript (All of which imply VB.Net code) * js, jscript, javascript (All of which imply JScript.Net code) .PARAMETER NoCLIProjectFile Specifies that MSbuild should execute without supplying a project file at the command line. If no project file is supplied at the command line, MSBuild will execute the first project file that ends with "proj" in the current directory. -NoCLIProjectFile will only work if there are no previous *proj files in the current directory so ensure that the current directory does not contain any *proj files prior to supplying the -NoCLIProjectFile switch. .PARAMETER TargetName Specifies the target name in the MSbuild project file. This can be any value. If not specified, "TestTarget" is used as the default. .PARAMETER TaskName Specifies the task name in the MSbuild project file. This can be any value. If not specified, "TestTask" is used as the default. .PARAMETER UsePropertyFunctions As an alternative to supplying inline .NET code that is compiled and executed, MSBuild Property Functions allow a developer to supply XML parameters consisting of embedded .NET code that is interpreted and executed on the fly without compilation. Specifying -UsePropertyFunctions will prompt Invoke-ATHMSBuild to use Property Functions as an alternative to embedded .NET source code. .PARAMETER PropertyName Specifies the XML property name to use in the MSbuild project file. This can be any value. If not specified, "TestProperty" is used as the default. .PARAMETER UseCustomTaskFactory Specifies that a custom task factory assembly will be dropped and used to execute code rather than embedding executable code within the project file. .PARAMETER TaskFactoryName Specifies the task factory name in the MSbuild project file. This can be any value. If not specified, "TestTaskFactory" is used as the default. .PARAMETER UseCustomLogger Specifies that a custom logger assembly will be dropped and used to execute code rather than embedding executable code within the project file. .PARAMETER UseUnregisterAssemblyTask Specifies that an assembly that implements a custom assembly registration method will be dropped and used to execute code rather than embedding executable code within the project file. .PARAMETER CustomEngineDllPath Specifies the full path of the MSBuild custom engine/logger assembly that is written to disk. If not specified, "CustomEngine.dll" will be written to the current directory. .PARAMETER ProjectFileContent Specifies custom MSbuild project XML content. Supplying custom content overrides default behavior where a template project is generated dynamically. .PARAMETER TestGuid Optionally, specify a test GUID value to use to override the generated test GUID behavior. .OUTPUTS PSObject Outputs an object consisting of relevant execution details. The following object properties may be populated: * TechniqueID - Specifies the relevant MITRE ATT&CK Technique ID. * TestSuccess - Will be set to True if it was determined that the MSBuild project contents successfully executed. This will not be set to True if -ProjectFileContent was supplied. * TestGuid - Specifies the test GUID that was used for the test. This property will not be populated when -ProjectFileContent is specified. * ExecutionType - Indicates how the MSBuild project file was executed: InlineSourceCode, PropertyFunctions, CustomLogger, CustomTaskFactory, CustomUnregisterFunction, CustomProjectFileContent * ProjectFilePath - Specifies the full path of the MSBuild project file that was written to disk. * ProjectFileHashSHA256 - Specifies the SHA256 hash of the MSBuild project file that was written to disk. * ProjectContents - Specifies the contents of the MSBuild project file that was written to disk. * CustomEnginePath - If -UseCustomTaskFactory or -UseCustomLogger is supplied, this specifies the full path of the custom task factory or logger assembly DLL that was written to disk. Otherwise, this property is empty. * CustomEngineHashSHA256 - If -UseCustomTaskFactory or -UseCustomLogger is supplied, this specifies the SHA256 of the custom task factory or logger assembly DLL that was written to disk. Otherwise, this property is empty. * RunnerFilePath - Specifies the full path of MSBuild runner. * RunnerProcessId - Specifies the process ID of MSBuild runner. * RunnerCommandLine - Specifies the commandline of MSBuild runner. * RunnerChildProcessId - Specifies the process ID of process that was executed as the result of the MSBuild project content executing. This property will not be populated if user-supplied project content is supplied via -ProjectFileContent. * RunnerChildProcessCommandLine - Specifies the commandline of process that was executed as the result of the HTA content executing. This property will not be populated if user-supplied project content is supplied via -ProjectFileContent. .EXAMPLE Invoke-ATHMSBuild .EXAMPLE Invoke-ATHMSBuild -ProjectFilePath test.txt Drops and executes the MSBuild project file from the specified path/filename. .EXAMPLE Invoke-ATHMSBuild -NoCLIProjectFile Drops a .proj file to the current directory and executes it without supplying any command-line arguments to MSBuild. .EXAMPLE Copy-Item -Path C:\Windows\Microsoft.NET\Framework64\v4.0.30319\MSBuild.exe -Destination foo.txt Invoke-ATHMSBuild -MSBuildFilePath foo.txt Copies MSBuild to a local directory, rename it, and execute .NET code with it. .EXAMPLE Invoke-ATHMSBuild -Language javascript Specifies JScript.NET as an alternative .NET language with which to compile and execute. .EXAMPLE Invoke-ATHMSBuild -TargetName Foo -TaskName Bar Populating the generated project file XML with non-default target and task names. .EXAMPLE Invoke-ATHMSBuild -UsePropertyFunctions Using property functions as an alternative to compiling and executing inline .NET code. .EXAMPLE Invoke-ATHMSBuild -UseCustomTaskFactory Proxying custom execution through a custom task factory assembly as an alternative to compiling and executing inline .NET code. .EXAMPLE Invoke-ATHMSBuild -UseCustomLogger Proxying custom execution through a custom logger assembly as an alternative to compiling and executing inline .NET code. .EXAMPLE Invoke-ATHMSBuild -UseUnregisterAssemblyTask Proxying custom execution through a custom assembly unregistration function as an alternative to compiling and executing inline .NET code. .EXAMPLE $CustomProjectContent = @' <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Target Name="TestTarget"> <TestTask /> </Target> <UsingTask TaskName="TestTask" TaskFactory="CodeTaskFactory" AssemblyFile="C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Microsoft.Build.Tasks.v4.0.dll" > <Task> <Code Language="cs"> <![CDATA[ System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo("powershell.exe", "-nop -Command Write-Host Foo; Start-Sleep -Seconds 2; exit"); startInfo.UseShellExecute = false; startInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden; System.Diagnostics.Process.Start(startInfo); ]]> </Code> </Task> </UsingTask> </Project> '@ Invoke-ATHMSBuild -ProjectFileContent $CustomProjectContent Executes custom MSBuild project content rather than using a generated template project file. #> [CmdletBinding(DefaultParameterSetName = 'InlineSourceCode')] param ( [Parameter(Mandatory, ParameterSetName = 'CustomProjectFileContents')] [String] [ValidateNotNullOrEmpty()] $ProjectFileContent, [Parameter(ParameterSetName = 'InlineSourceCode')] [Parameter(ParameterSetName = 'CustomTaskFactory')] [Parameter(ParameterSetName = 'CustomLogger')] [Parameter(ParameterSetName = 'CustomUnregisterFunction')] [Parameter(ParameterSetName = 'PropertyFunctions')] [Parameter(ParameterSetName = 'CustomProjectFileContents')] [String] [ValidateNotNullOrEmpty()] $ProjectFilePath = 'test.proj', [Parameter(ParameterSetName = 'InlineSourceCode')] [Parameter(ParameterSetName = 'CustomTaskFactory')] [Parameter(ParameterSetName = 'CustomLogger')] [Parameter(ParameterSetName = 'CustomUnregisterFunction')] [Parameter(ParameterSetName = 'PropertyFunctions')] [Parameter(ParameterSetName = 'CustomProjectFileContents')] [Switch] $NoCLIProjectFile, [Parameter(ParameterSetName = 'InlineSourceCode')] [Parameter(ParameterSetName = 'CustomTaskFactory')] [Parameter(ParameterSetName = 'CustomLogger')] [Parameter(ParameterSetName = 'CustomUnregisterFunction')] [Parameter(ParameterSetName = 'PropertyFunctions')] [Parameter(ParameterSetName = 'CustomProjectFileContents')] [String] [ValidateNotNullOrEmpty()] $MSBuildFilePath = "$([Runtime.InteropServices.RuntimeEnvironment]::GetRuntimeDirectory())MSBuild.exe", [Parameter(ParameterSetName = 'InlineSourceCode')] [String] [ValidateSet('cs', 'c#', 'csharp', 'vb', 'vbs', 'visualbasic', 'vbscript', 'js', 'jscript', 'javascript')] $Language = 'cs', [Parameter(Mandatory, ParameterSetName = 'PropertyFunctions')] [Switch] $UsePropertyFunctions, [Parameter(ParameterSetName = 'PropertyFunctions')] [String] [ValidateNotNullOrEmpty()] $PropertyName = 'TestProperty', [Parameter(ParameterSetName = 'InlineSourceCode')] [Parameter(ParameterSetName = 'CustomTaskFactory')] [Parameter(ParameterSetName = 'CustomLogger')] [Parameter(ParameterSetName = 'CustomUnregisterFunction')] [Parameter(ParameterSetName = 'PropertyFunctions')] [String] [ValidateNotNullOrEmpty()] $TargetName = 'TestTarget', [Parameter(ParameterSetName = 'InlineSourceCode')] [Parameter(ParameterSetName = 'CustomTaskFactory')] [Parameter(ParameterSetName = 'CustomLogger')] [Parameter(ParameterSetName = 'PropertyFunctions')] [String] [ValidateNotNullOrEmpty()] $TaskName = 'TestTask', [Parameter(ParameterSetName = 'CustomTaskFactory')] [String] [ValidateNotNullOrEmpty()] $TaskFactoryName = 'TestTaskFactory', [Parameter(Mandatory, ParameterSetName = 'CustomTaskFactory')] [Switch] $UseCustomTaskFactory, [Parameter(Mandatory, ParameterSetName = 'CustomLogger')] [Switch] $UseCustomLogger, [Parameter(Mandatory, ParameterSetName = 'CustomUnregisterFunction')] [Switch] $UseUnregisterAssemblyTask, [Parameter(ParameterSetName = 'CustomTaskFactory')] [Parameter(ParameterSetName = 'CustomLogger')] [Parameter(ParameterSetName = 'CustomUnregisterFunction')] [String] [ValidateNotNullOrEmpty()] $CustomEngineDllPath = 'CustomEngine.dll', [Parameter(ParameterSetName = 'InlineSourceCode')] [Parameter(ParameterSetName = 'CustomTaskFactory')] [Parameter(ParameterSetName = 'CustomLogger')] [Parameter(ParameterSetName = 'CustomUnregisterFunction')] [Parameter(ParameterSetName = 'PropertyFunctions')] [Parameter(ParameterSetName = 'CustomProjectFileContents')] [Guid] $TestGuid = (New-Guid) ) $CustomEngineDllHash = $null $MSBuildCommandLine = $null $MSBuildTaskExecuted = $null $MSBuildProcessId = $null $ProcessWMICommandLine = $null $ParentProcessPath = $null $SpawnedProcCommandLine = $null $SpawnedProcProcessId = $null $ExecutionType = $null $TestGuidToUse = $TestGuid $MSBuildFullPath = Resolve-Path -Path $MSBuildFilePath -ErrorAction Stop # Validate that the MSBuild supplied is actually MSBuild. $MSBuildFileInfo = Get-Item -Path $MSBuildFullPath -ErrorAction Stop if ($MSBuildFileInfo.VersionInfo.OriginalFilename -ne 'MSBuild.exe') { Write-Error "The MSBuild executable supplied is not MSBuild.exe: $MSBuildFullPath" return } $ParentDir = Split-Path -Path $ProjectFilePath -Parent $FileName = Split-Path -Path $ProjectFilePath -Leaf if (($ParentDir -eq '') -or ($ParentDir -eq '.')) { $ParentDir = $PWD.Path } if (!(Test-Path -Path $ParentDir -PathType Container)) { Write-Error "The following directory does not exist: $ParentDir" return } $FullProjectPath = Join-Path -Path $ParentDir -ChildPath $FileName $ParentEngineDir = Split-Path -Path $CustomEngineDllPath -Parent $EngineFileName = Split-Path -Path $CustomEngineDllPath -Leaf if (($ParentEngineDir -eq '') -or ($ParentEngineDir -eq '.')) { $ParentEngineDir = $PWD.Path } if (!(Test-Path -Path $ParentEngineDir -PathType Container)) { Write-Error "The following directory does not exist: $ParentEngineDir" return } $FullCustomEnginePath = Join-Path -Path $ParentEngineDir -ChildPath $EngineFileName $MSBuildCommandLine = "`"$MSBuildFullPath`"" if ($NoCLIProjectFile) { if (!$FileName.ToLower().EndsWith('proj')) { Write-Error "When not specifying a project file at the command-line, the project file on disk must end with a *proj extension." return } $ProjFileCount = Get-ChildItem -Path $ParentDir\*proj -File | Measure-Object | Select-Object -ExpandProperty Count if ($ProjFileCount -gt 1) { Write-Error "There cannot be more than one *proj file in $ParentDir. The following files were found: $((Get-ChildItem -Path $ParentDir\*proj | Select-Object -ExpandProperty Name) -join ', '). Either delete the files or create a new directory that doesn't have any *proj files in it." return } } $TypeDef = @" using System; using System.Diagnostics; using System.Collections.Generic; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; using System.Runtime.InteropServices; namespace AtomicTestHarnesses { public class MyAssemblyRegistration { [ComUnregisterFunction] public static void UnregisterFunction(Type t) { ProcessStartInfo startInfo = new ProcessStartInfo("powershell.exe", "-nop -Command Write-Host $TestGuid; Start-Sleep -Seconds 2; exit"); startInfo.UseShellExecute = false; Process.Start(startInfo); } } public class MyLogger : Logger { public override void Initialize(IEventSource eventSource) { eventSource.MessageRaised += new BuildMessageEventHandler(eventSource_MessageRaised); } void eventSource_MessageRaised(object sender, BuildMessageEventArgs e) { Guid testGuid; if ((e.SenderName == "Message") && Guid.TryParse(e.Message, out testGuid)) { Console.WriteLine("Message test: " + testGuid); ProcessStartInfo startInfo = new ProcessStartInfo("powershell.exe", "-nop -Command Write-Host " + testGuid + "; Start-Sleep -Seconds 2; exit"); startInfo.UseShellExecute = false; startInfo.WindowStyle = ProcessWindowStyle.Hidden; Process.Start(startInfo); } } } public class MyTask : ITask { private IBuildEngine buildEngine; private ITaskHost hostObject; public IBuildEngine BuildEngine { get { return this.buildEngine; } set { this.buildEngine = value; } } public ITaskHost HostObject { get { return this.hostObject; } set { this.hostObject = value; } } public bool Execute() { return true; } } public class $TaskFactoryName : ITaskFactory { private IDictionary<string, TaskPropertyInfo> taskParameterTypeInfo; public string FactoryName { get { return "Custom Task Factory"; } } public Type TaskType { get { return typeof(MyTask); } set {} } public TaskPropertyInfo[] GetTaskParameters() { TaskPropertyInfo[] array = new TaskPropertyInfo[this.taskParameterTypeInfo.Count]; this.taskParameterTypeInfo.Values.CopyTo(array, 0); return array; } public bool Initialize(string taskName, IDictionary<string, TaskPropertyInfo> taskParameters, string taskElementContents, IBuildEngine taskFactoryLoggingHost) { Console.WriteLine("Task contents: " + taskElementContents); ProcessStartInfo startInfo = new ProcessStartInfo("powershell.exe", "-nop -Command Write-Host " + taskElementContents + "; Start-Sleep -Seconds 2; exit"); startInfo.UseShellExecute = false; startInfo.WindowStyle = ProcessWindowStyle.Hidden; Process.Start(startInfo); this.taskParameterTypeInfo = taskParameters; return true; } public ITask CreateTask(IBuildEngine loggingHost) { MyTask task = new MyTask(); return task; } public void CleanupTask(ITask task) { } } } "@ switch ($PSCmdlet.ParameterSetName) { 'InlineSourceCode' { $ExecutionType = 'InlineSourceCode' $FullCustomEnginePath = $null $ProjectTemplate = @" <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Target Name="$TargetName"> <$TaskName /> </Target> <UsingTask TaskName="$TaskName" TaskFactory="CodeTaskFactory" AssemblyFile="$([Runtime.InteropServices.RuntimeEnvironment]::GetRuntimeDirectory())Microsoft.Build.Tasks.v4.0.dll" > <Task> <Code Language="$Language"> <![CDATA[ REPLACEME ]]> </Code> </Task> </UsingTask> </Project> "@ $ProjectTemplateJScript = @" <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Target Name="$TargetName"> <$TaskName /> </Target> <UsingTask TaskName="$TaskName" TaskFactory="CodeTaskFactory" AssemblyFile="$([Runtime.InteropServices.RuntimeEnvironment]::GetRuntimeDirectory())Microsoft.Build.Tasks.v4.0.dll" > <Task> <Reference Include="System" /> <Code Language="$Language"> <![CDATA[ REPLACEME ]]> </Code> </Task> </UsingTask> </Project> "@ $CSharpCode = @" System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo("powershell.exe", "-nop -Command Write-Host $($TestGuid); Start-Sleep -Seconds 2; exit"); startInfo.UseShellExecute = false; startInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden; System.Diagnostics.Process.Start(startInfo); "@ $VBDotNetCode = @" Dim startInfo As New System.Diagnostics.ProcessStartInfo startInfo.FileName = "powershell.exe" startInfo.Arguments = "-nop -Command Write-Host $($TestGuid); Start-Sleep -Seconds 2; exit" startInfo.UseShellExecute = False startInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden System.Diagnostics.Process.Start(startInfo) "@ $JScriptDotNetCode = @" var startInfo; startInfo = new System.Diagnostics.ProcessStartInfo("powershell.exe", "-nop -Command Write-Host $($TestGuid); Start-Sleep -Seconds 2; exit"); startInfo.UseShellExecute = false; startInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden; System.Diagnostics.Process.Start(startInfo); "@ switch ($Language) { 'cs' { $ProcRunnerCode = $CSharpCode } 'c#' { $ProcRunnerCode = $CSharpCode } 'csharp' { $ProcRunnerCode = $CSharpCode } 'vb' { $ProcRunnerCode = $VBDotNetCode } 'vbs' { $ProcRunnerCode = $VBDotNetCode <# Despite the naming, VB.Net is interpreted #> } 'visualbasic' { $ProcRunnerCode = $VBDotNetCode } 'vbscript' { $ProcRunnerCode = $VBDotNetCode <# Despite the naming, VB.Net is interpreted #> } 'js' { $ProcRunnerCode = $JScriptDotNetCode; $ProjectTemplate = $ProjectTemplateJScript } 'jscript' { $ProcRunnerCode = $JScriptDotNetCode; $ProjectTemplate = $ProjectTemplateJScript } 'javascript' { $ProcRunnerCode = $JScriptDotNetCode; $ProjectTemplate = $ProjectTemplateJScript } } $ProjectTemplate = $ProjectTemplate.Replace('REPLACEME', $ProcRunnerCode) } 'CustomProjectFileContents' { $ExecutionType = 'CustomProjectFileContent' $FullCustomEnginePath = $null $TestGuidToUse = $null $ProjectTemplate = $ProjectFileContent } 'PropertyFunctions' { $ExecutionType = 'PropertyFunctions' $FullCustomEnginePath = $null $ProjectTemplate = @" <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Target Name="$TargetName"> <PropertyGroup> <$PropertyName>`$([System.Diagnostics.Process]::Start("powershell.exe", "-nop -Command Write-Host $($TestGuid); Start-Sleep -Seconds 2; exit"))</$PropertyName> </PropertyGroup> </Target> </Project> "@ } 'CustomUnregisterFunction' { $ExecutionType = 'CustomUnregisterFunction' Add-Type -TypeDefinition $TypeDef -ReferencedAssemblies "$([Runtime.InteropServices.RuntimeEnvironment]::GetRuntimeDirectory())Microsoft.Build.Framework.dll", "$([Runtime.InteropServices.RuntimeEnvironment]::GetRuntimeDirectory())Microsoft.Build.Utilities.v4.0.dll" -OutputAssembly $FullCustomEnginePath -ErrorAction Stop $CustomEngineDllHash = Get-FileHash -Path $FullCustomEnginePath | Select-Object -ExpandProperty Hash $ProjectTemplate = @" <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Target Name="$TargetName"> <UnregisterAssembly Assemblies="$FullCustomEnginePath" /> </Target> </Project> "@ } 'CustomLogger' { $ExecutionType = 'CustomLogger' Add-Type -TypeDefinition $TypeDef -ReferencedAssemblies "$([Runtime.InteropServices.RuntimeEnvironment]::GetRuntimeDirectory())Microsoft.Build.Framework.dll", "$([Runtime.InteropServices.RuntimeEnvironment]::GetRuntimeDirectory())Microsoft.Build.Utilities.v4.0.dll" -OutputAssembly $FullCustomEnginePath -ErrorAction Stop $CustomEngineDllHash = Get-FileHash -Path $FullCustomEnginePath | Select-Object -ExpandProperty Hash $ProjectTemplate = @" <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Target Name="$TargetName"> <Message Text="$TestGuid" /> </Target> </Project> "@ $MSBuildCommandLine += " /logger:$FullCustomEnginePath" } 'CustomTaskFactory' { $ExecutionType = 'CustomTaskFactory' Add-Type -TypeDefinition $TypeDef -ReferencedAssemblies "$([Runtime.InteropServices.RuntimeEnvironment]::GetRuntimeDirectory())Microsoft.Build.Framework.dll", "$([Runtime.InteropServices.RuntimeEnvironment]::GetRuntimeDirectory())Microsoft.Build.Utilities.v4.0.dll" -OutputAssembly $FullCustomEnginePath -ErrorAction Stop $CustomEngineDllHash = Get-FileHash -Path $FullCustomEnginePath | Select-Object -ExpandProperty Hash $ProjectTemplate = @" <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Target Name="$TargetName"> <$TaskName /> </Target> <UsingTask TaskName="$TaskName" TaskFactory="$TaskFactoryName" AssemblyFile="$FullCustomEnginePath" > <Task>$TestGuid</Task> </UsingTask> </Project> "@ } } Out-File -FilePath $FullProjectPath -InputObject $ProjectTemplate -Force $ProjectHash = Get-FileHash -Path $FullProjectPath -Algorithm SHA256 | Select-Object -ExpandProperty Hash if (-not $NoCLIProjectFile) { $MSBuildCommandLine += " $($FullProjectPath)" } # Only run the following if non-custom project content is supplied (i.e. -ProjectFileContent is not supplied) if ($ExecutionType -ne 'CustomProjectFileContent') { # Remove any stale events Get-Event -SourceIdentifier 'ChildProcSpawned' -ErrorAction SilentlyContinue | Remove-Event Get-EventSubscriber -SourceIdentifier 'ProcessSpawned' -ErrorAction SilentlyContinue | Unregister-Event # Trigger an event any time powershell.exe has $TestGuid in the command line. # This event should correspond to the mshta or rundll process that launched it. $WMIEventQuery = "SELECT * FROM __InstanceCreationEvent WITHIN 1 WHERE TargetInstance ISA 'Win32_Process' AND TargetInstance.Name = 'powershell.exe' AND TargetInstance.CommandLine LIKE '%$($TestGuid)%'" Write-Verbose "Registering MSBuild.exe child process creation WMI event using the following WMI event query: $WMIEventQuery" $null = Register-CimIndicationEvent -SourceIdentifier 'ProcessSpawned' -Query $WMIEventQuery -Action { $SpawnedProcInfo = [PSCustomObject] @{ ProcessId = $EventArgs.NewEvent.TargetInstance.ProcessId ProcessCommandLine = $EventArgs.NewEvent.TargetInstance.CommandLine } New-Event -SourceIdentifier 'ChildProcSpawned' -MessageData $SpawnedProcInfo Stop-Process -Id $EventArgs.NewEvent.TargetInstance.ProcessId } } $ProcessStartup = New-CimInstance -ClassName Win32_ProcessStartup -ClientOnly $ProcessStartupInstance = Get-CimInstance -InputObject $ProcessStartup $ProcessStartupInstance.ShowWindow = [UInt16] 0 # Hide the window if ($UsePropertyFunctions) { # Set %MSBUILDENABLEALLPROPERTYFUNCTIONS% in the child MSBuild process so that property function restrictions are lifted. [String[]] $AllEnvVars = (Get-ChildItem Env:\* | ForEach-Object { "$($_.Name)=$($_.Value)" }) + 'MSBUILDENABLEALLPROPERTYFUNCTIONS=1' $ProcessStartupInstance.EnvironmentVariables = $AllEnvVars } $ProcStartResult = Invoke-CimMethod -ClassName Win32_Process -MethodName Create -Arguments @{ CommandLine = $MSBuildCommandLine; CurrentDirectory = $PWD.Path; ProcessStartupInformation = $ProcessStartupInstance } if ($ProcStartResult.ReturnValue -eq 0) { $MSBuildProcessId = $ProcStartResult.ProcessId if ($ExecutionType -eq 'CustomProjectFileContent') { # When custom task XML is supplied, there may be a race condition where process information cannot be retrieved. $ParentProcessPath = $MSBuildFullPath $ProcessWMICommandLine = $MSBuildCommandLine } else { # Retrieval via WMI is a more authoritative source $ParentProcess = Get-CimInstance -ClassName 'Win32_Process' -Filter "ProcessId = $MSBuildProcessId" -Property 'CommandLine', 'ExecutablePath' $ParentProcessPath = $ParentProcess.ExecutablePath $ProcessWMICommandLine = $ParentProcess.CommandLine } } else { Write-Error "MSbuild process failed to start." } if ($ExecutionType -ne 'CustomProjectFileContent') { $ChildProcSpawnedEvent = Wait-Event -SourceIdentifier 'ChildProcSpawned' -Timeout 10 $ChildProcInfo = $null if ($ChildProcSpawnedEvent) { $MSBuildTaskExecuted = $True $ChildProcInfo = $ChildProcSpawnedEvent.MessageData $SpawnedProcCommandLine = $ChildProcInfo.ProcessCommandLine $SpawnedProcProcessId = $ChildProcInfo.ProcessId $ChildProcSpawnedEvent | Remove-Event } else { Write-Error "MSBuild child process was not spawned." } # Cleanup Unregister-Event -SourceIdentifier 'ProcessSpawned' } [PSCustomObject] @{ TechniqueID = 'T1127.001' TestSuccess = $MSBuildTaskExecuted TestGuid = $TestGuidToUse ExecutionType = $ExecutionType ProjectFilePath = $FullProjectPath ProjectFileHashSHA256 = $ProjectHash ProjectContents = $ProjectTemplate CustomEnginePath = $FullCustomEnginePath CustomEngineHashSHA256 = $CustomEngineDllHash RunnerFilePath = $ParentProcessPath RunnerProcessId = $MSBuildProcessId RunnerCommandLine = $ProcessWMICommandLine RunnerChildProcessId = $SpawnedProcProcessId RunnerChildProcessCommandLine = $SpawnedProcCommandLine } } |