RemotePSpy: Remote PowerShell Visibility for Older Versions

Posted on 28 January 2019 by Matt Hillman


PowerShell has long been leveraged used by attackers due to its power and flexibility. While commands and scripts can be executed in a local PowerShell session, it is also possible to execute PowerShell on a remote host. This can help an attacker move laterally, and avoid dropping a script file to disk.

The difficulty in detecting and responding to remote PowerShell varies a lot depending on the version of PowerShell being used. Later versions include comprehensive logging features that can include full transcription of entire PowerShell sessions with all input and output. But earlier versions have much more rudimentary logging which can make it very difficult to know exactly what happened in a given session.

To improve this situation, we introduce RemotePSpy, a tool for obtaining full details of what happens in remote PowerShell sessions on both older and newer versions of PowerShell.


Please note this is an early version of a prototype utility, so not everything in the PowerShell Remote Protocol is supported. It should still provide a useful insight into remote PowerShell sessions, and provide a great starting point for any other research into the protocol.


Demo and Example Usage

RemotePSpy gives a live view, approximating what would be displayed on the attacker's screen as remote PowerShell is executed. An example of this is shown in the animated GIF below, showing a side-by-side of an attacker screen on the left, with the target on the right running RemotePSpy.



One interesting thing about interactive sessions is that you can actually see when the attacker uses tab complete, which uses the TabExpansion command under the hood.


At the same time as this live screen view, all input and output is logged to RemotePSpy.log in a more verbose form:


RemotePSpy Interactive PowerShell Session Log Output


Non-interactive sessions work in much the same way. A script executed from a file or in a ScriptBlock passed on the command line will be sent to the remote host, where its contents are logged, followed by its output.

Below is an example of running Add-NetUser from PowerView in PowerSploit to add a local administrator account to a remote host via PowerShell remoting (the script has been snipped down for brevity, the original function is in


2019-01-07 11:00:25,503 - RemotePSpy.simple_cmd - INFO - Runspace: 506ddad2-79cc-4dd9-8c98-60c836c1c3b9, Pipeline: 56e3f7dd-e082-4354-8236-e25eec70b875, Destination: 2, Command: function Add-NetUser {

        Adds a domain user or a local user to the current (or remote) machine,        

< …… SNIP …… >

        else {
            Add-NetGroupUser -UserName $UserName -GroupName $GroupName -ComputerName $ComputerName
            "[*] User $UserName successfully added to group $GroupName on host $ComputerName"

Add-NetUser -UserName AcidBurn -Password H@cktheplanet -ComputerName latmov-win7-02

2019-01-07 11:00:29,325 - RemotePSpy.simple_cmd - INFO - Empty message data in PIPELINE_OUTPUT message. Runspace: 506ddad2-79cc-4dd9-8c98-60c836c1c3b9, Pipeline: 56e3f7dd-e082-4354-8236-e25eec70b875, Destination: 1
2019-01-07 11:00:29,325 - RemotePSpy.simple_cmd - INFO - Runspace: 506ddad2-79cc-4dd9-8c98-60c836c1c3b9, Pipeline: 56e3f7dd-e082-4354-8236-e25eec70b875, Destination: 1,  output: [*] User AcidBurn successfully created on host latmov-win7-02

This script adds the user using pure PowerShell, so you do not see any commands specific to adding the user appear on the target, just a wsmprovhost.exe process that hosts the remote PowerShell. Without the detailed logging of newer PowerShell versions it would be almost impossible to know what happened in this remote session, but RemotePSpy can reveal what happened.



Installation and Usage

RemotePSpy can be installed via pip:

pip install remotepspy


Alternatively, source code is available on github at:


Simply execute RemotePSpy.exe to start monitoring and logging, and press Return when you are finished. The log will be written to the current working directory, named RemotePSpy.log.


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.


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 them in your Python site-packages directory.


For those that want to get into the code, there is comprehensive logging at different layers in the protocol. Each Python logger name is defined by a LOGGER_NAME constant in each class within the code, and some especially useful logs you can generate are shown in the table below. These allow you to get a full trace of the protocol at various layers.


Key Log Data Logger Name Constant Level
Command Trace SimpleCommandTracer.LOGGER_NAME INFO
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


For the standard executable RemotePSpy.exe logging is only configurable by editing the source code in the current release. We hope to change this in future releases to provide easier configuration.


Why Use RemotePSpy?

We created this utility because of the poor logging in earlier versions of PowerShell. Newer versions (5.0+) have very comprehensive logging options, but these features have not been backported to most older versions. To illustrate this, an overview of the logging options in each version if PowerShell is given here.


PowerShell 2.0:

  • Standard PowerShell logs: Basic engine state and runspace pool information – no details on any script/commands executed;
  • PowerShell Analytic log: Includes PowerShell Remote Protocol fragments as hex strings. Impossible to read meaningfully without a lot of further processing;
  • WinRM Analytic log: Fragmented WSMan SOAP messages. This is the transport protocol for remote PowerShell, but cannot be trivially read by a human and needs a lot of processing to work out what commands were executed.

PowerShell 3.0:

  • “Module Logging” capability: Enabled in Group Policy, this adds pipeline execution events for the specified modules (a wildcard enables it for all). The result is that logs include some commands, variable initializations and output, though not complete details and not in all cases.

PowerShell 4.0:

  • Many of the advanced logging features are backported from 5.0, details of which are in the next section;
  • Notably missing from these backported features is suspicious script block logging.

PowerShell 5.0+:

  • Script Block Logging: Enabled in Group Policy, logs the processing of commands, script blocks, functions, and scripts, but does not capture their output;
  • Suspicious Script Block Logging: If Script Block Logging is not enabled in Group Policy, it will still log suspicious script blocks at Warning level. Suspicious script blocks are identified based on a wide ranging set of criteria which you can read more about in the PowerShell source code here;
  • Transcription: Logs a full session transcript of all commands, script, input and output in a PowerShell session. This of course, provides a complete record of everything that occurred. Transcripts are written as text files to a location specified in Group Policy.


It is clear then that later versions of PowerShell offer a comprehensive set of logging options, but that earlier versions make it much more difficult to obtain useful information without taking steps to extract this from analytic data that requires significant processing. This is where RemotePSpy comes in, to allow obtaining more useful data on a live system.


How it Works


RemotePSpy uses Event Tracing for Windows (ETW) to obtain the data needed to decode a remote PowerShell session. By default it uses the Microsoft-Windows-WinRM provider, but it is also possible to use Microsoft-Windows-PowerShell, although the latter makes certain state tracking the code must perform a bit more challenging.

The WinRM provider gives a full trace of WinRM messages. WinRM is a remote management protocol for Windows, and it can perform many tasks. One of these is being the transport layer for the PowerShell Remote Protocol (PSRP). It is therefore possible to decode this WinRM traffic, extract the PSRP layer, and decode that to work out what is happening inside a remote PowerShell session.

The PowerShell provider lets us obtain the PSRP protocol directly, but because of how the data is presented it makes it a bit more challenging to accurately track the context of the “shell” in which a given PowerShell command is running. In the current version, this can produce some unnecessary warnings in the trace, so the WinRM provider is used by default.

The process of decoding each protocol layer is quite complex, so it is not presented here, but look out for a future post presenting this in detail for those who want to know how it all works at a much deeper level.


Future Work

  • We hope to add additional features to RemotePSpy, including:
  • Parsing of Windows event logs to extract analytic data which can be decoded in much the same way as the ETW data. This may be useful to respond to any incidents on machines with older PowerShell that happen to have these logs available;
  • Publishing the command trace log to the Windows event log, instead of Python style log files;
  • Easier configuration with command line arguments and/or a configuration file;
  • Installation via pip, both as an executable script and a library for easy access to underlying classes.


Want more like this? Follow us on Twitter and LinkedIn.