Refactoring and adding test cases for T1118 (#872)
* Refactoring and adding test cases for T1118 Developed a new test harness for InstallUtil variant execution and built many new tests around it. * T1118 test refactoring and documentation * All installer assemblies now output to %TEMP% by default so as to not pollute an atomics directory. * Get-CommandLineArgument and Invoke-BuildAndInvokeInstallUtilAssembly are now fully documented. * Cleanup commands added * Any mention of payload was removed. This isn't offensive code and we should give that impression. * Removed Rollback and Commit methods from the installer source code. I do not see it as a necessity to test this functionality. Co-authored-by: Matt Graeber <mattifestation@users.noreply.github.com> Co-authored-by: Carrie Roberts <clr2of8@gmail.com>
This commit is contained in:
+433
-46
@@ -1,72 +1,459 @@
|
||||
---
|
||||
attack_technique: T1118
|
||||
display_name: InstallUtil
|
||||
|
||||
atomic_tests:
|
||||
- name: InstallUtil uninstall method call
|
||||
- name: CheckIfInstallable method call
|
||||
description: |
|
||||
Executes the CheckIfInstallable class constructor runner instead of executing InstallUtil.
|
||||
supported_platforms:
|
||||
- windows
|
||||
input_arguments:
|
||||
test_harness:
|
||||
description: location of the test harness script - Invoke-BuildAndInvokeInstallUtilAssembly
|
||||
type: Path
|
||||
default: PathToAtomicsFolder\T1118\src\InstallUtilTestHarness.ps1
|
||||
assembly_dir:
|
||||
description: directory to drop the compiled installer assembly
|
||||
type: Path
|
||||
default: $Env:TEMP\
|
||||
assembly_filename:
|
||||
description: filename of the compiled installer assembly
|
||||
type: String
|
||||
default: T1118.dll
|
||||
invocation_method:
|
||||
description: the type of InstallUtil invocation variant - Executable, InstallHelper, or CheckIfInstallable
|
||||
type: String
|
||||
default: CheckIfInstallable
|
||||
executor:
|
||||
name: powershell
|
||||
elevation_required: false
|
||||
command: |
|
||||
# Import the required test harness function, Invoke-BuildAndInvokeInstallUtilAssembly
|
||||
. #{test_harness}
|
||||
|
||||
$InstallerAssemblyDir = "#{assembly_dir}"
|
||||
$InstallerAssemblyFileName = "#{assembly_filename}"
|
||||
$InstallerAssemblyFullPath = Join-Path -Path $InstallerAssemblyDir -ChildPath $InstallerAssemblyFileName
|
||||
|
||||
$ExpectedOutput = 'Constructor_'
|
||||
|
||||
$TestArgs = @{
|
||||
OutputAssemblyDirectory = $InstallerAssemblyDir
|
||||
OutputAssemblyFileName = $InstallerAssemblyFileName
|
||||
InvocationMethod = '#{invocation_method}'
|
||||
}
|
||||
|
||||
$ActualOutput = Invoke-BuildAndInvokeInstallUtilAssembly @TestArgs -MinimumViableAssembly
|
||||
|
||||
if ($ActualOutput -ne $ExpectedOutput) {
|
||||
throw @"
|
||||
CheckIfInstallable method execution test failure. Installer assembly execution output did not match the expected output.
|
||||
Expected: $ExpectedOutput
|
||||
Actual: $ActualOutput
|
||||
"@
|
||||
}
|
||||
cleanup_command: |
|
||||
$InstallerAssemblyDir = "#{assembly_dir}"
|
||||
$InstallerAssemblyFileName = "#{assembly_filename}"
|
||||
$InstallerAssemblyFullPath = Join-Path -Path $InstallerAssemblyDir -ChildPath $InstallerAssemblyFileName
|
||||
|
||||
Remove-Item -Path $InstallerAssemblyFullPath
|
||||
- name: InstallHelper method call
|
||||
description: |
|
||||
Executes the InstallHelper class constructor runner instead of executing InstallUtil.
|
||||
supported_platforms:
|
||||
- windows
|
||||
input_arguments:
|
||||
test_harness:
|
||||
description: location of the test harness script - Invoke-BuildAndInvokeInstallUtilAssembly
|
||||
type: Path
|
||||
default: PathToAtomicsFolder\T1118\src\InstallUtilTestHarness.ps1
|
||||
assembly_dir:
|
||||
description: directory to drop the compiled installer assembly
|
||||
type: Path
|
||||
default: $Env:TEMP\
|
||||
assembly_filename:
|
||||
description: filename of the compiled installer assembly
|
||||
type: String
|
||||
default: T1118.dll
|
||||
invocation_method:
|
||||
description: the type of InstallUtil invocation variant - Executable, InstallHelper, or CheckIfInstallable
|
||||
type: String
|
||||
default: InstallHelper
|
||||
executor:
|
||||
name: powershell
|
||||
elevation_required: false
|
||||
command: |
|
||||
# Import the required test harness function, Invoke-BuildAndInvokeInstallUtilAssembly
|
||||
. #{test_harness}
|
||||
|
||||
$InstallerAssemblyDir = "#{assembly_dir}"
|
||||
$InstallerAssemblyFileName = "#{assembly_filename}"
|
||||
$InstallerAssemblyFullPath = Join-Path -Path $InstallerAssemblyDir -ChildPath $InstallerAssemblyFileName
|
||||
|
||||
$CommandLine = "/logfile= /logtoconsole=false `"$InstallerAssemblyFullPath`""
|
||||
$ExpectedOutput = 'Constructor_'
|
||||
|
||||
$TestArgs = @{
|
||||
OutputAssemblyDirectory = $InstallerAssemblyDir
|
||||
OutputAssemblyFileName = $InstallerAssemblyFileName
|
||||
InvocationMethod = '#{invocation_method}'
|
||||
CommandLine = $CommandLine
|
||||
}
|
||||
|
||||
$ActualOutput = Invoke-BuildAndInvokeInstallUtilAssembly @TestArgs -MinimumViableAssembly
|
||||
|
||||
if ($ActualOutput -ne $ExpectedOutput) {
|
||||
throw @"
|
||||
InstallHelper method execution test failure. Installer assembly execution output did not match the expected output.
|
||||
Expected: $ExpectedOutput
|
||||
Actual: $ActualOutput
|
||||
"@
|
||||
}
|
||||
cleanup_command: |
|
||||
$InstallerAssemblyDir = "#{assembly_dir}"
|
||||
$InstallerAssemblyFileName = "#{assembly_filename}"
|
||||
$InstallerAssemblyFullPath = Join-Path -Path $InstallerAssemblyDir -ChildPath $InstallerAssemblyFileName
|
||||
|
||||
Remove-Item -Path $InstallerAssemblyFullPath
|
||||
- name: InstallUtil class constructor method call
|
||||
description: |
|
||||
Executes the installer assembly class constructor.
|
||||
supported_platforms:
|
||||
- windows
|
||||
input_arguments:
|
||||
test_harness:
|
||||
description: location of the test harness script - Invoke-BuildAndInvokeInstallUtilAssembly
|
||||
type: Path
|
||||
default: PathToAtomicsFolder\T1118\src\InstallUtilTestHarness.ps1
|
||||
assembly_dir:
|
||||
description: directory to drop the compiled installer assembly
|
||||
type: Path
|
||||
default: $Env:TEMP\
|
||||
assembly_filename:
|
||||
description: filename of the compiled installer assembly
|
||||
type: String
|
||||
default: T1118.dll
|
||||
invocation_method:
|
||||
description: the type of InstallUtil invocation variant - Executable, InstallHelper, or CheckIfInstallable
|
||||
type: String
|
||||
default: Executable
|
||||
executor:
|
||||
name: powershell
|
||||
elevation_required: false
|
||||
command: |
|
||||
# Import the required test harness function, Invoke-BuildAndInvokeInstallUtilAssembly
|
||||
. #{test_harness}
|
||||
|
||||
$InstallerAssemblyDir = "#{assembly_dir}"
|
||||
$InstallerAssemblyFileName = "#{assembly_filename}"
|
||||
$InstallerAssemblyFullPath = Join-Path -Path $InstallerAssemblyDir -ChildPath $InstallerAssemblyFileName
|
||||
|
||||
$CommandLine = "/logfile= /logtoconsole=false `"$InstallerAssemblyFullPath`""
|
||||
$ExpectedOutput = 'Constructor_'
|
||||
|
||||
$TestArgs = @{
|
||||
OutputAssemblyDirectory = $InstallerAssemblyDir
|
||||
OutputAssemblyFileName = $InstallerAssemblyFileName
|
||||
InvocationMethod = '#{invocation_method}'
|
||||
CommandLine = $CommandLine
|
||||
}
|
||||
|
||||
$ActualOutput = Invoke-BuildAndInvokeInstallUtilAssembly @TestArgs -MinimumViableAssembly
|
||||
|
||||
if ($ActualOutput -ne $ExpectedOutput) {
|
||||
throw @"
|
||||
InstallUtil class constructor execution test failure. Installer assembly execution output did not match the expected output.
|
||||
Expected: $ExpectedOutput
|
||||
Actual: $ActualOutput
|
||||
"@
|
||||
}
|
||||
cleanup_command: |
|
||||
$InstallerAssemblyDir = "#{assembly_dir}"
|
||||
$InstallerAssemblyFileName = "#{assembly_filename}"
|
||||
$InstallerAssemblyFullPath = Join-Path -Path $InstallerAssemblyDir -ChildPath $InstallerAssemblyFileName
|
||||
|
||||
Remove-Item -Path $InstallerAssemblyFullPath
|
||||
- name: InstallUtil Install method call
|
||||
description: |
|
||||
Executes the Install Method
|
||||
supported_platforms:
|
||||
- windows
|
||||
input_arguments:
|
||||
test_harness:
|
||||
description: location of the test harness script - Invoke-BuildAndInvokeInstallUtilAssembly
|
||||
type: Path
|
||||
default: PathToAtomicsFolder\T1118\src\InstallUtilTestHarness.ps1
|
||||
assembly_dir:
|
||||
description: directory to drop the compiled installer assembly
|
||||
type: Path
|
||||
default: $Env:TEMP\
|
||||
assembly_filename:
|
||||
description: filename of the compiled installer assembly
|
||||
type: String
|
||||
default: T1118.dll
|
||||
invocation_method:
|
||||
description: the type of InstallUtil invocation variant - Executable, InstallHelper, or CheckIfInstallable
|
||||
type: String
|
||||
default: Executable
|
||||
executor:
|
||||
name: powershell
|
||||
elevation_required: false
|
||||
command: |
|
||||
# Import the required test harness function, Invoke-BuildAndInvokeInstallUtilAssembly
|
||||
. #{test_harness}
|
||||
|
||||
$InstallerAssemblyDir = "#{assembly_dir}"
|
||||
$InstallerAssemblyFileName = "#{assembly_filename}"
|
||||
$InstallerAssemblyFullPath = Join-Path -Path $InstallerAssemblyDir -ChildPath $InstallerAssemblyFileName
|
||||
|
||||
$CommandLine = "/logfile= /logtoconsole=false /installtype=notransaction /action=install `"$InstallerAssemblyFullPath`""
|
||||
$ExpectedOutput = 'Constructor_Install_'
|
||||
|
||||
$TestArgs = @{
|
||||
OutputAssemblyDirectory = $InstallerAssemblyDir
|
||||
OutputAssemblyFileName = $InstallerAssemblyFileName
|
||||
InvocationMethod = '#{invocation_method}'
|
||||
CommandLine = $CommandLine
|
||||
}
|
||||
|
||||
$ActualOutput = Invoke-BuildAndInvokeInstallUtilAssembly @TestArgs
|
||||
|
||||
if ($ActualOutput -ne $ExpectedOutput) {
|
||||
throw @"
|
||||
InstallUtil Install method execution test failure. Installer assembly execution output did not match the expected output.
|
||||
Expected: $ExpectedOutput
|
||||
Actual: $ActualOutput
|
||||
"@
|
||||
}
|
||||
cleanup_command: |
|
||||
$InstallerAssemblyDir = "#{assembly_dir}"
|
||||
$InstallerAssemblyFileName = "#{assembly_filename}"
|
||||
$InstallerAssemblyFullPath = Join-Path -Path $InstallerAssemblyDir -ChildPath $InstallerAssemblyFileName
|
||||
|
||||
Remove-Item -Path $InstallerAssemblyFullPath
|
||||
- name: InstallUtil Uninstall method call - /U variant
|
||||
description: |
|
||||
Executes the Uninstall Method
|
||||
supported_platforms:
|
||||
- windows
|
||||
input_arguments:
|
||||
output_file:
|
||||
description: location of the payload
|
||||
test_harness:
|
||||
description: location of the test harness script - Invoke-BuildAndInvokeInstallUtilAssembly
|
||||
type: Path
|
||||
default: '%tmp%\T1118.dll'
|
||||
source:
|
||||
description: location of the source code to compile
|
||||
default: PathToAtomicsFolder\T1118\src\InstallUtilTestHarness.ps1
|
||||
assembly_dir:
|
||||
description: directory to drop the compiled installer assembly
|
||||
type: Path
|
||||
default: PathToAtomicsFolder\T1118\src\T1118.cs
|
||||
|
||||
dependency_executor_name: powershell
|
||||
dependencies:
|
||||
- description: |
|
||||
Source code must exist on disk at specified location (#{source})
|
||||
prereq_command: |
|
||||
if (Test-Path #{source}) {exit 0} else {exit 1}
|
||||
get_prereq_command: |
|
||||
New-Item -Type Directory (split-path #{source}) -ErrorAction ignore | Out-Null
|
||||
Invoke-WebRequest "https://github.com/redcanaryco/atomic-red-team/raw/master/atomics/T1118/src/T1118.cs" -OutFile "#{source}"
|
||||
|
||||
default: $Env:TEMP\
|
||||
assembly_filename:
|
||||
description: filename of the compiled installer assembly
|
||||
type: String
|
||||
default: T1118.dll
|
||||
invocation_method:
|
||||
description: the type of InstallUtil invocation variant - Executable, InstallHelper, or CheckIfInstallable
|
||||
type: String
|
||||
default: Executable
|
||||
executor:
|
||||
name: command_prompt
|
||||
name: powershell
|
||||
elevation_required: false
|
||||
command: |
|
||||
C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe /target:library /out:#{output_file} #{source}
|
||||
C:\Windows\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe /logfile= /LogToConsole=false /U #{output_file}
|
||||
cleanup_command: |
|
||||
del #{output_file} >nul 2>&1
|
||||
# Import the required test harness function, Invoke-BuildAndInvokeInstallUtilAssembly
|
||||
. #{test_harness}
|
||||
|
||||
- name: InstallUtil GetHelp method call
|
||||
$InstallerAssemblyDir = "#{assembly_dir}"
|
||||
$InstallerAssemblyFileName = "#{assembly_filename}"
|
||||
$InstallerAssemblyFullPath = Join-Path -Path $InstallerAssemblyDir -ChildPath $InstallerAssemblyFileName
|
||||
|
||||
$CommandLine = "/logfile= /logtoconsole=false /U `"$InstallerAssemblyFullPath`""
|
||||
$ExpectedOutput = 'Constructor_Uninstall_'
|
||||
|
||||
$TestArgs = @{
|
||||
OutputAssemblyDirectory = $InstallerAssemblyDir
|
||||
OutputAssemblyFileName = $InstallerAssemblyFileName
|
||||
InvocationMethod = '#{invocation_method}'
|
||||
CommandLine = $CommandLine
|
||||
}
|
||||
|
||||
$ActualOutput = Invoke-BuildAndInvokeInstallUtilAssembly @TestArgs
|
||||
|
||||
if ($ActualOutput -ne $ExpectedOutput) {
|
||||
throw @"
|
||||
InstallUtil Uninstall method execution test failure. Installer assembly execution output did not match the expected output.
|
||||
Expected: $ExpectedOutput
|
||||
Actual: $ActualOutput
|
||||
"@
|
||||
}
|
||||
cleanup_command: |
|
||||
$InstallerAssemblyDir = "#{assembly_dir}"
|
||||
$InstallerAssemblyFileName = "#{assembly_filename}"
|
||||
$InstallerAssemblyFullPath = Join-Path -Path $InstallerAssemblyDir -ChildPath $InstallerAssemblyFileName
|
||||
|
||||
Remove-Item -Path $InstallerAssemblyFullPath
|
||||
- name: InstallUtil Uninstall method call - '/installtype=notransaction /action=uninstall' variant
|
||||
description: |
|
||||
Executes the Uninstall Method
|
||||
supported_platforms:
|
||||
- windows
|
||||
input_arguments:
|
||||
output_file:
|
||||
description: location of the payload
|
||||
test_harness:
|
||||
description: location of the test harness script - Invoke-BuildAndInvokeInstallUtilAssembly
|
||||
type: Path
|
||||
default: '%tmp%\T1118.dll'
|
||||
source:
|
||||
description: location of the source code to compile
|
||||
default: PathToAtomicsFolder\T1118\src\InstallUtilTestHarness.ps1
|
||||
assembly_dir:
|
||||
description: directory to drop the compiled installer assembly
|
||||
type: Path
|
||||
default: PathToAtomicsFolder\T1118\src\T1118.cs
|
||||
|
||||
dependency_executor_name: powershell
|
||||
dependencies:
|
||||
- description: |
|
||||
Source code must exist on disk at specified location (#{source})
|
||||
prereq_command: |
|
||||
if (Test-Path #{source}) {exit 0} else {exit 1}
|
||||
get_prereq_command: |
|
||||
New-Item -Type Directory (split-path #{source}) -ErrorAction ignore | Out-Null
|
||||
Invoke-WebRequest "https://github.com/redcanaryco/atomic-red-team/raw/master/atomics/T1118/src/T1118.cs" -OutFile "#{source}"
|
||||
|
||||
default: $Env:TEMP\
|
||||
assembly_filename:
|
||||
description: filename of the compiled installer assembly
|
||||
type: String
|
||||
default: T1118.dll
|
||||
invocation_method:
|
||||
description: the type of InstallUtil invocation variant - Executable, InstallHelper, or CheckIfInstallable
|
||||
type: String
|
||||
default: Executable
|
||||
executor:
|
||||
name: command_prompt
|
||||
name: powershell
|
||||
elevation_required: false
|
||||
command: |
|
||||
C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe /target:library /out:#{output_file} #{source}
|
||||
C:\Windows\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe /? #{output_file}
|
||||
# Import the required test harness function, Invoke-BuildAndInvokeInstallUtilAssembly
|
||||
. #{test_harness}
|
||||
|
||||
$InstallerAssemblyDir = "#{assembly_dir}"
|
||||
$InstallerAssemblyFileName = "#{assembly_filename}"
|
||||
$InstallerAssemblyFullPath = Join-Path -Path $InstallerAssemblyDir -ChildPath $InstallerAssemblyFileName
|
||||
|
||||
$CommandLine = "/logfile= /logtoconsole=false /installtype=notransaction /action=uninstall `"$InstallerAssemblyFullPath`""
|
||||
$ExpectedOutput = 'Constructor_Uninstall_'
|
||||
|
||||
$TestArgs = @{
|
||||
OutputAssemblyDirectory = $InstallerAssemblyDir
|
||||
OutputAssemblyFileName = $InstallerAssemblyFileName
|
||||
InvocationMethod = '#{invocation_method}'
|
||||
CommandLine = $CommandLine
|
||||
}
|
||||
|
||||
$ActualOutput = Invoke-BuildAndInvokeInstallUtilAssembly @TestArgs
|
||||
|
||||
if ($ActualOutput -ne $ExpectedOutput) {
|
||||
throw @"
|
||||
InstallUtil Uninstall method execution test failure. Installer assembly execution output did not match the expected output.
|
||||
Expected: $ExpectedOutput
|
||||
Actual: $ActualOutput
|
||||
"@
|
||||
}
|
||||
cleanup_command: |
|
||||
del #{output_file} >nul 2>&1
|
||||
$InstallerAssemblyDir = "#{assembly_dir}"
|
||||
$InstallerAssemblyFileName = "#{assembly_filename}"
|
||||
$InstallerAssemblyFullPath = Join-Path -Path $InstallerAssemblyDir -ChildPath $InstallerAssemblyFileName
|
||||
|
||||
Remove-Item -Path $InstallerAssemblyFullPath
|
||||
- name: InstallUtil HelpText method call
|
||||
description: |
|
||||
Executes the Uninstall Method
|
||||
supported_platforms:
|
||||
- windows
|
||||
input_arguments:
|
||||
test_harness:
|
||||
description: location of the test harness script - Invoke-BuildAndInvokeInstallUtilAssembly
|
||||
type: Path
|
||||
default: PathToAtomicsFolder\T1118\src\InstallUtilTestHarness.ps1
|
||||
assembly_dir:
|
||||
description: directory to drop the compiled installer assembly
|
||||
type: Path
|
||||
default: $Env:TEMP\
|
||||
assembly_filename:
|
||||
description: filename of the compiled installer assembly
|
||||
type: String
|
||||
default: T1118.dll
|
||||
invocation_method:
|
||||
description: the type of InstallUtil invocation variant - Executable, InstallHelper, or CheckIfInstallable
|
||||
type: String
|
||||
default: Executable
|
||||
executor:
|
||||
name: powershell
|
||||
elevation_required: false
|
||||
command: |
|
||||
# Import the required test harness function, Invoke-BuildAndInvokeInstallUtilAssembly
|
||||
. #{test_harness}
|
||||
|
||||
$InstallerAssemblyDir = "#{assembly_dir}"
|
||||
$InstallerAssemblyFileName = "#{assembly_filename}"
|
||||
$InstallerAssemblyFullPath = Join-Path -Path $InstallerAssemblyDir -ChildPath $InstallerAssemblyFileName
|
||||
|
||||
$CommandLine = "/? `"$InstallerAssemblyFullPath`""
|
||||
$ExpectedOutput = 'Constructor_HelpText_'
|
||||
|
||||
$TestArgs = @{
|
||||
OutputAssemblyDirectory = $InstallerAssemblyDir
|
||||
OutputAssemblyFileName = $InstallerAssemblyFileName
|
||||
InvocationMethod = '#{invocation_method}'
|
||||
CommandLine = $CommandLine
|
||||
}
|
||||
|
||||
$ActualOutput = Invoke-BuildAndInvokeInstallUtilAssembly @TestArgs
|
||||
|
||||
if ($ActualOutput -ne $ExpectedOutput) {
|
||||
throw @"
|
||||
InstallUtil HelpText property execution test failure. Installer assembly execution output did not match the expected output.
|
||||
Expected: $ExpectedOutput
|
||||
Actual: $ActualOutput
|
||||
"@
|
||||
}
|
||||
cleanup_command: |
|
||||
$InstallerAssemblyDir = "#{assembly_dir}"
|
||||
$InstallerAssemblyFileName = "#{assembly_filename}"
|
||||
$InstallerAssemblyFullPath = Join-Path -Path $InstallerAssemblyDir -ChildPath $InstallerAssemblyFileName
|
||||
|
||||
Remove-Item -Path $InstallerAssemblyFullPath
|
||||
- name: InstallUtil evasive invocation
|
||||
description: |
|
||||
Executes an InstallUtil assembly by renaming InstallUtil.exe and using a nonstandard extension for the assembly.
|
||||
supported_platforms:
|
||||
- windows
|
||||
input_arguments:
|
||||
test_harness:
|
||||
description: location of the test harness script - Invoke-BuildAndInvokeInstallUtilAssembly
|
||||
type: Path
|
||||
default: PathToAtomicsFolder\T1118\src\InstallUtilTestHarness.ps1
|
||||
executor:
|
||||
name: powershell
|
||||
elevation_required: false
|
||||
command: |
|
||||
# Import the required test harness function, Invoke-BuildAndInvokeInstallUtilAssembly
|
||||
. #{test_harness}
|
||||
|
||||
$InstallerAssemblyDir = "$Env:windir\System32\Tasks"
|
||||
$InstallerAssemblyFileName = 'readme.txt'
|
||||
$InstallerAssemblyFullPath = Join-Path -Path $InstallerAssemblyDir -ChildPath $InstallerAssemblyFileName
|
||||
|
||||
$CommandLine = "readme.txt"
|
||||
$ExpectedOutput = 'Constructor_'
|
||||
|
||||
# Explicitly set the directory so that a relative path to readme.txt can be supplied.
|
||||
Set-Location "$Env:windir\System32\Tasks"
|
||||
|
||||
Copy-Item -Path "$([System.Runtime.InteropServices.RuntimeEnvironment]::GetRuntimeDirectory())InstallUtil.exe" -Destination "$Env:windir\System32\Tasks\notepad.exe"
|
||||
|
||||
$TestArgs = @{
|
||||
OutputAssemblyDirectory = $InstallerAssemblyDir
|
||||
OutputAssemblyFileName = $InstallerAssemblyFileName
|
||||
InvocationMethod = 'Executable'
|
||||
CommandLine = $CommandLine
|
||||
InstallUtilPath = "$Env:windir\System32\Tasks\notepad.exe"
|
||||
}
|
||||
|
||||
$ActualOutput = Invoke-BuildAndInvokeInstallUtilAssembly @TestArgs -MinimumViableAssembly
|
||||
|
||||
if ($ActualOutput -ne $ExpectedOutput) {
|
||||
throw @"
|
||||
Evasive Installutil invocation test failure. Installer assembly execution output did not match the expected output.
|
||||
Expected: $ExpectedOutput
|
||||
Actual: $ActualOutput
|
||||
"@
|
||||
}
|
||||
cleanup_command: |
|
||||
Remove-Item -Path "$Env:windir\System32\Tasks\readme.txt"
|
||||
Remove-Item -Path "$Env:windir\System32\Tasks\readme.InstallLog"
|
||||
Remove-Item -Path "$Env:windir\System32\Tasks\readme.InstallState"
|
||||
Remove-Item -Path "$Env:windir\System32\Tasks\notepad.exe"
|
||||
@@ -0,0 +1,325 @@
|
||||
function Get-CommandLineArgument {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
|
||||
Parses a command-line string and returns arguments as a string array.
|
||||
|
||||
.DESCRIPTION
|
||||
|
||||
Get-CommandLineArgument is a wrapper for CommandLineToArgvW that is used to parse a command-line string and return each argument as a string array.
|
||||
|
||||
This function was created with the following use cases in mind:
|
||||
|
||||
1) A mechanism to programmatically supply a .NET executable entrypoint method with command-line arguments in the expected String[] format.
|
||||
2) To use existing OS functionality to parse and interpret special command-line characters. For example, an argument with spaces inside double quotes should be returned as a single string. Wrapping CommandLineToArgvW eliminates the need to develop error-prone parsing logic.
|
||||
|
||||
.PARAMETER CommandLine
|
||||
|
||||
Specifies a single command line string to be parsed.
|
||||
|
||||
.EXAMPLE
|
||||
|
||||
Get-CommandLineArgument -CommandLine '/c echo "hello, world!"'
|
||||
|
||||
.OUTPUTS
|
||||
|
||||
System.String[]
|
||||
|
||||
Outputs an array of parsed command line arguments.
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
[OutputType([String[]])]
|
||||
param (
|
||||
[Parameter(Mandatory)]
|
||||
[String]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
$CommandLine
|
||||
)
|
||||
|
||||
if (-not ('GetCommandLineArgumentHelper.NativeMethods' -as [Type])) {
|
||||
$Signature = @'
|
||||
[DllImport("shell32.dll", CharSet = CharSet.Unicode)]
|
||||
public static extern IntPtr CommandLineToArgvW([MarshalAs(UnmanagedType.LPWStr)] string cmdLine, out int numArgs);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
public static extern IntPtr LocalFree(IntPtr hMem);
|
||||
'@
|
||||
|
||||
Add-Type -MemberDefinition $Signature -Name NativeMethods -Namespace GetCommandLineArgumentHelper
|
||||
}
|
||||
|
||||
$NumOfArgs = 0
|
||||
|
||||
$StrArrayPtr = [GetCommandLineArgumentHelper.NativeMethods]::CommandLineToArgvW($CommandLine, [Ref] $NumOfArgs)
|
||||
|
||||
$CmdlineArgArray = $null
|
||||
|
||||
if ($StrArrayPtr -eq [IntPtr]::Zero) {
|
||||
Write-Error "CommandLineToArgvW failed when parsing the following: $Cmdline"
|
||||
} else {
|
||||
$CmdlineArgArray = New-Object -TypeName String[]($NumOfArgs)
|
||||
|
||||
for ($i = 0; $i -lt $NumOfArgs; $i++) {
|
||||
$CurrentStringPtr = [System.Runtime.InteropServices.Marshal]::ReadIntPtr($StrArrayPtr, ($i * [IntPtr]::Size))
|
||||
$CmdlineArgArray[$i] = [System.Runtime.InteropServices.Marshal]::PtrToStringUni($CurrentStringPtr)
|
||||
}
|
||||
|
||||
# Free the string array buffer that was allocated when CommandLineToArgvW was called.
|
||||
$null = [GetCommandLineArgumentHelper.NativeMethods]::LocalFree($StrArrayPtr)
|
||||
}
|
||||
|
||||
return $CmdlineArgArray
|
||||
}
|
||||
|
||||
function Invoke-BuildAndInvokeInstallUtilAssembly {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
|
||||
Builds and invokes an installer assembly to validate InstallUtil coverage.
|
||||
|
||||
.DESCRIPTION
|
||||
|
||||
Invoke-BuildAndInvokeInstallUtilAssembly compiles and executes a test installer assembly for the purposes of validating InstallUtil coverage.
|
||||
|
||||
.PARAMETER OutputAssemblyDirectory
|
||||
|
||||
Specifies the directory where the compiled installer assembly is to be written.
|
||||
|
||||
.PARAMETER OutputAssemblyFileName
|
||||
|
||||
Specifies the filename to be used for the compiled installer assembly. Based on how installer assemblies are written, there is no requirement for any specific file extension.
|
||||
|
||||
An installer assembly is typically a DLL but any file extension is permitted. The use of no file extension is also permitted.
|
||||
|
||||
.PARAMETER InvocationMethod
|
||||
|
||||
Specifies the method in which the installer assembly is invoked. The following methods are known to execute installer assemblies:
|
||||
|
||||
- Executable
|
||||
|
||||
Invokes in installer assembly using InstallUtil.exe.
|
||||
|
||||
- InstallHelper
|
||||
|
||||
The InstallHelper method is what InstallUtil.exe is a wrapper executable for. InstallUtil.exe simply passes its command-line arguments on to this method. More information can be found here: https://docs.microsoft.com/en-us/dotnet/api/system.configuration.install.managedinstallerclass.installhelper?view=netframework-4.8
|
||||
|
||||
This execution method is supported to test execution of an installer assembly outside of InstallUtil.exe.
|
||||
|
||||
Note: it is advisable to start a new PowerShell session each time this invocation type is selected because once the assembly is loaded into the current PowerShell process, it cannot be removed or changed.
|
||||
|
||||
- CheckIfInstallable
|
||||
|
||||
The CheckIfInstallable method executes the constructor of an installer assembly. More information about this method can be found here: https://docs.microsoft.com/en-us/dotnet/api/system.configuration.install.assemblyinstaller.checkifinstallable
|
||||
|
||||
This execution method is supported to test execution of an installer assembly outside of InstallUtil.exe.
|
||||
|
||||
Note: it is advisable to start a new PowerShell session each time this invocation type is selected because once the assembly is loaded into the current PowerShell process, it cannot be removed or changed.
|
||||
|
||||
To-do: Implement Jame Forshaw's InstallState abuse. https://www.tiraniddo.dev/2017/08/dg-on-windows-10-s-abusing-installutil.html
|
||||
|
||||
.PARAMETER InstallUtilPath
|
||||
|
||||
Optionally specifies the path where InstallUtil is to execute from. By default, InstallUtil.exe is executed from the .NET directory based on the .NET runtime being used by PowerShell.
|
||||
|
||||
This parameter supports executing InstallUtil.exe from alternate directories for the purposes of validating command-line evasion.
|
||||
|
||||
This parameter only applies when the "Executable" InvocationMethod is used.
|
||||
|
||||
.PARAMETER CommandLine
|
||||
|
||||
Specifies the InstallUtil command line options to use.
|
||||
|
||||
This parameter only applies when the "Executable" or "InstallHelper" InvocationMethods are used.
|
||||
|
||||
.PARAMETER MinimumViableAssembly
|
||||
|
||||
Specifies that the installer assembly to be compiled will contain only a constructor and no other relevant installer components (e.g. Install, Uninstall, Commit, etc.).
|
||||
|
||||
This parameter was designed to validate detections that might place additional scrutiny on assemblies that have the "traditional look and feel" of an installer assembly.
|
||||
|
||||
.EXAMPLE
|
||||
|
||||
Invoke-BuildAndInvokeInstallUtilAssembly -OutputAssemblyDirectory $PWD -OutputAssemblyFileName 'Test.dll' -InvocationMethod Executable -CommandLine "$PWD\Test.dll" -MinimumViableAssembly
|
||||
|
||||
.EXAMPLE
|
||||
|
||||
Invoke-BuildAndInvokeInstallUtilAssembly -OutputAssemblyDirectory $PWD -OutputAssemblyFileName 'Hello.txt' -InvocationMethod CheckIfInstallable -MinimumViableAssembly
|
||||
|
||||
.EXAMPLE
|
||||
|
||||
Invoke-BuildAndInvokeInstallUtilAssembly -OutputAssemblyDirectory $PWD -OutputAssemblyFileName 'Foo' -InvocationMethod InstallHelper -CommandLine "/U $PWD\Foo"
|
||||
|
||||
.OUTPUTS
|
||||
|
||||
System.String
|
||||
|
||||
Outputs a string indicating successful execution of the InstallUtil test assembly.
|
||||
|
||||
.NOTES
|
||||
|
||||
When writing atomic tests against this function, be sure to validate that the string returned matches the expected output.
|
||||
|
||||
A test should only be considered successful when it is confirmed that the compiled assembly writes its relevant output to a temporary file.
|
||||
|
||||
For example, when the built installer assembly executes its constructor, Invoke-BuildAndInvokeInstallUtilAssembly is expected to return "Constructor_". If the installer Uninstall method is invoked using the "/U" command-line switch, Invoke-BuildAndInvokeInstallUtilAssembly is expected to return "Constructor_Uninstall_".
|
||||
|
||||
The text written by the built installer assembly is used to signal successful execution of the installer. Without such a signaling mechanism, there wouldn't be a way to positively confirm that the installer assembly loaded and executed.
|
||||
#>
|
||||
|
||||
[OutputType([String])]
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[Parameter(Mandatory)]
|
||||
[String]
|
||||
[ValidateScript({ Test-Path -Path $_ -IsValid -PathType Container })]
|
||||
$OutputAssemblyDirectory,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[String]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
$OutputAssemblyFileName,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[String]
|
||||
[ValidateSet('Executable', 'InstallHelper', 'CheckIfInstallable')]
|
||||
$InvocationMethod,
|
||||
|
||||
[String]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
$InstallUtilPath = "$([System.Runtime.InteropServices.RuntimeEnvironment]::GetRuntimeDirectory())InstallUtil.exe",
|
||||
|
||||
[String]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
$CommandLine,
|
||||
|
||||
[Switch]
|
||||
$MinimumViableAssembly
|
||||
)
|
||||
|
||||
$OutputAssemblyFullPath = Join-Path -Path $OutputAssemblyDirectory -ChildPath $OutputAssemblyFileName
|
||||
|
||||
$TempFilePath = New-TemporaryFile
|
||||
|
||||
Write-Verbose "Invocation method specified: $InvocationMethod"
|
||||
|
||||
Write-Verbose "Installer assembly output will be written to: $($TempFilePath.FullName)"
|
||||
|
||||
# Insert the temp path into the installer source code.
|
||||
# Writing to this file serves as a testable means to validate that each component of the installer executed successfully.
|
||||
$MinimumViableInstallerCode = @"
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Configuration.Install;
|
||||
using System.ComponentModel;
|
||||
|
||||
[RunInstaller(true)]
|
||||
public class InstallUtilAtomicTest : Installer
|
||||
{
|
||||
public InstallUtilAtomicTest()
|
||||
{
|
||||
using (StreamWriter w = File.AppendText(@"$($TempFilePath.FullName)")) {
|
||||
w.Write("Constructor_");
|
||||
}
|
||||
}
|
||||
}
|
||||
"@
|
||||
|
||||
$InstallerCode = @"
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Configuration.Install;
|
||||
using System.ComponentModel;
|
||||
|
||||
[RunInstaller(true)]
|
||||
public class InstallUtilAtomicTest : Installer {
|
||||
public InstallUtilAtomicTest() {
|
||||
using (StreamWriter w = File.AppendText(@"$($TempFilePath.FullName)")) {
|
||||
w.Write("Constructor_");
|
||||
}
|
||||
}
|
||||
|
||||
public override void Install(System.Collections.IDictionary savedState) {
|
||||
using (StreamWriter w = File.AppendText(@"$($TempFilePath.FullName)")) {
|
||||
w.Write("Install_");
|
||||
}
|
||||
}
|
||||
|
||||
public override void Uninstall(System.Collections.IDictionary savedState) {
|
||||
using (StreamWriter w = File.AppendText(@"$($TempFilePath.FullName)")) {
|
||||
w.Write("Uninstall_");
|
||||
}
|
||||
}
|
||||
|
||||
public override string HelpText {
|
||||
get {
|
||||
using (StreamWriter w = File.AppendText(@"$($TempFilePath.FullName)")) {
|
||||
w.Write("HelpText_");
|
||||
}
|
||||
|
||||
return "Executed: HelpText property\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
"@
|
||||
|
||||
# Validate that InstallUtil.exe is present and is the proper Windows-signed, in-box version
|
||||
$FileInfo = Get-Item -Path $InstallUtilPath -ErrorAction Stop
|
||||
|
||||
if ($FileInfo.VersionInfo.OriginalFilename -ne 'InstallUtil.exe') {
|
||||
throw "$InstallUtilPath is not InstallUtil."
|
||||
}
|
||||
|
||||
$SignerInfo = Get-AuthenticodeSignature -FilePath $InstallUtilPath -ErrorAction Stop
|
||||
|
||||
if (-not $SignerInfo.IsOSBinary) {
|
||||
throw "$InstallUtilPath is not a built-in, Windows-signed utility."
|
||||
}
|
||||
|
||||
if (-not ('InstallUtilAtomicTest' -as [Type])) {
|
||||
$Source = $InstallerCode
|
||||
|
||||
if ($MinimumViableAssembly) { $Source = $MinimumViableInstallerCode }
|
||||
|
||||
Add-Type -TypeDefinition $Source -ReferencedAssemblies 'System.Configuration.Install' -OutputAssembly $OutputAssemblyFullPath -ErrorAction Stop
|
||||
}
|
||||
|
||||
$OutputAssemblyFullPath = Resolve-Path -Path $OutputAssemblyFullPath -ErrorAction Stop
|
||||
|
||||
Write-Verbose "Compiled assembly written to: $OutputAssemblyFullPath"
|
||||
|
||||
if ($CommandLine) {
|
||||
[String[]] $CommandLineArgArray = Get-CommandLineArgument -CommandLine $CommandLine
|
||||
}
|
||||
|
||||
switch ($InvocationMethod) {
|
||||
'Executable' {
|
||||
Write-Verbose "InstallUtil launching from: $InstallUtilPath"
|
||||
Write-Verbose "Command line arguments specified: $CommandLine"
|
||||
|
||||
Start-Process -FilePath $InstallUtilPath -ArgumentList $CommandLineArgArray -NoNewWindow -Wait
|
||||
}
|
||||
|
||||
'InstallHelper' {
|
||||
Write-Verbose "Command line arguments specified: $CommandLine"
|
||||
|
||||
# Ensure the System.Configuration.Install assembly is loaded.
|
||||
Add-Type -AssemblyName System.Configuration.Install -ErrorAction Stop
|
||||
|
||||
try {
|
||||
[System.Configuration.Install.ManagedInstallerClass]::InstallHelper($CommandLineArgArray)
|
||||
} catch { }
|
||||
}
|
||||
|
||||
'CheckIfInstallable' {
|
||||
$null = [Configuration.Install.AssemblyInstaller]::CheckIfInstallable($OutputAssemblyFullPath)
|
||||
}
|
||||
}
|
||||
|
||||
$InstallerExecutionResults = Get-Content -Path $TempFilePath -Raw -ErrorAction Stop
|
||||
|
||||
# Delete the temp installer assembly output file.
|
||||
Remove-Item -Path $TempFilePath
|
||||
|
||||
return $InstallerExecutionResults.TrimEnd()
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using System.Configuration.Install;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
/*
|
||||
Author: Casey Smith, Twitter: @subTee
|
||||
License: BSD 3-Clause
|
||||
Step One:
|
||||
C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe /target:library T1118.cs
|
||||
Step Two:
|
||||
C:\Windows\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe /U /logfile= /logtoconsole=false T1118.dll
|
||||
|
||||
HelpText Invocation:
|
||||
C:\Windows\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe /? T1118.dll
|
||||
|
||||
|
||||
*/
|
||||
|
||||
public class Program
|
||||
{
|
||||
public static void Main()
|
||||
{
|
||||
Console.WriteLine("Hey There From Main()");
|
||||
//Add any behaviour here to throw off sandbox execution/analysts :)
|
||||
//These binaries can exhibit one behavior when executed in sandbox, and entirely different one when invoked
|
||||
//by InstallUtil.exe
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[System.ComponentModel.RunInstaller(true)]
|
||||
public class Sample : System.Configuration.Install.Installer
|
||||
{
|
||||
//The Methods can be Uninstall/Install. Install is transactional, and really unnecessary.
|
||||
public override void Uninstall(System.Collections.IDictionary savedState)
|
||||
{
|
||||
|
||||
Console.WriteLine("Hello There From Uninstall, If you are reading this, prevention has failed.\n");
|
||||
}
|
||||
|
||||
// Override the property 'HelpText'.
|
||||
//
|
||||
public override string HelpText
|
||||
{
|
||||
get
|
||||
{
|
||||
|
||||
return "Hello There From HelpText, If you are reading this, prevention has failed.\n";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user