From ea5b2a5b399dc32bec1a4c71e8bfd5c9f28de623 Mon Sep 17 00:00:00 2001 From: Alexandre Beaulieu Date: Fri, 13 Mar 2020 10:59:45 -0400 Subject: [PATCH 1/5] feat: Added `--no-downgrade` mode. --- CHANGELOG.adoc | 1 + README.md | 24 ++++++++++++++++++++++++ bin/pyrdp-mitm.py | 2 ++ pyrdp/mitm/MCSMITM.py | 29 +++++++++++++++-------------- pyrdp/mitm/RDPMITM.py | 4 ++-- pyrdp/mitm/SlowPathMITM.py | 21 +++++++++++---------- pyrdp/mitm/config.py | 5 ++++- pyrdp/mitm/state.py | 11 ++++++++--- 8 files changed, 67 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 0a92de08c..4e8b73187 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -17,6 +17,7 @@ For a detailed view of what has changed, refer to the {uri-repo}/commits/master[ * Default Docker Compose command now `pyrdp-mitm -h` to avoid confusing crash on `docker-compose up` ({uri-issue}173[#173]) * Documentation updates and fixes ({uri-issue}165[#165], {uri-issue}166[#166], {uri-issue}172[#172]) * Added `--disable-active-clipboard` switch to prevent clipboard request injection +* Added `--no-downgrade` switch to prevent protocol downgrading where possible. === Bug fixes diff --git a/README.md b/README.md index 29ce90026..480248d83 100644 --- a/README.md +++ b/README.md @@ -306,6 +306,30 @@ After 5 seconds, input / output is restored back to normal. #### Other MITM arguments Run `pyrdp-mitm.py --help` for a full list of arguments. +##### `--no-downgrade` + +This argument is useful when running PyRDP in Honeypot scenarios to avoid scanner fingerprinting. +When the switch is enabled, PyRDP will not downgrade unsupported extensions and let them the traffic through +transparently. The player will likely not be able to successfully replay video traffic, but the following supported +channels should still be accessible: + +- Keystroke recording +- Mouse position updates +- Clipboard access (passively) +- Drive access (passively) + +This feature is still a work in progress and some downgrades are currently unavoidable to allow the connection +to be established. The following downgrades are currently not affected by this switch and will still happen: + +- FIPS Encryption +- Non-TLS encryption protocols +- ClientInfo compression +- Virtual Channel compression + +If being able to eventually replay the full session is important, a good solution is to record the raw RDP traffic +using Wireshark and keep the TLS master secrets. Whenever PyRDP adds support for additional extensions, it would +then become possible to extract a valid RDP replay file from the raw network capture. + ### Using the PyRDP Player Use `pyrdp-player.py` to run the player. diff --git a/bin/pyrdp-mitm.py b/bin/pyrdp-mitm.py index 5b5cf8ca9..2c42e6a19 100755 --- a/bin/pyrdp-mitm.py +++ b/bin/pyrdp-mitm.py @@ -49,6 +49,7 @@ def main(): parser.add_argument("--crawler-match-file", help="File to be used by the crawler to chose what to download when scraping the client shared drives.", default=None) parser.add_argument("--crawler-ignore-file", help="File to be used by the crawler to chose what folders to avoid when scraping the client shared drives.", default=None) parser.add_argument("--no-replay", help="Disable replay recording", action="store_true") + parser.add_argument("--no-downgrade", help="Disables downgrading of unsupported extensions. This makes PyRDP harder to fingerprint but might impact the player's ability to replay captured traffic.", action="store_true") args = parser.parse_args() outDir = Path(args.output) @@ -76,6 +77,7 @@ def main(): config.crawlerMatchFileName = args.crawler_match_file config.crawlerIgnoreFileName = args.crawler_ignore_file config.recordReplays = not args.no_replay + config.downgrade = not args.no_downgrade config.disableActiveClipboardStealing = args.disable_active_clipboard diff --git a/pyrdp/mitm/MCSMITM.py b/pyrdp/mitm/MCSMITM.py index d044849d6..902e2b334 100644 --- a/pyrdp/mitm/MCSMITM.py +++ b/pyrdp/mitm/MCSMITM.py @@ -88,22 +88,23 @@ def onConnectInitial(self, pdu: MCSConnectInitialPDU): rdpClientDataPDU.securityData.encryptionMethods &= ~EncryptionMethod.ENCRYPTION_FIPS rdpClientDataPDU.securityData.extEncryptionMethods &= ~EncryptionMethod.ENCRYPTION_FIPS - # This disables the support for the Graphics pipeline extension, which is a completely different way to - # transfer graphics from server to client. https://msdn.microsoft.com/en-us/library/dn366933.aspx - rdpClientDataPDU.coreData.earlyCapabilityFlags &= ~ClientCapabilityFlag.RNS_UD_CS_SUPPORT_DYNVC_GFX_PROTOCOL + if not self.state.config.downgrade: + # This disables the support for the Graphics pipeline extension, which is a completely different way to + # transfer graphics from server to client. https://msdn.microsoft.com/en-us/library/dn366933.aspx + rdpClientDataPDU.coreData.earlyCapabilityFlags &= ~ClientCapabilityFlag.RNS_UD_CS_SUPPORT_DYNVC_GFX_PROTOCOL - # Remove 24bpp and 32bpp support, fall back to 16bpp. - # 2018-12-14: This is only there because there is a bug in the pyrdp player where 24bpp - # decompression in rle.c causes random crashes. If this bug is fixed, we could remove this. - rdpClientDataPDU.coreData.supportedColorDepths &= ~SupportedColorDepth.RNS_UD_32BPP_SUPPORT - rdpClientDataPDU.coreData.supportedColorDepths &= ~SupportedColorDepth.RNS_UD_24BPP_SUPPORT - rdpClientDataPDU.coreData.highColorDepth &= ~HighColorDepth.HIGH_COLOR_24BPP + # Remove 24bpp and 32bpp support, fall back to 16bpp. + # 2018-12-14: This is only there because there is a bug in the pyrdp player where 24bpp + # decompression in rle.c causes random crashes. If this bug is fixed, we could remove this. + rdpClientDataPDU.coreData.supportedColorDepths &= ~SupportedColorDepth.RNS_UD_32BPP_SUPPORT + rdpClientDataPDU.coreData.supportedColorDepths &= ~SupportedColorDepth.RNS_UD_24BPP_SUPPORT + rdpClientDataPDU.coreData.highColorDepth &= ~HighColorDepth.HIGH_COLOR_24BPP - if rdpClientDataPDU.coreData.highColorDepth == 0: - # Means the requested color depth was 24bpp, fallback to 16bpp - rdpClientDataPDU.coreData.highColorDepth |= HighColorDepth.HIGH_COLOR_16BPP + if rdpClientDataPDU.coreData.highColorDepth == 0: + # Means the requested color depth was 24bpp, fallback to 16bpp + rdpClientDataPDU.coreData.highColorDepth |= HighColorDepth.HIGH_COLOR_16BPP - rdpClientDataPDU.coreData.earlyCapabilityFlags &= ~ClientCapabilityFlag.RNS_UD_CS_WANT_32BPP_SESSION + rdpClientDataPDU.coreData.earlyCapabilityFlags &= ~ClientCapabilityFlag.RNS_UD_CS_WANT_32BPP_SESSION self.recorder.record(rdpClientDataPDU, PlayerPDUType.CLIENT_DATA) @@ -267,4 +268,4 @@ def onServerDisconnectProviderUltimatum(self, pdu: MCSDisconnectProviderUltimatu Forward a server disconnect provider ultimatum to the client. :param pdu: the disconnect provider ultimatum """ - self.client.sendPDU(pdu) \ No newline at end of file + self.client.sendPDU(pdu) diff --git a/pyrdp/mitm/RDPMITM.py b/pyrdp/mitm/RDPMITM.py index c7775fa80..ad12c5a1c 100644 --- a/pyrdp/mitm/RDPMITM.py +++ b/pyrdp/mitm/RDPMITM.py @@ -72,7 +72,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 = RDPMITMState(config) """The MITM state""" self.client = RDPLayerSet() @@ -393,4 +393,4 @@ def enableForwarding(): waitForPayload, enableForwarding ]) - sequencer.run() \ No newline at end of file + sequencer.run() diff --git a/pyrdp/mitm/SlowPathMITM.py b/pyrdp/mitm/SlowPathMITM.py index 3b6da52d1..d2a32bf33 100644 --- a/pyrdp/mitm/SlowPathMITM.py +++ b/pyrdp/mitm/SlowPathMITM.py @@ -59,21 +59,22 @@ def onConfirmActive(self, pdu: ConfirmActivePDU): :param pdu: the confirm active PDU """ - # Force RDP server to send bitmap events instead of order events. - pdu.parsedCapabilitySets[CapabilityType.CAPSTYPE_ORDER].orderFlags = OrderFlag.NEGOTIATEORDERSUPPORT | OrderFlag.ZEROBOUNDSDELTASSUPPORT - pdu.parsedCapabilitySets[CapabilityType.CAPSTYPE_ORDER].orderSupport = b"\x00" * 32 - # Disable virtual channel compression if CapabilityType.CAPSTYPE_VIRTUALCHANNEL in pdu.parsedCapabilitySets: pdu.parsedCapabilitySets[CapabilityType.CAPSTYPE_VIRTUALCHANNEL].flags = VirtualChannelCompressionFlag.VCCAPS_NO_COMPR - # Override the bitmap cache capability set with null values. - if CapabilityType.CAPSTYPE_BITMAPCACHE in pdu.parsedCapabilitySets: - pdu.parsedCapabilitySets[CapabilityType.CAPSTYPE_BITMAPCACHE] = Capability(CapabilityType.CAPSTYPE_BITMAPCACHE, b"\x00" * 36) + if not self.state.config.downgrade: + # Force RDP server to send bitmap events instead of order events. + pdu.parsedCapabilitySets[CapabilityType.CAPSTYPE_ORDER].orderFlags = OrderFlag.NEGOTIATEORDERSUPPORT | OrderFlag.ZEROBOUNDSDELTASSUPPORT + pdu.parsedCapabilitySets[CapabilityType.CAPSTYPE_ORDER].orderSupport = b"\x00" * 32 + + # Override the bitmap cache capability set with null values. + if CapabilityType.CAPSTYPE_BITMAPCACHE in pdu.parsedCapabilitySets: + pdu.parsedCapabilitySets[CapabilityType.CAPSTYPE_BITMAPCACHE] = Capability(CapabilityType.CAPSTYPE_BITMAPCACHE, b"\x00" * 36) - # Disable surface commands - if CapabilityType.CAPSETTYPE_SURFACE_COMMANDS in pdu.parsedCapabilitySets: - pdu.parsedCapabilitySets[CapabilityType.CAPSETTYPE_SURFACE_COMMANDS].cmdFlags = 0 + # Disable surface commands + if CapabilityType.CAPSETTYPE_SURFACE_COMMANDS in pdu.parsedCapabilitySets: + pdu.parsedCapabilitySets[CapabilityType.CAPSETTYPE_SURFACE_COMMANDS].cmdFlags = 0 def onDemandActive(self, pdu: DemandActivePDU): """ diff --git a/pyrdp/mitm/config.py b/pyrdp/mitm/config.py index 7040d280f..e363216e0 100644 --- a/pyrdp/mitm/config.py +++ b/pyrdp/mitm/config.py @@ -44,6 +44,9 @@ def __init__(self): self.recordReplays: bool = True """Whether replays should be recorded or not""" + self.downgrade: bool = True + """Whether to actively downgrade unsupported extensions.""" + self.payload: str = "" """Payload to send automatically upon connection""" @@ -77,4 +80,4 @@ def fileDir(self) -> Path: """ Get the directory for intercepted files. """ - return self.outDir / "files" \ No newline at end of file + return self.outDir / "files" diff --git a/pyrdp/mitm/state.py b/pyrdp/mitm/state.py index eb6258526..3255fe591 100644 --- a/pyrdp/mitm/state.py +++ b/pyrdp/mitm/state.py @@ -13,6 +13,7 @@ from pyrdp.parser import createFastPathParser from pyrdp.pdu import ClientChannelDefinition from pyrdp.security import RC4CrypterProxy, SecuritySettings +from pyrdp.mitm.config import MITMConfig class RDPMITMState: @@ -20,10 +21,13 @@ class RDPMITMState: State object for the RDP MITM. This is for data that needs to be shared across components. """ - def __init__(self): + def __init__(self, config: MITMConfig): self.requestedProtocols: Optional[NegotiationProtocols] = None """The original request protocols""" + self.config = config + """The MITM configuration.""" + self.useTLS = False """Whether the connection uses TLS or not""" @@ -93,5 +97,6 @@ def createFastPathLayer(self, mode: ParserMode) -> FastPathLayer: :param mode: the mode of the layer (client or server) """ - parser = createFastPathParser(self.useTLS, self.securitySettings.encryptionMethod, self.crypters[mode], mode) - return FastPathLayer(parser) \ No newline at end of file + parser = createFastPathParser( + self.useTLS, self.securitySettings.encryptionMethod, self.crypters[mode], mode) + return FastPathLayer(parser) From 537ad473be70af89126cab872ae06f8f0e3f142e Mon Sep 17 00:00:00 2001 From: Alexandre Beaulieu Date: Fri, 13 Mar 2020 11:15:21 -0400 Subject: [PATCH 2/5] doc: fixed some typos in README. --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 480248d83..546dbf818 100644 --- a/README.md +++ b/README.md @@ -309,26 +309,26 @@ Run `pyrdp-mitm.py --help` for a full list of arguments. ##### `--no-downgrade` This argument is useful when running PyRDP in Honeypot scenarios to avoid scanner fingerprinting. -When the switch is enabled, PyRDP will not downgrade unsupported extensions and let them the traffic through -transparently. The player will likely not be able to successfully replay video traffic, but the following supported -channels should still be accessible: +When the switch is enabled, PyRDP will not downgrade unsupported extensions and let the traffic through +transparently. The player will likely not be able to successfully replay video traffic, but the following +supported channels should still be accessible: - Keystroke recording - Mouse position updates - Clipboard access (passively) - Drive access (passively) -This feature is still a work in progress and some downgrades are currently unavoidable to allow the connection -to be established. The following downgrades are currently not affected by this switch and will still happen: +This feature is still a work in progress and some downgrading is currently unavoidable to allow the connection +to be established. The following are currently not affected by this switch and will still be disabled: - FIPS Encryption - Non-TLS encryption protocols - ClientInfo compression - Virtual Channel compression -If being able to eventually replay the full session is important, a good solution is to record the raw RDP traffic -using Wireshark and keep the TLS master secrets. Whenever PyRDP adds support for additional extensions, it would -then become possible to extract a valid RDP replay file from the raw network capture. +**NOTE**: If being able to eventually replay the full session is important, a good solution is to record the raw +RDP traffic using Wireshark and keep the TLS master secrets. Whenever PyRDP adds support for additional extensions, +it would then become possible to extract a valid RDP replay file from the raw network capture. ### Using the PyRDP Player Use `pyrdp-player.py` to run the player. From c2ec5ea9db5fa0522be98378cceae9521aac7128 Mon Sep 17 00:00:00 2001 From: Alexandre Beaulieu Date: Fri, 13 Mar 2020 15:29:41 -0400 Subject: [PATCH 3/5] fix: Flipped downgrade logic conditions. --- pyrdp/mitm/MCSMITM.py | 2 +- pyrdp/mitm/SlowPathMITM.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrdp/mitm/MCSMITM.py b/pyrdp/mitm/MCSMITM.py index 902e2b334..a68b01485 100644 --- a/pyrdp/mitm/MCSMITM.py +++ b/pyrdp/mitm/MCSMITM.py @@ -88,7 +88,7 @@ def onConnectInitial(self, pdu: MCSConnectInitialPDU): rdpClientDataPDU.securityData.encryptionMethods &= ~EncryptionMethod.ENCRYPTION_FIPS rdpClientDataPDU.securityData.extEncryptionMethods &= ~EncryptionMethod.ENCRYPTION_FIPS - if not self.state.config.downgrade: + if self.state.config.downgrade: # This disables the support for the Graphics pipeline extension, which is a completely different way to # transfer graphics from server to client. https://msdn.microsoft.com/en-us/library/dn366933.aspx rdpClientDataPDU.coreData.earlyCapabilityFlags &= ~ClientCapabilityFlag.RNS_UD_CS_SUPPORT_DYNVC_GFX_PROTOCOL diff --git a/pyrdp/mitm/SlowPathMITM.py b/pyrdp/mitm/SlowPathMITM.py index d2a32bf33..2d35d0344 100644 --- a/pyrdp/mitm/SlowPathMITM.py +++ b/pyrdp/mitm/SlowPathMITM.py @@ -63,7 +63,7 @@ def onConfirmActive(self, pdu: ConfirmActivePDU): if CapabilityType.CAPSTYPE_VIRTUALCHANNEL in pdu.parsedCapabilitySets: pdu.parsedCapabilitySets[CapabilityType.CAPSTYPE_VIRTUALCHANNEL].flags = VirtualChannelCompressionFlag.VCCAPS_NO_COMPR - if not self.state.config.downgrade: + if self.state.config.downgrade: # Force RDP server to send bitmap events instead of order events. pdu.parsedCapabilitySets[CapabilityType.CAPSTYPE_ORDER].orderFlags = OrderFlag.NEGOTIATEORDERSUPPORT | OrderFlag.ZEROBOUNDSDELTASSUPPORT pdu.parsedCapabilitySets[CapabilityType.CAPSTYPE_ORDER].orderSupport = b"\x00" * 32 From 584352b3470d051502c569ee36e20a409f6154c0 Mon Sep 17 00:00:00 2001 From: Alexandre Beaulieu Date: Mon, 16 Mar 2020 11:56:28 -0400 Subject: [PATCH 4/5] doc: Updated CHANGELOG Co-Authored-By: Olivier Bilodeau --- CHANGELOG.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 4e8b73187..13bc6fb16 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -17,7 +17,7 @@ For a detailed view of what has changed, refer to the {uri-repo}/commits/master[ * Default Docker Compose command now `pyrdp-mitm -h` to avoid confusing crash on `docker-compose up` ({uri-issue}173[#173]) * Documentation updates and fixes ({uri-issue}165[#165], {uri-issue}166[#166], {uri-issue}172[#172]) * Added `--disable-active-clipboard` switch to prevent clipboard request injection -* Added `--no-downgrade` switch to prevent protocol downgrading where possible. +* Added `--no-downgrade` switch to prevent protocol downgrading where possible {uri-issue}189[#189] === Bug fixes From 257051141b8e3e48b40882e3ea8775308928ecc4 Mon Sep 17 00:00:00 2001 From: Alexandre Beaulieu Date: Mon, 16 Mar 2020 12:06:24 -0400 Subject: [PATCH 5/5] doc: Updated ToC. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 546dbf818..2a8e14671 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ In August 2019, PyRDP was demo'ed at BlackHat Arsenal ([slides](https://docs.goo - [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) + - [--no-downgrade](#--no-downgrade) * [Using the PyRDP Player](#using-the-pyrdp-player) + [Playing a replay file](#playing-a-replay-file) + [Listening for live connections](#listening-for-live-connections)