fix rdp stander security layer server side bug, fix lisense automata, ready for next release

This commit is contained in:
speyrefitte
2015-01-05 18:34:50 +01:00
parent f4808d0ae2
commit 7ff5c5caa3
16 changed files with 353 additions and 457 deletions

View File

@@ -7,7 +7,7 @@ before_install:
- sudo apt-get install python-qt4
- ln -s /usr/lib/python2.7/dist-packages/PyQt4/ $VIRTUAL_ENV/lib/python2.7/site-packages/
- ln -s /usr/lib/python2.7/dist-packages/sip.so $VIRTUAL_ENV/lib/python2.7/site-packages/
- pip install qt4reactor pyopenssl twisted service_identity
- pip install qt4reactor pyopenssl twisted service_identity rsa
install:
- python setup.py install

View File

@@ -21,14 +21,14 @@ sudo apt-get install python-qt4
x86 | x86_64
----|-------
[PyQt4 x86](http://sourceforge.net/projects/pyqt/files/PyQt4/PyQt-4.11.3/PyQt4-4.11.3-gpl-Py2.7-Qt4.8.6-x32.exe) | [PyQt4 x86_64](http://sourceforge.net/projects/pyqt/files/PyQt4/PyQt-4.11.3/PyQt4-4.11.3-gpl-Py2.7-Qt4.8.6-x64.exe/download)
[PyWin32 x86](http://sourceforge.net/projects/pywin32/files/pywin32/Build%20218/pywin32-218.win32-py2.7.exe/download) | [PyWin32 x86_64](http://sourceforge.net/projects/pywin32/files/pywin32/Build%20218/pywin32-218.win-amd64-py2.7.exe/download)
[PyQt4](http://sourceforge.net/projects/pyqt/files/PyQt4/PyQt-4.11.3/PyQt4-4.11.3-gpl-Py2.7-Qt4.8.6-x32.exe) | [PyQt4](http://sourceforge.net/projects/pyqt/files/PyQt4/PyQt-4.11.3/PyQt4-4.11.3-gpl-Py2.7-Qt4.8.6-x64.exe/download)
[PyWin32](http://sourceforge.net/projects/pywin32/files/pywin32/Build%20218/pywin32-218.win32-py2.7.exe/download) | [PyWin32](http://sourceforge.net/projects/pywin32/files/pywin32/Build%20218/pywin32-218.win-amd64-py2.7.exe/download)
### Build
```
$ git clone https://github.com/citronneur/rdpy.git rdpy
$ pip install twisted pyopenssl qt4reactor
$ pip install twisted pyopenssl qt4reactor service_identity rsa
$ python rdpy/setup.py install
```
@@ -81,36 +81,32 @@ $ rdpy-vncscreenshot.py [-p password] [-o output_file_path] XXX.XXX.XXX.XXX[:590
### rdpy-rdpproxy
rdpy-rdpproxy is a RDP proxy. It is used to manage and control access to the RDP servers as well as watch live sessions through any RDP client. It can be compared to a HTTP reverse proxy with added spy features.
rdpy-rdpproxy is a RDP proxy with shadow and record function.
```
$ rdpy-rdpproxy.py -f credentials_file_path -k private_key_file_path -c certificate_file_path [-i admin_ip[:admin_port]] listen_port
$ rdpy-rdpproxy.py -t target_ip[:target_port] [-k private_key_file_path] [-c certificate_file_path] [-i admin_ip[:admin_port]] listen_port
```
The credentials file is JSON file that must conform with the following format:
The target ip and port represent the target host.
The private key file and the certificate file are classic cryptographic files for SSL connections. The RDP protocol can negotiate its own security layer. The CredSSP security layer is planned for an upcoming release. If one of both parameters are omitted, the server use standard RDP as security layer.
The IP and port admin are used in order to shadow active sessions thanks to a RDP client (rdpy-rdpclient, remina, mstsc) set username parameter like name of session printed by proxy.
Exemple :
```
{
"domain1":
{
"username1":
[
{"ip":"machine1", "port":3389"},
{"ip":"machine2", "port":3389"}
],
"username2":
[
{"ip":"machine1", "port":3389"}
]
}
}
$ rdpy-rdpproxy.py -t [my_computer] -i 0.0.0.0:56654 3389
$ INFO : Shadow listener on 0.0.0.0:56654
$ INFO : **************************************************
$ INFO : Now connected
$ INFO : ['super-administrator']
$ INFO : **************************************************
```
In this exemple domain1\username1 can access to machine1 and machine2 and domain1\username2 can only access to machine1.
The private key file and the certificate file are classic cryptographic files for SSL connections. The RDP protocol can negotiate its own security layer but RDPY is limited to SSL. The CredSSP security layer is planned for an upcoming release. The basic RDP security layer is not supported (windows wp sp1&2).
The IP and port admin are used in order to spy active sessions thanks to a RDP client (rdpy-rdpclient, remina, mstsc). Common values are 127.0.0.1:3389 to protect from connections by unauthorized user.
To shadow 'super-administrator' session :
```
$ rdpy-rdpclient.py -u super-administrator 127.0.0.1:56654
```
## RDPY Qt Widget

View File

@@ -35,16 +35,17 @@ class RDPClientQtFactory(rdp.ClientFactory):
"""
@summary: Factory create a RDP GUI client
"""
def __init__(self, width, height, username, password, domain, fullscreen, keyboardLayout, optimized):
def __init__(self, width, height, username, password, domain, fullscreen, keyboardLayout, optimized, security):
"""
@param width: width of client
@param heigth: heigth of client
@param username: username present to the server
@param password: password present to the server
@param domain: microsoft domain
@param fullscreen: show widget in fullscreen mode
@param keyboardLayout: keyboard layout
@param optimized: enable optimized session orders
@param width: {integer} width of client
@param heigth: {integer} heigth of client
@param username: {str} username present to the server
@param password: {str} password present to the server
@param domain: {str} microsoft domain
@param fullscreen: {bool} show widget in fullscreen mode
@param keyboardLayout: {str} (fr|en) keyboard layout
@param optimized: {bool} enable optimized session orders
@param security: {str} (ssl | rdp | nego)
"""
self._width = width
self._height = height
@@ -54,8 +55,12 @@ class RDPClientQtFactory(rdp.ClientFactory):
self._fullscreen = fullscreen
self._keyboardLayout = keyboardLayout
self._optimized = optimized
self._nego = security == "nego"
if self._nego:
self._security = "ssl"
else:
self._security = security
self._w = None
self._basicRDPSecurity = False
def buildObserver(self, controller, addr):
"""
@@ -66,9 +71,9 @@ class RDPClientQtFactory(rdp.ClientFactory):
@return: RDPClientQt
"""
#create client observer
client = RDPClientQt(controller, self._width, self._height)
self._client = RDPClientQt(controller, self._width, self._height)
#create qt widget
self._w = client.getWidget()
self._w = self._client.getWidget()
self._w.setWindowTitle('rdpy-rdpclient')
if self._fullscreen:
self._w.showFullScreen()
@@ -82,14 +87,9 @@ class RDPClientQtFactory(rdp.ClientFactory):
controller.setHostname(socket.gethostname())
if self._optimized:
controller.setPerformanceSession()
controller.setSecurityLevel(self._security)
if self._basicRDPSecurity:
controller.setRDPBasicSecurity()
return client
def startedConnecting(self, connector):
pass
return self._client
def clientConnectionLost(self, connector, reason):
"""
@@ -98,8 +98,12 @@ class RDPClientQtFactory(rdp.ClientFactory):
@param reason: str use to advertise reason of lost connection
"""
#try reconnect with basic RDP security
if reason.type == RDPSecurityNegoFail and not self._basicRDPSecurity:
self._basicRDPSecurity = True
if reason.type == RDPSecurityNegoFail and self._nego:
#stop nego
log.info("due to security nego error back to standard RDP security layer")
self._nego = False
self._security = "rdp"
self._client._widget.hide()
connector.connect()
return
@@ -205,12 +209,9 @@ if __name__ == '__main__':
width = QtGui.QDesktopWidget().screenGeometry().width()
height = QtGui.QDesktopWidget().screenGeometry().height()
log.info("keyboard layout set to %s"%keyboardLayout)
from twisted.internet import reactor
reactor.connectTCP(ip, int(port), RDPClientQtFactory(width, height, username, password, domain, fullscreen, keyboardLayout, optimized))
reactor.connectTCP(ip, int(port), RDPClientQtFactory(width, height, username, password, domain, fullscreen, keyboardLayout, optimized, "nego"))
reactor.runReturn()
app.exec_()

View File

@@ -23,10 +23,10 @@ RDP proxy with spy capabilities
---------------------------
Client RDP -> | ProxyServer | ProxyClient | -> Server RDP
---------------------------
| ProxyAdmin |
------------
| ProxyShadow |
--------------
^
Admin ----------------------|
Shadow -------------------|
"""
import sys, os, getopt, json
@@ -37,77 +37,32 @@ from rdpy.ui import view
from twisted.internet import reactor
from PyQt4 import QtCore, QtGui
#log._LOG_LEVEL = log.Level.INFO
log._LOG_LEVEL = log.Level.INFO
class ProxyServer(rdp.RDPServerObserver):
"""
@summary: Server side of proxy
"""
def __init__(self, controller, credentialProvider):
_SESSIONS_ = {}
def __init__(self, controller, target):
"""
@param controller: RDPServerController
@param credentialProvider: CredentialProvider
@param controller: {RDPServerController}
@param target: {tuple(ip, port)}
"""
rdp.RDPServerObserver.__init__(self, controller)
self._credentialProvider = credentialProvider
self._target = target
self._client = None
self._window = None
def showSelectView(self, machines):
"""
@summary: Show select sever view to the client
@param machines: [(ip, port)]
"""
self._machines = machines
width, height = self._controller.getScreen()
self._window = view.Window(width, height, QtGui.QColor(8, 24, 66))
self._window.addView(view.Anchor(width / 2 - 250, 100,
view.Label("Please select following server",
500, 50, QtGui.QFont('arial', 18, QtGui.QFont.Bold),
backgroundColor = QtGui.QColor(8, 24, 66))))
self._window.addView(view.Anchor(width / 2 - 250, 150,
view.List(["%s:%s"%(ip, port) for ip, port in machines],
500, 500, self.onSelectMachine,
QtGui.QColor(8, 24, 66))), True)
self._window.update(view.RDPRenderer(self._controller), True)
def onSelectMachine(self, index):
"""
@summary: Callback of view.List in Select server view
@param: index in list
"""
ip, port = self._machines[index]
width, height = self._controller.getScreen()
domain, username, password = self._controller.getCredentials()
reactor.connectTCP(ip, port, ProxyClientFactory(self, width, height, domain, username, password))
def clientConnected(self, client):
"""
@summary: Event throw by client when it's ready
@param client: ProxyClient
@param client: {ProxyClient}
"""
self._client = client
#need to reevaluate color depth
self._controller.setColorDepth(self._client._controller.getColorDepth())
def showMessage(self, message):
"""
@summary: Print a message to the client
@param message: string
"""
width, height = self._controller.getScreen()
popup = view.Window(width, height, QtGui.QColor(8, 24, 66))
popup.addView(view.Anchor(width / 2 - 250, height / 2 - 25,
view.Label(message, 500, 50,
QtGui.QFont('arial', 18, QtGui.QFont.Bold),
backgroundColor = QtGui.QColor(8, 24, 66))))
popup.update(view.RDPRenderer(self._controller), True)
ProxyServer._SESSIONS_[self._controller.getHostname()] = client
nowConnected()
def onReady(self):
"""
@@ -115,23 +70,15 @@ class ProxyServer(rdp.RDPServerObserver):
First time this event is called is when human client is connected
Second time is after color depth nego, because color depth nego
restart a connection sequence
Use to connect proxy client or show available server
@see: rdp.RDPServerObserver.onReady
"""
if self._client is None:
#try a connection
domain, username, password = self._controller.getCredentials()
machines = self._credentialProvider.getProxyPass(domain, username)
if len(machines) == 0:
self.showMessage("No servers attach to account %s\\%s"%(domain, username))
elif len(machines) == 1:
ip, port = machines[0]
width, height = self._controller.getScreen()
reactor.connectTCP(ip, port, ProxyClientFactory(self, width, height,
reactor.connectTCP(self._target[0], int(self._target[1]), ProxyClientFactory(self, width, height,
domain, username, password))
else:
self.showSelectView(machines)
else:
#refresh client
width, height = self._controller.getScreen()
@@ -144,6 +91,9 @@ class ProxyServer(rdp.RDPServerObserver):
"""
if self._client is None:
return
del ProxyServer._SESSIONS_[self._controller.getHostname()]
nowConnected()
#close proxy client
self._client._controller.close()
@@ -154,13 +104,9 @@ class ProxyServer(rdp.RDPServerObserver):
@param isPressed: True if key is down
@see: rdp.RDPServerObserver.onKeyEventScancode
"""
#no client connected
if not self._client is None:
if self._client is None:
return
self._client._controller.sendKeyEventScancode(code, isPressed)
elif not self._window is None and isPressed:
self._window.keyEvent(code)
self._window.update(view.RDPRenderer(self._controller))
def onKeyEventUnicode(self, code, isPressed):
"""
@@ -169,7 +115,6 @@ class ProxyServer(rdp.RDPServerObserver):
@param isPressed: True if key is down
@see: rdp.RDPServerObserver.onKeyEventUnicode
"""
#no client connected domain
if self._client is None:
return
self._client._controller.sendKeyEventUnicode(code, isPressed)
@@ -183,7 +128,6 @@ class ProxyServer(rdp.RDPServerObserver):
@param isPressed: True if mouse button is pressed
@see: rdp.RDPServerObserver.onPointerEvent
"""
#no client connected
if self._client is None:
return
self._client._controller.sendPointerEvent(x, y, button, isPressed)
@@ -192,14 +136,14 @@ class ProxyServerFactory(rdp.ServerFactory):
"""
@summary: Factory on listening events
"""
def __init__(self, credentialProvider, privateKeyFilePath, certificateFilePath):
def __init__(self, target, privateKeyFilePath = None, certificateFilePath = None):
"""
@param credentialProvider: CredentialProvider
@param privateKeyFilePath: file contain server private key
@param certificateFilePath: file contain server certificate
@param target: {tuple(ip, prt)}
@param privateKeyFilePath: {str} file contain server private key (if none -> back to standard RDP security)
@param certificateFilePath: {str} file contain server certificate (if none -> back to standard RDP security)
"""
rdp.ServerFactory.__init__(self, privateKeyFilePath, certificateFilePath, 16)
self._credentialProvider = credentialProvider
rdp.ServerFactory.__init__(self, 16, privateKeyFilePath, certificateFilePath)
self._target = target
def buildObserver(self, controller, addr):
"""
@@ -207,23 +151,19 @@ class ProxyServerFactory(rdp.ServerFactory):
@param addr: destination address
@see: rdp.ServerFactory.buildObserver
"""
return ProxyServer(controller, self._credentialProvider)
return ProxyServer(controller, self._target)
class ProxyClient(rdp.RDPClientObserver):
"""
@summary: Client side of proxy
"""
_CONNECTED_ = []
def __init__(self, controller, server, name = None):
def __init__(self, controller, server):
"""
@param controller: rdp.RDPClientController
@param server: ProxyServer
@param name: name of session None if you don't
want to spy this session
"""
rdp.RDPClientObserver.__init__(self, controller)
self._server = server
self._name = name
self._connected = False
def onReady(self):
@@ -240,8 +180,6 @@ class ProxyClient(rdp.RDPClientObserver):
else:
self._connected = True
if not self._name is None:
ProxyClient._CONNECTED_.append(self)
self._server.clientConnected(self)
def onClose(self):
@@ -249,8 +187,6 @@ class ProxyClient(rdp.RDPClientObserver):
@summary: Event inform that stack is close
@see: rdp.RDPClientObserver.onClose
"""
if not self._name is None:
ProxyClient._CONNECTED_.remove(self)
def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
"""
@@ -302,57 +238,18 @@ class ProxyClientFactory(rdp.ClientFactory):
controller.setDomain(self._domain)
controller.setUsername(self._username)
controller.setPassword(self._password)
return ProxyClient(controller, self._server, "%s\\%s on %s"%(self._domain, self._username, addr))
return ProxyClient(controller, self._server)
def startedConnecting(self, connector):
pass
def clientConnectionLost(self, connector, reason):
pass
def clientConnectionFailed(self, connector, reason):
pass
class ProxyAdmin(rdp.RDPServerObserver):
class Shadow(rdp.RDPServerObserver):
"""
@summary: Use to manage admin session
Add GUI to select which session to see
Just escape key is authorized during spy session
To switch from spy state to admin state
"""
class State(object):
GUI = 0 #->list of active session
SPY = 1 #->watch active session
def __init__(self, controller):
"""
@param server: rdp.RDPServerController
"""
rdp.RDPServerObserver.__init__(self, controller)
self._spy = None
self._state = ProxyAdmin.State.GUI
def initView(self):
"""
@summary: Initialize Admin GUI view
"""
self._sessions = list(ProxyClient._CONNECTED_) #copy at t time
width, height = self._controller.getScreen()
self._window = view.Window(width, height, QtGui.QColor(8, 24, 66))
self._window.addView(view.Anchor(width / 2 - 250, 100,
view.Label("Please select following session",
500, 50, QtGui.QFont('arial', 18, QtGui.QFont.Bold),
backgroundColor = QtGui.QColor(8, 24, 66))))
self._window.addView(view.Anchor(width / 2 - 250, 150,
view.List([p._name for p in self._sessions],
500, 500, self.onSelect,
QtGui.QColor(8, 24, 66))), True)
def clientConnected(self, client):
pass
self._client = None
def onReady(self):
"""
@@ -360,73 +257,39 @@ class ProxyAdmin(rdp.RDPServerObserver):
May be called after an setColorDepth too
@see: rdp.RDPServerObserver.onReady
"""
if self._state == ProxyAdmin.State.GUI:
self.initView()
self._window.update(view.RDPRenderer(self._controller), True)
elif self._state == ProxyAdmin.State.SPY:
if self._client is None:
username = self._controller.getUsername()
if not ProxyServer._SESSIONS_.has_key(username):
log.info("invalid session name [%s]"%username)
self._controller.close()
return
self._client = ProxyClient(ProxyServer._SESSIONS_[username]._controller, self)
self._controller.setColorDepth(self._client._controller.getColorDepth())
else:
#refresh client
width, height = self._controller.getScreen()
self._spy._controller.sendRefreshOrder(0, 0, width, height)
self._client._controller.sendRefreshOrder(0, 0, width, height)
def onClose(self):
"""
@summary: Stack is close
@see: rdp.RDPServerObserver.onClose
"""
pass
def onKeyEventScancode(self, code, isPressed):
""" Shadow
"""
@summary: Event call when a keyboard event
is catch in scan code format
@param code: scan code of key
@param isPressed: True if key is down
@see: rdp.RDPServerObserver.onKeyEventScancode
"""
if self._state == ProxyAdmin.State.GUI:
if not isPressed:
return
self._window.keyEvent(code)
self._window.update(view.RDPRenderer(self._controller))
elif code == 1:
#escape button refresh GUI
self._state = ProxyAdmin.State.GUI
self._spy._controller.removeClientObserver(self._spy)
self.onReady()
def onKeyEventUnicode(self, code, isPressed):
""" Shadow
"""
@summary: Event call when a keyboard event is catch in unicode format
Admin GUI add filter for this event
@param code: unicode of key
@param isPressed: True if key is down
@see: rdp.RDPServerObserver.onKeyEventUnicode
"""
pass
def onPointerEvent(self, x, y, button, isPressed):
""" Shadow
"""
@summary: Event call on mouse event
Admin GUI add filter for this event
@param x: x position
@param y: y position
@param button: 1, 2 or 3 button
@param isPressed: True if mouse button is pressed
@see: rdp.RDPServerObserver.onPointerEvent
"""
pass
def onSelect(self, index):
"""
@summary: Callback of list view of active session
Connect to select session
@param index: index in sessions array
"""
self._state = ProxyAdmin.State.SPY
self._spy = ProxyClient(self._sessions[index]._controller, self)
self._controller.setColorDepth(self._spy._controller.getColorDepth())
class ProxyAdminFactory(rdp.ServerFactory):
class ShadowFactory(rdp.ServerFactory):
"""
@summary: Factory for admin session
"""
@@ -435,7 +298,7 @@ class ProxyAdminFactory(rdp.ServerFactory):
@param privateKeyFilePath: private key for admin session
@param certificateFilePath: certificate for admin session
"""
rdp.ServerFactory.__init__(self, privateKeyFilePath, certificateFilePath, 16)
rdp.ServerFactory.__init__(self, 16, privateKeyFilePath, certificateFilePath)
def buildObserver(self, controller, addr):
"""
@@ -445,116 +308,58 @@ class ProxyAdminFactory(rdp.ServerFactory):
@return: ProxyAdmin
@see: rdp.ServerFactory.buildObserver
"""
return ProxyAdmin(controller)
class CredentialProvider(object):
"""
@summary: Credential provider for proxy
"""
def __init__(self, config):
"""
@param config: rdp proxy config
"""
self._config = config
def getAccount(self, domain, username):
"""
@summary: Find account that match domain::username in config file
@param domain: Windows domain
@param username: username for session
@return: [(unicode(ip), port] or None if not found
"""
if not self._config.has_key(domain) or not self._config[domain].has_key(username):
return None
return self._config[domain][username]
def getProxyPass(self, domain, username):
"""
@summary: Find list of server available for thi account
@param domain: domain to check
@param username: username in domain
@return: [(ip, port)]
"""
account = self.getAccount(domain, username)
if account is None:
return []
return [(str(machine["ip"]), machine["port"]) for machine in account]
return Shadow(controller)
def help():
"""
@summary: Print help in console
"""
print "Usage: rdpy-rdpproxy -f credential_file_path -k private_key_file_path -c certificate_file_path [-i admin_ip[:admin_port]] listen_port"
print "Usage: rdpy-rdpproxy -t target_ip[:target_port] [-k private_key_file_path (mandatory for SSL)] [-c certificate_file_path (mandatory for SSL)] [-i admin_ip[:admin_port]] listen_port"
def loadConfig(configFilePath):
"""
@summary: Load and check config file
@param configFilePath: config file path
"""
if not os.path.isfile(configFilePath):
log.error("File %s doesn't exist"%configFilePath)
return None
def parseIpPort(interface, defaultPort = "3389"):
if ':' in interface:
return interface.split(':')
else:
return interface, defaultPort
f = open(configFilePath, 'r')
config = json.load(f)
f.close()
return config
def nowConnected():
log.info("*" * 50)
log.info("Now connected")
log.info(ProxyServer._SESSIONS_.keys())
log.info("*" * 50)
if __name__ == '__main__':
configFilePath = None
target = None
privateKeyFilePath = None
certificateFilePath = None
adminInterface = None
shadowInterface = None
try:
opts, args = getopt.getopt(sys.argv[1:], "hf:k:c:i:")
opts, args = getopt.getopt(sys.argv[1:], "ht:k:c:i:")
except getopt.GetoptError:
help()
for opt, arg in opts:
if opt == "-h":
help()
sys.exit()
elif opt == "-f":
configFilePath = arg
elif opt == "-t":
target = arg
elif opt == "-k":
privateKeyFilePath = arg
elif opt == "-c":
certificateFilePath = arg
elif opt == "-i":
adminInterface = arg
shadowInterface = arg
if configFilePath is None:
print "Config file is mandatory"
if target is None:
log.error("Target is mandatory")
help()
sys.exit()
if certificateFilePath is None:
print "Certificate file is mandatory"
help()
sys.exit()
reactor.listenTCP(int(args[0]), ProxyServerFactory(parseIpPort(target), privateKeyFilePath, certificateFilePath))
if privateKeyFilePath is None:
print "Private key file is mandatory"
help()
sys.exit()
#load config file
config = loadConfig(configFilePath)
if config is None:
log.error("Bad configuration file")
sys.exit()
#use to init font
app = QtGui.QApplication(sys.argv)
reactor.listenTCP(int(args[0]), ProxyServerFactory(CredentialProvider(config), privateKeyFilePath, certificateFilePath))
if not adminInterface is None:
if ':' in adminInterface:
adminInterface, adminPort = adminInterface.split(':')
else:
adminInterface, adminPort = adminInterface, "3390"
log.info("Admin listen on %s:%s"%(adminInterface, adminPort))
reactor.listenTCP(int(adminPort), ProxyAdminFactory(privateKeyFilePath, certificateFilePath), interface = adminInterface)
if not shadowInterface is None:
shadowInterface, shadowPort = parseIpPort(shadowInterface)
log.info("Shadow listener on %s:%s"%(shadowInterface, shadowPort))
reactor.listenTCP(int(shadowPort), ShadowFactory(privateKeyFilePath, certificateFilePath), interface = shadowInterface)
reactor.run()

View File

@@ -29,6 +29,7 @@ from PyQt4 import QtCore, QtGui
from rdpy.protocol.rdp import rdp
from rdpy.ui.qt4 import RDPBitmapToQtImage
import rdpy.core.log as log
from rdpy.core.error import RDPSecurityNegoFail
from twisted.internet import task
#set log level
@@ -39,18 +40,23 @@ class RDPScreenShotFactory(rdp.ClientFactory):
@summary: Factory for screenshot exemple
"""
__INSTANCE__ = 0
def __init__(self, width, height, path, timeout):
__STATE__ = []
def __init__(self, reactor, app, width, height, path, timeout):
"""
@param width: width of screen
@param height: height of screen
@param path: path of output screenshot
@param timeout: close connection after timeout s without any updating
@param reactor: twisted reactor
@param width: {integer} width of screen
@param height: {integer} height of screen
@param path: {str} path of output screenshot
@param timeout: {float} close connection after timeout s without any updating
"""
RDPScreenShotFactory.__INSTANCE__ += 1
self._reactor = reactor
self._app = app
self._width = width
self._height = height
self._path = path
self._timeout = timeout
self._security = "ssl"
def clientConnectionLost(self, connector, reason):
"""
@@ -58,11 +64,18 @@ class RDPScreenShotFactory(rdp.ClientFactory):
@param connector: twisted connector use for rdp connection (use reconnect to restart connection)
@param reason: str use to advertise reason of lost connection
"""
if reason.type == RDPSecurityNegoFail and self._security != "rdp":
log.info("due to RDPSecurityNegoFail try standard security layer")
self._security = "rdp"
connector.connect()
return
log.info("connection lost : %s"%reason)
RDPScreenShotFactory.__STATE__.append((connector.host, connector.port, reason))
RDPScreenShotFactory.__INSTANCE__ -= 1
if(RDPScreenShotFactory.__INSTANCE__ == 0):
reactor.stop()
app.exit()
self._reactor.stop()
self._app.exit()
def clientConnectionFailed(self, connector, reason):
"""
@@ -71,10 +84,11 @@ class RDPScreenShotFactory(rdp.ClientFactory):
@param reason: str use to advertise reason of lost connection
"""
log.info("connection failed : %s"%reason)
RDPScreenShotFactory.__STATE__.append((connector.host, connector.port, reason))
RDPScreenShotFactory.__INSTANCE__ -= 1
if(RDPScreenShotFactory.__INSTANCE__ == 0):
reactor.stop()
app.exit()
self._reactor.stop()
self._app.exit()
def buildObserver(self, controller, addr):
@@ -87,20 +101,24 @@ class RDPScreenShotFactory(rdp.ClientFactory):
"""
@summary: observer that connect, cache every image received and save at deconnection
"""
def __init__(self, controller, width, height, path, timeout):
def __init__(self, controller, width, height, security, path, timeout, reactor):
"""
@param controller: RDPClientController
@param width: width of screen
@param height: height of screen
@param path: path of output screenshot
@param timeout: close connection after timeout s without any updating
@param controller: {RDPClientController}
@param width: {integer} width of screen
@param height: {integer} height of screen
@param security: {str} (ssl | rdp) security level
@param path: {str} path of output screenshot
@param timeout: {float} close connection after timeout s without any updating
@param reactor: twisted reactor
"""
rdp.RDPClientObserver.__init__(self, controller)
controller.setScreen(width, height);
controller.setSecurityLevel(security)
self._buffer = QtGui.QImage(width, height, QtGui.QImage.Format_RGB32)
self._path = path
self._timeout = timeout
self._startTimeout = False
self._reactor = reactor
def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
"""
@@ -112,7 +130,7 @@ class RDPScreenShotFactory(rdp.ClientFactory):
qp.drawImage(destLeft, destTop, image, 0, 0, destRight - destLeft + 1, destBottom - destTop + 1)
if not self._startTimeout:
self._startTimeout = False
reactor.callLater(self._timeout, self.checkUpdate)
self._reactor.callLater(self._timeout, self.checkUpdate)
def onReady(self):
"""
@@ -130,7 +148,37 @@ class RDPScreenShotFactory(rdp.ClientFactory):
def checkUpdate(self):
self._controller.close();
return ScreenShotObserver(controller, self._width, self._height, self._path, self._timeout)
return ScreenShotObserver(controller, self._width, self._height, self._security, self._path, self._timeout, self._reactor)
def main(width, height, path, timeout, hosts):
"""
@summary: main algorithm
@param height: {integer} height of screenshot
@param width: {integer} width of screenshot
@param timeout: {float} in sec
@param hosts: {list(str(ip[:port]))}
@return: {list(tuple(ip, port, Failure instance)} list of connection state
"""
#create application
app = QtGui.QApplication(sys.argv)
#add qt4 reactor
import qt4reactor
qt4reactor.install()
from twisted.internet import reactor
for host in hosts:
if ':' in host:
ip, port = host.split(':')
else:
ip, port = host, "3389"
reactor.connectTCP(ip, int(port), RDPScreenShotFactory(reactor, app, width, height, path + "%s.jpg"%ip, timeout))
reactor.runReturn()
app.exec_()
return RDPScreenShotFactory.__STATE__
def help():
print "Usage: rdpy-rdpscreenshot [options] ip[:port]"
@@ -163,22 +211,4 @@ if __name__ == '__main__':
elif opt == "-t":
timeout = float(arg)
#create application
app = QtGui.QApplication(sys.argv)
#add qt4 reactor
import qt4reactor
qt4reactor.install()
from twisted.internet import reactor
for arg in args:
if ':' in arg:
ip, port = arg.split(':')
else:
ip, port = arg, "3389"
reactor.connectTCP(ip, int(port), RDPScreenShotFactory(width, height, path + "%s.jpg"%ip, timeout))
reactor.runReturn()
app.exec_()
main(width, height, path, timeout, args)

View File

@@ -30,6 +30,7 @@ class Level(object):
INFO = 1
WARNING = 2
ERROR = 3
NONE = 4
_LOG_LEVEL = Level.DEBUG

View File

@@ -142,10 +142,10 @@ class EncryptionLevel(object):
@see: http://msdn.microsoft.com/en-us/library/cc240518.aspx
"""
ENCRYPTION_LEVEL_NONE = 0x00000000
ENCRYPTION_LEVEL_LOW = 0x00000000
ENCRYPTION_LEVEL_CLIENT_COMPATIBLE = 0x00000000
ENCRYPTION_LEVEL_HIGH = 0x00000000
ENCRYPTION_LEVEL_FIPS = 0x00000000
ENCRYPTION_LEVEL_LOW = 0x00000001
ENCRYPTION_LEVEL_CLIENT_COMPATIBLE = 0x00000002
ENCRYPTION_LEVEL_HIGH = 0x00000003
ENCRYPTION_LEVEL_FIPS = 0x00000004
class ChannelOptions(object):
"""
@@ -355,7 +355,7 @@ class ProprietaryServerCertificate(CompositeType):
self.wSignatureBlobType = UInt16Le(0x0008, constant = True)
self.wSignatureBlobLen = UInt16Le(lambda:(sizeof(self.SignatureBlob) - 8))
self.SignatureBlob = String(readLen = self.wSignatureBlobLen)
self.padding = String("\x00" * 8, readLen = UInt8(8))
self.padding = String(b"\x00" * 8, readLen = UInt8(8))
def getPublicKey(self):
"""
@@ -380,7 +380,7 @@ class ProprietaryServerCertificate(CompositeType):
md5Digest = md5.new()
md5Digest.update(s.getvalue())
return md5Digest.digest() + "\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01"
return md5Digest.digest() + "\x00" + "\xff" * 46 + "\x01"
def sign(self):
"""

View File

@@ -25,8 +25,9 @@
from rdpy.core.type import CompositeType, UInt8, UInt16Le, UInt32Le, String, sizeof, FactoryType, ArrayType, Stream
from rdpy.core.error import InvalidExpectedDataException
import rdpy.core.log as log
import sec
import sec, gcc
from rdpy.security import rc4
from rdpy.security import rsa_wrapper as rsa
class MessageType(object):
"""
@@ -253,35 +254,31 @@ def createValidClientLicensingErrorMessage():
class LicenseManager(object):
"""
@summary: handle license automata
@summary: handle license automata (client side)
@see: http://msdn.microsoft.com/en-us/library/cc241890.aspx
"""
def __init__(self, transport):
"""
@param transport: layer use to send packet
"""
self._preMasterSecret = "\x00" * 64
self._clientRandom = "\x00" * 32
self._serverRandom = None
self._serverEncryptedChallenge = None
self._transport = transport
self._username = ""
self._hostname = ""
def generateKeys(self):
"""
@summary: generate key for license session
@summary: generate keys for license session
"""
masterSecret = sec.masterSecret(self._preMasterSecret, self._clientRandom, self._serverRandom)
sessionKeyBlob = sec.masterSecret(masterSecret, self._serverRandom, self._clientRandom)
self._macSalt = sessionKeyBlob[:16]
self._licenseKey = sec.finalHash(sessionKeyBlob[16:32], self._clientRandom, self._serverRandom)
def recv(self, s):
"""
@summary: receive license packet from PDU layer
@return true when license automata is finish
"""
with open("/tmp/toto", "wb") as f:
f.write(s.getvalue()[s.pos:].encode('base64'))
licPacket = LicPacket()
s.readType(licPacket)
@@ -290,14 +287,11 @@ class LicenseManager(object):
return True
elif licPacket.bMsgtype.value == MessageType.LICENSE_REQUEST:
self._serverRandom = licPacket.licensingMessage.serverRandom.value
self.generateKeys()
self.sendClientNewLicenseRequest()
self.sendClientNewLicenseRequest(licPacket.licensingMessage)
return False
elif licPacket.bMsgtype.value == MessageType.PLATFORM_CHALLENGE:
self._serverEncryptedChallenge = licPacket.licensingMessage.encryptedPlatformChallenge.blobData.value
self.sendClientChallengeResponse()
self.sendClientChallengeResponse(licPacket.licensingMessage)
return False
#yes get a new license
@@ -308,34 +302,52 @@ class LicenseManager(object):
raise InvalidExpectedDataException("Not a valid license packet")
def sendClientNewLicenseRequest(self):
def sendClientNewLicenseRequest(self, licenseRequest):
"""
@summary: Create new license request in response to server license request
@param licenseRequest: {ServerLicenseRequest}
@see: http://msdn.microsoft.com/en-us/library/cc241989.aspx
@see: http://msdn.microsoft.com/en-us/library/cc241918.aspx
"""
#get server information
serverRandom = licenseRequest.serverRandom.value
s = Stream(licenseRequest.serverCertificate.blobData.value)
serverCertificate = gcc.ServerCertificate()
s.readType(serverCertificate)
#generate crypto values
clientRandom = rsa.random(256)
preMasterSecret = rsa.random(384)
masterSecret = sec.masterSecret(preMasterSecret, clientRandom, serverRandom)
sessionKeyBlob = sec.masterSecret(masterSecret, serverRandom, clientRandom)
self._macSalt = sessionKeyBlob[:16]
self._licenseKey = sec.finalHash(sessionKeyBlob[16:32], clientRandom, serverRandom)
#format message
message = ClientNewLicenseRequest()
message.clientRandom.value = self._clientRandom
message.encryptedPreMasterSecret.blobData = String(self._preMasterSecret + "\x00" * 8)
message.ClientMachineName.blobData = String(self._hostname + "\x00")
message.ClientUserName.blobData = String(self._username + "\x00")
message.clientRandom.value = clientRandom
message.encryptedPreMasterSecret.blobData.value = rsa.encrypt(preMasterSecret[::-1], serverCertificate.certData.getPublicKey())[::-1] + "\x00" * 8
message.ClientMachineName.blobData.value = self._hostname + "\x00"
message.ClientUserName.blobData.value = self._username + "\x00"
self._transport.sendFlagged(sec.SecurityFlag.SEC_LICENSE_PKT, LicPacket(message))
def sendClientChallengeResponse(self):
def sendClientChallengeResponse(self, platformChallenge):
"""
@summary: generate valid challenge response
@param platformChallenge: {ServerPlatformChallenge}
"""
serverEncryptedChallenge = platformChallenge.encryptedPlatformChallenge.blobData.value
#decrypt server challenge
#it should be TEST word in unicode format
serverChallenge = rc4.crypt(rc4.RC4Key(self._licenseKey), self._serverEncryptedChallenge)
serverChallenge = rc4.crypt(rc4.RC4Key(self._licenseKey), serverEncryptedChallenge)
#generate hwid
s = Stream()
s.writeType((UInt32Le(2), String(self._hostname + "\x00" * 16)))
s.writeType((UInt32Le(2), String(self._hostname + self._username + "\x00" * 16)))
hwid = s.getvalue()[:20]
message = ClientPLatformChallengeResponse()
message.encryptedPlatformChallengeResponse.blobData.value = self._serverEncryptedChallenge
message.encryptedPlatformChallengeResponse.blobData.value = serverEncryptedChallenge
message.encryptedHWID.blobData.value = rc4.crypt(rc4.RC4Key(self._licenseKey), hwid)
message.MACData.value = sec.macData(self._macSalt, serverChallenge + hwid)

View File

@@ -539,4 +539,4 @@ class MultiFragmentUpdate(CompositeType):
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.MaxRequestSize = UInt32Le(0xffffffff)
self.MaxRequestSize = UInt32Le(0)

View File

@@ -29,7 +29,7 @@ import caps, order
class PDUType(object):
"""
Data PDU type primary index
@summary: Data PDU type primary index
@see: http://msdn.microsoft.com/en-us/library/cc240576.aspx
"""
PDUTYPE_DEMANDACTIVEPDU = 0x11
@@ -40,7 +40,7 @@ class PDUType(object):
class PDUType2(object):
"""
Data PDU type secondary index
@summary: Data PDU type secondary index
@see: http://msdn.microsoft.com/en-us/library/cc240577.aspx
"""
PDUTYPE2_UPDATE = 0x02
@@ -70,7 +70,7 @@ class PDUType2(object):
class StreamId(object):
"""
Stream priority
@summary: Stream priority
@see: http://msdn.microsoft.com/en-us/library/cc240577.aspx
"""
STREAM_UNDEFINED = 0x00
@@ -80,7 +80,7 @@ class StreamId(object):
class CompressionOrder(object):
"""
PDU compression order
@summary: PDU compression order
@see: http://msdn.microsoft.com/en-us/library/cc240577.aspx
"""
CompressionTypeMask = 0x0F
@@ -90,7 +90,7 @@ class CompressionOrder(object):
class CompressionType(object):
"""
PDU compression type
@summary: PDU compression type
@see: http://msdn.microsoft.com/en-us/library/cc240577.aspx
"""
PACKET_COMPR_TYPE_8K = 0x0
@@ -100,7 +100,7 @@ class CompressionType(object):
class Action(object):
"""
Action flag use in Control PDU packet
@summary: Action flag use in Control PDU packet
@see: http://msdn.microsoft.com/en-us/library/cc240492.aspx
"""
CTRLACTION_REQUEST_CONTROL = 0x0001
@@ -110,7 +110,7 @@ class Action(object):
class PersistentKeyListFlag(object):
"""
Use to determine the number of persistent key packet
@summary: Use to determine the number of persistent key packet
@see: http://msdn.microsoft.com/en-us/library/cc240495.aspx
"""
PERSIST_FIRST_PDU = 0x01
@@ -118,7 +118,7 @@ class PersistentKeyListFlag(object):
class BitmapFlag(object):
"""
Use in bitmap update PDU
@summary: Use in bitmap update PDU
@see: http://msdn.microsoft.com/en-us/library/cc240612.aspx
"""
BITMAP_COMPRESSION = 0x0001
@@ -126,7 +126,7 @@ class BitmapFlag(object):
class UpdateType(object):
"""
Use in update PDU to determine which type of update
@summary: Use in update PDU to determine which type of update
@see: http://msdn.microsoft.com/en-us/library/cc240608.aspx
"""
UPDATETYPE_ORDERS = 0x0000
@@ -136,7 +136,7 @@ class UpdateType(object):
class InputMessageType(object):
"""
Use in slow-path input PDU
@summary: Use in slow-path input PDU
@see: http://msdn.microsoft.com/en-us/library/cc240583.aspx
"""
INPUT_EVENT_SYNC = 0x0000
@@ -148,7 +148,7 @@ class InputMessageType(object):
class PointerFlag(object):
"""
Use in Pointer event
@summary: Use in Pointer event
@see: http://msdn.microsoft.com/en-us/library/cc240586.aspx
"""
PTRFLAGS_HWHEEL = 0x0400
@@ -163,7 +163,7 @@ class PointerFlag(object):
class KeyboardFlag(object):
"""
Use in scan code key event
@summary: Use in scan code key event
@see: http://msdn.microsoft.com/en-us/library/cc240584.aspx
"""
KBDFLAGS_EXTENDED = 0x0100
@@ -172,7 +172,7 @@ class KeyboardFlag(object):
class FastPathUpdateType(object):
"""
Use in Fast Path update packet
@summary: Use in Fast Path update packet
@see: http://msdn.microsoft.com/en-us/library/cc240622.aspx
"""
FASTPATH_UPDATETYPE_ORDERS = 0x0
@@ -189,14 +189,14 @@ class FastPathUpdateType(object):
class FastPathOutputCompression(object):
"""
Flag for compression
@summary: Flag for compression
@see: http://msdn.microsoft.com/en-us/library/cc240622.aspx
"""
FASTPATH_OUTPUT_COMPRESSION_USED = 0x2
class Display(object):
"""
Use in supress output PDU
@summary: Use in supress output PDU
@see: http://msdn.microsoft.com/en-us/library/cc240648.aspx
"""
SUPPRESS_DISPLAY_UPDATES = 0x00
@@ -204,7 +204,7 @@ class Display(object):
class ErrorInfo(object):
"""
Error code use in Error info PDU
@summary: Error code use in Error info PDU
@see: http://msdn.microsoft.com/en-us/library/cc240544.aspx
"""
ERRINFO_RPC_INITIATED_DISCONNECT = 0x00000001
@@ -417,12 +417,12 @@ class ErrorInfo(object):
class ShareControlHeader(CompositeType):
"""
PDU share control header
@summary: PDU share control header
@see: http://msdn.microsoft.com/en-us/library/cc240576.aspx
"""
def __init__(self, totalLength, pduType, userId):
"""
Set pduType as constant
@summary: Set pduType as constant
@param totalLength: total length of PDU packet
"""
CompositeType.__init__(self)
@@ -433,7 +433,7 @@ class ShareControlHeader(CompositeType):
class ShareDataHeader(CompositeType):
"""
PDU share data header
@summary: PDU share data header
@see: http://msdn.microsoft.com/en-us/library/cc240577.aspx
"""
def __init__(self, size, pduType2 = 0, shareId = 0):
@@ -448,7 +448,7 @@ class ShareDataHeader(CompositeType):
class PDU(CompositeType):
"""
Main PDU message
@summary: Main PDU message
"""
def __init__(self, userId = 0, pduMessage = None):
CompositeType.__init__(self)
@@ -456,7 +456,7 @@ class PDU(CompositeType):
def PDUMessageFactory():
"""
build message in accordance of type self.shareControlHeader.pduType.value
@summary: build message in accordance of type self.shareControlHeader.pduType.value
"""
for c in [DemandActivePDU, ConfirmActivePDU, DataPDU, DeactiveAllPDU]:
if self.shareControlHeader.pduType.value == c._PDUTYPE_:
@@ -475,7 +475,7 @@ class PDU(CompositeType):
class DemandActivePDU(CompositeType):
"""
@see: http://msdn.microsoft.com/en-us/library/cc240485.aspx
Main use for capabilities exchange server -> client
@summary: Main use for capabilities exchange server -> client
"""
#may declare the PDU type
_PDUTYPE_ = PDUType.PDUTYPE_DEMANDACTIVEPDU
@@ -494,7 +494,7 @@ class DemandActivePDU(CompositeType):
class ConfirmActivePDU(CompositeType):
"""
@see: http://msdn.microsoft.com/en-us/library/cc240488.aspx
Main use for capabilities confirm client -> sever
@summary: Main use for capabilities confirm client -> sever
"""
#may declare the PDU type
_PDUTYPE_ = PDUType.PDUTYPE_CONFIRMACTIVEPDU
@@ -512,7 +512,7 @@ class ConfirmActivePDU(CompositeType):
class DeactiveAllPDU(CompositeType):
"""
Use to signal already connected session
@summary: Use to signal already connected session
@see: http://msdn.microsoft.com/en-us/library/cc240536.aspx
"""
#may declare the PDU type
@@ -526,7 +526,7 @@ class DeactiveAllPDU(CompositeType):
class DataPDU(CompositeType):
"""
Generic PDU packet use after connection sequence
@summary: Generic PDU packet use after connection sequence
"""
#may declare the PDU type
_PDUTYPE_ = PDUType.PDUTYPE_DATAPDU
@@ -537,7 +537,7 @@ class DataPDU(CompositeType):
def PDUDataFactory():
"""
Create object in accordance self.shareDataHeader.pduType2 value
@summary: Create object in accordance self.shareDataHeader.pduType2 value
"""
for c in [UpdateDataPDU, SynchronizeDataPDU, ControlDataPDU, ErrorInfoDataPDU, FontListDataPDU, FontMapDataPDU, PersistentListPDU, ClientInputEventPDU, ShutdownDeniedPDU, ShutdownRequestPDU, SupressOutputDataPDU]:
if self.shareDataHeader.pduType2.value == c._PDUTYPE2_:
@@ -584,7 +584,7 @@ class ControlDataPDU(CompositeType):
class ErrorInfoDataPDU(CompositeType):
"""
Use to inform error in PDU layer
@summary: Use to inform error in PDU layer
@see: http://msdn.microsoft.com/en-us/library/cc240544.aspx
"""
_PDUTYPE2_ = PDUType2.PDUTYPE2_SET_ERROR_INFO_PDU
@@ -600,7 +600,7 @@ class ErrorInfoDataPDU(CompositeType):
class FontListDataPDU(CompositeType):
"""
Use to indicate list of font. Deprecated packet
@summary: Use to indicate list of font. Deprecated packet
client -> server
@see: http://msdn.microsoft.com/en-us/library/cc240498.aspx
"""
@@ -618,7 +618,7 @@ class FontListDataPDU(CompositeType):
class FontMapDataPDU(CompositeType):
"""
Use to indicate map of font. Deprecated packet (maybe the same as FontListDataPDU)
@summary: Use to indicate map of font. Deprecated packet (maybe the same as FontListDataPDU)
server -> client
@see: http://msdn.microsoft.com/en-us/library/cc240498.aspx
"""
@@ -636,7 +636,7 @@ class FontMapDataPDU(CompositeType):
class PersistentListEntry(CompositeType):
"""
Use to record persistent key in PersistentListPDU
@summary: Use to record persistent key in PersistentListPDU
@see: http://msdn.microsoft.com/en-us/library/cc240496.aspx
"""
def __init__(self):
@@ -646,7 +646,7 @@ class PersistentListEntry(CompositeType):
class PersistentListPDU(CompositeType):
"""
Use to indicate that bitmap cache was already
@summary: Use to indicate that bitmap cache was already
Fill with some keys from previous session
@see: http://msdn.microsoft.com/en-us/library/cc240495.aspx
"""
@@ -671,7 +671,7 @@ class PersistentListPDU(CompositeType):
class ClientInputEventPDU(CompositeType):
"""
PDU use to send client inputs in slow path mode
@summary: PDU use to send client inputs in slow path mode
@see: http://msdn.microsoft.com/en-us/library/cc746160.aspx
"""
_PDUTYPE2_ = PDUType2.PDUTYPE2_INPUT
@@ -684,7 +684,7 @@ class ClientInputEventPDU(CompositeType):
class ShutdownRequestPDU(CompositeType):
"""
PDU use to signal that the session will be closed
@summary: PDU use to signal that the session will be closed
client -> server
"""
_PDUTYPE2_ = PDUType2.PDUTYPE2_SHUTDOWN_REQUEST
@@ -693,7 +693,7 @@ class ShutdownRequestPDU(CompositeType):
class ShutdownDeniedPDU(CompositeType):
"""
PDU use to signal which the session will be closed is connected
@summary: PDU use to signal which the session will be closed is connected
server -> client
"""
_PDUTYPE2_ = PDUType2.PDUTYPE2_SHUTDOWN_DENIED
@@ -737,7 +737,7 @@ class RefreshRectPDU(CompositeType):
class UpdateDataPDU(CompositeType):
"""
Update data PDU use by server to inform update image or palet
@summary: Update data PDU use by server to inform update image or palet
for example
@see: http://msdn.microsoft.com/en-us/library/cc240608.aspx
"""
@@ -754,7 +754,7 @@ class UpdateDataPDU(CompositeType):
def UpdateDataFactory():
"""
Create object in accordance self.updateType value
@summary: Create object in accordance self.updateType value
"""
for c in [BitmapUpdateDataPDU]:
if self.updateType.value == c._UPDATE_TYPE_:
@@ -771,7 +771,7 @@ class UpdateDataPDU(CompositeType):
class FastPathUpdatePDU(CompositeType):
"""
Fast path update PDU packet
@summary: Fast path update PDU packet
@see: http://msdn.microsoft.com/en-us/library/cc240622.aspx
"""
def __init__(self, updateData = None):
@@ -782,7 +782,7 @@ class FastPathUpdatePDU(CompositeType):
def UpdateDataFactory():
"""
Create correct object in accordance to self.updateHeader field
@summary: Create correct object in accordance to self.updateHeader field
"""
for c in [FastPathBitmapUpdateDataPDU]:
if (self.updateHeader.value & 0xf) == c._FASTPATH_UPDATE_TYPE_:
@@ -799,7 +799,7 @@ class FastPathUpdatePDU(CompositeType):
class BitmapUpdateDataPDU(CompositeType):
"""
PDU use to send raw bitmap compressed or not
@summary: PDU use to send raw bitmap compressed or not
@see: http://msdn.microsoft.com/en-us/library/dd306368.aspx
"""
_UPDATE_TYPE_ = UpdateType.UPDATETYPE_BITMAP
@@ -814,7 +814,7 @@ class BitmapUpdateDataPDU(CompositeType):
class OrderUpdateDataPDU(CompositeType):
"""
PDU type use to communicate Accelerated order (GDI)
@summary: PDU type use to communicate Accelerated order (GDI)
@see: http://msdn.microsoft.com/en-us/library/cc241571.aspx
@todo: not implemented yet but need it
"""
@@ -827,7 +827,7 @@ class OrderUpdateDataPDU(CompositeType):
class BitmapCompressedDataHeader(CompositeType):
"""
Compressed header of bitmap
@summary: Compressed header of bitmap
@see: http://msdn.microsoft.com/en-us/library/cc240644.aspx
"""
def __init__(self, bodySize = 0, scanWidth = 0, uncompressedSize = 0, conditional = lambda:True):
@@ -846,7 +846,7 @@ class BitmapCompressedDataHeader(CompositeType):
class BitmapData(CompositeType):
"""
Bitmap data here the screen capture
@summary: Bitmap data here the screen capture
"""
def __init__(self, destLeft = 0, destTop = 0, destRight = 0, destBottom = 0, width = 0, height = 0, bitsPerPixel = 0, bitmapDataStream = ""):
"""
@@ -874,7 +874,7 @@ class BitmapData(CompositeType):
class FastPathBitmapUpdateDataPDU(CompositeType):
"""
Fast path version of bitmap update PDU
@summary: Fast path version of bitmap update PDU
@see: http://msdn.microsoft.com/en-us/library/dd306368.aspx
"""
_FASTPATH_UPDATE_TYPE_ = FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP
@@ -887,7 +887,7 @@ class FastPathBitmapUpdateDataPDU(CompositeType):
class SlowPathInputEvent(CompositeType):
"""
PDU use in slow-path sending client inputs
@summary: PDU use in slow-path sending client inputs
@see: http://msdn.microsoft.com/en-us/library/cc240583.aspx
"""
def __init__(self, messageData = None):
@@ -911,7 +911,7 @@ class SlowPathInputEvent(CompositeType):
class PointerEvent(CompositeType):
"""
Event use to communicate mouse position
@summary: Event use to communicate mouse position
@see: http://msdn.microsoft.com/en-us/library/cc240586.aspx
"""
_INPUT_MESSAGE_TYPE_ = InputMessageType.INPUT_EVENT_MOUSE
@@ -924,7 +924,7 @@ class PointerEvent(CompositeType):
class ScancodeKeyEvent(CompositeType):
"""
Event use to communicate keyboard informations
@summary: Event use to communicate keyboard informations
@see: http://msdn.microsoft.com/en-us/library/cc240584.aspx
"""
_INPUT_MESSAGE_TYPE_ = InputMessageType.INPUT_EVENT_SCANCODE
@@ -937,7 +937,7 @@ class ScancodeKeyEvent(CompositeType):
class UnicodeKeyEvent(CompositeType):
"""
Event use to communicate keyboard informations
@summary: Event use to communicate keyboard informations
@see: http://msdn.microsoft.com/en-us/library/cc240585.aspx
"""
_INPUT_MESSAGE_TYPE_ = InputMessageType.INPUT_EVENT_UNICODE

View File

@@ -411,7 +411,7 @@ class Server(PDULayer):
self._clientCapabilities[cap.capabilitySetType] = cap
#find use full flag
self._clientFastPathSupported = bool(self._clientCapabilities[caps.CapsType.CAPSTYPE_GENERAL].capability.extraFlags.value & (caps.GeneralExtraFlag.FASTPATH_OUTPUT_SUPPORTED | caps.GeneralExtraFlag.LONG_CREDENTIALS_SUPPORTED))
self._clientFastPathSupported = bool(self._clientCapabilities[caps.CapsType.CAPSTYPE_GENERAL].capability.extraFlags.value & caps.GeneralExtraFlag.FASTPATH_OUTPUT_SUPPORTED)
self.setNextState(self.recvClientSynchronizePDU)

View File

@@ -123,22 +123,26 @@ class RDPClientController(pdu.layer.PDUClientListener):
@param layout: us | fr
"""
if layout == "fr":
self._mcsLayer._clientSettings.getBlock(gcc.MessageType.CS_CORE).kbdLayout.value = gcc.KeyboardLayout.FRENCH
self._mcsLayer._clientSettings.CS_CORE.kbdLayout.value = gcc.KeyboardLayout.FRENCH
elif layout == "us":
self._mcsLayer._clientSettings.getBlock(gcc.MessageType.CS_CORE).kbdLayout.value = gcc.KeyboardLayout.US
self._mcsLayer._clientSettings.CS_CORE.kbdLayout.value = gcc.KeyboardLayout.US
def setHostname(self, hostname):
"""
@summary: set hostname of machine
"""
self._mcsLayer._clientSettings.getBlock(gcc.MessageType.CS_CORE).clientName.value = hostname[:15] + "\x00" * (15 - len(hostname))
self._mcsLayer._clientSettings.CS_CORE.clientName.value = hostname[:15] + "\x00" * (15 - len(hostname))
self._secLayer._licenceManager._hostname = hostname
def setRDPBasicSecurity(self):
def setSecurityLevel(self, level):
"""
@summary: Request basic security
@param level: {str} (ssl | rdp)
"""
if level == "rdp":
self._x224Layer._requestedProtocol = x224.Protocols.PROTOCOL_RDP
elif level == "ssl":
self._x224Layer._requestedProtocol = x224.Protocols.PROTOCOL_SSL
def addClientObserver(self, observer):
"""
@@ -362,6 +366,12 @@ class RDPServerController(pdu.layer.PDUServerListener):
"""
return self._tpktLayer
def getHostname(self):
"""
@return: name of client (information done by RDP)
"""
return self._mcsLayer._clientSettings.CS_CORE.clientName.value.strip('\x00')
def getUsername(self):
"""
@summary: Must be call after on ready event else always empty string
@@ -414,7 +424,8 @@ class RDPServerController(pdu.layer.PDUServerListener):
"""
@summary: Set color depth of session
if PDU stack is already connected send a deactive-reactive sequence
@param colorDepth: depth of session (15, 16, 24)
and an onReady message is re send when client is ready
@param colorDepth: {integer} depth of session (15, 16, 24)
"""
self._colorDepth = colorDepth
self._pduLayer._serverCapabilities[pdu.caps.CapsType.CAPSTYPE_BITMAP].capability.preferredBitsPerPixel.value = colorDepth
@@ -526,15 +537,15 @@ class ServerFactory(layer.RawLayerServerFactory):
"""
@summary: Factory of Server RDP protocol
"""
def __init__(self, privateKeyFileName, certificateFileName, colorDepth):
def __init__(self, colorDepth, privateKeyFileName = None, certificateFileName = None):
"""
@param privateKeyFileName: file contain server private key
@param certficiateFileName: file that contain public key
@param colorDepth: color depth of session
@param privateKeyFileName: file contain server private key (if none -> back to standard RDP security)
@param certficiateFileName: file that contain public key (if none -> back to standard RDP security)
"""
self._colorDepth = colorDepth
self._privateKeyFileName = privateKeyFileName
self._certificateFileName = certificateFileName
self._colorDepth = colorDepth
def connectionLost(self, tpktLayer, reason):
"""

View File

@@ -287,7 +287,7 @@ class Server(X224Layer):
message.protocolNeg.selectedProtocol.value = self._selectedProtocol
self._transport.send(message)
if self._selectedProtocol == Protocols.PROTOCOL_SSL:
log.debug("*" * 5 + " select SSL layer")
log.debug("*" * 10 + " select SSL layer " + "*" * 10)
#_transport is TPKT and transport is TCP layer of twisted
self._transport.transport.startTLS(ServerTLSContext(self._serverPrivateKeyFileName, self._serverCertificateFileName))

View File

@@ -61,6 +61,7 @@ def int2bytes(i, fill_size=None):
def random(size):
"""
@summary: wrapper around rsa.randnum.read_random_bits function
@param size: {integer] size in bits
@return: {str} random bytes array
"""
return rsa.randnum.read_random_bits(size)

View File

@@ -29,6 +29,48 @@ import unittest
from rdpy.protocol.rdp import lic, sec
import rdpy.core.type as type
#dump of server request
SERVERREQUEST = """
AQNfCBkr6c1CVLRPx7PPYVgzW5uMQ1pSvtzs9XlTt74jwjslAAAGACwAAABNAGkAYwByAG8AcwBv
AGYAdAAgAEMAbwByAHAAbwByAGEAdABpAG8AbgAAAAgAAABBADAAMgAAAA0ABAABAAAAAwDZBwIA
AAACAAAAWAMAADCCA1QwggJAoAMCAQICCAGemF/kFo3QMAkGBSsOAwIdBQAwODE2MBUGA1UEBx4O
AFMASQBSAEEARABFAEwwHQYDVQQDHhYAVgBTAEkALQBTAFIAVgAtADAAMAA0MB4XDTcwMTAyMTA4
MDMxN1oXDTQ5MTAyMTA4MDMxN1owODE2MBUGA1UEBx4OAFMASQBSAEEARABFAEwwHQYDVQQDHhYA
VgBTAEkALQBTAFIAVgAtADAAMAA0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0mh+
rgsNk7WCR8Ck46jTV6OgnMgsFmdt0RqBJqtFwk+uqH8cBKFohtD9CeHTeQYkk+8m1C5GDpjQ/5BK
jaBfZHW+g5lZQITKbTi2n8TZfDg76wHvfl71JzwB84AG736KOZSOkBg8mJk3xf8LX5hbYHPQCRc5
y+06CWOyPiBYCEElXPOqsZYwum72N/NIsrKe98QHLn+X6MSbZ9Qzw6jnoBFgjk65bzpcChaOhnlW
875D6+8KZXgFmHT6MSJX7eabKw1zbKXszYpPvPfMDybp+6z125AJ+GxJ847AQ10xOV2X13ZVfSvS
Np62ogDD23duWHvcwLmHJ+45Jdgt7y4C0wIDAQABo2owaDAPBgNVHRMECDAGAQH/AgEAMFUGCSsG
AQQBgjcSCARIQwBDAFIATQBWAEoANgBWAEMAVABUAFcAUgBXAEcATQA4AEgASwBIAFkAMwBKAEsA
OABXADMAVgBNAEIARABXAFQAUQBGAAAAMAkGBSsOAwIdBQADggEBAD2NQPpBREuD+CZI9T4bUF9s
2yyQ8HidxJ+7NMcDyW4Ozid/SrAT9JP42RQrBosgq8S8teWNubrsNMsJjGaeueeQisLP1OigXQH1
8DcnLUc5TZKIsKjBqxJ40tssR5KaOfjGHXX5VoADsthnp3wO4bRuaXyhpLQzN1bUNKQk0Fok6Fzt
zo/rNEiAQv+M/Y1vmCoTuulXwxymZ+UdbxHPr/IvmxD0Gcmz8Qm45Lpypt2t0lGwNiuV2/3dm13C
spjIGxJFf/26QiRuZi5cps2YsOaQfW3xByklE5EbZWYIlsTCQpht+owKqnoPy7aD2On8z0IyGgY3
YdsOzHDvRZ+S0hxhBAAAMIIEXTCCA0mgAwIBAgIFAQAAABgwCQYFKw4DAh0FADA4MTYwFQYDVQQH
Hg4AUwBJAFIAQQBEAEUATDAdBgNVBAMeFgBWAFMASQAtAFMAUgBWAC0AMAAwADQwHhcNMTMxMTI3
MTQyNDIyWhcNMzgwMTE5MDMxNDA3WjCBqDGBpTAnBgNVBAMeIABuAGMAYQBjAG4AXwBuAHAAOgAx
ADkAMgAuADEANgA4MDUGA1UEBx4uAG4AYwBhAGMAbgBfAG4AcAA6ADEAOQAyAC4AMQA2ADgALgAx
ADMANQAuADMAMDBDBgNVBAUePAAxAEIAYwBLAGUAYQBiADcAWABPAGoAbgBXAEUATQBuAHgAMQBw
AFMAaQA2AEoAUgBBAHUAMAA9AA0ACjBYMAkGBSsOAwIPBQADSwAwSAJBANGDkkicUIVwIX2apm1b
U9WXVMnVT+S081PVP87vGp6VtzYpT9cMNKv70Qi2U3MQoQ4MuKS1XN2uc9SrNC7RWoUCAwEAAaOC
Ac8wggHLMBQGCSsGAQQBgjcSBAEB/wQEAQAFADA8BgkrBgEEAYI3EgIBAf8ELE0AaQBjAHIAbwBz
AG8AZgB0ACAAQwBvAHIAcABvAHIAYQB0AGkAbwBuAAAAMIHNBgkrBgEEAYI3EgUBAf8EgbwAMAAA
AQAAAAIAAAAJBAAAHABKAGYASgCwAAEAMwBkADIANgA3ADkANQA0AC0AZQBlAGIANwAtADEAMQBk
ADEALQBiADkANABlAC0AMAAwAGMAMAA0AGYAYQAzADAAOAAwAGQAAAAzAGQAMgA2ADcAOQA1ADQA
LQBlAGUAYgA3AC0AMQAxAGQAMQAtAGIAOQA0AGUALQAwADAAYwAwADQAZgBhADMAMAA4ADAAZAAA
AAAAABAAgOQAAAAAADB0BgkrBgEEAYI3EgYBAf8EZAAwAAAAABgASABWAFMASQAtAFMAUgBWAC0A
MAAwADQAAAA1ADUAMAA0ADEALQAwADAAOAAtADIAOQAxADMAMwA5ADEALQA4ADQANgAwADkAAABT
AEkAUgBBAEQARQBMAAAAAAAwLwYDVR0jAQH/BCUwI6EapBhWAFMASQAtAFMAUgBWAC0AMAAwADQA
AACCBQEAAAAYMAkGBSsOAwIdBQADggEBAGFI8teU2iypPG2BbpNLa+zZeb87MNjtzHgQlC0RfoK0
dY91t3OXA5pdyD5IRRkvGUVKBf/5G/OVZZvKTnyh3daopD6InPy1X6Wlx5QveCL8ydd/H+ezm0zl
KlMg0pImG0V2NX50q4b9R4I5xiaA7mUeww6rz6iAh1iB2CYHOc/uGAS1/PPtHqnfss5bY+wZR+tW
dvyMFvn6DbvzAN9wT2KIcv1LtMGgxv28v+dnqGhcxrLRE4nRjMIV/AKpBJXhgH7DaUEc1NJNDPUt
5Pdz4qy8WM2d8JO0yNXQVqykJahgt5TJxCoP46SPFUU5XBbNTZuKv7CtPkSxgtrdrzwFTtcAAAAA
AAAAAAAAAAAAAAAAAQAAAA4ADgBtaWNyb3NvZnQuY29tAA==
"""
class TestLic(unittest.TestCase):
"""
@summary: Test case for MCS automata
@@ -71,9 +113,6 @@ class TestLic(unittest.TestCase):
t = Transport()
l = lic.LicenseManager(t)
s = type.Stream()
s.writeType(lic.LicPacket(lic.ServerLicenseRequest()))
#reinit position
s.pos = 0
s = type.Stream(SERVERREQUEST.decode("base64"))
self.assertFalse(l.recv(s) and t._state, "Bad message after license request")

View File

@@ -26,7 +26,7 @@ import os, sys
sys.path.insert(1, os.path.join(sys.path[0], '..'))
import unittest
import rdpy.core.rc4 as rc4
import rdpy.security.rc4 as rc4
class RC4Test(unittest.TestCase):