PowerShell Remoting
PowerShell Remoting is a comprehensive built-in remoting subsystem that is a part of Microsoft's native Windows management framework (WMF) and Windows remote management (WinRM). This feature allows you to handle most remoting tasks in any configuration you might encounter by creating a remote PowerShell session to Windows hosts and executing commands in the created session. The integration includes out-of-the-box commands which supports agentless forensics for remote hosts.
Utilities · Powershell Remoting
Details
| ID | PowerShell Remoting |
|---|---|
| Provider | Microsoft |
| Category | Utilities |
| From Version | 6.0.0 |
| Docker Image | demisto/powershell-ubuntu:7.5.0.10049856 |
| Supported Modules | Agentix XSIAM |
README
PowerShell Remoting is a comprehensive built-in remoting subsystem that is a part of Microsoft’s native Windows management framework (WMF) and Windows remote management (WinRM).
This feature allows you to handle most remoting tasks in any configuration you might encounter by creating a remote PowerShell session to Windows hosts and executing commands in the created session.
The integration includes out-of-the-box commands which supports agentless forensics for remote hosts.
For more information about setting up PowerShell Remoting, see the PowerShell Remoting - Configuration guide.
Configure PowerShell Remoting in Cortex
| Parameter | Description | Required |
|---|---|---|
| Domain | Fully Qualified Domain Name suffix to be added to the hostname. For example mydomain.local | False |
| DNS | The IP address of the DNS server the integration will use to resolve your hosts. | False |
| Username | Username in the target machine. This can be a local or domain user with administrative privileges | True |
| Password | True | |
| Test Host | Hostname or IP address to use as a test for the integration connectivity. | False |
| Authentication Method | True | |
| Trust any certificate (not secure) | False | |
| Use SSL (HTTPS) | False |
Configuration Notes
Please note that in order for the integration to function properly, Basic Authentication is required to be enabled for the target host. This is due to the library relying on Basic Auth headers to pass an authentication token even when MFA is enabled.
Commands
You can execute these commands from the CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.
ps-remote-command
Executes remote PowerShell commands on a single host.
Base Command
ps-remote-command
Input
| Argument Name | Description | Required |
|---|---|---|
| host | A single hostname or IP address on which to run the command. | Required |
| command | PowerShell commands (can be single or multiple in order of execution) to run on the target machine. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| PsRemote.Host | string | The host on which the command was invoked. |
| PsRemote.FQDN | string | The Fully Qualified Domain Name of the host on which the command was invoked. |
| PsRemote.CommandResult | list | The result of the command run from the target machine. |
| PsRemote.CommandName | string | The command sent to the target machine, used as an ID of that query. |
| PsRemote.UTCTime | string | Time the command finished execution in UTC time. |
Command Example
!ps-remote-command host=XSOAR-XSOAR command=whoami
Context Example
{
"PsRemote": {
"UTCTime": "2021-07-18T15:46:07.2006094+00:00",
"FQDN": "XSOAR-XSOAR.winrm.local",
"Host": "XSOAR-XSOAR",
"CommandName": "whoami",
"CommandResult": [
"winrm\\administrator\n"
]
}
}
Human Readable Output
Result for PowerShell Remote Command: whoami
winrm\administrator
ps-remote-download-file
Downloads a file from the remote endpoint.
Base Command
ps-remote-download-file
Input
| Argument Name | Description | Required |
|---|---|---|
| host | Hostname or the IP address on which to run the command. | Required |
| path | The path of the file to download. | Required |
| zip_file | Whether to compress the file. Possible values: “true” and “false”. Default is “false”. Possible values are: true, false. Default is false. | Optional |
| check_hash | Whether to compare the value of the original file with the downloaded file and return an error if any differences are found. Possible values: “true” and “false”. Default is “false”. Possible values are: true, false. Default is false. | Optional |
| host_prefix | Whether to use the host as a prefix for the name of the downloaded version of the file. Possible values: “true” and “false”. Default is “false”. Possible values are: true, false. Default is false. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| PsRemoteDownloadedFile.Host | string | The host from which the file was downloaded. |
| PsRemoteDownloadedFile.FQDN | string | The Fully Qualified Domain Name of the host from which the file was downloaded. |
| PsRemoteDownloadedFile.FileName | String | File name. |
| PsRemoteDownloadedFile.FileSize | Number | File size. |
| PsRemoteDownloadedFile.FileMD5 | String | The MD5 hash of the file. |
| PsRemoteDownloadedFile.FileSHA1 | String | The SHA1 hash of the file. |
| PsRemoteDownloadedFile.FileSHA256 | String | The SHA256 hash of the file. |
| PsRemoteDownloadedFile.FileExtension | String | The extension of the file. |
| File.Size | Number | The size of the file in bytes. |
| File.SHA1 | String | The SHA1 hash of the file. |
| File.SHA256 | String | The SHA256 hash of the file. |
| File.SHA512 | String | The SHA512 hash of the file. |
| File.Name | String | The full file name. |
| File.SSDeep | String | The ssdeep hash of the file. |
| File.EntryID | String | The ID for locating the file in the War Room. |
| File.Info | String | The file information. |
| File.Type | String | The file type. |
| File.MD5 | String | The MD5 hash of the file. |
| File.Extension | String | The file extension, for example: “txt”. |
Command Example
!ps-remote-download-file host="XSOAR-XSOAR" path="c:\\XSOAR-XSOAR.etl" zip_file="true" check_hash="true" host_prefix="true"
ps-remote-upload-file
Uploads a file to the remote endpoint.
Base Command
ps-remote-upload-file
Input
| Argument Name | Description | Required |
|---|---|---|
| host | A single hostname or IP address on which to run the command. | Required |
| path | The path of the file to upload. | Required |
| entry_id | The file entry ID to upload. | Required |
| zip_file | Whether to compress the file before upload. Possible values: “true” and “false”. Default is “false”. Possible values are: true, false. Default is false. | Optional |
| check_hash | Whether to compare the values of the original file and uploaded file and return an error if any differences are found. Possible values: “true” and “false”. Default is “false”. Possible values are: true, false. Default is false. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| PsRemoteUploadedFile.FileName | String | File name. |
| PsRemoteUploadedFile.FilePath | String | File path in the remote server. |
| PsRemoteUploadedFile.FileSize | Number | File size. |
| PsRemoteUploadedFile.FileMD5 | String | The MD5 hash of the file. |
| PsRemoteUploadedFile.FileSHA1 | String | The SHA1 hash of the file. |
| PsRemoteUploadedFile.FileSHA256 | String | The SHA256 hash of the file. |
| PsRemoteUploadedFile.FileExtension | String | The extension of the file. |
| PsRemoteUploadedFile.Host | string | The host to which the file was uploaded. |
| PsRemote.FQDN | string | The Fully Qualified Domain Name of the host the file was uploaded to. |
Command Example
!ps-remote-upload-file entry_id=105@1d0796aa-dde9-4f18-8f04-bbe92434ba81 host="XSOAR-XSOAR" path="c:\\tmpetl.etl"
Human Readable Output
File 1d0796aa-dde9-4f18-8f04-bbe92434ba81_105@1d0796aa-dde9-4f18-8f04-bbe92434ba81 was uploaded successfully as: c:\tmpetl.etl
ps-remote-etl-create-start
This command starts the recording of an ETL file on a Windows endpoint. An ETL file is just like a PCAP file which is created by the Windows Netsh command.
Base Command
ps-remote-etl-create-start
Input
| Argument Name | Description | Required |
|---|---|---|
| host | A single hostname or IP address on which to run the command. | Required |
| etl_path | The path on the hostname on which to create the ETL file. For example c:\temp\myhost.etl. Default is “C:\Users\<username>\AppData\Local\Temp\NetTraces\NetTrace.etl”. | Required |
| etl_filter | The filter to apply when creating the ETL file. For example IPv4.Address=1.1.1.1 to capture traffic just from the 1.1.1.1 IP address. If no filter is specified all traffic will be recorded. For more examples, see: https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2012-r2-and-2012/jj129382(v=ws.11)#using-filters-to-limit-etl-trace-file-details. | Optional |
| etl_max_size | The maximum file size for the ETL. Once the file has reached this size, the capture will stop. For example 10MB. The default size is 10MB. Default is 10. | Optional |
| overwrite | Whether to overwrite the file in the path. Possible values: “yes” and “no”. Default is “no”. Possible values are: yes, no. Default is no. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| PsRemote.CommandResult | string | The start ETL command results. |
| PsRemote.EtlFileName | string | The name of the ETL file. |
| PsRemote.EtlFilePath | unknown | The path and name of the ETL file. |
| PsRemote.CommandName | string | The name of the command that ran on the host. |
| PsRemote.FQDN | string | The Full Qualified Domain Name of the host. |
| PsRemote.Host | string | The name of the host. |
Command Example
!ps-remote-etl-create-stop host="XSOAR-XSOAR"
Human Readable Output
Trace configuration: ——————————————————————- Status: Running Trace File: C:\XSOAR-XSOAR__a.etl Append: Off Circular: On Max Size: 10 MB Report: Off
Context Example
{
"PsRemote": {
"CommandResult": [
"Trace configuration:\n",
"-------------------------------------------------------------------\n",
"Status: Running\n",
"Trace File: C:\\XSOAR-XSOAR__a.etl\n",
"Append: Off\n",
"Circular: On\n",
"Max Size: 10 MB\n",
"Report: Off\n"
],
"FQDN": "XSOAR-XSOAR.winrm.local",
"EtlFilePath": "c:\\XSOAR-XSOAR__a.etl",
"UTCTime": "2021-07-18T15:58:08.2528721+00:00",
"Host": "XSOAR-XSOAR",
"CommandName": "netsh trace start capture=yes traceFile=c:\\XSOAR-XSOAR__a.etl maxsize=10 overwrite=no ",
"EtlFileName": "XSOAR-XSOAR__a.etl"
}
}
ps-remote-etl-create-stop
Ends the recording of an ETL file on a Windows endpoint.
Base Command
ps-remote-etl-create-stop
Input
| Argument Name | Description | Required |
|---|---|---|
| host | A single Hostname or IP address on which to run the command. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| PsRemote.CommandResult | string | The stop ETL command results. |
| PsRemote.CommandName | string | The name of the command that ran on the host. |
| PsRemote.EtlFileName | string | The name of the ETL file. |
| PsRemote.EtlFilePath | unknown | The path and name of the ETL file. |
| PsRemote.Host | string | The host the command was invoked on. |
| PsRemote.FQDN | string | The Fully Qualified Domain Name of the host on which the command was invoked. |
Command Example
!ps-remote-etl-create-stop host="XSOAR-XSOAR"
Human Readable Output
Correlating traces … done Merging traces … done Generating data collection … done The trace file and additional troubleshooting information have been compiled as “c:\XSOAR-XSOAR__a.cab”. File location = c:\XSOAR-XSOAR__a.etl Tracing session was successfully stopped.
Context Example
{
"PsRemote": {
"CommandResult": "Correlating traces ... done Merging traces ... done Generating data collection ... done The trace file and additional troubleshooting information have been compiled as \"c:\\XSOAR-XSOAR__a.cab\". File location = c:\\XSOAR-XSOAR__a.etl Tracing session was successfully stopped. ",
"FQDN": "XSOAR-XSOAR.winrm.local",
"EtlFilePath": "c:\\XSOAR-XSOAR__a.etl",
"Host": "XSOAR-XSOAR",
"CommandName": "netsh trace stop",
"EtlFileName": "XSOAR-XSOAR__a.etl"
}
}
ps-remote-export-registry
Exports the specified registry of hive to a file.
Base Command
ps-remote-export-registry
Input
| Argument Name | Description | Required |
|---|---|---|
| host | A single hostname or IP address on which to run the command. | Required |
| reg_key_hive | The registry key or hive to export. For example, reg_key_hive=HKEY_LOCAL_MACHINE. If the user specifies “all”, the entire registry will be exported. Default is “all”. Default is all. |
Optional |
| file_path | The path and name on the Windows host where the registry file will be created. For example, file_path=c:\hklm.reg. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| PsRemote.CommandResult | string | The command results in the remote host - regedit for all or reg export. |
| PsRemote.RegistryFilePath | string | The path and name of the registry file. |
| PsRemote.RegistryFileName | string | The name of the registry file. |
| PsRemote.Command.Name | string | The name of the command that ran on the host. |
| PsRemote.Fqdn | string | The Full Qualified Domain Name of the host. |
| PsRemote.Host | string | The host on which the command was invoked. |
| PsRemote.FQDN | string | The Fully Qualified Domain Name of the host on which the command was invoked. |
Command Example
!ps-remote-export-registry host="XSOAR-XSOAR" file_path="c:\\XSOAR-XSOAR__a.reg"
Human Readable Output
Ran Export Registry.
Registry file expected path: c:\XSOAR-XSOAR__a.reg
Context Example
{
"PsRemote": {
"RegistryFileName": "XSOAR-XSOAR__a.reg",
"RegistryFilePath": "c:\\XSOAR-XSOAR__a.reg",
"FQDN": "XSOAR-XSOAR.winrm.local",
"CommandName": "regedit /e c:\\XSOAR-XSOAR__a.reg",
"Host": "XSOAR-XSOAR",
"CommandResult": null
}
}
ps-remote-export-mft
Extracts the master file table from the volume.
Base Command
ps-remote-export-mft
Input
| Argument Name | Description | Required |
|---|---|---|
| host | A single hostname or IP address on which to run the command. | Required |
| volume | The volume from which to retrieve its master file table. | Optional |
| output_path | The path in which the MFT file is to be created. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| PsRemote.ExportMFT | unknown | The extract master file table command result. |
| PsRemote.Host | string | The host on which the command was invoked. |
| PsRemote.FQDN | string | The Fully Qualified Domain Name of the host on which the command was invoked. |
Command Example
!ps-remote-export-mft host="XSOAR-XSOAR" volume=C
Human Readable Output
MFT Export results
ComputerName MFT File MFT Size MFT Volume NetworkPath PSComputerName PSShowComputerName RunspaceId XSOAR-XSOAR C:\Users\ADMINI~1\AppData\Local\Temp\zzthh5sh.hl2 222 MB C \\XSOAR-XSOAR\C$\Users\ADMINI~1\AppData\Local\Temp\zzthh5sh.hl2 XSOAR-XSOAR.winrm.local true {“value”:”58aa1f39-f86d-4f18-978a-c9257295df49”,”Guid”:”58aa1f39-f86d-4f18-978a-c9257295df49”}
Context Example
{
"PsRemote": {
"ExportMFT": {
"ComputerName": "XSOAR-XSOAR",
"MFT File": "C:\\Users\\ADMINI~1\\AppData\\Local\\Temp\\zzthh5sh.hl2",
"MFT Size": "222 MB",
"PSComputerName": "XSOAR-XSOAR.winrm.local",
"NetworkPath": "\\\\XSOAR-XSOAR\\C$\\Users\\ADMINI~1\\AppData\\Local\\Temp\\zzthh5sh.hl2",
"MFT Volume": "C",
"RunspaceId": "58aa1f39-f86d-4f18-978a-c9257295df49",
"PSShowComputerName": true
},
"Host": "XSOAR-XSOAR",
"FQDN": "XSOAR-XSOAR.winrm.local"
}
}
Configuration parameters
domain— Domaindns— DNScredentials— Username (required)host— Test Hostauth_method— Authentication Method (required)insecure— Trust any certificate (not secure)ssl— Use SSL (HTTPS)
Commands (7)
-
ps-remote-commandExecutes remote PowerShell commands on a single host.
-
ps-remote-download-fileDownloads a file from the remote endpoint.
-
ps-remote-etl-create-startThis command starts the recording of an ETL file on a Windows endpoint. An ETL file is just like a PCAP file which is created by the Windows Netsh command.
-
ps-remote-etl-create-stopEnds the recording of an ETL file on a Windows endpoint.
-
ps-remote-export-mftExtracts the master file table from the volume.
-
ps-remote-export-registryExports the specified registry of hive to a file.
-
ps-remote-upload-fileUploads a file to the remote endpoint.
. $PSScriptRoot\CommonServerPowerShell.ps1 # remove progress messages such as [oo ] - used to hide archiving operations $global:ProgressPreference = 'SilentlyContinue' $script:INTEGRATION_NAME = "PowerShell Remoting" $script:COMMAND_PREFIX = "ps-remote" $script:INTEGRATION_ENTRY_CONTEXT = "PsRemote" $script:INSECURE_WARNING = "Unix does not currently support CA or CN checks." $script:ValidIpAddressRegex = "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$" #### HELPER FUNCTIONS #### function CreateNewSession { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '', Scope='Function')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingUsernameAndPasswordParams', '', Scope='Function')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '', Scope = 'Function')] param([string]$fqdn, [string]$username, [string]$password, [bool]$insecure, [bool]$ssl, [string]$auth_method) $credential = ConvertTo-SecureString "$password" -AsPlainText -Force $ps_credential = New-Object System.Management.Automation.PSCredential($username, $credential) $session_option_params = @{ "SkipCACheck" = $insecure "SkipCNCheck" = $insecure } $session_options = New-PSSessionOption @session_option_params $sessions_params = @{ "ComputerName" = $fqdn "Credential" = $ps_credential "SessionOption" = $session_options "Authentication" = $auth_method "ErrorAction" = "Stop" "WarningAction" = "SilentlyContinue" "UseSSL" = $ssl } try { $session = New-PSSession @sessions_params } catch { if ($_.Exception.Message -Match $script:INSECURE_WARNING) { throw "HTTPS certificate check isn't currently supported. Please enable 'Trust any certificate' or disable 'Use SSL'" } throw $_.Exception.Message } return $session <# .DESCRIPTION Creates new pssession using Negotiate authentication. .PARAMETER fqdn Fully qualified domain name (FQDN) to connect. .PARAMETER username Username is the name of a system user. .PARAMETER password Password of the system user. .PARAMETER insecure Wheter to trust any TLS/SSL Certificate) or not. .EXAMPLE CreateNewSession("myhost.example.com", "user", "pass") .OUTPUTS PSSession - PSSession object. .LINK https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/new-pssession?view=powershell-7 #> } class RemotingClient { [string]$host [string]$fqdn [string]$username [string]$password [string]$domain [string]$dns [string]$auth_method [psobject]$session [bool]$insecure [bool]$ssl [bool]$proxy [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '', Scope = 'Function')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingUsernameAndPasswordParams', '', Scope='Function')] RemotingClient([string]$hosts, [string]$username, [string]$password, [string]$domain, [string]$dns, [string]$auth_method, [bool]$insecure, [bool]$ssl, [bool]$proxy) { # set the container's resolv.conf to use to provided dns # http://manpages.ubuntu.com/manpages/xenial/man5/resolv.conf.5.html if ($dns) { "nameserver $dns" | Set-Content -Path \etc\resolv.conf } $this.domain = If ($domain) {".$domain"} Else {""} if (! ($hosts -match $script:ValidIpAddressRegex)) { # if not IP add the domain to the host $this.host = $hosts $this.fqdn = $hosts + $domain } else { $this.host = $hosts $this.fqdn = $hosts } $this.username = $username $this.password = $password $this.insecure = $insecure $this.ssl = $ssl $this.auth_method = $auth_method $this.proxy = $proxy <# .DESCRIPTION RemotingClient connect to a remote hostname and run processes on it remotely. .PARAMETER domain The host's domain. .PARAMETER dns Domain Name System (DNS) is the hierarchical and decentralized naming system for computers connected to the network. .PARAMETER username Username is the name of a system user. .PARAMETER password Password of the system user. .PARAMETER insecure Wheter to trust any TLS/SSL Certificate) or not. .PARAMETER ssl Wheter use SSL protocol to establish a connection. .PARAMETER auth_method Authentication method to use when creating a new session. .PARAMETER proxy Wheter to user system proxy configuration or not. .EXAMPLE $client = [RemotingClient]::new("MyComputer", "username", "password") #> } CreateSession() { $this.session = CreateNewSession -fqdn $this.fqdn -username $this.username -password $this.password -insecure $this.insecure -ssl $this.ssl $this.auth_method <# .DESCRIPTION This method is for internal use. It creates session to the remote hosts. .EXAMPLE $client.CreateSession() .LINK https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/new-pssession?view=powershell-7 https://docs.microsoft.com/en-us/powershell/partnercenter/multi-factor-auth?view=partnercenterps-3.0#exchange-online-powershell #> } CloseSession() { if ($this.session) { Remove-PSSession $this.session } <# .DESCRIPTION This method is for internal use. It removes an active session. .EXAMPLE $client.CloseSession() .LINK https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/remove-pssession?view=powershell-7 https://docs.microsoft.com/en-us/powershell/partnercenter/multi-factor-auth?view=partnercenterps-3.0#exchange-online-powershell #> } [System.Array]InvokeCommandInSession([string]$remote_command) { if (!$this.session) { $this.CreateSession() } $temp = $script:Demisto.UniqueFile() $file_name = $script:Demisto.Investigation().id + "_" + $temp + ".ps1" $remote_command | Out-File -FilePath $file_name $result = Invoke-Command -Session $this.session -FilePath $file_name return $result <# .DESCRIPTION This method invokes a command in the session via a temporary file. .EXAMPLE $client.InvokeCommandInSession("$PSVersionTable") .LINK https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/invoke-command?view=powershell-7.1 #> } CopyItemFromSession([string]$path, [string]$file_name) { if (!$this.session) { $this.CreateSession() } Copy-Item -FromSession $this.session $path -Destination $file_name <# .DESCRIPTION This method copies an item from the session in specified path to the destination file_name. .EXAMPLE $client.CopyItemFromSession("remote\file.txt", "local\file.txt") .LINK https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.management/copy-item?view=powershell-7.1 #> } CopyItemToSession([string]$path, [string]$file_name) { if (!$this.session) { $this.CreateSession() } Copy-Item -ToSession $this.session $path -Destination $file_name <# .DESCRIPTION This method copies an item from the local path to the session destination file_name. .EXAMPLE $client.CopyItemFromSession("remote\file.txt", "local\file.txt") .LINK https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.management/copy-item?view=powershell-7.1 #> } [PSCustomObject]ExportMFT([string]$volume, [string]$output_file_path) { if (!$this.session) { $this.CreateSession() } $remote_script_block = { Param($volume, $output_file_path) if ($volume -ne 0) { $Win32_Volume = Get-CimInstance -Class Win32_Volume -Filter "DriveLetter LIKE '$( $volume ):'" if ($Win32_Volume.FileSystem -ne "NTFS") { Write-Error "$volume is not an NTFS filesystem." break } } else { $Win32_Volume = Get-CimInstance -Class Win32_Volume -Filter "DriveLetter LIKE '$( $env:SystemDrive )'" if ($Win32_Volume.FileSystem -ne "NTFS") { Write-Error "$env:SystemDrive is not an NTFS filesystem." break } } if (-not$output_file_path) { $output_file_path = $env:TEMP + "\$([IO.Path]::GetRandomFileName() )" } #region WinAPI $GENERIC_READWRITE = 0x80000000 $FILE_SHARE_READWRITE = 0x02 -bor 0x01 $OPEN_EXISTING = 0x03 $DynAssembly = New-Object System.Reflection.AssemblyName('MFT') $AssemblyBuilder = [AppDomain]::CurrentDomain.DefineDynamicAssembly($DynAssembly, [Reflection.Emit.AssemblyBuilderAccess]::Run) $ModuleBuilder = $AssemblyBuilder.DefineDynamicModule('InMemory', $false) $TypeBuilder = $ModuleBuilder.DefineType('kernel32', 'Public, Class') $DllImportConstructor = [Runtime.InteropServices.DllImportAttribute].GetConstructor(@([String])) $SetLastError = [Runtime.InteropServices.DllImportAttribute].GetField('SetLastError') $SetLastErrorCustomAttribute = New-Object Reflection.Emit.CustomAttributeBuilder($DllImportConstructor, @('kernel32.dll'), [Reflection.FieldInfo[]]@($SetLastError), @($True)) #CreateFile $PInvokeMethodBuilder = $TypeBuilder.DefinePInvokeMethod('CreateFile', 'kernel32.dll', ([Reflection.MethodAttributes]::Public -bor [Reflection.MethodAttributes]::Static), [Reflection.CallingConventions]::Standard, [IntPtr], [Type[]]@([String],[Int32],[UInt32],[IntPtr],[UInt32],[UInt32],[IntPtr]), [Runtime.InteropServices.CallingConvention]::Winapi, [Runtime.InteropServices.CharSet]::Ansi) $PInvokeMethodBuilder.SetCustomAttribute($SetLastErrorCustomAttribute) #CloseHandle $PInvokeMethodBuilder = $TypeBuilder.DefinePInvokeMethod('CloseHandle', 'kernel32.dll', ([Reflection.MethodAttributes]::Public -bor [Reflection.MethodAttributes]::Static), [Reflection.CallingConventions]::Standard, [Bool], [Type[]]@([IntPtr]), [Runtime.InteropServices.CallingConvention]::Winapi, [Runtime.InteropServices.CharSet]::Auto) $PInvokeMethodBuilder.SetCustomAttribute($SetLastErrorCustomAttribute) $Kernel32 = $TypeBuilder.CreateType() #endregion WinAPI # Get handle to volume if ($volume -ne 0) { $VolumeHandle = $Kernel32::CreateFile(('\\.\' + $volume + ':'), $GENERIC_READWRITE, $FILE_SHARE_READWRITE, [IntPtr]::Zero, $OPEN_EXISTING, 0, [IntPtr]::Zero) } else { $VolumeHandle = $Kernel32::CreateFile(('\\.\' + $env:SystemDrive), $GENERIC_READWRITE, $FILE_SHARE_READWRITE, [IntPtr]::Zero, $OPEN_EXISTING, 0, [IntPtr]::Zero) $volume = ($env:SystemDrive).TrimEnd(':') } if ($VolumeHandle -eq -1) { Write-Error "Unable to obtain read handle for volume." break } # Create a FileStream to read from the volume handle $FileStream = New-Object IO.FileStream($VolumeHandle, [IO.FileAccess]::Read) # Read VBR from volume $VolumeBootRecord = New-Object Byte[](512) if ($FileStream.Read($VolumeBootRecord, 0, $VolumeBootRecord.Length) -ne 512) { Write-Error "Error reading volume boot record." } # Parse MFT offset from VBR and set stream to its location $MftOffset = [Bitconverter]::ToInt32($VolumeBootRecord[0x30..0x37], 0) * 0x1000 $FileStream.Position = $MftOffset # Read MFT's file record header $MftFileRecordHeader = New-Object byte[](48) if ($FileStream.Read($MftFileRecordHeader, 0, $MftFileRecordHeader.Length) -ne $MftFileRecordHeader.Length) { Write-Error "Error reading MFT file record header." } # Parse values from MFT's file record header $OffsetToAttributes = [Bitconverter]::ToInt16($MftFileRecordHeader[0x14..0x15], 0) $AttributesRealSize = [Bitconverter]::ToInt32($MftFileRecordHeader[0x18..0x21], 0) # Read MFT's full file record $MftFileRecord = New-Object byte[]($AttributesRealSize) $FileStream.Position = $MftOffset if ($FileStream.Read($MftFileRecord, 0, $MftFileRecord.Length) -ne $AttributesRealSize) { Write-Error "Error reading MFT file record." } # Parse MFT's attributes from file record $Attributes = New-object byte[]($AttributesRealSize - $OffsetToAttributes) [Array]::Copy($MftFileRecord, $OffsetToAttributes, $Attributes, 0, $Attributes.Length) # Find Data attribute $CurrentOffset = 0 do { $AttributeType = [Bitconverter]::ToInt32($Attributes[$CurrentOffset..$( $CurrentOffset + 3 )], 0) $AttributeSize = [Bitconverter]::ToInt32($Attributes[$( $CurrentOffset + 4 )..$( $CurrentOffset + 7 )], 0) $CurrentOffset += $AttributeSize } until ($AttributeType -eq 128) # Parse data attribute from all attributes $DataAttribute = $Attributes[$( $CurrentOffset - $AttributeSize )..$( $CurrentOffset - 1 )] # Parse MFT size from data attribute $MftSize = [Bitconverter]::ToUInt64($DataAttribute[0x30..0x37], 0) # Parse data runs from data attribute $OffsetToDataRuns = [Bitconverter]::ToInt16($DataAttribute[0x20..0x21], 0) $DataRuns = $DataAttribute[$OffsetToDataRuns..$( $DataAttribute.Length - 1 )] # Convert data run info to string[] for calculations $DataRunStrings = ([Bitconverter]::ToString($DataRuns)).Split('-') # Setup to read MFT $FileStreamOffset = 0 $DataRunStringsOffset = 0 $TotalBytesWritten = 0 $MftData = New-Object byte[](0x1000) $OutputFileStream = [IO.File]::OpenWrite($output_file_path) do { $StartBytes = [int]($DataRunStrings[$DataRunStringsOffset][0]).ToString() $LengthBytes = [int]($DataRunStrings[$DataRunStringsOffset][1]).ToString() $DataRunStart = "0x" for ($i = $StartBytes; $i -gt 0; $i--) { $DataRunStart += $DataRunStrings[($DataRunStringsOffset + $LengthBytes + $i)] } $DataRunLength = "0x" for ($i = $LengthBytes; $i -gt 0; $i--) { $DataRunLength += $DataRunStrings[($DataRunStringsOffset + $i)] } $FileStreamOffset += ([int]$DataRunStart * 0x1000) $FileStream.Position = $FileStreamOffset for ($i = 0; $i -lt [int]$DataRunLength; $i++) { if ($FileStream.Read($MftData, 0, $MftData.Length) -ne $MftData.Length) { Write-Warning "Possible error reading MFT data on $env:COMPUTERNAME." } $OutputFileStream.Write($MftData, 0, $MftData.Length) $TotalBytesWritten += $MftData.Length } $DataRunStringsOffset += $StartBytes + $LengthBytes + 1 } until ($TotalBytesWritten -eq $MftSize) $FileStream.Dispose() $OutputFileStream.Dispose() $Properties = @{ NetworkPath = "\\$( $env:COMPUTERNAME )\C$\$($output_file_path.TrimStart('C:\') )" ComputerName = $env:COMPUTERNAME 'MFT Size' = "$( $MftSize / 1024 / 1024 ) MB" 'MFT Volume' = $volume 'MFT File' = $output_file_path } New-Object -TypeName PSObject -Property $Properties } return Invoke-Command -Session $this.session -ScriptBlock $remote_script_block -ArgumentList @($volume, $output_file_path) } } function TestModuleCommand { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Scope='Function')] param([RemotingClient]$client) if ($client.ssl) { $res = Test-Connection -TcpPort 5986 -TargetName $client.fqdn -ErrorAction Stop if (-not $res) { throw 'Could not create connection for host ' + $client.fqdn + ' via port 5986.' } } else { $res = Test-Connection -TcpPort 5985 -TargetName $client.fqdn -ErrorAction Stop if (-not $res) { throw 'Could not create connection for host ' + $client.fqdn + ' via port 5985.' } } $tmp = $client.InvokeCommandInSession('$PSVersionTable') $client.CloseSession() $human_readable = "ok" return $human_readable, $null, $null } function CreatResultsMap { param([System.Array]$raw_result, [string]$command, [string]$fqdn, [string]$hosts) $now = Get-Date -Format "o" $results_map = @{ Host = $hosts FQDN = $fqdn CommandName = $command UTCTime = $now CommandResult = [System.Collections.ArrayList]::new() } For ($i=0; $i -lt $raw_result.Length; $i++) { # base name if($raw_result[$i].GetType().Fullname -eq 'System.Management.Automation.PSObject') { $item = ($raw_result[$i] | select-object -ExcludeProperty PSComputerName,PSShowComputerName,RunspaceId) } elseif ($raw_result[$i].GetType().Fullname -eq 'System.String') { if ([string]::IsNullOrEmpty($raw_result[$i])) { continue } $item = ($raw_result[$i] | out-string) } $results_map['CommandResult'] += $item } return $results_map <# .DESCRIPTION Create result map from single computer result #> } function InvokeCommandCommand { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Scope='Function')] param([RemotingClient]$client, [string]$command) $title = "Result for PowerShell Remote Command: $command `n" $raw_result = $client.InvokeCommandInSession($command) $client.CloseSession() $results_map = CreatResultsMap -raw_result $raw_result -command $command -fqdn $client.fqdn -hosts $client.host # extract command results per computer if ($results_map) { $entry_context = @{ $script:INTEGRATION_ENTRY_CONTEXT = $results_map } $human_readable = $title + $raw_result return $human_readable, $entry_context, $raw_result } else { return $title + 'No results', $null, $null } <# .DESCRIPTION Runs invoke-command on existing session. #> } function DownloadFileCommand([RemotingClient]$client, [string]$path, [string]$zip_file, [string]$check_hash, [bool]$host_as_prefix) { $temp = $script:Demisto.UniqueFile() $file_name = $script:Demisto.Investigation().id + "_$temp" # assert file exists in the system $command = '[System.IO.File]::Exists("' + $path + '")' $raw_result = $client.InvokeCommandInSession($command) if (-Not $raw_result -or ($raw_result -eq $False)) { $client.CloseSession() throw "$path was not found on the remote host." } if ($host_as_prefix -eq 'true') { $file_name_parent = Split-Path $path $pre_rename_leaf = Split-Path $path -leaf $file_name_leaf = $client.host + '_' + $pre_rename_leaf $command = "Rename-Item -Path $path -NewName $file_name_leaf" $client.InvokeCommandInSession($command) $path = "$file_name_parent/$file_name_leaf" } if ($zip_file -eq 'true') { # zip file at the host $pre_zip_path = $path $path = "$path.zip" $command = "Compress-Archive -Path $pre_zip_path -Update -DestinationPath $path" $client.InvokeCommandInSession($command) } if ($check_hash -eq 'true') { # save orig hash $command = "(Get-FileHash $path -Algorithm MD5).Hash" $src_hash = $client.InvokeCommandInSession($command) } $client.CopyItemFromSession($path, $file_name) if ($zip_file -eq 'true') { # clean zip from host $command = "Remove-Item $path" $client.InvokeCommandInSession($command) } if ($host_as_prefix -eq 'true') { if ($zip_file -eq 'true') { $command = "Rename-Item -Path $pre_zip_path -NewName $pre_rename_leaf" } else { $command = "Rename-Item -Path $path -NewName $pre_rename_leaf" } $client.InvokeCommandInSession($command) } $client.CloseSession() if ($check_hash -eq 'true') { # compare src-dst hashes $dst_hash = (Get-FileHash $file_name -Algorithm MD5).Hash if ($src_hash -ne $dst_hash) { throw "Failed check_hash: The downloaded file has a different hash than the file in the host. RemoteHostHash=$src_hash DownloadedHash=$dst_hash" } } # add file details to context $file_name_leaf = Split-Path $path -leaf $file_extension = [System.IO.Path]::GetExtension($file_name_leaf) $file_extension = If ($file_extension) {$file_extension.SubString(1, $file_extension.length - 1)} else {""} $entry_context = @{ PsRemoteDownloadedFile = @{ FQDN = $client.fqdn Host = $client.host FileName = $file_name_leaf FileSize = Get-Item $file_name | ForEach-Object { [math]::ceiling($_.length / 1kb) } FileSHA1 = (Get-FileHash $file_name -Algorithm SHA1).Hash FileSHA256 = (Get-FileHash $file_name -Algorithm SHA256).Hash FileMD5 = (Get-FileHash $file_name -Algorithm MD5).Hash FileExtension = $file_extension } } $demisto_results = @{ Type = 3 ContentsFormat = "text" Contents = "" File = $file_name_leaf FileID = $temp EntryContext = $entry_context } $script:Demisto.Results($demisto_results) } function StartETLCommand { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Scope='Function')] param([RemotingClient]$client, [string]$etl_path, [string]$etl_filter, [string]$etl_max_size, [string]$overwrite) $command = "netsh trace start capture=yes traceFile=$etl_path maxsize=$etl_max_size overwrite=$overwrite $etl_filter" $raw_result = $client.InvokeCommandInSession($command) $client.CloseSession() $results_map = CreatResultsMap -raw_result $raw_result -command $command -fqdn $client.fqdn -hosts $client.host if ($results_map) { $results_map['EtlFilePath'] = $etl_path $results_map['EtlFileName'] = Split-Path $etl_path -leaf $entry_context = @{ $script:INTEGRATION_ENTRY_CONTEXT = $results_map } $human_readable = $title + $raw_result return $human_readable, $entry_context, $raw_result } else { return $title + 'No results', $null, $null } } function StopETLCommand([RemotingClient]$client) { $command = 'netsh trace stop' $raw_results = $client.InvokeCommandInSession($command) $client.CloseSession() $etl_path = $raw_results | Select-String -Pattern "File location = " if ($etl_path) { # clean "File location = " $etl_path = $etl_path.ToString() $etl_path = $etl_path.Substring(16, $etl_path.Length - 16) } else { $etl_path = "" } if ($raw_results) { $raw_results = [string]$raw_results } $entry_context = @{ $script:INTEGRATION_ENTRY_CONTEXT = @{ FQDN = $client.fqdn Host = $client.host CommandName = $command CommandResult = $raw_results EtlFilePath = $etl_path EtlFileName = if ($etl_path) {Split-Path $etl_path -leaf} else {""} } } $human_readable = $raw_results return $human_readable, $entry_context, $raw_result } function ExportRegistryCommand([RemotingClient]$client, [string]$reg_key_hive, [string]$output_file_path) { $command = if ($reg_key_hive -eq 'all') {"regedit /e $output_file_path"} else {"reg export $reg_key_hive $output_file_path"} $raw_results = $client.InvokeCommandInSession($command) Start-Sleep -Seconds 30 $client.CloseSession() $title = "Ran Export Registry. `n" $entry_context = @{ $script:INTEGRATION_ENTRY_CONTEXT = @{ FQDN = $client.fqdn Host = $client.host CommandResult = $raw_results CommandName = $command RegistryFilePath = $output_file_path RegistryFileName = Split-Path $output_file_path -leaf } } $human_readable = $title + "Registry file expected path: $output_file_path" return $human_readable, $entry_context, $raw_results } function UploadFileCommand([RemotingClient]$client, [string]$entry_id, [string]$dst_path, [string]$zip_file, [string]$check_hash) { $src_path = $script:Demisto.GetFilePath($entry_id).path $file_exists = Test-Path $src_path -PathType Leaf if (-Not $file_exists) { throw "Could not find $entry_id file, please make sure you entered it correctly." } if ($zip_file -eq 'true') { # compress file before upload $old_path = $src_path $src_path = "$src_path.zip" $dst_path = "$dst_path.zip" Compress-Archive -Path $old_path -Update -DestinationPath $src_path } if ($check_hash -eq 'true') { # save hash before upload $src_hash = (Get-FileHash $src_path -Algorithm MD5).Hash } $client.CopyItemToSession($src_path, $dst_path) if ($check_hash -eq 'true') { $command = "(Get-FileHash $dst_path -Algorithm MD5).Hash" $dst_hash = $client.InvokeCommandInSession($command) if ($src_hash -ne $dst_hash) { throw "Failed check_hash: The uploaded file has a different hash than the local file. LocalFileHash=$src_hash UploadedFileHash=$dst_hash" } } $client.CloseSession() $file_name_leaf = Split-Path $src_path -leaf $file_ext = [System.IO.Path]::GetExtension($file_name_leaf) $file_ext = If ($file_ext) {$file_ext.SubString(1, $file_ext.length - 1)} else {""} $entry_context = @{ PsRemoteUploadedFile = @{ FQDN = $client.fqdn Host = $client.host FilePath = $dst_path FileName = $file_name_leaf FileSize = Get-Item $src_path | ForEach-Object { [math]::ceiling($_.length / 1kb) } FileSHA1 = (Get-FileHash $src_path -Algorithm SHA1).Hash FileSHA256 = (Get-FileHash $src_path -Algorithm SHA256).Hash FileMD5 = (Get-FileHash $src_path -Algorithm MD5).Hash FileExtension = $file_ext } } $human_readable = "File $file_name_leaf was uploaded successfully as: $dst_path" return $human_readable, $entry_context, $null } function ExportMFTCommand([RemotingClient]$client, [string]$volume, [string]$output_file_path) { $raw_response = $client.ExportMFT($volume, $output_file_path) $client.CloseSession() $entry_context = @{ $script:INTEGRATION_ENTRY_CONTEXT = @{ FQDN = $client.fqdn Host = $client.host ExportMFT = $raw_response } } $human_readable = TableToMarkdown $raw_response 'MFT Export results:' return $human_readable, $entry_context, $raw_result } function Main { $command = $Demisto.GetCommand() $command_args = $Demisto.Args() $params = $Demisto.Params() <# Proxy currently isn't supported by PWSH New-Pssession, However further effort might yield an implementation, leaving this parameter for feature development if required. #> $no_proxy = $false $insecure = (ConvertTo-Boolean $params.insecure) $ssl = (ConvertTo-Boolean $params.ssl) try { $hosts = if ($command -eq 'test-module') {ArgToList $params.host } else {ArgToList $command_args.host} if ($hosts.Length -gt 1) { throw "too many hosts were provided. please provide just a single host" } $domain = if ($params.domain) {"." + $params.domain} else {""} $client = [RemotingClient]::new($hosts, $params.credentials.identifier, $params.credentials.password, $domain, $params.dns, $params.auth_method, $insecure, $ssl, $no_proxy) # Executing command $Demisto.Debug("Command being called is $command") switch ($command) { "test-module" { ($human_readable, $entry_context, $raw_response) = TestModuleCommand $client } "$script:COMMAND_PREFIX-command" { ($human_readable, $entry_context, $raw_response) = InvokeCommandCommand $client $command_args.command } "$script:COMMAND_PREFIX-download-file" { DownloadFileCommand -client $client -path $command_args.path -zip_file $command_args.zip_file -check_hash $command_args.check_hash -host_as_prefix (ConvertTo-Boolean $command_args.host_prefix) return } "$script:COMMAND_PREFIX-etl-create-start" { ($human_readable, $entry_context, $raw_response) = StartETLCommand -client $client -etl_path $command_args.etl_path -etl_filter $command_args.etl_filter -etl_max_size $command_args.etl_max_size -overwrite $command_args.overwrite } "$script:COMMAND_PREFIX-etl-create-stop" { ($human_readable, $entry_context, $raw_response) = StopETLCommand $client } "$script:COMMAND_PREFIX-export-registry" { ($human_readable, $entry_context, $raw_response) = ExportRegistryCommand -client $client -reg_key_hive $command_args.reg_key_hive -output_file_path $command_args.file_path } "$script:COMMAND_PREFIX-upload-file" { # to be tested ($human_readable, $entry_context, $raw_response) = UploadFileCommand -client $client -entry_id $command_args.entry_id -dst_path $command_args.path -zip_file $command_args.zip_file -check_hash $command_args.check_hash } "$script:COMMAND_PREFIX-export-mft" { ($human_readable, $entry_context, $raw_response) = ExportMFTCommand -client $client -volume $command_args.volume -output_file_path $command_args.output_path } Default { $Demisto.Error("Unsupported command was entered: $command.") } } # Return results to Demisto Server ReturnOutputs $human_readable $entry_context $raw_response | Out-Null } catch { $Demisto.debug("Integration: $script:INTEGRATION_NAME Command: $command Arguments: $( $command_args | ConvertTo-Json ) Error: $( $_.Exception.Message )") if ($command -ne "test-module") { ReturnError "Error:Integration: $script:INTEGRATION_NAME Command: $command Arguments: $( $command_args | ConvertTo-Json ) Error: $( $_.Exception )" | Out-Null } else { $err_msg = $_.Exception.Message if ($err_msg -match 'Authorization failed') { $err_msg = 'The integration was able to resolve and perform a connectivity test to the specified host. There seems to be an issue with the credentials or host settings. For further troubleshooting please refer to the Powershell Remoting article in https://xsoar.pan.dev/docs/reference/index.' } ReturnError $err_msg } } } # Execute Main when not in Tests if ($MyInvocation.ScriptName -notlike "*.tests.ps1" -AND -NOT$Test) { Main }