Skip to content

Instantly share code, notes, and snippets.

@xshill xshill/pcap-to-replay.py Secret
Last active Sep 26, 2019

Embed
What would you like to do?
Sample script that was used to create a replay file from a pcap using PyRDP and scapy
#!/usr/bin/python3
import logging
import os
from progressbar import progressbar
from scapy.all import *
from pyrdp.enum import ParserMode, PlayerMessageType
from pyrdp.layer import SegmentationLayer, TPKTLayer, X224Layer, MCSLayer, TLSSecurityLayer, SlowPathLayer, FastPathLayer, VirtualChannelLayer, ClipboardLayer
from pyrdp.mcs import MCSRouter
from pyrdp.parser import ClientInfoParser, BasicFastPathParser
from pyrdp.pdu import FormatDataResponsePDU
from pyrdp.recording import FileLayer, Recorder, RecordingFastPathObserver, RecordingSlowPathObserver
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 CustomRecorder(Recorder):
currentTimeStamp: float = None
def getCurrentTimeStamp(self) -> float:
return self.currentTimeStamp
def setTimeStamp(self, timeStamp: float):
self.currentTimeStamp = timeStamp
class RDP(MCSRouter):
def __init__(self, mode: ParserMode, recorder: Recorder):
self.recorder = recorder
self.currentClipboardData = None
self.segmentation = SegmentationLayer()
self.tpkt = TPKTLayer()
self.x224 = X224Layer()
self.mcs = MCSLayer()
self.security = TLSSecurityLayer()
self.io = SlowPathLayer()
self.fastPath = FastPathLayer(BasicFastPathParser(mode))
self.clipboardSecurity = TLSSecurityLayer()
self.virtualChannelLayer = VirtualChannelLayer()
self.clipboardLayer = ClipboardLayer()
self.segmentation.attachLayer(0, self.fastPath)
self.segmentation.attachLayer(3, self.tpkt)
self.security.securityHeaderExpected = True
self.security.createObserver(onClientInfoReceived = self.onClientInfoReceived, onLicensingDataReceived = self.onLicensingDataReceived)
self.io.addObserver(RecordingSlowPathObserver(recorder))
self.fastPath.addObserver(RecordingFastPathObserver(recorder, PlayerMessageType.FAST_PATH_OUTPUT if mode == ParserMode.CLIENT else PlayerMessageType.FAST_PATH_INPUT))
self.clipboardLayer.createObserver(onPDUReceived = self.onClipboard)
self.mcs.addObserver(self)
self.tpkt.setNext(self.x224)
self.x224.setNext(self.mcs)
self.security.setNext(self.io)
self.clipboardSecurity.setNext(self.virtualChannelLayer)
self.virtualChannelLayer.setNext(self.clipboardLayer)
MCSRouter.__init__(self, self.mcs)
def recv(self, data: bytes):
self.segmentation.recv(data)
def recvChannel(self, pdu):
if pdu.channelID == 1003:
# I/O channel
self.security.recv(pdu.payload)
elif pdu.channelID == 1004:
# Clipboard channel
self.clipboardSecurity.recv(pdu.payload)
def onSendDataRequest(self, pdu):
self.recvChannel(pdu)
def onSendDataIndication(self, pdu):
self.recvChannel(pdu)
def onClientInfoReceived(self, data):
pdu = ClientInfoParser().parse(data)
recorder.record(pdu, PlayerMessageType.CLIENT_INFO)
def onLicensingDataReceived(self, pdu):
self.security.securityHeaderExpected = False
def onClipboard(self, pdu):
if isinstance(pdu, FormatDataResponsePDU):
if pdu.requestedFormatData != self.currentClipboardData:
self.recorder.record(pdu, PlayerMessageType.CLIPBOARD_DATA)
self.currentClipboardData = pdu.requestedFormatData
logging.basicConfig(level = logging.CRITICAL)
logging.getLogger("scapy").setLevel(logging.ERROR)
CLIENT = "10.0.0.3"
SERVER = "10.0.0.2"
directory = os.path.dirname(os.path.realpath(__file__))
packets = rdpcap(directory + "capture.pcap")
replayFile = open("replay.pyrdp", "wb")
fileLayer = FileLayer(replayFile)
recorder = CustomRecorder([fileLayer])
client = RDP(ParserMode.CLIENT, recorder)
server = RDP(ParserMode.SERVER, recorder)
rdp = {
CLIENT: client,
SERVER: server
}
# The last packet of this pcap is incomplete
for packet in progressbar(packets[: -1]):
# The packets start with a Wireshark exported PDU structure
source, destination, data = parseExportedPdu(packet)
try:
recorder.setTimeStamp(packet.time)
rdp[destination].recv(data)
server.security.securityHeaderExpected = client.security.securityHeaderExpected
except NotImplementedError:
pass
recorder.record(None, PlayerMessageType.CONNECTION_CLOSE)
@bobskee

This comment has been minimized.

Copy link

bobskee commented Jun 25, 2019

Hey @xshill, is there any chance you could update this snippet? Sadly, as you predicted in the PyRDP blog on GoSecure, the library has changed and the script does not work anymore. I even tried to revert to some PyRDP commits around Dec 18th 2018 but that did not work (out-of-the-box) either. I am also not sure if using the old library would work with captures of RDP sessions using the latest updates. I have some captures of network traffic (which I am able to decrypt using Wireshark) that I would like to play back using the PyRDP player. Thanks!

@bobskee

This comment has been minimized.

Copy link

bobskee commented Jun 25, 2019

Also: keep up that fantastic work with pyrdp! :)

@xshill

This comment has been minimized.

Copy link
Owner Author

xshill commented Jun 25, 2019

Thanks! I'll look into it.

@bobskee

This comment has been minimized.

Copy link

bobskee commented Jun 25, 2019

Awesome! I'd be happy to provide you with a sample if you need one. When using the older commits (and deleting the progressbar) it stumbles upon this issue:

Traceback (most recent call last):
File "./pcap-to-replay.py", line 128, in
rdp[destination].recv(data)
File "./pcap-to-replay.py", line 73, in recv
self.segmentation.recv(data)
File "/usr/local/lib/python3.6/dist-packages/pyrdp-1.0.0-py3.6-linux-x86_64.egg/pyrdp/layer/segmentation.py", line 82, in recv
layer.recv(forwarded)
File "/usr/local/lib/python3.6/dist-packages/pyrdp-1.0.0-py3.6-linux-x86_64.egg/pyrdp/layer/buffered.py", line 46, in recv
self.pduReceived(pdu, self.hasNext)
File "/usr/local/lib/python3.6/dist-packages/pyrdp-1.0.0-py3.6-linux-x86_64.egg/pyrdp/layer/layer.py", line 117, in pduReceived
self.next.recv(pdu.payload)
File "/usr/local/lib/python3.6/dist-packages/pyrdp-1.0.0-py3.6-linux-x86_64.egg/pyrdp/layer/x224.py", line 73, in recv
self.pduReceived(pdu, pdu.header == X224PDUType.X224_TPDU_DATA)
File "/usr/local/lib/python3.6/dist-packages/pyrdp-1.0.0-py3.6-linux-x86_64.egg/pyrdp/layer/layer.py", line 117, in pduReceived
self.next.recv(pdu.payload)
File "/usr/local/lib/python3.6/dist-packages/pyrdp-1.0.0-py3.6-linux-x86_64.egg/pyrdp/layer/mcs.py", line 23, in recv
self.pduReceived(pdu, self.hasNext)
File "/usr/local/lib/python3.6/dist-packages/pyrdp-1.0.0-py3.6-linux-x86_64.egg/pyrdp/layer/layer.py", line 114, in pduReceived
self.observer.onPDUReceived(pdu)
File "/usr/local/lib/python3.6/dist-packages/pyrdp-1.0.0-py3.6-linux-x86_64.egg/pyrdp/core/observer.py", line 78, in call
self.composite.doCall(self.item, args, kwargs)
File "/usr/local/lib/python3.6/dist-packages/pyrdp-1.0.0-py3.6-linux-x86_64.egg/pyrdp/core/observer.py", line 50, in doCall
getattr(observer, item)(*args, **kwargs)
File "/usr/local/lib/python3.6/dist-packages/pyrdp-1.0.0-py3.6-linux-x86_64.egg/pyrdp/layer/layer.py", line 42, in onPDUReceived
self.handlerspdu.header
File "./pcap-to-replay.py", line 87, in onSendDataIndication
self.recvChannel(pdu)
File "./pcap-to-replay.py", line 81, in recvChannel
self.clipboardSecurity.recv(pdu.payload)
File "/usr/local/lib/python3.6/dist-packages/pyrdp-1.0.0-py3.6-linux-x86_64.egg/pyrdp/layer/rdp/security.py", line 145, in recv
self.next.recv(data)
File "/usr/local/lib/python3.6/dist-packages/pyrdp-1.0.0-py3.6-linux-x86_64.egg/pyrdp/layer/rdp/virtual_channel/virtual_channel.py", line 37, in recv
self.pduReceived(virtualChannelPDU, self.hasNext)
File "/usr/local/lib/python3.6/dist-packages/pyrdp-1.0.0-py3.6-linux-x86_64.egg/pyrdp/layer/layer.py", line 117, in pduReceived
self.next.recv(pdu.payload)
File "/usr/local/lib/python3.6/dist-packages/pyrdp-1.0.0-py3.6-linux-x86_64.egg/pyrdp/layer/layer.py", line 120, in recv
pdu = self.mainParser.parse(data)
File "/usr/local/lib/python3.6/dist-packages/pyrdp-1.0.0-py3.6-linux-x86_64.egg/pyrdp/parser/rdp/virtual_channel/clipboard.py", line 25, in parse
clipboardPDU = ClipboardPDU(ClipboardMessageType(msgType), msgFlags, payload)
File "/usr/lib/python3.6/enum.py", line 293, in call
return cls.new(cls, value)
File "/usr/lib/python3.6/enum.py", line 535, in new
return cls.missing(value)
File "/usr/lib/python3.6/enum.py", line 548, in missing
raise ValueError("%r is not a valid %s" % (value, cls.name))
ValueError: 17522 is not a valid ClipboardMessageType

When using the most recent version of pyrdp (and changing PlayerMessageType to PlayerPDUType in pcap-to-replay.py) it breaks because of the MCSRouter rewrite.

I used Wireshark's function 'Export to PDU's' with the 'OSI layer 7' after I decrypted the TLS session to be used as input for the script (tried both pcap and pcap-ng file formats). The client and server IP-addresses in the pcap-to-replay.py script were changed accordingly.

Thanks again!

@bobskee

This comment has been minimized.

Copy link

bobskee commented Jul 23, 2019

Hey @xshill, I do not want to appear impatient but have you got any updates so far?

@xshill

This comment has been minimized.

Copy link
Owner Author

xshill commented Jul 24, 2019

Sorry about the delay, I tried forwarding it to the team but I think they were more focused on other issues. It's currently the end of the term for me so school is taking a lot of my time. I would also like this feature to work so I'll be looking into it once I have more free time.

@xshill

This comment has been minimized.

Copy link
Owner Author

xshill commented Jul 24, 2019

Also, thanks for the reminder!

@bobskee

This comment has been minimized.

Copy link

bobskee commented Jul 24, 2019

Alright, good to hear! Good luck with school. I hope to hear from the team (or you) soon :)

@xshill

This comment has been minimized.

Copy link
Owner Author

xshill commented Aug 14, 2019

All right, now that school and DefCon is over, I can finally get to work on this!

@xshill

This comment has been minimized.

Copy link
Owner Author

xshill commented Aug 14, 2019

@bobskee I just pushed a new branch called pyrdp-replay to the repo. Let me know if this works for the PCAPs you have. For now the script only expects one session per input PCAP.

Installation:
Follow the same instructions for installation as the master branch. You should reinstall for this branch because of the added dependencies and the new file in bin/.

Usage:

usage: pyrdp-replay.py [-h] input client server output

positional arguments:
  input       Path to PCAP file with exported PDUs
  client      Client IP
  server      Server IP
  output      Output file path

optional arguments:
  -h, --help  show this help message and exit
@bobskee

This comment has been minimized.

Copy link

bobskee commented Aug 15, 2019

That is awesome! It looks like there was a lot of interest in your demo at blackhat :)

I tested the new script and it succesfully extracted the files that were transferred in the session. However, even though the replay file has quite the same size as I expected (from a sample pyrdp mitm capture), the player cannot play it successfully. A part of the screen is visible and the log output (with keystrokes etc.) seems to match but the player isn't able to play the session. If you'd like I can provide you a sample somehow. Is there any other platform I can contact you on?

Thanks!

@xshill

This comment has been minimized.

Copy link
Owner Author

xshill commented Aug 15, 2019

The player cannot play the session at all? Were there any exceptions when running the player or pyrdp-replay?

You can find me on keybase (username: xshill).

@bobskee

This comment has been minimized.

Copy link

bobskee commented Aug 15, 2019

It shows like a quarter (top left) of the original screen and the progressbar (timeline) is filled at about 80% which I cannot adjust. AFAIK there are no exceptions and both programs run fine.

@xshill

This comment has been minimized.

Copy link
Owner Author

xshill commented Aug 15, 2019

Quick question: can you tell me what the color depth is for your session? It should be under ClientData->clientCoreData in Wireshark. I assume this session wasn't captured by PyRDP?

@xshill

This comment has been minimized.

Copy link
Owner Author

xshill commented Aug 15, 2019

Oh and yes, a sample would be nice :)

@bobskee

This comment has been minimized.

Copy link

bobskee commented Aug 15, 2019

Color depth is '8 bits-per-pixel (bpp) (0xca01)' according to Wireshark. The session was actually captured by PyRDP but at this moment I am not certain anymore that the capture only contains a single session. I will check that tomorrow and I will also provide you with a sample then (have to strip some data first or do a new capture if I am uploading it here in public since I don't have a Twitter account to contact you privately)

@xshill

This comment has been minimized.

Copy link
Owner Author

xshill commented Aug 15, 2019

I think you can just use your github if you want to create a keybase account.

@robeving

This comment has been minimized.

Copy link

robeving commented Sep 25, 2019

Hi @xshill. I've been playing with your branch - it looks really cool.

I can use pyrdp-replay to get an output file (providing I add some exception handling in recv line 81). The output file is around 100k which seems about right. The pcap contains one session with everything from login to terminate, I've exported the decrypted PDUs. PCAP looks OK.

The player can open the file and get the login and keystrokes out but not the actual screen capture.

I feel really close to this actually working well. I'm happy to try and debug this if you have some pointers to where I can look. Alternatively I can give you the PCAP and outfile.

@xshill

This comment has been minimized.

Copy link
Owner Author

xshill commented Sep 26, 2019

@robeving I assume this is you on Twitter?

The issue is with how PyRDP handles graphics vs. how RDP normally handles it. Basically there's two ways to handle output: either the server sends drawing orders that the client then renders locally or it sends bitmaps of the screen. Normal RDP usually sends GDI drawing orders over the network that you have to render locally. To avoid implementing a bunch of drawing orders, we coded PyRDP so it only accepts bitmaps. This works fine for situations when PyRDP is running but, obviously, since we have no support for drawing orders, it means we can't parse output for "normal" RDP connections.

So, basically, we need to implement support for the drawing orders API before this becomes usable with generic RDP pcaps. Judging from this spec, I wouldn't expect that to happen soon because there's a bunch of drawing order types to implement for it to work properly.

It was bit of an oversight on our part not to mention that on the blog post - guess we were too used to only handling bitmaps. I'll add this to the README in the pyrdp-replay branch and create an issue to track this.

@robeving

This comment has been minimized.

Copy link

robeving commented Sep 26, 2019

Thanks this is really helpful just knowing there is a lot more work to do.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.