finish proxy

This commit is contained in:
speyrefitte
2014-07-25 18:15:42 +02:00
parent 9abd493e4a
commit 3f854bd18b
10 changed files with 311 additions and 72 deletions

View File

@@ -19,7 +19,7 @@
#
"""
RDP proxy recorder and spyer
RDP proxy recorder and spy function
Proxy RDP protocol
"""
@@ -29,8 +29,10 @@ sys.path.insert(1, os.path.join(sys.path[0], '..'))
from rdpy.base import log, error
from rdpy.protocol.rdp import rdp
from rdpy.network.proxy import IProxyClient
from twisted.internet import reactor
clientMacro = None
from PyQt4 import QtCore, QtGui
class ProxyServer(rdp.RDPServerObserver):
"""
Server side of proxy
@@ -49,10 +51,8 @@ class ProxyServer(rdp.RDPServerObserver):
Event throw by client when it's ready
@param client: ProxyClient
"""
global clientMacro
self._client = client
clientMacro = client
self._controller.setColorDepth(self._client._controller.getColorDepth())
self._controller.setColorDepth(self._client.getColorDepth())
def onReady(self):
"""
@@ -62,18 +62,18 @@ class ProxyServer(rdp.RDPServerObserver):
domain, username, password = self._controller.getCredentials()
if self._credentialProvider.isAdmin(domain, username, password):
self.clientConnected(ProxyClient(clientMacro._controller, self))
self.clientConnected(AdminGUI(self))
return
try:
ip, port, domain, username, password = self._credentialProvider.getCredentials(domain, username, password)
dstIp, dstPort, dstDomain, dstUsername, dstPassword = self._credentialProvider.getCredentials(domain, username, password)
except error.InvalidExpectedDataException as e:
log.info(e.message)
self._controller.close()
#self._controller.close()
return
width, height = self._controller.getScreen()
reactor.connectTCP(ip, port, ProxyClientFactory(self, width, height))
reactor.connectTCP(dstIp, dstPort, ProxyClientFactory(self, width, height, dstDomain, dstUsername, dstPassword, "%s/%s -> %s %s/%s"%(domain, username, dstIp, dstDomain, dstUsername)))
def onKeyEventScancode(self, code, isPressed):
"""
@@ -85,7 +85,7 @@ class ProxyServer(rdp.RDPServerObserver):
if self._client is None:
return
self._client._controller.sendKeyEventScancode(code, isPressed)
self._client.sendKeyEventScancode(code, isPressed)
def onKeyEventUnicode(self, code, isPressed):
"""
@@ -97,7 +97,7 @@ class ProxyServer(rdp.RDPServerObserver):
if self._client is None:
return
self._client._controller.sendKeyEventUnicode(code, isPressed)
self._client.sendKeyEventUnicode(code, isPressed)
def onPointerEvent(self, x, y, button, isPressed):
"""
@@ -111,9 +111,9 @@ class ProxyServer(rdp.RDPServerObserver):
if self._client is None:
return
self._client._controller.sendPointerEvent(x, y, button, isPressed)
self._client.sendPointerEvent(x, y, button, isPressed)
class ProxyClient(rdp.RDPClientObserver):
class ProxyClient(rdp.RDPClientObserver, IProxyClient):
"""
Client side of proxy
"""
@@ -121,10 +121,34 @@ class ProxyClient(rdp.RDPClientObserver):
"""
@param controller: RDPClientObserver
@param server: ProxyServer
@param name: name of session
"""
rdp.RDPClientObserver.__init__(self, controller)
self._server = server
def getColorDepth(self):
"""
@return: color depth of session (15,16,24,32)
"""
return self._controller.getColorDepth()
def sendKeyEventScancode(self, code, isPressed):
"""
Send key event with scan code
@param code: scan code
@param isPressed: True if keyboard key is pressed
"""
self._controller.sendKeyEventScancode(code, isPressed)
def sendPointerEvent(self, x, y, button, isPressed):
"""
Send Pointer event
@param x: x position
@param y: y position
@param button: mouse button 1|2|3
"""
self._controller.sendPointerEvent(x, y, button, isPressed)
def onReady(self):
"""
Event use to signal that RDP stack is ready
@@ -150,12 +174,15 @@ class ProxyClient(rdp.RDPClientObserver):
self._server._controller.sendUpdate(destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data)
class ProxyServerFactory(rdp.ServerFactory):
"""
Factory on listening events
"""
def __init__(self, credentialProvider):
"""
@param config: rdp-proxy configuration
@param credentialProvider: CredentialProvider
"""
rdp.ServerFactory.__init__(self, "/home/sylvain/dev/certificate/rdpy.key", "/home/sylvain/dev/certificate/rdpy.crt", 16)
rdp.ServerFactory.__init__(self, "/home/speyrefitte/dev/certificate/rdpy.key", "/home/speyrefitte/dev/certificate/rdpy.crt", 16)
self._credentialProvider = credentialProvider
def buildObserver(self, controller):
@@ -175,27 +202,144 @@ class ProxyServerFactory(rdp.ServerFactory):
pass
class ProxyClientFactory(rdp.ClientFactory):
def __init__(self, server, width, height):
"""
Factory for proxy client
"""
_CLIENT_PROXY_ = {}
def __init__(self, server, width, height, domain, username, password, name):
"""
@param server: ProxyServer
@param width: screen width
@param height: screen height
@param domain: domain session
@param username: username session
@param password: password session
"""
self._server = server
self._width = width
self._height = height
self._domain = domain
self._username = username
self._password = password
self._name = name
def buildObserver(self, controller):
"""
Implement rdp.ClientFactory
Build observer (ProxyClient)
@param controller: rdp.RDPClientController
"""
#set screen resolution
controller.setScreen(self._width, self._height)
return ProxyClient(controller, self._server)
#set credential
controller.setDomain(self._domain)
controller.setUsername(self._username)
controller.setPassword(self._password)
proxy = ProxyClient(controller, self._server)
ProxyClientFactory._CLIENT_PROXY_[self._name] = proxy
return proxy
def startedConnecting(self, connector):
pass
def clientConnectionLost(self, connector, reason):
pass
if ProxyClientFactory._CLIENT_PROXY_.has_key(self._name):
del ProxyClientFactory._CLIENT_PROXY_[self._name]
def clientConnectionFailed(self, connector, reason):
if ProxyClientFactory._CLIENT_PROXY_.has_key(self._name):
del ProxyClientFactory._CLIENT_PROXY_[self._name]
class CredentialProvider(object):
"""
Credential provider for proxy
"""
def __init__(self, config):
"""
@param config: rdp proxy config
"""
self._config = config
def getCredentials(self, domain, username, password):
"""
@param condig: rdp config
@param domain: domain to check
@param username: username in domain
@param password: password in domain
@return: (ip, port, domain, username, password) or None if error
"""
if not self._config['domain'].has_key(domain):
raise error.InvalidExpectedDataException("Unknown domain %s"%(domain))
for user in self._config['domain'][domain]:
if user['username'] == username and user['password'] == password:
return str(user['credentials']['ip']), user['credentials']['port'], str(user['credentials']['domain']), str(user['credentials']['username']), str(user['credentials']['password'])
raise error.InvalidExpectedDataException("Unknown credential %s\%s"%(domain, username))
def isAdmin(self, domain, username, password):
"""
@return: True if credential match admin credential
"""
return self._config['admin']['domain'] == domain and self._config['admin']['username'] == username and self._config['admin']['password'] == password
class AdminGUI(IProxyClient):
"""
Simple Administration GUI
"""
def __init__(self, server):
"""
@param server: ProxyServer
@param colorDepth: color depth use to generate screen
"""
self._server = server
self._colorDepth = self._server._controller.getColorDepth()
self._activeSessions = [ (x, y) for x, y in ProxyClientFactory._CLIENT_PROXY_.iteritems() ]
self._current = 0
self.display()
def getColorDepth(self):
return self._colorDepth
def sendKeyEventScancode(self, code, isPressed):
#enter key
if code == 28:
clientController = self._activeSessions[self._current][1]._controller
self._server.clientConnected(ProxyClient(clientController, self._server))
#force refresh for new admin screen
width, height = self._server._controller.getScreen()
clientController.sendRefreshOrder(0, 0, width, height)
def sendPointerEvent(self, x, y, button, isPressed):
pass
def display(self):
"""
Draw GUI that list active session
"""
i = 0
for name, session in self._activeSessions:
screen = QtGui.QImage(200, 20, QtGui.QImage.Format_RGB16)
if i == self._current:
screen.fill(QtCore.Qt.darkGray)
else:
screen.fill(QtCore.Qt.black)
with QtGui.QPainter(screen) as qp:
if i == self._current:
qp.setPen(QtCore.Qt.black)
else:
qp.setPen(QtCore.Qt.gray)
qp.setFont(QtGui.QFont('arial', 12))
qp.drawText(screen.rect(), QtCore.Qt.AlignCenter, name)
ptr = screen.bits()
ptr.setsize(screen.byteCount())
self._server._controller.sendUpdate(0, i*25, 199, 19, 200, 20, self._colorDepth, False, ptr.asstring())
i+= 1
def help():
"""
Print help in consoe
Print help in console
"""
print "Usage: rdpy-rdpproxy -f config_file_path listen_port"
@@ -223,37 +367,6 @@ def loadConfig(configFilePath):
return config
class CredentialProvider(object):
"""
Credential provider for proxy
"""
def __init__(self, config):
"""
@param config: rdp proxy config
"""
self._config = config
def getCredentials(self, domain, username, password):
"""
@param condig: rdp config
@param domain: domain to check
@param username: username in domain
@param password: password in domain
@return: (ip, port, domain, username, password) or None if error
"""
if not self._config['domain'].has_key(domain):
raise error.InvalidExpectedDataException("Unknown domain %s"%(domain))
for user in self._config['domain'][domain]:
if user['username'] == username and user['password'] == password:
return user['credentials']['ip'], user['credentials']['port'], user['credentials']['domain'], user['credentials']['username'], user['credentials']['password']
raise error.InvalidExpectedDataException("Unknown credential %s\%s"%(domain, username))
def isAdmin(self, domain, username, password):
"""
@return: True if credential match admin credential
"""
return self._config['admin']['domain'] == domain and self._config['admin']['username'] == username and self._config['admin']['password'] == password
if __name__ == '__main__':
configFilePath = None
try:
@@ -278,5 +391,8 @@ if __name__ == '__main__':
log.error("Bad configuration file")
sys.exit()
#use to init font
app = QtGui.QApplication(sys.argv)
reactor.listenTCP(int(args[0]), ProxyServerFactory(CredentialProvider(config)))
reactor.run()

View File

@@ -25,7 +25,7 @@ RDPY use Layer Protocol design (like twisted)
from rdpy.base.error import CallPureVirtualFuntion
class StreamListener(object):
class IStreamListener(object):
"""
Interface use to inform that we can handle receive stream
"""
@@ -36,9 +36,9 @@ class StreamListener(object):
default is to pass data to presentation layer
@param s: raw Stream receive from transport layer
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "recv", "StreamListener"))
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "recv", "IStreamListener"))
class StreamSender(object):
class IStreamSender(object):
"""
Interface use to show stream sender capability
"""
@@ -47,7 +47,7 @@ class StreamSender(object):
Send Stream on layer
@param data: Type or tuple element handle by transport layer
'''
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "send", "StreamSender"))
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "send", "IStreamSender"))
class Layer(object):
"""
@@ -82,7 +82,7 @@ class Layer(object):
if not self._transport is None:
self._transport.close()
class LayerAutomata(Layer, StreamListener):
class LayerAutomata(Layer, IStreamListener):
"""
Layer with automata state
we can set next recv function used for Stream packet
@@ -111,7 +111,7 @@ from twisted.internet import protocol
#first that handle stream
from type import Stream
class RawLayer(protocol.Protocol, LayerAutomata, StreamSender):
class RawLayer(protocol.Protocol, LayerAutomata, IStreamSender):
"""
Inherit from protocol twisted class
allow this protocol to wait until expected size of packet

53
rdpy/network/proxy.py Normal file
View File

@@ -0,0 +1,53 @@
#
# Copyright (c) 2014 Sylvain Peyrefitte
#
# This file is part of rdpy.
#
# rdpy is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
"""
Proxy Interface
client -> ProxyServer | ProxyClient -> server
"""
from rdpy.base.error import CallPureVirtualFuntion
class IProxyClient(object):
"""
Proxy interface for client side
"""
def getColorDepth(self):
"""
@return: color depth of session (15,16,24,32)
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onUpdate", "IProxyClient"))
def sendKeyEventScancode(self, code, isPressed):
"""
Send key event with scan code
@param code: scan code
@param isPressed: True if keyboard key is pressed
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onUpdate", "IProxyClient"))
def sendPointerEvent(self, x, y, button, isPressed):
"""
Send Pointer event
@param x: x position
@param y: y position
@param button: mouse button 1|2|3
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onUpdate", "IProxyClient"))

View File

@@ -24,7 +24,7 @@ Each channel have a particular role.
The main channel is the graphical channel.
It exist channel for file system order, audio channel, clipboard etc...
"""
from rdpy.network.layer import LayerAutomata, StreamSender, Layer
from rdpy.network.layer import LayerAutomata, IStreamSender, Layer
from rdpy.network.type import sizeof, Stream, UInt8, UInt16Le, String
from rdpy.base.error import InvalidExpectedDataException, InvalidValue, InvalidSize
from rdpy.protocol.rdp.ber import writeLength
@@ -65,7 +65,7 @@ class MCSLayer(LayerAutomata):
the main layer of RDP protocol
is why he can do everything and more!
"""
class MCSProxySender(Layer, StreamSender):
class MCSProxySender(Layer, IStreamSender):
"""
Proxy use to set as transport layer for upper channel
use to abstract channel id for presentation layer

View File

@@ -260,6 +260,14 @@ class FastPathOutputCompression(object):
"""
FASTPATH_OUTPUT_COMPRESSION_USED = 0x2
class Display(object):
"""
Use in supress output PDU
@see: http://msdn.microsoft.com/en-us/library/cc240648.aspx
"""
SUPPRESS_DISPLAY_UPDATES = 0x00
ALLOW_DISPLAY_UPDATES = 0x01
class ErrorInfo(object):
"""
Error code use in Error info PDU
@@ -638,7 +646,7 @@ class DataPDU(CompositeType):
"""
Create object in accordance self.shareDataHeader.pduType2 value
"""
for c in [UpdateDataPDU, SynchronizeDataPDU, ControlDataPDU, ErrorInfoDataPDU, FontListDataPDU, FontMapDataPDU, PersistentListPDU, ClientInputEventPDU, ShutdownDeniedPDU, ShutdownRequestPDU]:
for c in [UpdateDataPDU, SynchronizeDataPDU, ControlDataPDU, ErrorInfoDataPDU, FontListDataPDU, FontMapDataPDU, PersistentListPDU, ClientInputEventPDU, ShutdownDeniedPDU, ShutdownRequestPDU, SuppressOutputDataPDU]:
if self.shareDataHeader.pduType2.value == c._PDUTYPE2_:
return c()
log.debug("unknown PDU data type : %s"%hex(self.shareDataHeader.pduType2.value))
@@ -799,6 +807,41 @@ class ShutdownDeniedPDU(CompositeType):
def __init__(self):
CompositeType.__init__(self)
class InclusiveRectangle(CompositeType):
"""
@see: http://msdn.microsoft.com/en-us/library/cc240643.aspx
"""
def __init__(self, conditional = lambda:True):
CompositeType.__init__(self)
self.left = UInt16Le()
self.top = UInt16Le()
self.right = UInt16Le()
self.bottom = UInt16Le()
class SuppressOutputDataPDU(CompositeType):
"""
@see: http://msdn.microsoft.com/en-us/library/cc240648.aspx
"""
_PDUTYPE2_ = PDUType2.PDUTYPE2_SUPPRESS_OUTPUT
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.allowDisplayUpdates = UInt8()
self.pad3Octets = (UInt8(), UInt8(), UInt8())
self.desktopRect = InclusiveRectangle(conditional = lambda:(self.allowDisplayUpdates.value == Display.ALLOW_DISPLAY_UPDATES))
class RefreshRectPDU(CompositeType):
"""
@see: http://msdn.microsoft.com/en-us/library/cc240646.aspx
"""
_PDUTYPE2_ = PDUType2.PDUTYPE2_REFRESH_RECT
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.numberOfAreas = UInt8(lambda:len(self.areasToRefresh._array))
self.pad3Octets = (UInt8(), UInt8(), UInt8())
self.areasToRefresh = ArrayType(InclusiveRectangle, readLen = self.numberOfAreas)
class UpdateDataPDU(CompositeType):
"""
Update data PDU use by server to inform update image or palet

View File

@@ -124,7 +124,7 @@ class PDULayer(LayerAutomata):
"""
self.sendPDU(data.DataPDU(pduData, self._shareId))
class Client(PDULayer, tpkt.FastPathListener):
class Client(PDULayer, tpkt.IFastPathListener):
"""
Client automata of PDU layer
"""
@@ -159,7 +159,7 @@ class Client(PDULayer, tpkt.FastPathListener):
def setFastPathSender(self, fastPathSender):
"""
@param fastPathSender: tpkt.FastPathSender
@note: implement tpkt.FastPathListener
@note: implement tpkt.IFastPathListener
"""
self._fastPathSender = fastPathSender
@@ -301,7 +301,7 @@ class Client(PDULayer, tpkt.FastPathListener):
def recvFastPath(self, fastPathS):
"""
Implement FastPathListener interface
Implement IFastPathListener interface
Fast path is needed by RDP 8.0
@param fastPathS: Stream that contain fast path data
"""
@@ -410,7 +410,7 @@ class Client(PDULayer, tpkt.FastPathListener):
pdu.slowPathInputEvents._array = [data.SlowPathInputEvent(x) for x in pointerEvents]
self.sendDataPDU(pdu)
class Server(PDULayer, tpkt.FastPathListener):
class Server(PDULayer, tpkt.IFastPathListener):
"""
Server Automata of PDU layer
"""
@@ -433,7 +433,7 @@ class Server(PDULayer, tpkt.FastPathListener):
def setFastPathSender(self, fastPathSender):
"""
@param fastPathSender: tpkt.FastPathSender
@note: implement tpkt.FastPathListener
@note: implement tpkt.IFastPathListener
"""
self._fastPathSender = fastPathSender
@@ -577,7 +577,7 @@ class Server(PDULayer, tpkt.FastPathListener):
def recvFastPath(self, fastPathS):
"""
Implement FastPathListener interface
Implement IFastPathListener interface
Fast path is needed by RDP 8.0
@param fastPathS: Stream that contain fast path data
"""

View File

@@ -213,6 +213,23 @@ class RDPClientController(pdu.layer.PDUClientListener):
except InvalidValue:
log.info("try send bad key event")
def sendRefreshOrder(self, left, top, right, bottom):
"""
Force server to resend a particular zone
@param left: left coordinate
@param top: top coordinate
@param right: right coordinate
@param bottom: bottom coordinate
"""
refreshPDU = pdu.data.RefreshRectPDU()
rect = pdu.data.InclusiveRectangle()
rect.left.value = left
rect.top.value = top
rect.right.value = right
rect.bottom.value = bottom
refreshPDU.areasToRefresh._array.append(rect)
self._pduLayer.sendDataPDU(refreshPDU)
def close(self):
"""
Close protocol stack
@@ -279,6 +296,12 @@ class RDPServerController(pdu.layer.PDUServerListener):
"""
return (self.getDomain(), self.getUsername(), self.getPassword())
def getColorDepth(self):
"""
@return: color depth define by server
"""
return self._colorDepth
def getScreen(self):
"""
@return: tuple(width, height) of client asked screen
@@ -299,6 +322,9 @@ class RDPServerController(pdu.layer.PDUServerListener):
if PDU stack is already connected send a deactive-reactive sequence
@param colorDepth: depth of session (15, 16, 24)
"""
self._colorDepth = colorDepth
if self._pduLayer._serverCapabilities[pdu.caps.CapsType.CAPSTYPE_BITMAP].capability.preferredBitsPerPixel.value == colorDepth:
return
self._pduLayer._serverCapabilities[pdu.caps.CapsType.CAPSTYPE_BITMAP].capability.preferredBitsPerPixel.value = colorDepth
if self._isReady:
#restart connection sequence

View File

@@ -24,7 +24,7 @@ This layer have main goal to negociate SSL transport
RDP basic security is not supported by RDPY (because is not a true security layer...)
"""
from rdpy.network.layer import LayerAutomata, StreamSender
from rdpy.network.layer import LayerAutomata, IStreamSender
from rdpy.network.type import UInt8, UInt16Le, UInt16Be, UInt32Le, CompositeType, sizeof
from rdpy.base.error import InvalidExpectedDataException
@@ -107,7 +107,7 @@ class Negotiation(CompositeType):
self.selectedProtocol = UInt32Le(conditional = lambda: (self.code.value != NegociationType.TYPE_RDP_NEG_FAILURE))
self.failureCode = UInt32Le(conditional = lambda: (self.code.value == NegociationType.TYPE_RDP_NEG_FAILURE))
class TPDULayer(LayerAutomata, StreamSender):
class TPDULayer(LayerAutomata, IStreamSender):
"""
TPDU layer management
there is an connection automata

View File

@@ -34,7 +34,7 @@ class Action(object):
FASTPATH_ACTION_FASTPATH = 0x0
FASTPATH_ACTION_X224 = 0x3
class FastPathListener(object):
class IFastPathListener(object):
"""
Fast path packet listener
Usually PDU layer
@@ -48,11 +48,11 @@ class FastPathListener(object):
def setFastPathSender(self, fastPathSender):
"""
@param fastPathSender: FastPathSender
@param fastPathSender: IFastPathSender
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "setFastPathSender", "recvFastPath"))
class FastPathSender(object):
class IFastPathSender(object):
"""
Fast path send capability
"""
@@ -60,9 +60,9 @@ class FastPathSender(object):
"""
@param fastPathS: type transform to stream and send as fastpath
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "sendFastPath", "FastPathSender"))
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "sendFastPath", "IFastPathSender"))
class TPKT(RawLayer, FastPathSender):
class TPKT(RawLayer, IFastPathSender):
"""
TPKT layer in RDP protocol stack
This layer only handle size of packet and determine if is a fast path packet
@@ -70,7 +70,7 @@ class TPKT(RawLayer, FastPathSender):
def __init__(self, presentation, fastPathListener):
"""
@param presentation: presentation layer, in RDP case is TPDU layer
@param fastPathListener: FastPathListener
@param fastPathListener: IFastPathListener
"""
RawLayer.__init__(self, presentation)
#last packet version read from header

View File

@@ -299,6 +299,7 @@ class QRemoteDesktop(QtGui.QWidget):
#draw in widget
with QtGui.QPainter(self) as qp:
qp.drawImage(0, 0, self._buffer)
self._refresh = []
def mouseMoveEvent(self, event):