Skip to content

Commit

Permalink
Aligned diverged branch with master
Browse files Browse the repository at this point in the history
  • Loading branch information
obilodeau committed Aug 24, 2021
2 parents f50a4a8 + c134518 commit fa5f437
Show file tree
Hide file tree
Showing 6 changed files with 213 additions and 21 deletions.
4 changes: 2 additions & 2 deletions pyrdp/layer/rdp/virtual_channel/dynamic_channel.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#
# This file is part of the PyRDP project.
# Copyright (C) 2018 GoSecure Inc.
# Copyright (C) 2018, 2020 GoSecure Inc.
# Licensed under the GPLv3 or later.
#

Expand All @@ -13,5 +13,5 @@ class DynamicChannelLayer(Layer):
Layer to receive and send DynamicChannel channel (drdynvc) packets.
"""

def __init__(self, parser = DynamicChannelParser()):
def __init__(self, parser: DynamicChannelParser):
super().__init__(parser)
11 changes: 10 additions & 1 deletion pyrdp/logging/StatCounter.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#
# This file is part of the PyRDP project.
# Copyright (C) 2019 GoSecure Inc.
# Copyright (C) 2019-2020 GoSecure Inc.
# Licensed under the GPLv3 or later.
#

Expand Down Expand Up @@ -111,6 +111,15 @@ class STAT:
CLIPBOARD_PASTE = "clipboardPastes"
# Number of times data has been pasted by either end

DYNAMIC_CHANNEL = "dynamicChannel"
# Number of Dynamic Virtual Channel PDUs coming from either end

DYNAMIC_CHANNEL_CLIENT = "dynamicChannelClient"
# Number of Dynamic Virtual Channel PDUs coming from the client

DYNAMIC_CHANNEL_SERVER = "dynamicChannelServer"
# Number of Dynamic Virtual Channel PDUs coming from the server


class StatCounter:
"""
Expand Down
75 changes: 75 additions & 0 deletions pyrdp/mitm/DynamicChannelMITM.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#
# This file is part of the PyRDP project.
# Copyright (C) 2020 GoSecure Inc.
# Licensed under the GPLv3 or later.
#
import binascii
from logging import LoggerAdapter
from typing import Dict

from pyrdp.core import Subject
from pyrdp.layer.rdp.virtual_channel.dynamic_channel import DynamicChannelLayer
from pyrdp.logging.StatCounter import STAT, StatCounter
from pyrdp.mitm.state import RDPMITMState
from pyrdp.pdu.rdp.virtual_channel.dynamic_channel import CreateRequestPDU, DataPDU, \
DynamicChannelPDU


class DynamicChannelMITM(Subject):
"""
MITM component for the dynamic virtual channels (drdynvc).
"""

def __init__(self, client: DynamicChannelLayer, server: DynamicChannelLayer, log: LoggerAdapter,
statCounter: StatCounter, state: RDPMITMState):
"""
:param client: DynamicChannel layer for the client side
:param server: DynamicChannel layer for the server side
:param log: logger for this component
:param statCounter: Object to keep miscellaneous stats for the current connection
:param state: the state of the PyRDP MITM connection.
"""
super().__init__()

self.client = client
self.server = server
self.state = state
self.log = log
self.statCounter = statCounter

self.channels: Dict[int, str] = {}

self.client.createObserver(
onPDUReceived=self.onClientPDUReceived,
)

self.server.createObserver(
onPDUReceived=self.onServerPDUReceived,
)

def onClientPDUReceived(self, pdu: DynamicChannelPDU):
self.statCounter.increment(STAT.DYNAMIC_CHANNEL_CLIENT, STAT.DYNAMIC_CHANNEL)
self.handlePDU(pdu, self.server)

def onServerPDUReceived(self, pdu: DynamicChannelPDU):
self.statCounter.increment(STAT.DYNAMIC_CHANNEL_SERVER, STAT.DEVICE_REDIRECTION)
self.handlePDU(pdu, self.client)

def handlePDU(self, pdu: DynamicChannelPDU, destination: DynamicChannelLayer):
"""
Handle the logic for a PDU and send the PDU to its destination.
:param pdu: the PDU that was received
:param destination: the destination layer
"""
if isinstance(pdu, CreateRequestPDU):
self.channels[pdu.channelId] = pdu.channelName
self.log.info("Dynamic virtual channel creation received: ID: %(channelId)d Name: %(channelName)s", {"channelId": pdu.channelId, "channelName": pdu.channelName})
elif isinstance(pdu, DataPDU):
if pdu.channelId not in self.channels:
self.log.error("Received a data PDU in an unkown channel: %(channelId)s", {"channelId": pdu.channelId})
else:
self.log.debug("Data PDU for channel %(channelName)s: %(data)s", {"data": binascii.hexlify(pdu.payload), "channelName": self.channels[pdu.channelId]})
else:
self.log.debug("Dynamic Channel PDU received: %(dynVcPdu)s", {"dynVcPdu": pdu})

destination.sendPDU(pdu)
35 changes: 34 additions & 1 deletion pyrdp/mitm/RDPMITM.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
from pyrdp.enum import MCSChannelName, ParserMode, PlayerPDUType, ScanCode, SegmentationPDUType
from pyrdp.layer import ClipboardLayer, DeviceRedirectionLayer, LayerChainItem, RawLayer, \
VirtualChannelLayer
from pyrdp.layer.rdp.virtual_channel.dynamic_channel import DynamicChannelLayer
from pyrdp.layer.segmentation import SegmentationObserver
from pyrdp.logging import RC4LoggingObserver
from pyrdp.logging.StatCounter import StatCounter
from pyrdp.logging.adapters import SessionLogger
Expand All @@ -25,6 +27,7 @@
from pyrdp.mitm.AttackerMITM import AttackerMITM
from pyrdp.mitm.ClipboardMITM import ActiveClipboardStealer, PassiveClipboardStealer
from pyrdp.mitm.DeviceRedirectionMITM import DeviceRedirectionMITM
from pyrdp.mitm.DynamicChannelMITM import DynamicChannelMITM
from pyrdp.mitm.FastPathMITM import FastPathMITM
from pyrdp.mitm.FileCrawlerMITM import FileCrawlerMITM
from pyrdp.mitm.MCSMITM import MCSMITM
Expand All @@ -38,6 +41,7 @@
from pyrdp.mitm.config import MITMConfig
from pyrdp.mitm.layerset import RDPLayerSet
from pyrdp.mitm.state import RDPMITMState
from pyrdp.parser.rdp.virtual_channel.dynamic_channel import DynamicChannelParser
from pyrdp.recording import FileLayer, RecordingFastPathObserver, RecordingSlowPathObserver, \
Recorder
from pyrdp.security import NTLMSSPState
Expand Down Expand Up @@ -274,6 +278,8 @@ def buildChannel(self, client: MCSServerChannel, server: MCSClientChannel):
self.buildClipboardChannel(client, server)
elif self.state.channelMap[channelID] == MCSChannelName.DEVICE_REDIRECTION:
self.buildDeviceChannel(client, server)
elif self.state.channelMap[channelID] == MCSChannelName.DYNAMIC_CHANNEL:
self.buildDynamicChannel(client, server)
else:
self.buildVirtualChannel(client, server)

Expand Down Expand Up @@ -366,7 +372,10 @@ 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.statCounter, self.state, self.tcp)
deviceRedirection = DeviceRedirectionMITM(clientLayer, serverLayer,
self.getLog(MCSChannelName.DEVICE_REDIRECTION),
self.statCounter, self.state, self.tcp)

self.channelMITMs[client.channelID] = deviceRedirection

if self.config.enableCrawler:
Expand All @@ -375,6 +384,30 @@ def buildDeviceChannel(self, client: MCSServerChannel, server: MCSClientChannel)
if self.attacker:
self.attacker.setDeviceRedirectionComponent(deviceRedirection)

def buildDynamicChannel(self, client: MCSServerChannel, server: MCSClientChannel):
"""
Build the MITM component for the dynamic channel.
Ref: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedyc/0147004d-1542-43ab-9337-93338f218587
:param client: MCS channel for the client side
:param server: MCS channel for the server side
"""

clientSecurity = self.state.createSecurityLayer(ParserMode.SERVER, True)
clientVirtualChannel = VirtualChannelLayer(activateShowProtocolFlag=False)
clientLayer = DynamicChannelLayer(DynamicChannelParser(isClient=True))
serverSecurity = self.state.createSecurityLayer(ParserMode.CLIENT, True)
serverVirtualChannel = VirtualChannelLayer(activateShowProtocolFlag=False)
serverLayer = DynamicChannelLayer(DynamicChannelParser(isClient=False))

clientLayer.addObserver(LayerLogger(self.getClientLog(MCSChannelName.DYNAMIC_CHANNEL)))
serverLayer.addObserver(LayerLogger(self.getServerLog(MCSChannelName.DYNAMIC_CHANNEL)))

LayerChainItem.chain(client, clientSecurity, clientVirtualChannel, clientLayer)
LayerChainItem.chain(server, serverSecurity, serverVirtualChannel, serverLayer)

dynamicChannelMITM = DynamicChannelMITM(clientLayer, serverLayer, self.getLog(MCSChannelName.DYNAMIC_CHANNEL), self.statCounter, self.state)
self.channelMITMs[client.channelID] = dynamicChannelMITM

def buildVirtualChannel(self, client: MCSServerChannel, server: MCSClientChannel):
"""
Build a generic MITM component for any virtual channel.
Expand Down
97 changes: 81 additions & 16 deletions pyrdp/parser/rdp/virtual_channel/dynamic_channel.py
Original file line number Diff line number Diff line change
@@ -1,54 +1,119 @@
#
# This file is part of the PyRDP project.
# Copyright (C) 2018, 2021 GoSecure Inc.
# Copyright (C) 2018-2021 GoSecure Inc.
# Licensed under the GPLv3 or later.
#

from io import BytesIO

from pyrdp.core import Uint16LE, Uint32LE, Uint8
from pyrdp.core import Uint16LE, Uint8
from pyrdp.enum.virtual_channel.dynamic_channel import CbId, DynamicChannelCommand
from pyrdp.parser import Parser
from pyrdp.pdu import PDU
from pyrdp.pdu.rdp.virtual_channel.dynamic_channel import CreateRequestPDU, CreateResponsePDU, DynamicChannelPDU
from pyrdp.pdu.rdp.virtual_channel.dynamic_channel import CreateRequestPDU, DataPDU, \
DynamicChannelPDU


class DynamicChannelParser(Parser):
"""
Parser for the dynamic channel (drdynvc) packets.
"""

def __init__(self):
def __init__(self, isClient):
super().__init__()
self.isClient = isClient

if self.isClient:
# Parsers and writers unique for client

self.parsers = {

}

self.writers = {
DynamicChannelCommand.CREATE: self.writeCreateRequest
}
else:
# Parsers and writers unique for server

self.parsers = {
DynamicChannelCommand.CREATE: self.parseCreateRequest
}

self.writers = {

}

# Parsers and writers for both client and server

self.parsers.update({
DynamicChannelCommand.DATA: self.parseData
})

self.writers.update({
DynamicChannelCommand.DATA: self.writeData
})

def doParse(self, data: bytes) -> PDU:
stream = BytesIO(data)
header = Uint8.unpack(stream)
cbid = (header & 0b00000011)
sp = (header & 0b00001100) >> 2
cmd = (header & 0b11110000) >> 4
pdu = DynamicChannelPDU(cbid, sp, cmd, stream.read())
if cmd in self.parsers:
return self.parsers[cmd](pdu)
else:
return pdu

if cmd == DynamicChannelCommand.CREATE:
channelId = self.readChannelId(stream, cbid)
channelName = ""
def parseCreateRequest(self, pdu: DynamicChannelPDU) -> CreateRequestPDU:
"""
https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedyc/4448ba4d-9a72-429f-8b65-6f4ec44f2985
:param pdu: The PDU with the payload to decode.
"""
stream = BytesIO(pdu.payload)
channelId = self.readChannelId(stream, pdu.cbid)
channelName = ""
char = stream.read(1).decode()
while char != "\x00":
channelName += char
char = stream.read(1).decode()
while char != "\x00":
channelName += char
char = stream.read(1).decode()
return CreateRequestPDU(cbid, sp, channelId, channelName)
return DynamicChannelPDU(cbid, sp, cmd, stream.read())
return CreateRequestPDU(pdu.cbid, pdu.sp, channelId, channelName)

def writeCreateRequest(self, pdu: CreateRequestPDU, stream: BytesIO):
"""
https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedyc/4448ba4d-9a72-429f-8b65-6f4ec44f2985
"""
self.writeChannelId(stream, pdu.cbid, pdu.channelId)
stream.write(pdu.channelName.encode() + b"\x00")

def parseData(self, pdu: DynamicChannelPDU) -> DataPDU:
"""
https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedyc/15b59886-db44-47f1-8da3-47c8fcd82803
"""
stream = BytesIO(pdu.payload)
channelId = self.readChannelId(stream, pdu.cbid)
data = stream.read()
return DataPDU(pdu.cbid, pdu.sp, channelId, payload=data)

def writeData(self, pdu: DataPDU, stream: BytesIO):
"""
https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedyc/15b59886-db44-47f1-8da3-47c8fcd82803
"""
self.writeChannelId(stream, pdu.cbid, pdu.channelId)
stream.write(pdu.payload)

def write(self, pdu: DynamicChannelPDU) -> bytes:
stream = BytesIO()
header = pdu.cbid
header |= pdu.sp << 2
header |= pdu.cmd << 4
Uint8.pack(header, stream)
if isinstance(pdu, CreateResponsePDU):
self.writeChannelId(stream, pdu.cbid, pdu.channelId)
Uint32LE.pack(pdu.creationStatus, stream)
if pdu.cmd in self.writers:
self.writers[pdu.cmd](pdu, stream)
else:
raise NotImplementedError()
stream.write(pdu.payload)

return stream.getvalue()

def readChannelId(self, stream: BytesIO, cbid: int):
Expand Down
12 changes: 11 additions & 1 deletion pyrdp/pdu/rdp/virtual_channel/dynamic_channel.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#
# This file is part of the PyRDP project.
# Copyright (C) 2018 GoSecure Inc.
# Copyright (C) 2018-2020 GoSecure Inc.
# Licensed under the GPLv3 or later.
#

Expand Down Expand Up @@ -41,3 +41,13 @@ def __init__(self, cbid, sp, channelId: int, creationStatus: int):
super().__init__(cbid, sp, DynamicChannelCommand.CREATE)
self.channelId = channelId
self.creationStatus = creationStatus


class DataPDU(DynamicChannelPDU):
"""
https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedyc/15b59886-db44-47f1-8da3-47c8fcd82803
"""

def __init__(self, cbid, sp, channelId: int, payload: bytes):
super().__init__(cbid, sp, DynamicChannelCommand.DATA, payload=payload)
self.channelId = channelId

0 comments on commit fa5f437

Please sign in to comment.