Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add PyRDP Pcap Extractor #188

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# PyRDP
**Warning:** currently, the pyrdp-replay branch will most likely only work for traffic captured while PyRDP was running, because we don't implement the usual RDP output API. See [#153](https://github.com/GoSecure/pyrdp/issues/153).

PyRDP is a Python 3 Remote Desktop Protocol (RDP) Man-in-the-Middle (MITM) and library.

![PyRDP Logo](https://raw.githubusercontent.com/GoSecure/pyrdp/master/docs/pyrdp-logo.svg?sanitize=true)
Expand Down
147 changes: 147 additions & 0 deletions bin/pyrdp-replay.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
#!/usr/bin/python3

#
# This file is part of the PyRDP project.
# Copyright (C) 2018, 2020 GoSecure Inc.
# Licensed under the GPLv3 or later.
#

import argparse
from pathlib import Path
import struct

from progressbar import progressbar
from scapy.all import *

from pyrdp.logging import LOGGER_NAMES, SessionLogger
from pyrdp.mitm import MITMConfig, RDPMITM
from pyrdp.mitm.MITMRecorder import MITMRecorder
from pyrdp.mitm.state import RDPMITMState
from pyrdp.recording import FileLayer


def bytesToIP(data: bytes):
return ".".join(str(b) for b in data)


def parseExportedPdu(packet: packet.Raw):
source = packet.load[12: 16]
source = bytesToIP(source)

destination = packet.load[20: 24]
destination = bytesToIP(destination)

data = packet.load[60:]
return source, destination, data


class CustomMITMRecorder(MITMRecorder):
currentTimeStamp: int = None

def getCurrentTimeStamp(self) -> int:
return self.currentTimeStamp

def setTimeStamp(self, timeStamp: int):
self.currentTimeStamp = timeStamp


class RDPReplayerConfig(MITMConfig):
@property
def replayDir(self) -> Path:
return self.outDir

@property
def fileDir(self) -> Path:
return self.outDir


class RDPReplayer(RDPMITM):
def __init__(self, output_path: str):
def sendBytesStub(_: bytes):
pass

output_path = Path(output_path)
output_directory = output_path.absolute().parent

logger = logging.getLogger(LOGGER_NAMES.MITM_CONNECTIONS)
log = SessionLogger(logger, "replay")

config = RDPReplayerConfig()
config.outDir = output_directory
# We'll set up the recorder ourselves
config.recordReplays = False

replay_transport = FileLayer(output_path)
state = RDPMITMState()
super().__init__(log, log, config, state, CustomMITMRecorder([replay_transport], state))

self.client.tcp.sendBytes = sendBytesStub
self.server.tcp.sendBytes = sendBytesStub
self.state.useTLS = True

def start(self):
pass

def recv(self, data: bytes, from_client: bool):
if from_client:
self.client.tcp.dataReceived(data)
else:
self.server.tcp.dataReceived(data)

def setTimeStamp(self, timeStamp: float):
self.recorder.setTimeStamp(int(timeStamp * 1000))

def connectToServer(self):
pass

def startTLS(self):
pass

def sendPayload(self):
pass


def main():
arg_parser = argparse.ArgumentParser()
arg_parser.add_argument("input", help="Path to PCAP file with exported PDUs. "
"Using Wireshark: File -> Export PDUs to File "
"then pick OSI Layer 7 and click Ok and "
"save the result as a pcap file.")
arg_parser.add_argument("client", help="Client IP address")
arg_parser.add_argument("server", help="Server IP address (usually it's the MITM IP address)")
arg_parser.add_argument("output", help="Output file that will be playable in pyrdp-player.py")
arguments = arg_parser.parse_args(sys.argv[1 :])

logging.basicConfig(level=logging.CRITICAL)
logging.getLogger("scapy").setLevel(logging.ERROR)
client_ip = arguments.client
server_ip = arguments.server

input_path = arguments.input
output_path = arguments.output
packets = rdpcap(input_path)

replayer = RDPReplayer(output_path)

for packet in progressbar(packets):
# The packets start with a Wireshark exported PDU structure
source, destination, data = parseExportedPdu(packet)

if source not in [client_ip, server_ip] or destination not in [client_ip, server_ip]:
continue

try:
replayer.setTimeStamp(float(packet.time))
replayer.recv(data, source == client_ip)
except NotImplementedError as e:
raise e

try:
replayer.tcp.recordConnectionClose()
except struct.error as e:
print("Couldn't close the connection cleanly. "
"Are you sure you got source and destination correct?")


if __name__ == "__main__":
main()
9 changes: 5 additions & 4 deletions pyrdp/mitm/RDPMITM.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,16 @@
from pyrdp.mitm.TCPMITM import TCPMITM
from pyrdp.mitm.VirtualChannelMITM import VirtualChannelMITM
from pyrdp.mitm.X224MITM import X224MITM
from pyrdp.recording import FileLayer, RecordingFastPathObserver, RecordingSlowPathObserver
from pyrdp.recording import FileLayer, RecordingFastPathObserver, RecordingSlowPathObserver, \
Recorder


class RDPMITM:
"""
Main MITM class. The job of this class is to orchestrate the components for all the protocols.
"""

def __init__(self, mainLogger: SessionLogger, crawlerLogger: SessionLogger, config: MITMConfig):
def __init__(self, mainLogger: SessionLogger, crawlerLogger: SessionLogger, config: MITMConfig, state: RDPMITMState=None, recorder: Recorder=None):
"""
:param log: base logger to use for the connection
:param config: the MITM configuration
Expand All @@ -72,7 +73,7 @@ def __init__(self, mainLogger: SessionLogger, crawlerLogger: SessionLogger, conf
self.statCounter = StatCounter()
"""Class to keep track of connection-related statistics such as # of mouse events, # of output events, etc."""

self.state = RDPMITMState()
self.state = state if state is not None else RDPMITMState()
"""The MITM state"""

self.client = RDPLayerSet()
Expand All @@ -84,7 +85,7 @@ def __init__(self, mainLogger: SessionLogger, crawlerLogger: SessionLogger, conf
self.player = TwistedPlayerLayerSet()
"""Layers on the attacker side"""

self.recorder = MITMRecorder([], self.state)
self.recorder = recorder if recorder is not None else MITMRecorder([], self.state)
"""Recorder for this connection"""

self.channelMITMs = {}
Expand Down
5 changes: 4 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,21 @@
scripts=[
'bin/pyrdp-clonecert.py',
'bin/pyrdp-mitm.py',
'bin/pyrdp-player.py'
'bin/pyrdp-player.py',
'bin/pyrdp-replay.py'
],
install_requires=[
'appdirs',
'cryptography',
'names',
'progressbar2',
'pyasn1',
'pycryptodome',
'pyopenssl',
'PySide2',
'pytz',
'rsa',
'scapy',
'service_identity',
'twisted',
'dbus-python;platform_system!="Windows"',
Expand Down