RemotePSpy provides live monitoring of remote PowerShell sessions, which is particularly useful for older (pre-5.0) versions of PowerShell which do not have comprehensive logging facilities built in.
It uses Event Tracing for Windows (ETW) to obtain message data from the WinRM protocol that is used as a transport for remote PowerShell, and decodes the various protocol layers to provide a trace of the script commands executed and their input/output.
This is currently an early research prototype and so does not support every aspect of the PowerShell Remote Protocol (PSRP). It can still provide useful insights as to what is happening in a remote PowerShell session.
The easiest way to install is using pip:
pip install remotepspy
Dependencies should be installed automatically when installing via pip, but in case you wish to install from source they are listed here:
- Python 3.7
- pywintrace (https://github.com/fireeye/pywintrace or https://pypi.org/project/pywintrace/)
- psutils (https://pypi.org/project/pywintrace/)
- libwim-15.dll from https://wimlib.net/ (bundled in the RemotePSpy package for convenience)
The libwim-15.dll is only required if using the (recommended, and default) Microsoft-Windows-WinRM ETW provider as a data source. It is used to decompress certain stream objects in the WinRM/WSMan protocol which contain PSRP message fragments. Just put it in the same directory as the main Python script.
RemotePSpy to start monitoring and logging, and press Return when you are finished. The log will be
written to the current working directory, named
The tool will also print an approximate replica of what the user of remote PowerShell would see on their screen to stdout alongside the more verbose information in the log file. More complex logging is available, see "Logging" below.
If you installed Python to be in your PATH, the RemotePSpy executable scripts will also be in your PATH. Otherwise you may need to look for then in your Python site-packages directory.
PowerShell ETW Provider Version
RemotePSpy uses the (recommended) WinRM ETW provider as a data source. If you wish to use the PowerShell
ETW provider instead, you can execute
This version may produce some unecessary warnings due to the added complexity of tracking certain state based on how the data is provided in this particular ETW provider.
The code consists of a number of fairly modular classes which can accept input at different layers in the protocol stack. This allows them to be plugged together in slightly different ways depending on where the data is obtained from and in what form. Most classes accept a callback function which they use to pass on the result they produce to the next layer in the stack.
A description of the main classes used is given below, followed by some processing flows which show which class feeds into which in different scenarios.
ETWWinRM – Obtains ETW events from the WinRM provider.
ETWPowerShell – Obtains ETW events from the PowerShell provider.
PowerShellETWParser – Identifies Shell context for PowerShell ETW events, passing on the fragment data to PSRPDefragmenter. Also provides thread synchronization.
SoapDefragmenter – Re-assembles full WSMan SOAP messages from WinRM ETW events, passing the complete SOAP on to WSManPS.
WSManPS – Filters out non-PowerShell related WinRM, tracks Shell context, parses out PSRP fragment data from the WSMan SOAP, and passes on the fragment data to PSRPDefragmenter.
PSRPDefragmenter – Re-assembles PSRP fragments into full PRSP messages. Fragments are assembled by ObjectID, and uniqueness of ObjectID is ensured by taking Shell context into account. Passes final PSRP messages on to PSRPParser.
PSRPParser – Decodes the raw binary PSRP message, extracting header details like RPID, Pipeline ID, and MessageType. Passes decoded messages on to SimpleCommandTracer.
SimpleCommandTracer – Interprets PSRP messages using MessageType, and extracts and prints/logs commands, arguments, and their output. Includes partial decoding of serialized PowerShell objects. Not a complete implementation of every possible feature, but attempts to cover most common cases to allow for execution trace.
Processing flow when using Microsoft-Windows-WinRM ETW provider:
ETWWinRM -> SoapDefragmenter -> WSManPS -> PSRPDefragmenter -> PSRPParser -> SimpleCommandTracer
Processing flow when using Microsoft-Windows-PowerShell ETW provider:
ETWPowerShell -> PowerShellETWParser -> PSRPDefragmenter -> PSRPParser -> SimpleCommandTracer
There is comprehensive logging at each layer in the protocol stack. This allows debugging at various levels, and was especially helpful during development. Hopefully it can also be useful to anyone wanting to investigate remote PowerShell in action, as it is possible to get a full trace of the protocol at all the key layers.
Currently, the source code at the end of the script where loggers are configured must be edited to change logging. Future releases should hopefully provide a better method for such configuration.
Each logger name is defined by a constant, LOGGER_NAME, in the class that uses it. Every logger is defined as a child of "RemotePSpy" (e.g. RemotePSpy.etw). Programmatically, loggers can be configured by using logging.getLogger() with the appropriate log name.
For quick reference, the following loggers and levels will provide you with protocol traces at different layers in the protocol:
|Key Log Data||Logger Name Constant||Level|
|Full ETW event trace||ETWWinRM.LOGGER_NAME||DEBUG|
|Full WSMan SOAP message trace||SoapDefragmenter.LOGGER_NAME||INFO|
|Full trace of each PSRP fragment||PSRPDefragmenter.LOGGER_NAME||DEBUG|
|Full PSRP message trace||PSRPParser.LOGGER_NAME||DEBUG|
Note that SimpleCommandTracer outputs a trace on stdout as well, and this attempts to somewhat replicate the display as the remote PowerShell user would see it. This is different to the command trace log which logs each command, pipeline method call, and pipeline output in a more precise way, including additional context such as RPID and Pipeline ID.
A fuller summary of what appears in each log at which level is given below:
- Parsing errors
- Unsupported type or pipeline method encountered
- The actual command trace
- Pipeline method called with no arguments
- Full PSRP message trace
- Out-of-order fragment received
- Non-fatal Shell tracking anomalies
- End fragment found
- Tracking Shell ID found in fragment data that was not explicitly tracked before
- Full trace of each PSRP fragment
- Serious parsing errors
- Less serious parsing errors
- Overwriting of existing shell/command tracking contexts
- Shell and command tracking info
- Full trace of WSMan messages that are ignored due to not being related to PowerShell
- Full WSMan SOAP message trace
- SOAP chunk processed
- Shell tracking info
- Shell context identification errors
- Catch-all exceptions from lower layers
- Full ETW event trace
- ETW session start/stop