Compare commits
58 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cef16a9f64 | ||
|
|
9aea135fd9 | ||
|
|
4109b7a6fe | ||
|
|
d3b0ae5e90 | ||
|
|
a1f9afa87a | ||
|
|
b8ff4136b6 | ||
|
|
ce04150790 | ||
|
|
bcec1aad25 | ||
|
|
c18a4c4101 | ||
|
|
3e37899ae4 | ||
|
|
cd6e14e7ef | ||
|
|
629d2160af | ||
|
|
e23def3179 | ||
|
|
a23ae25a1f | ||
|
|
763ed2e3ee | ||
|
|
11d66a4818 | ||
|
|
9b99365f80 | ||
|
|
bd7c708bf3 | ||
|
|
1deb2d69ea | ||
|
|
0a5a1fd12c | ||
|
|
c97b451ce3 | ||
|
|
80f989a804 | ||
|
|
c6e100f9a6 | ||
|
|
15df00ec20 | ||
|
|
342349cf41 | ||
|
|
d6043106e3 | ||
|
|
bd7e73a6e7 | ||
|
|
fc1685e652 | ||
|
|
4320824aae | ||
|
|
5a438174b9 | ||
|
|
bb9483e7e1 | ||
|
|
bd362263f7 | ||
|
|
20de5f6f82 | ||
|
|
95052a323f | ||
|
|
0abf18d130 | ||
|
|
b57b3d7398 | ||
|
|
0695825d98 | ||
|
|
8fb4893b6f | ||
|
|
30a16fbb7a | ||
|
|
98494d0e73 | ||
|
|
a7058f1c54 | ||
|
|
1e2f284e97 | ||
|
|
8cd789480f | ||
|
|
e9a93d117b | ||
|
|
3fe16130d8 | ||
|
|
31b0920a87 | ||
|
|
36c05faa11 | ||
|
|
1c3119cffd | ||
|
|
d6bb21565d | ||
|
|
d6428430eb | ||
|
|
a4f4d71929 | ||
|
|
9e211c0199 | ||
|
|
5bd78cc012 | ||
|
|
e139a2c7eb | ||
|
|
349a8a7227 | ||
|
|
35514a2849 | ||
|
|
222ee76c91 | ||
|
|
30c3611bb9 |
@@ -7,7 +7,7 @@ before_install:
|
|||||||
- sudo apt-get install python-qt4
|
- 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/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/
|
- ln -s /usr/lib/python2.7/dist-packages/sip.so $VIRTUAL_ENV/lib/python2.7/site-packages/
|
||||||
- pip install qt4reactor pyopenssl twisted service_identity rsa
|
- pip install qt4reactor pyopenssl twisted service_identity rsa pyasn1
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- python setup.py install
|
- python setup.py install
|
||||||
|
|||||||
33
README.md
33
README.md
@@ -1,8 +1,8 @@
|
|||||||
# RDPY [](https://travis-ci.org/citronneur/rdpy)
|
# RDPY [](https://travis-ci.org/citronneur/rdpy) [](http://badge.fury.io/py/rdpy)
|
||||||
|
|
||||||
Remote Desktop Protocol in twisted python.
|
Remote Desktop Protocol in twisted python.
|
||||||
|
|
||||||
RDPY is a pure Python implementation of the Microsoft RDP (Remote Desktop Protocol) protocol (client and server side). RDPY is built over the event driven network engine Twisted.
|
RDPY is a pure Python implementation of the Microsoft RDP (Remote Desktop Protocol) protocol (client and server side). RDPY is built over the event driven network engine Twisted. RDPY support standard RDP security layer, RDP over SSL and NLA authentication (through ntlmv2 authentication protocol).
|
||||||
|
|
||||||
RDPY provides the following RDP and VNC binaries :
|
RDPY provides the following RDP and VNC binaries :
|
||||||
* RDP Man In The Middle proxy which record session
|
* RDP Man In The Middle proxy which record session
|
||||||
@@ -33,6 +33,12 @@ Example for Debian based systems :
|
|||||||
sudo apt-get install python-qt4
|
sudo apt-get install python-qt4
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### OS X
|
||||||
|
Example for OS X to install PyQt with homebrew
|
||||||
|
```
|
||||||
|
$ brew install qt sip pyqt
|
||||||
|
```
|
||||||
|
|
||||||
#### Windows
|
#### Windows
|
||||||
|
|
||||||
x86 | x86_64
|
x86 | x86_64
|
||||||
@@ -44,7 +50,7 @@ x86 | x86_64
|
|||||||
|
|
||||||
```
|
```
|
||||||
$ git clone https://github.com/citronneur/rdpy.git rdpy
|
$ git clone https://github.com/citronneur/rdpy.git rdpy
|
||||||
$ pip install twisted pyopenssl qt4reactor service_identity rsa
|
$ pip install twisted pyopenssl qt4reactor service_identity rsa pyasn1
|
||||||
$ python rdpy/setup.py install
|
$ python rdpy/setup.py install
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -107,7 +113,7 @@ $ rdpy-rdpmitm.py -o output_dir [-l listen_port] [-k private_key_file_path] [-c
|
|||||||
```
|
```
|
||||||
|
|
||||||
Output directory is used to save the rss file with following format (YYYYMMDDHHMMSS_ip_index.rss)
|
Output directory is used to save the rss file with following format (YYYYMMDDHHMMSS_ip_index.rss)
|
||||||
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 private key file and the certificate file are classic cryptographic files for SSL connections. The RDP protocol can negotiate its own security layer If one of both parameters are omitted, the server use standard RDP as security layer.
|
||||||
|
|
||||||
### rdpy-rdphoneypot
|
### rdpy-rdphoneypot
|
||||||
|
|
||||||
@@ -117,7 +123,7 @@ rdpy-rdphoneypot is an RDP honey Pot. Use Recorded Session Scenario to replay sc
|
|||||||
$ rdpy-rdphoneypot.py [-l listen_port] [-k private_key_file_path] [-c certificate_file_path] rss_file_path_1 ... rss_file_path_N
|
$ rdpy-rdphoneypot.py [-l listen_port] [-k private_key_file_path] [-c certificate_file_path] rss_file_path_1 ... rss_file_path_N
|
||||||
```
|
```
|
||||||
|
|
||||||
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 private key file and the certificate file are classic cryptographic files for SSL connections. The RDP protocol can negotiate its own security layer. If one of both parameters are omitted, the server use standard RDP as security layer.
|
||||||
You can specify more than one files to match more common screen size.
|
You can specify more than one files to match more common screen size.
|
||||||
|
|
||||||
### rdpy-rssplayer
|
### rdpy-rssplayer
|
||||||
@@ -151,7 +157,7 @@ class MyRDPFactory(rdp.ClientFactory):
|
|||||||
|
|
||||||
def buildObserver(self, controller, addr):
|
def buildObserver(self, controller, addr):
|
||||||
|
|
||||||
class MyObserver(rdp.RDPClientObserver)
|
class MyObserver(rdp.RDPClientObserver):
|
||||||
|
|
||||||
def onReady(self):
|
def onReady(self):
|
||||||
"""
|
"""
|
||||||
@@ -176,6 +182,11 @@ class MyRDPFactory(rdp.ClientFactory):
|
|||||||
@param data: bitmap data
|
@param data: bitmap data
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def onSessionReady(self):
|
||||||
|
"""
|
||||||
|
@summary: Windows session is ready
|
||||||
|
"""
|
||||||
|
|
||||||
def onClose(self):
|
def onClose(self):
|
||||||
"""
|
"""
|
||||||
@summary: Call when stack is close
|
@summary: Call when stack is close
|
||||||
@@ -184,7 +195,7 @@ class MyRDPFactory(rdp.ClientFactory):
|
|||||||
return MyObserver(controller)
|
return MyObserver(controller)
|
||||||
|
|
||||||
from twisted.internet import reactor
|
from twisted.internet import reactor
|
||||||
reactor.connectTCP("XXX.XXX.XXX.XXX", 3389), MyRDPFactory())
|
reactor.connectTCP("XXX.XXX.XXX.XXX", 3389, MyRDPFactory())
|
||||||
reactor.run()
|
reactor.run()
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -196,7 +207,7 @@ class MyRDPFactory(rdp.ServerFactory):
|
|||||||
|
|
||||||
def buildObserver(self, controller, addr):
|
def buildObserver(self, controller, addr):
|
||||||
|
|
||||||
class MyObserver(rdp.RDPServerObserver)
|
class MyObserver(rdp.RDPServerObserver):
|
||||||
|
|
||||||
def onReady(self):
|
def onReady(self):
|
||||||
"""
|
"""
|
||||||
@@ -225,7 +236,7 @@ class MyRDPFactory(rdp.ServerFactory):
|
|||||||
@summary: Event call on mouse event
|
@summary: Event call on mouse event
|
||||||
@param x: x position
|
@param x: x position
|
||||||
@param y: y position
|
@param y: y position
|
||||||
@param button: 1, 2 or 3 button
|
@param button: 1, 2, 3, 4 or 5 button
|
||||||
@param isPressed: True if mouse button is pressed
|
@param isPressed: True if mouse button is pressed
|
||||||
@see: rdp.RDPServerObserver.onPointerEvent
|
@see: rdp.RDPServerObserver.onPointerEvent
|
||||||
"""
|
"""
|
||||||
@@ -256,7 +267,7 @@ class MyRFBFactory(rfb.ClientFactory):
|
|||||||
reactor.stop()
|
reactor.stop()
|
||||||
|
|
||||||
def buildObserver(self, controller, addr):
|
def buildObserver(self, controller, addr):
|
||||||
class MyObserver(rfb.RFBClientObserver)
|
class MyObserver(rfb.RFBClientObserver):
|
||||||
|
|
||||||
def onReady(self):
|
def onReady(self):
|
||||||
"""
|
"""
|
||||||
@@ -294,6 +305,6 @@ class MyRFBFactory(rfb.ClientFactory):
|
|||||||
return MyObserver(controller)
|
return MyObserver(controller)
|
||||||
|
|
||||||
from twisted.internet import reactor
|
from twisted.internet import reactor
|
||||||
reactor.connectTCP("XXX.XXX.XXX.XXX", 3389), MyRFBFactory())
|
reactor.connectTCP("XXX.XXX.XXX.XXX", 3389, MyRFBFactory())
|
||||||
reactor.run()
|
reactor.run()
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -115,7 +115,11 @@ class RDPClientQtFactory(rdp.ClientFactory):
|
|||||||
self._nego = security == "nego"
|
self._nego = security == "nego"
|
||||||
self._recodedPath = recodedPath
|
self._recodedPath = recodedPath
|
||||||
if self._nego:
|
if self._nego:
|
||||||
self._security = "ssl"
|
#compute start nego nla need credentials
|
||||||
|
if username != "" and password != "":
|
||||||
|
self._security = rdp.SecurityLevel.RDP_LEVEL_NLA
|
||||||
|
else:
|
||||||
|
self._security = rdp.SecurityLevel.RDP_LEVEL_SSL
|
||||||
else:
|
else:
|
||||||
self._security = security
|
self._security = security
|
||||||
self._w = None
|
self._w = None
|
||||||
@@ -163,12 +167,12 @@ class RDPClientQtFactory(rdp.ClientFactory):
|
|||||||
#stop nego
|
#stop nego
|
||||||
log.info("due to security nego error back to standard RDP security layer")
|
log.info("due to security nego error back to standard RDP security layer")
|
||||||
self._nego = False
|
self._nego = False
|
||||||
self._security = "rdp"
|
self._security = rdp.SecurityLevel.RDP_LEVEL_RDP
|
||||||
self._client._widget.hide()
|
self._client._widget.hide()
|
||||||
connector.connect()
|
connector.connect()
|
||||||
return
|
return
|
||||||
|
|
||||||
QtGui.QMessageBox.warning(self._w, "Warning", "Lost connection : %s"%reason)
|
log.info("Lost connection : %s"%reason)
|
||||||
reactor.stop()
|
reactor.stop()
|
||||||
app.exit()
|
app.exit()
|
||||||
|
|
||||||
@@ -178,7 +182,7 @@ class RDPClientQtFactory(rdp.ClientFactory):
|
|||||||
@param connector: twisted connector use for rdp connection (use reconnect to restart connection)
|
@param connector: twisted connector use for rdp connection (use reconnect to restart connection)
|
||||||
@param reason: str use to advertise reason of lost connection
|
@param reason: str use to advertise reason of lost connection
|
||||||
"""
|
"""
|
||||||
QtGui.QMessageBox.warning(self._w, "Warning", "Connection failed : %s"%reason)
|
log.info("Connection failed : %s"%reason)
|
||||||
reactor.stop()
|
reactor.stop()
|
||||||
app.exit()
|
app.exit()
|
||||||
|
|
||||||
@@ -190,7 +194,7 @@ def autoDetectKeyboardLayout():
|
|||||||
if os.name == 'posix':
|
if os.name == 'posix':
|
||||||
from subprocess import check_output
|
from subprocess import check_output
|
||||||
result = check_output(["setxkbmap", "-print"])
|
result = check_output(["setxkbmap", "-print"])
|
||||||
if "azerty" in result:
|
if 'azerty' in result:
|
||||||
return "fr"
|
return "fr"
|
||||||
elif os.name == 'nt':
|
elif os.name == 'nt':
|
||||||
import win32api, win32con, win32process
|
import win32api, win32con, win32process
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
RDP Honey pot use Rss scenario file to simulate RDP server
|
RDP Honey pot use Rss scenario file to simulate RDP server
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys, os, getopt, time
|
import sys, os, getopt, time, datetime
|
||||||
|
|
||||||
from rdpy.core import log, error, rss
|
from rdpy.core import log, error, rss
|
||||||
from rdpy.protocol.rdp import rdp
|
from rdpy.protocol.rdp import rdp
|
||||||
@@ -54,23 +54,18 @@ class HoneyPotServer(rdp.RDPServerObserver):
|
|||||||
width, height = self._controller.getScreen()
|
width, height = self._controller.getScreen()
|
||||||
size = width * height
|
size = width * height
|
||||||
rssFilePath = sorted(self._rssFileSizeList, key = lambda x: abs(x[0][0] * x[0][1] - size))[0][1]
|
rssFilePath = sorted(self._rssFileSizeList, key = lambda x: abs(x[0][0] * x[0][1] - size))[0][1]
|
||||||
log.info("select file (%s, %s) -> %s"%(width, height, rssFilePath))
|
log.info("%s --- select file (%s, %s) -> %s"%(datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.%fZ'),width, height, rssFilePath))
|
||||||
self._rssFile = rss.createReader(rssFilePath)
|
self._rssFile = rss.createReader(rssFilePath)
|
||||||
|
|
||||||
domain, username, password = self._controller.getCredentials()
|
domain, username, password = self._controller.getCredentials()
|
||||||
hostname = self._controller.getHostname()
|
hostname = self._controller.getHostname()
|
||||||
log.info("""Credentials:
|
log.info("""%s --- Credentials: domain: %s username: %s password: %s hostname: %s"""%(datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.%fZ'), domain, username, password, hostname));
|
||||||
\tdomain : %s
|
|
||||||
\tusername : %s
|
|
||||||
\tpassword : %s
|
|
||||||
\thostname : %s
|
|
||||||
"""%(domain, username, password, hostname));
|
|
||||||
self.start()
|
self.start()
|
||||||
|
|
||||||
def onClose(self):
|
def onClose(self):
|
||||||
""" HoneyPot """
|
""" HoneyPot """
|
||||||
|
|
||||||
def onKeyEventScancode(self, code, isPressed):
|
def onKeyEventScancode(self, code, isPressed, isExtended):
|
||||||
""" HoneyPot """
|
""" HoneyPot """
|
||||||
|
|
||||||
def onKeyEventUnicode(self, code, isPressed):
|
def onKeyEventUnicode(self, code, isPressed):
|
||||||
@@ -125,7 +120,7 @@ class HoneyPotServerFactory(rdp.ServerFactory):
|
|||||||
@param addr: destination address
|
@param addr: destination address
|
||||||
@see: rdp.ServerFactory.buildObserver
|
@see: rdp.ServerFactory.buildObserver
|
||||||
"""
|
"""
|
||||||
log.info("Connection from %s:%s"%(addr.host, addr.port))
|
log.info("%s --- Connection from %s:%s"%(datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.%fZ'), addr.host, addr.port))
|
||||||
return HoneyPotServer(controller, self._rssFileSizeList)
|
return HoneyPotServer(controller, self._rssFileSizeList)
|
||||||
|
|
||||||
def readSize(filePath):
|
def readSize(filePath):
|
||||||
@@ -146,10 +141,12 @@ def help():
|
|||||||
@summary: Print help in console
|
@summary: Print help in console
|
||||||
"""
|
"""
|
||||||
print """
|
print """
|
||||||
Usage: rdpy-rdphoneypot.py rss_filepath(1..n)
|
Usage: rdpy-rdphoneypot.py
|
||||||
|
[-L logfile]
|
||||||
[-l listen_port default 3389]
|
[-l listen_port default 3389]
|
||||||
[-k private_key_file_path (mandatory for SSL)]
|
[-k private_key_file_path (mandatory for SSL)]
|
||||||
[-c certificate_file_path (mandatory for SSL)]
|
[-c certificate_file_path (mandatory for SSL)]
|
||||||
|
rss_filepath(1..n)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
@@ -159,13 +156,15 @@ if __name__ == '__main__':
|
|||||||
rssFileSizeList = []
|
rssFileSizeList = []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
opts, args = getopt.getopt(sys.argv[1:], "hl:k:c:")
|
opts, args = getopt.getopt(sys.argv[1:], "hl:k:c:L:")
|
||||||
except getopt.GetoptError:
|
except getopt.GetoptError:
|
||||||
help()
|
help()
|
||||||
for opt, arg in opts:
|
for opt, arg in opts:
|
||||||
if opt == "-h":
|
if opt == "-h":
|
||||||
help()
|
help()
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
elif opt == "-L":
|
||||||
|
log._LOG_FILE = arg
|
||||||
elif opt == "-l":
|
elif opt == "-l":
|
||||||
listen = arg
|
listen = arg
|
||||||
elif opt == "-k":
|
elif opt == "-k":
|
||||||
@@ -174,11 +173,12 @@ if __name__ == '__main__':
|
|||||||
certificateFilePath = arg
|
certificateFilePath = arg
|
||||||
|
|
||||||
#build size map
|
#build size map
|
||||||
log.info("Build size map")
|
log.info("%s --- Start rdphoneypot"%datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.%fZ'))
|
||||||
|
log.info("%s --- Build size map"%datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.%fZ'))
|
||||||
for arg in args:
|
for arg in args:
|
||||||
size = readSize(arg)
|
size = readSize(arg)
|
||||||
rssFileSizeList.append((size, arg))
|
rssFileSizeList.append((size, arg))
|
||||||
log.info("(%s, %s) -> %s"%(size[0], size[1], arg))
|
log.info("%s --- (%s, %s) -> %s"%(datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.%fZ'), size[0], size[1], arg))
|
||||||
|
|
||||||
reactor.listenTCP(int(listen), HoneyPotServerFactory(rssFileSizeList, privateKeyFilePath, certificateFilePath))
|
reactor.listenTCP(int(listen), HoneyPotServerFactory(rssFileSizeList, privateKeyFilePath, certificateFilePath))
|
||||||
reactor.run()
|
reactor.run()
|
||||||
@@ -29,7 +29,10 @@ Client RDP -> | ProxyServer | ProxyClient | -> Server RDP
|
|||||||
-----------------
|
-----------------
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys, os, getopt, time
|
import sys
|
||||||
|
import os
|
||||||
|
import argparse
|
||||||
|
import time
|
||||||
|
|
||||||
from rdpy.core import log, error, rss
|
from rdpy.core import log, error, rss
|
||||||
from rdpy.protocol.rdp import rdp
|
from rdpy.protocol.rdp import rdp
|
||||||
@@ -37,10 +40,12 @@ from twisted.internet import reactor
|
|||||||
|
|
||||||
log._LOG_LEVEL = log.Level.INFO
|
log._LOG_LEVEL = log.Level.INFO
|
||||||
|
|
||||||
|
|
||||||
class ProxyServer(rdp.RDPServerObserver):
|
class ProxyServer(rdp.RDPServerObserver):
|
||||||
"""
|
"""
|
||||||
@summary: Server side of proxy
|
@summary: Server side of proxy
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, controller, target, clientSecurityLevel, rssRecorder):
|
def __init__(self, controller, target, clientSecurityLevel, rssRecorder):
|
||||||
"""
|
"""
|
||||||
@param controller: {RDPServerController}
|
@param controller: {RDPServerController}
|
||||||
@@ -69,39 +74,43 @@ class ProxyServer(rdp.RDPServerObserver):
|
|||||||
@see: rdp.RDPServerObserver.onReady
|
@see: rdp.RDPServerObserver.onReady
|
||||||
"""
|
"""
|
||||||
if self._client is None:
|
if self._client is None:
|
||||||
#try a connection
|
# try a connection
|
||||||
domain, username, password = self._controller.getCredentials()
|
domain, username, password = self._controller.getCredentials()
|
||||||
self._rss.credentials(username, password, domain, self._controller.getHostname())
|
self._rss.credentials(username, password,
|
||||||
|
domain, self._controller.getHostname())
|
||||||
|
|
||||||
width, height = self._controller.getScreen()
|
width, height = self._controller.getScreen()
|
||||||
self._rss.screen(width, height, self._controller.getColorDepth())
|
self._rss.screen(width, height, self._controller.getColorDepth())
|
||||||
|
|
||||||
reactor.connectTCP(self._target[0], int(self._target[1]), ProxyClientFactory(self, width, height,
|
reactor.connectTCP(self._target[0], int(self._target[1]), ProxyClientFactory(self, width, height,
|
||||||
domain, username, password,self._clientSecurityLevel))
|
domain, username, password, self._clientSecurityLevel))
|
||||||
|
|
||||||
def onClose(self):
|
def onClose(self):
|
||||||
"""
|
"""
|
||||||
@summary: Call when human client close connection
|
@summary: Call when human client close connection
|
||||||
@see: rdp.RDPServerObserver.onClose
|
@see: rdp.RDPServerObserver.onClose
|
||||||
"""
|
"""
|
||||||
#end scenario
|
# end scenario
|
||||||
self._rss.close()
|
self._rss.close()
|
||||||
|
|
||||||
#close network stack
|
# close network stack
|
||||||
if self._client is None:
|
if self._client is None:
|
||||||
return
|
return
|
||||||
self._client._controller.close()
|
self._client._controller.close()
|
||||||
|
|
||||||
def onKeyEventScancode(self, code, isPressed):
|
def onKeyEventScancode(self, code, isPressed, isExtended):
|
||||||
"""
|
"""
|
||||||
@summary: Event call when a keyboard event is catch in scan code format
|
@summary: Event call when a keyboard event is catch in scan code format
|
||||||
@param code: {int} scan code of key
|
@param code: {integer} scan code of key
|
||||||
@param isPressed: {bool} True if key is down
|
@param isPressed: {boolean} True if key is down
|
||||||
|
@param isExtended: {boolean} True if a special key
|
||||||
@see: rdp.RDPServerObserver.onKeyEventScancode
|
@see: rdp.RDPServerObserver.onKeyEventScancode
|
||||||
"""
|
"""
|
||||||
if self._client is None:
|
if self._client is None:
|
||||||
return
|
return
|
||||||
self._client._controller.sendKeyEventScancode(code, isPressed)
|
self._client._controller.sendKeyEventScancode(
|
||||||
|
code, isPressed, isExtended)
|
||||||
|
self._rss.keyScancode(code, isPressed)
|
||||||
|
|
||||||
def onKeyEventUnicode(self, code, isPressed):
|
def onKeyEventUnicode(self, code, isPressed):
|
||||||
"""
|
"""
|
||||||
@@ -113,13 +122,14 @@ class ProxyServer(rdp.RDPServerObserver):
|
|||||||
if self._client is None:
|
if self._client is None:
|
||||||
return
|
return
|
||||||
self._client._controller.sendKeyEventUnicode(code, isPressed)
|
self._client._controller.sendKeyEventUnicode(code, isPressed)
|
||||||
|
self._rss.keyUnicode(code, isPressed)
|
||||||
|
|
||||||
def onPointerEvent(self, x, y, button, isPressed):
|
def onPointerEvent(self, x, y, button, isPressed):
|
||||||
"""
|
"""
|
||||||
@summary: Event call on mouse event
|
@summary: Event call on mouse event
|
||||||
@param x: {int} x position
|
@param x: {int} x position
|
||||||
@param y: {int} y position
|
@param y: {int} y position
|
||||||
@param button: {int} 1, 2 or 3 button
|
@param button: {int} 1, 2, 3, 4 or 5 button
|
||||||
@param isPressed: {bool} True if mouse button is pressed
|
@param isPressed: {bool} True if mouse button is pressed
|
||||||
@see: rdp.RDPServerObserver.onPointerEvent
|
@see: rdp.RDPServerObserver.onPointerEvent
|
||||||
"""
|
"""
|
||||||
@@ -127,10 +137,12 @@ class ProxyServer(rdp.RDPServerObserver):
|
|||||||
return
|
return
|
||||||
self._client._controller.sendPointerEvent(x, y, button, isPressed)
|
self._client._controller.sendPointerEvent(x, y, button, isPressed)
|
||||||
|
|
||||||
|
|
||||||
class ProxyServerFactory(rdp.ServerFactory):
|
class ProxyServerFactory(rdp.ServerFactory):
|
||||||
"""
|
"""
|
||||||
@summary: Factory on listening events
|
@summary: Factory on listening events
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, target, ouputDir, privateKeyFilePath, certificateFilePath, clientSecurity):
|
def __init__(self, target, ouputDir, privateKeyFilePath, certificateFilePath, clientSecurity):
|
||||||
"""
|
"""
|
||||||
@param target: {tuple(ip, prt)}
|
@param target: {tuple(ip, prt)}
|
||||||
@@ -138,11 +150,12 @@ class ProxyServerFactory(rdp.ServerFactory):
|
|||||||
@param certificateFilePath: {str} file contain server certificate (if none -> back to standard RDP security)
|
@param certificateFilePath: {str} file contain server certificate (if none -> back to standard RDP security)
|
||||||
@param clientSecurity: {str(ssl|rdp)} security layer use in client connection side
|
@param clientSecurity: {str(ssl|rdp)} security layer use in client connection side
|
||||||
"""
|
"""
|
||||||
rdp.ServerFactory.__init__(self, 16, privateKeyFilePath, certificateFilePath)
|
rdp.ServerFactory.__init__(
|
||||||
|
self, 16, privateKeyFilePath, certificateFilePath)
|
||||||
self._target = target
|
self._target = target
|
||||||
self._ouputDir = ouputDir
|
self._ouputDir = ouputDir
|
||||||
self._clientSecurity = clientSecurity
|
self._clientSecurity = clientSecurity
|
||||||
#use produce unique file by connection
|
# use produce unique file by connection
|
||||||
self._uniqueId = 0
|
self._uniqueId = 0
|
||||||
|
|
||||||
def buildObserver(self, controller, addr):
|
def buildObserver(self, controller, addr):
|
||||||
@@ -152,12 +165,14 @@ class ProxyServerFactory(rdp.ServerFactory):
|
|||||||
@see: rdp.ServerFactory.buildObserver
|
@see: rdp.ServerFactory.buildObserver
|
||||||
"""
|
"""
|
||||||
self._uniqueId += 1
|
self._uniqueId += 1
|
||||||
return ProxyServer(controller, self._target, self._clientSecurity, rss.createRecorder(os.path.join(self._ouputDir, "%s_%s_%s.rss"%(time.strftime('%Y%m%d%H%M%S'), addr.host, self._uniqueId))))
|
return ProxyServer(controller, self._target, self._clientSecurity, rss.createRecorder(os.path.join(self._ouputDir, "%s_%s_%s.rss" % (time.strftime('%Y%m%d%H%M%S'), addr.host, self._uniqueId))))
|
||||||
|
|
||||||
|
|
||||||
class ProxyClient(rdp.RDPClientObserver):
|
class ProxyClient(rdp.RDPClientObserver):
|
||||||
"""
|
"""
|
||||||
@summary: Client side of proxy
|
@summary: Client side of proxy
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, controller, server):
|
def __init__(self, controller, server):
|
||||||
"""
|
"""
|
||||||
@param controller: {rdp.RDPClientController}
|
@param controller: {rdp.RDPClientController}
|
||||||
@@ -173,15 +188,23 @@ class ProxyClient(rdp.RDPClientObserver):
|
|||||||
@see: rdp.RDPClientObserver.onReady
|
@see: rdp.RDPClientObserver.onReady
|
||||||
"""
|
"""
|
||||||
self._server.setClient(self)
|
self._server.setClient(self)
|
||||||
#maybe color depth change
|
# maybe color depth change
|
||||||
self._server._controller.setColorDepth(self._controller.getColorDepth())
|
self._server._controller.setColorDepth(
|
||||||
|
self._controller.getColorDepth())
|
||||||
|
|
||||||
|
def onSessionReady(self):
|
||||||
|
"""
|
||||||
|
@summary: Windows session is ready
|
||||||
|
@see: rdp.RDPClientObserver.onSessionReady
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
def onClose(self):
|
def onClose(self):
|
||||||
"""
|
"""
|
||||||
@summary: Event inform that stack is close
|
@summary: Event inform that stack is close
|
||||||
@see: rdp.RDPClientObserver.onClose
|
@see: rdp.RDPClientObserver.onClose
|
||||||
"""
|
"""
|
||||||
#end scenario
|
# end scenario
|
||||||
self._server._rss.close()
|
self._server._rss.close()
|
||||||
self._server._controller.close()
|
self._server._controller.close()
|
||||||
|
|
||||||
@@ -199,13 +222,17 @@ class ProxyClient(rdp.RDPClientObserver):
|
|||||||
@param data: {str} bitmap data
|
@param data: {str} bitmap data
|
||||||
@see: rdp.RDPClientObserver.onUpdate
|
@see: rdp.RDPClientObserver.onUpdate
|
||||||
"""
|
"""
|
||||||
self._server._rss.update(destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, rss.UpdateFormat.BMP if isCompress else rss.UpdateFormat.RAW, data)
|
self._server._rss.update(destLeft, destTop, destRight, destBottom, width, height,
|
||||||
self._server._controller.sendUpdate(destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data)
|
bitsPerPixel, rss.UpdateFormat.BMP if isCompress else rss.UpdateFormat.RAW, data)
|
||||||
|
self._server._controller.sendUpdate(
|
||||||
|
destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data)
|
||||||
|
|
||||||
|
|
||||||
class ProxyClientFactory(rdp.ClientFactory):
|
class ProxyClientFactory(rdp.ClientFactory):
|
||||||
"""
|
"""
|
||||||
@summary: Factory for proxy client
|
@summary: Factory for proxy client
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, server, width, height, domain, username, password, security):
|
def __init__(self, server, width, height, domain, username, password, security):
|
||||||
"""
|
"""
|
||||||
@param server: {ProxyServer}
|
@param server: {ProxyServer}
|
||||||
@@ -232,9 +259,9 @@ class ProxyClientFactory(rdp.ClientFactory):
|
|||||||
@see: rdp.ClientFactory.buildObserver
|
@see: rdp.ClientFactory.buildObserver
|
||||||
@return: ProxyClient
|
@return: ProxyClient
|
||||||
"""
|
"""
|
||||||
#set screen resolution
|
# set screen resolution
|
||||||
controller.setScreen(self._width, self._height)
|
controller.setScreen(self._width, self._height)
|
||||||
#set credential
|
# set credential
|
||||||
controller.setDomain(self._domain)
|
controller.setDomain(self._domain)
|
||||||
controller.setUsername(self._username)
|
controller.setUsername(self._username)
|
||||||
controller.setPassword(self._password)
|
controller.setPassword(self._password)
|
||||||
@@ -242,54 +269,55 @@ class ProxyClientFactory(rdp.ClientFactory):
|
|||||||
controller.setPerformanceSession()
|
controller.setPerformanceSession()
|
||||||
return ProxyClient(controller, self._server)
|
return ProxyClient(controller, self._server)
|
||||||
|
|
||||||
def help():
|
|
||||||
"""
|
|
||||||
@summary: Print help in console
|
|
||||||
"""
|
|
||||||
print """
|
|
||||||
Usage: rdpy-rdpmitm.py -o output_directory target
|
|
||||||
[-l listen_port default 3389]
|
|
||||||
[-k private_key_file_path (mandatory for SSL)]
|
|
||||||
[-c certificate_file_path (mandatory for SSL)]
|
|
||||||
[-r RDP standard security (XP or server 2003 client or older)]
|
|
||||||
"""
|
|
||||||
|
|
||||||
def parseIpPort(interface, defaultPort = "3389"):
|
def parseIpPort(interface, defaultPort="3389"):
|
||||||
if ':' in interface:
|
if ':' in interface:
|
||||||
return interface.split(':')
|
s = interface.split(':')
|
||||||
|
return s[0], int(s[1])
|
||||||
else:
|
else:
|
||||||
return interface, defaultPort
|
return interface, int(defaultPort)
|
||||||
|
|
||||||
|
|
||||||
|
def isDirectory(outputDirectory):
|
||||||
|
if outputDirectory is None or not os.path.dirname(outputDirectory):
|
||||||
|
log.error("{} is an invalid output directory or directory doesn't exist".format(
|
||||||
|
outputDirectory))
|
||||||
|
return outputDirectory
|
||||||
|
|
||||||
|
|
||||||
|
def mapSecurityLayer(layer):
|
||||||
|
return {
|
||||||
|
"rdp": rdp.SecurityLevel.RDP_LEVEL_RDP,
|
||||||
|
"tls": rdp.SecurityLevel.RDP_LEVEL_SSL,
|
||||||
|
"nla": rdp.SecurityLevel.RDP_LEVEL_NLA
|
||||||
|
}[layer]
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
listen = "3389"
|
p = argparse.ArgumentParser(
|
||||||
privateKeyFilePath = None
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||||
certificateFilePath = None
|
|
||||||
ouputDirectory = None
|
|
||||||
clientSecurity = "ssl"
|
|
||||||
|
|
||||||
try:
|
p.add_argument('-l', '--listen', type=parseIpPort, default="0.0.0.0:3389",
|
||||||
opts, args = getopt.getopt(sys.argv[1:], "hl:k:c:o:r")
|
help="<addr>[:<port>] to bind the server")
|
||||||
except getopt.GetoptError:
|
p.add_argument('-t', '--target', type=parseIpPort, required=True,
|
||||||
help()
|
help="<addr>[:<port>] of the target you want to connect to via proxy")
|
||||||
for opt, arg in opts:
|
p.add_argument('-o', '--output', type=isDirectory,
|
||||||
if opt == "-h":
|
help="output directory", required=True)
|
||||||
help()
|
p.add_argument('-s', '--sec', choices=["rdp", "tls", "nla"],
|
||||||
sys.exit()
|
default="rdp", help="set protocol security layer")
|
||||||
elif opt == "-l":
|
ssl = p.add_argument_group()
|
||||||
listen = arg
|
ssl.add_argument('-c', '--certificate', help="certificate for TLS connections")
|
||||||
elif opt == "-k":
|
ssl.add_argument('-k', '--key', help="private key of the given certificate for TLS connections")
|
||||||
privateKeyFilePath = arg
|
|
||||||
elif opt == "-c":
|
|
||||||
certificateFilePath = arg
|
|
||||||
elif opt == "-o":
|
|
||||||
ouputDirectory = arg
|
|
||||||
elif opt == "-r":
|
|
||||||
clientSecurity = "rdp"
|
|
||||||
|
|
||||||
if ouputDirectory is None or not os.path.dirname(ouputDirectory):
|
args = p.parse_args()
|
||||||
log.error("%s is an invalid output directory"%ouputDirectory)
|
|
||||||
help()
|
if args.certificate and args.key and not args.sec == "nla":
|
||||||
sys.exit()
|
args.sec = "tls"
|
||||||
|
|
||||||
|
log.info("running server on {addr}, using {sec} security layer, proxying to {target}".format(
|
||||||
|
addr=args.listen, sec=args.sec.upper(), target=args.target))
|
||||||
|
reactor.listenTCP(args.listen[1], ProxyServerFactory(
|
||||||
|
args.target, args.output, args.key, args.certificate, mapSecurityLayer(args.sec)),
|
||||||
|
interface=args.listen[0])
|
||||||
|
|
||||||
reactor.listenTCP(int(listen), ProxyServerFactory(parseIpPort(args[0]), ouputDirectory, privateKeyFilePath, certificateFilePath, clientSecurity))
|
|
||||||
reactor.run()
|
reactor.run()
|
||||||
@@ -23,7 +23,9 @@ example of use rdpy
|
|||||||
take screenshot of login page
|
take screenshot of login page
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys, os, getopt
|
import getopt
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
from PyQt4 import QtCore, QtGui
|
from PyQt4 import QtCore, QtGui
|
||||||
from rdpy.protocol.rdp import rdp
|
from rdpy.protocol.rdp import rdp
|
||||||
@@ -32,15 +34,17 @@ import rdpy.core.log as log
|
|||||||
from rdpy.core.error import RDPSecurityNegoFail
|
from rdpy.core.error import RDPSecurityNegoFail
|
||||||
from twisted.internet import task
|
from twisted.internet import task
|
||||||
|
|
||||||
#set log level
|
# set log level
|
||||||
log._LOG_LEVEL = log.Level.INFO
|
log._LOG_LEVEL = log.Level.INFO
|
||||||
|
|
||||||
|
|
||||||
class RDPScreenShotFactory(rdp.ClientFactory):
|
class RDPScreenShotFactory(rdp.ClientFactory):
|
||||||
"""
|
"""
|
||||||
@summary: Factory for screenshot exemple
|
@summary: Factory for screenshot exemple
|
||||||
"""
|
"""
|
||||||
__INSTANCE__ = 0
|
__INSTANCE__ = 0
|
||||||
__STATE__ = []
|
__STATE__ = []
|
||||||
|
|
||||||
def __init__(self, reactor, app, width, height, path, timeout):
|
def __init__(self, reactor, app, width, height, path, timeout):
|
||||||
"""
|
"""
|
||||||
@param reactor: twisted reactor
|
@param reactor: twisted reactor
|
||||||
@@ -56,7 +60,8 @@ class RDPScreenShotFactory(rdp.ClientFactory):
|
|||||||
self._height = height
|
self._height = height
|
||||||
self._path = path
|
self._path = path
|
||||||
self._timeout = timeout
|
self._timeout = timeout
|
||||||
self._security = "ssl"
|
#NLA server can't be screenshooting
|
||||||
|
self._security = rdp.SecurityLevel.RDP_LEVEL_SSL
|
||||||
|
|
||||||
def clientConnectionLost(self, connector, reason):
|
def clientConnectionLost(self, connector, reason):
|
||||||
"""
|
"""
|
||||||
@@ -66,11 +71,11 @@ class RDPScreenShotFactory(rdp.ClientFactory):
|
|||||||
"""
|
"""
|
||||||
if reason.type == RDPSecurityNegoFail and self._security != "rdp":
|
if reason.type == RDPSecurityNegoFail and self._security != "rdp":
|
||||||
log.info("due to RDPSecurityNegoFail try standard security layer")
|
log.info("due to RDPSecurityNegoFail try standard security layer")
|
||||||
self._security = "rdp"
|
self._security = rdp.SecurityLevel.RDP_LEVEL_RDP
|
||||||
connector.connect()
|
connector.connect()
|
||||||
return
|
return
|
||||||
|
|
||||||
log.info("connection lost : %s"%reason)
|
log.info("connection lost : %s" % reason)
|
||||||
RDPScreenShotFactory.__STATE__.append((connector.host, connector.port, reason))
|
RDPScreenShotFactory.__STATE__.append((connector.host, connector.port, reason))
|
||||||
RDPScreenShotFactory.__INSTANCE__ -= 1
|
RDPScreenShotFactory.__INSTANCE__ -= 1
|
||||||
if(RDPScreenShotFactory.__INSTANCE__ == 0):
|
if(RDPScreenShotFactory.__INSTANCE__ == 0):
|
||||||
@@ -90,7 +95,6 @@ class RDPScreenShotFactory(rdp.ClientFactory):
|
|||||||
self._reactor.stop()
|
self._reactor.stop()
|
||||||
self._app.exit()
|
self._app.exit()
|
||||||
|
|
||||||
|
|
||||||
def buildObserver(self, controller, addr):
|
def buildObserver(self, controller, addr):
|
||||||
"""
|
"""
|
||||||
@summary: build ScreenShot observer
|
@summary: build ScreenShot observer
|
||||||
@@ -123,7 +127,7 @@ class RDPScreenShotFactory(rdp.ClientFactory):
|
|||||||
"""
|
"""
|
||||||
image = RDPBitmapToQtImage(width, height, bitsPerPixel, isCompress, data);
|
image = RDPBitmapToQtImage(width, height, bitsPerPixel, isCompress, data);
|
||||||
with QtGui.QPainter(self._buffer) as qp:
|
with QtGui.QPainter(self._buffer) as qp:
|
||||||
#draw image
|
# draw image
|
||||||
qp.drawImage(destLeft, destTop, image, 0, 0, destRight - destLeft + 1, destBottom - destTop + 1)
|
qp.drawImage(destLeft, destTop, image, 0, 0, destRight - destLeft + 1, destBottom - destTop + 1)
|
||||||
if not self._startTimeout:
|
if not self._startTimeout:
|
||||||
self._startTimeout = False
|
self._startTimeout = False
|
||||||
@@ -133,19 +137,26 @@ class RDPScreenShotFactory(rdp.ClientFactory):
|
|||||||
"""
|
"""
|
||||||
@summary: callback use when RDP stack is connected (just before received bitmap)
|
@summary: callback use when RDP stack is connected (just before received bitmap)
|
||||||
"""
|
"""
|
||||||
log.info("connected %s"%addr)
|
log.info("connected %s" % addr)
|
||||||
|
|
||||||
|
def onSessionReady(self):
|
||||||
|
"""
|
||||||
|
@summary: Windows session is ready
|
||||||
|
@see: rdp.RDPClientObserver.onSessionReady
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
def onClose(self):
|
def onClose(self):
|
||||||
"""
|
"""
|
||||||
@summary: callback use when RDP stack is closed
|
@summary: callback use when RDP stack is closed
|
||||||
"""
|
"""
|
||||||
log.info("save screenshot into %s"%self._path)
|
log.info("save screenshot into %s" % self._path)
|
||||||
self._buffer.save(self._path)
|
self._buffer.save(self._path)
|
||||||
|
|
||||||
def checkUpdate(self):
|
def checkUpdate(self):
|
||||||
self._controller.close();
|
self._controller.close();
|
||||||
|
|
||||||
controller.setScreen(width, height);
|
controller.setScreen(self._width, self._height);
|
||||||
controller.setSecurityLevel(self._security)
|
controller.setSecurityLevel(self._security)
|
||||||
return ScreenShotObserver(controller, self._width, self._height, self._path, self._timeout, self._reactor)
|
return ScreenShotObserver(controller, self._width, self._height, self._path, self._timeout, self._reactor)
|
||||||
|
|
||||||
@@ -173,12 +184,13 @@ def main(width, height, path, timeout, hosts):
|
|||||||
else:
|
else:
|
||||||
ip, port = host, "3389"
|
ip, port = host, "3389"
|
||||||
|
|
||||||
reactor.connectTCP(ip, int(port), RDPScreenShotFactory(reactor, app, width, height, path + "%s.jpg"%ip, timeout))
|
reactor.connectTCP(ip, int(port), RDPScreenShotFactory(reactor, app, width, height, path + "%s.jpg" % ip, timeout))
|
||||||
|
|
||||||
reactor.runReturn()
|
reactor.runReturn()
|
||||||
app.exec_()
|
app.exec_()
|
||||||
return RDPScreenShotFactory.__STATE__
|
return RDPScreenShotFactory.__STATE__
|
||||||
|
|
||||||
|
|
||||||
def help():
|
def help():
|
||||||
print "Usage: rdpy-rdpscreenshot [options] ip[:port]"
|
print "Usage: rdpy-rdpscreenshot [options] ip[:port]"
|
||||||
print "\t-w: width of screen default value is 1024"
|
print "\t-w: width of screen default value is 1024"
|
||||||
@@ -187,7 +199,7 @@ def help():
|
|||||||
print "\t-t: timeout of connection without any updating order (default is 2s)"
|
print "\t-t: timeout of connection without any updating order (default is 2s)"
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
#default script argument
|
# default script argument
|
||||||
width = 1024
|
width = 1024
|
||||||
height = 800
|
height = 800
|
||||||
path = "/tmp/"
|
path = "/tmp/"
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ from PyQt4 import QtGui, QtCore
|
|||||||
|
|
||||||
from rdpy.core import log, rss
|
from rdpy.core import log, rss
|
||||||
from rdpy.ui.qt4 import QRemoteDesktop, RDPBitmapToQtImage
|
from rdpy.ui.qt4 import QRemoteDesktop, RDPBitmapToQtImage
|
||||||
|
from rdpy.core.scancode import scancodeToChar
|
||||||
log._LOG_LEVEL = log.Level.INFO
|
log._LOG_LEVEL = log.Level.INFO
|
||||||
|
|
||||||
class RssPlayerWidget(QRemoteDesktop):
|
class RssPlayerWidget(QRemoteDesktop):
|
||||||
@@ -45,9 +46,28 @@ class RssPlayerWidget(QRemoteDesktop):
|
|||||||
""" Not Handle """
|
""" Not Handle """
|
||||||
QRemoteDesktop.__init__(self, width, height, RssAdaptor())
|
QRemoteDesktop.__init__(self, width, height, RssAdaptor())
|
||||||
|
|
||||||
def drawInfos(self, domain, username, password, hostname):
|
class RssPlayerWindow(QtGui.QWidget):
|
||||||
QtGui.QMessageBox.about(self, "Credentials Event", "domain : %s\nusername : %s\npassword : %s\nhostname : %s" % (
|
"""
|
||||||
domain, username, password, hostname))
|
@summary: main window of rss player
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
super(RssPlayerWindow, self).__init__()
|
||||||
|
|
||||||
|
self._viewer = RssPlayerWidget(800, 600)
|
||||||
|
self._text = QtGui.QTextEdit()
|
||||||
|
self._text.setReadOnly(True)
|
||||||
|
self._text.setFixedHeight(150)
|
||||||
|
|
||||||
|
scrollViewer = QtGui.QScrollArea()
|
||||||
|
scrollViewer.setWidget(self._viewer)
|
||||||
|
|
||||||
|
layout = QtGui.QVBoxLayout()
|
||||||
|
layout.addWidget(scrollViewer, 1)
|
||||||
|
layout.addWidget(self._text, 2)
|
||||||
|
|
||||||
|
self.setLayout(layout)
|
||||||
|
|
||||||
|
self.setGeometry(0, 0, 800, 600)
|
||||||
|
|
||||||
def help():
|
def help():
|
||||||
print "Usage: rdpy-rssplayer [-h] rss_filepath"
|
print "Usage: rdpy-rssplayer [-h] rss_filepath"
|
||||||
@@ -64,16 +84,20 @@ def loop(widget, rssFile, nextEvent):
|
|||||||
|
|
||||||
if nextEvent.type.value == rss.EventType.UPDATE:
|
if nextEvent.type.value == rss.EventType.UPDATE:
|
||||||
image = RDPBitmapToQtImage(nextEvent.event.width.value, nextEvent.event.height.value, nextEvent.event.bpp.value, nextEvent.event.format.value == rss.UpdateFormat.BMP, nextEvent.event.data.value);
|
image = RDPBitmapToQtImage(nextEvent.event.width.value, nextEvent.event.height.value, nextEvent.event.bpp.value, nextEvent.event.format.value == rss.UpdateFormat.BMP, nextEvent.event.data.value);
|
||||||
widget.notifyImage(nextEvent.event.destLeft.value, nextEvent.event.destTop.value, image, nextEvent.event.destRight.value - nextEvent.event.destLeft.value + 1, nextEvent.event.destBottom.value - nextEvent.event.destTop.value + 1)
|
widget._viewer.notifyImage(nextEvent.event.destLeft.value, nextEvent.event.destTop.value, image, nextEvent.event.destRight.value - nextEvent.event.destLeft.value + 1, nextEvent.event.destBottom.value - nextEvent.event.destTop.value + 1)
|
||||||
|
|
||||||
elif nextEvent.type.value == rss.EventType.SCREEN:
|
elif nextEvent.type.value == rss.EventType.SCREEN:
|
||||||
widget.resize(nextEvent.event.width.value, nextEvent.event.height.value)
|
widget._viewer.resize(nextEvent.event.width.value, nextEvent.event.height.value)
|
||||||
|
|
||||||
elif nextEvent.type.value == rss.EventType.INFO:
|
elif nextEvent.type.value == rss.EventType.INFO:
|
||||||
widget.drawInfos(nextEvent.event.domain.value, nextEvent.event.username.value, nextEvent.event.password.value, nextEvent.event.hostname.value)
|
widget._text.append("Domain : %s\nUsername : %s\nPassword : %s\nHostname : %s\n" % (
|
||||||
|
nextEvent.event.domain.value, nextEvent.event.username.value, nextEvent.event.password.value, nextEvent.event.hostname.value))
|
||||||
|
elif nextEvent.type.value == rss.EventType.KEY_SCANCODE:
|
||||||
|
if nextEvent.event.isPressed.value == 0:
|
||||||
|
widget._text.moveCursor(QtGui.QTextCursor.End)
|
||||||
|
widget._text.insertPlainText(scancodeToChar(nextEvent.event.code.value))
|
||||||
|
|
||||||
elif nextEvent.type.value == rss.EventType.CLOSE:
|
elif nextEvent.type.value == rss.EventType.CLOSE:
|
||||||
widget.close()
|
|
||||||
return
|
return
|
||||||
|
|
||||||
e = rssFile.nextEvent()
|
e = rssFile.nextEvent()
|
||||||
@@ -92,8 +116,10 @@ if __name__ == '__main__':
|
|||||||
filepath = args[0]
|
filepath = args[0]
|
||||||
#create application
|
#create application
|
||||||
app = QtGui.QApplication(sys.argv)
|
app = QtGui.QApplication(sys.argv)
|
||||||
widget = RssPlayerWidget(800, 600)
|
|
||||||
widget.show()
|
mainWindow = RssPlayerWindow()
|
||||||
|
mainWindow.show()
|
||||||
|
|
||||||
rssFile = rss.createReader(filepath)
|
rssFile = rss.createReader(filepath)
|
||||||
start(widget, rssFile)
|
start(mainWindow, rssFile)
|
||||||
sys.exit(app.exec_())
|
sys.exit(app.exec_())
|
||||||
98
rdpy/core/filetimes.py
Normal file
98
rdpy/core/filetimes.py
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
# Copyright (c) 2009, David Buxton <david@gasmark6.com>
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions are
|
||||||
|
# met:
|
||||||
|
#
|
||||||
|
# * Redistributions of source code must retain the above copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer.
|
||||||
|
# * Redistributions in binary form must reproduce the above copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer in the
|
||||||
|
# documentation and/or other materials provided with the distribution.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||||
|
# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||||
|
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||||
|
# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
||||||
|
# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
"""Tools to convert between Python datetime instances and Microsoft times.
|
||||||
|
"""
|
||||||
|
from datetime import datetime, timedelta, tzinfo
|
||||||
|
from calendar import timegm
|
||||||
|
|
||||||
|
|
||||||
|
# http://support.microsoft.com/kb/167296
|
||||||
|
# How To Convert a UNIX time_t to a Win32 FILETIME or SYSTEMTIME
|
||||||
|
EPOCH_AS_FILETIME = 116444736000000000 # January 1, 1970 as MS file time
|
||||||
|
HUNDREDS_OF_NANOSECONDS = 10000000
|
||||||
|
|
||||||
|
|
||||||
|
ZERO = timedelta(0)
|
||||||
|
HOUR = timedelta(hours=1)
|
||||||
|
|
||||||
|
|
||||||
|
class UTC(tzinfo):
|
||||||
|
"""UTC"""
|
||||||
|
def utcoffset(self, dt):
|
||||||
|
return ZERO
|
||||||
|
|
||||||
|
def tzname(self, dt):
|
||||||
|
return "UTC"
|
||||||
|
|
||||||
|
def dst(self, dt):
|
||||||
|
return ZERO
|
||||||
|
|
||||||
|
|
||||||
|
utc = UTC()
|
||||||
|
|
||||||
|
|
||||||
|
def dt_to_filetime(dt):
|
||||||
|
"""Converts a datetime to Microsoft filetime format. If the object is
|
||||||
|
time zone-naive, it is forced to UTC before conversion.
|
||||||
|
|
||||||
|
>>> "%.0f" % dt_to_filetime(datetime(2009, 7, 25, 23, 0))
|
||||||
|
'128930364000000000'
|
||||||
|
|
||||||
|
>>> "%.0f" % dt_to_filetime(datetime(1970, 1, 1, 0, 0, tzinfo=utc))
|
||||||
|
'116444736000000000'
|
||||||
|
|
||||||
|
>>> "%.0f" % dt_to_filetime(datetime(1970, 1, 1, 0, 0))
|
||||||
|
'116444736000000000'
|
||||||
|
|
||||||
|
>>> dt_to_filetime(datetime(2009, 7, 25, 23, 0, 0, 100))
|
||||||
|
128930364000001000
|
||||||
|
"""
|
||||||
|
if (dt.tzinfo is None) or (dt.tzinfo.utcoffset(dt) is None):
|
||||||
|
dt = dt.replace(tzinfo=utc)
|
||||||
|
ft = EPOCH_AS_FILETIME + (timegm(dt.timetuple()) * HUNDREDS_OF_NANOSECONDS)
|
||||||
|
return ft + (dt.microsecond * 10)
|
||||||
|
|
||||||
|
|
||||||
|
def filetime_to_dt(ft):
|
||||||
|
"""Converts a Microsoft filetime number to a Python datetime. The new
|
||||||
|
datetime object is time zone-naive but is equivalent to tzinfo=utc.
|
||||||
|
|
||||||
|
>>> filetime_to_dt(116444736000000000)
|
||||||
|
datetime.datetime(1970, 1, 1, 0, 0)
|
||||||
|
|
||||||
|
>>> filetime_to_dt(128930364000000000)
|
||||||
|
datetime.datetime(2009, 7, 25, 23, 0)
|
||||||
|
|
||||||
|
>>> filetime_to_dt(128930364000001000)
|
||||||
|
datetime.datetime(2009, 7, 25, 23, 0, 0, 100)
|
||||||
|
"""
|
||||||
|
# Get seconds and remainder in terms of Unix epoch
|
||||||
|
(s, ns100) = divmod(ft - EPOCH_AS_FILETIME, HUNDREDS_OF_NANOSECONDS)
|
||||||
|
# Convert to datetime object
|
||||||
|
dt = datetime.utcfromtimestamp(s)
|
||||||
|
# Add remainder in as microseconds. Python 3.2 requires an integer
|
||||||
|
dt = dt.replace(microsecond=(ns100 // 10))
|
||||||
|
return dt
|
||||||
|
|
||||||
@@ -137,7 +137,7 @@ class RawLayerClientFactory(protocol.ClientFactory):
|
|||||||
"""
|
"""
|
||||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "connectionLost", "RawLayerClientFactory"))
|
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "connectionLost", "RawLayerClientFactory"))
|
||||||
|
|
||||||
class RawLayerServerFactory(protocol.ClientFactory):
|
class RawLayerServerFactory(protocol.ServerFactory):
|
||||||
"""
|
"""
|
||||||
@summary: Abstract class for Raw layer server factory
|
@summary: Abstract class for Raw layer server factory
|
||||||
"""
|
"""
|
||||||
@@ -200,7 +200,7 @@ class RawLayer(protocol.Protocol, LayerAutomata, IStreamSender):
|
|||||||
#add in buffer
|
#add in buffer
|
||||||
self._buffer += data
|
self._buffer += data
|
||||||
#while buffer have expected size call local callback
|
#while buffer have expected size call local callback
|
||||||
while len(self._buffer) >= self._expectedLen:
|
while self._expectedLen > 0 and len(self._buffer) >= self._expectedLen:
|
||||||
#expected data is first expected bytes
|
#expected data is first expected bytes
|
||||||
expectedData = Stream(self._buffer[0:self._expectedLen])
|
expectedData = Stream(self._buffer[0:self._expectedLen])
|
||||||
#rest is for next event of automata
|
#rest is for next event of automata
|
||||||
@@ -222,13 +222,19 @@ class RawLayer(protocol.Protocol, LayerAutomata, IStreamSender):
|
|||||||
"""
|
"""
|
||||||
self._factory.connectionLost(self, reason)
|
self._factory.connectionLost(self, reason)
|
||||||
|
|
||||||
|
def getDescriptor(self):
|
||||||
|
"""
|
||||||
|
@return: the twited file descriptor
|
||||||
|
"""
|
||||||
|
return self.transport
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
"""
|
"""
|
||||||
@summary: Close raw layer
|
@summary: Close raw layer
|
||||||
Use File descriptor directly to not use TLS close
|
Use File descriptor directly to not use TLS close
|
||||||
Because is bugged
|
Because is bugged
|
||||||
"""
|
"""
|
||||||
FileDescriptor.loseConnection(self.transport)
|
FileDescriptor.loseConnection(self.getDescriptor())
|
||||||
|
|
||||||
def expect(self, expectedLen, callback = None):
|
def expect(self, expectedLen, callback = None):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -33,13 +33,18 @@ class Level(object):
|
|||||||
NONE = 4
|
NONE = 4
|
||||||
|
|
||||||
_LOG_LEVEL = Level.DEBUG
|
_LOG_LEVEL = Level.DEBUG
|
||||||
|
_LOG_FILE = False
|
||||||
|
|
||||||
def log(message):
|
def log(message):
|
||||||
"""
|
"""
|
||||||
@summary: Main log function
|
@summary: Main log function
|
||||||
@param message: string to print
|
@param message: string to print
|
||||||
"""
|
"""
|
||||||
print message
|
if _LOG_FILE:
|
||||||
|
f = open(_LOG_FILE, "a+")
|
||||||
|
f.write("%s\n"%message)
|
||||||
|
f.close()
|
||||||
|
print "[*] %s"%message
|
||||||
|
|
||||||
def error(message):
|
def error(message):
|
||||||
"""
|
"""
|
||||||
@@ -48,7 +53,7 @@ def error(message):
|
|||||||
"""
|
"""
|
||||||
if _LOG_LEVEL > Level.ERROR:
|
if _LOG_LEVEL > Level.ERROR:
|
||||||
return
|
return
|
||||||
log("ERROR : %s"%message)
|
log("ERROR:\t%s"%message)
|
||||||
|
|
||||||
def warning(message):
|
def warning(message):
|
||||||
"""
|
"""
|
||||||
@@ -57,7 +62,7 @@ def warning(message):
|
|||||||
"""
|
"""
|
||||||
if _LOG_LEVEL > Level.WARNING:
|
if _LOG_LEVEL > Level.WARNING:
|
||||||
return
|
return
|
||||||
log("WARNING : %s"%message)
|
log("WARNING:\t%s"%message)
|
||||||
|
|
||||||
def info(message):
|
def info(message):
|
||||||
"""
|
"""
|
||||||
@@ -66,7 +71,7 @@ def info(message):
|
|||||||
"""
|
"""
|
||||||
if _LOG_LEVEL > Level.INFO:
|
if _LOG_LEVEL > Level.INFO:
|
||||||
return
|
return
|
||||||
log("INFO : %s"%message)
|
log("INFO:\t%s"%message)
|
||||||
|
|
||||||
def debug(message):
|
def debug(message):
|
||||||
"""
|
"""
|
||||||
@@ -75,4 +80,4 @@ def debug(message):
|
|||||||
"""
|
"""
|
||||||
if _LOG_LEVEL > Level.DEBUG:
|
if _LOG_LEVEL > Level.DEBUG:
|
||||||
return
|
return
|
||||||
log("DEBUG : %s"%message)
|
log("DEBUG:\t%s"%message)
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ class EventType(object):
|
|||||||
SCREEN = 0x0002
|
SCREEN = 0x0002
|
||||||
INFO = 0x0003
|
INFO = 0x0003
|
||||||
CLOSE = 0x0004
|
CLOSE = 0x0004
|
||||||
|
KEY_UNICODE = 0x0005
|
||||||
|
KEY_SCANCODE = 0x0006
|
||||||
|
|
||||||
class UpdateFormat(object):
|
class UpdateFormat(object):
|
||||||
"""
|
"""
|
||||||
@@ -56,7 +58,7 @@ class Event(CompositeType):
|
|||||||
"""
|
"""
|
||||||
@summary: Closure for event factory
|
@summary: Closure for event factory
|
||||||
"""
|
"""
|
||||||
for c in [UpdateEvent, ScreenEvent, InfoEvent, CloseEvent]:
|
for c in [UpdateEvent, ScreenEvent, InfoEvent, CloseEvent, KeyEventScancode, KeyEventUnicode]:
|
||||||
if self.type.value == c._TYPE_:
|
if self.type.value == c._TYPE_:
|
||||||
return c(readLen = self.length)
|
return c(readLen = self.length)
|
||||||
log.debug("unknown event type : %s"%hex(self.type.value))
|
log.debug("unknown event type : %s"%hex(self.type.value))
|
||||||
@@ -123,6 +125,26 @@ class CloseEvent(CompositeType):
|
|||||||
def __init__(self, readLen = None):
|
def __init__(self, readLen = None):
|
||||||
CompositeType.__init__(self, readLen = readLen)
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
|
|
||||||
|
class KeyEventUnicode(CompositeType):
|
||||||
|
"""
|
||||||
|
@summary: keyboard event (keylogger) as unicode event
|
||||||
|
"""
|
||||||
|
_TYPE_ = EventType.KEY_UNICODE
|
||||||
|
def __init__(self, readLen = None):
|
||||||
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
|
self.code = UInt32Le()
|
||||||
|
self.isPressed = UInt8()
|
||||||
|
|
||||||
|
class KeyEventScancode(CompositeType):
|
||||||
|
"""
|
||||||
|
@summary: keyboard event (keylogger)
|
||||||
|
"""
|
||||||
|
_TYPE_ = EventType.KEY_SCANCODE
|
||||||
|
def __init__(self, readLen = None):
|
||||||
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
|
self.code = UInt32Le()
|
||||||
|
self.isPressed = UInt8()
|
||||||
|
|
||||||
def timeMs():
|
def timeMs():
|
||||||
"""
|
"""
|
||||||
@return: {int} time stamp in milliseconds
|
@return: {int} time stamp in milliseconds
|
||||||
@@ -212,6 +234,28 @@ class FileRecorder(object):
|
|||||||
infoEvent.hostname.value = hostname
|
infoEvent.hostname.value = hostname
|
||||||
self.rec(infoEvent)
|
self.rec(infoEvent)
|
||||||
|
|
||||||
|
def keyUnicode(self, code, isPressed):
|
||||||
|
"""
|
||||||
|
@summary: record key event as unicode
|
||||||
|
@param code: unicode code
|
||||||
|
@param isPressed: True if a key press event
|
||||||
|
"""
|
||||||
|
keyEvent = KeyEventUnicode()
|
||||||
|
keyEvent.code.value = code
|
||||||
|
keyEvent.isPressed.value = 0 if isPressed else 1
|
||||||
|
self.rec(keyEvent)
|
||||||
|
|
||||||
|
def keyScancode(self, code, isPressed):
|
||||||
|
"""
|
||||||
|
@summary: record key event as scancode
|
||||||
|
@param code: scancode code
|
||||||
|
@param isPressed: True if a key press event
|
||||||
|
"""
|
||||||
|
keyEvent = KeyEventScancode()
|
||||||
|
keyEvent.code.value = code
|
||||||
|
keyEvent.isPressed.value = 0 if isPressed else 1
|
||||||
|
self.rec(keyEvent)
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
"""
|
"""
|
||||||
@summary: end of scenario
|
@summary: end of scenario
|
||||||
|
|||||||
60
rdpy/core/scancode.py
Normal file
60
rdpy/core/scancode.py
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
#
|
||||||
|
# Copyright (c) 2014-2015 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/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
"""
|
||||||
|
Basic virtual scancode mapping
|
||||||
|
"""
|
||||||
|
|
||||||
|
_SCANCODE_QWERTY_ = {
|
||||||
|
0x10 : "q",
|
||||||
|
0x11 : "w",
|
||||||
|
0x12 : "e",
|
||||||
|
0x13 : "r",
|
||||||
|
0x14 : "t",
|
||||||
|
0x15 : "y",
|
||||||
|
0x16 : "u",
|
||||||
|
0x17 : "i",
|
||||||
|
0x18 : "o",
|
||||||
|
0x19 : "p",
|
||||||
|
0x1e : "a",
|
||||||
|
0x1f : "s",
|
||||||
|
0x20 : "d",
|
||||||
|
0x21 : "f",
|
||||||
|
0x22 : "g",
|
||||||
|
0x23 : "h",
|
||||||
|
0x24 : "j",
|
||||||
|
0x25 : "k",
|
||||||
|
0x26 : "l",
|
||||||
|
0x2c : "z",
|
||||||
|
0x2d : "x",
|
||||||
|
0x2e : "c",
|
||||||
|
0x2f : "v",
|
||||||
|
0x30 : "b",
|
||||||
|
0x31 : "n",
|
||||||
|
0x32 : "m"
|
||||||
|
}
|
||||||
|
|
||||||
|
def scancodeToChar(code):
|
||||||
|
"""
|
||||||
|
@summary: try to convert native code to char code
|
||||||
|
@return: char
|
||||||
|
"""
|
||||||
|
if not _SCANCODE_QWERTY_.has_key(code):
|
||||||
|
return "<unknown scancode %x>"%code
|
||||||
|
return _SCANCODE_QWERTY_[code];
|
||||||
@@ -477,7 +477,7 @@ class CompositeType(Type):
|
|||||||
raise e
|
raise e
|
||||||
|
|
||||||
if not self._readLen is None and readLen < self._readLen.value:
|
if not self._readLen is None and readLen < self._readLen.value:
|
||||||
log.debug("Still have correct data in packet %s, read it as padding"%self.__class__)
|
log.debug("Still have correct data in packet %s, read %s bytes as padding"%(self.__class__, self._readLen.value - readLen))
|
||||||
s.read(self._readLen.value - readLen)
|
s.read(self._readLen.value - readLen)
|
||||||
|
|
||||||
def __write__(self, s):
|
def __write__(self, s):
|
||||||
|
|||||||
@@ -22,10 +22,11 @@
|
|||||||
@see: http://msdn.microsoft.com/en-us/library/cc241880.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc241880.aspx
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from rdpy.core.type import CompositeType, UInt8, UInt16Le, UInt32Le, String, sizeof, FactoryType, ArrayType, Stream
|
from rdpy.core.type import CompositeType, CallableValue, UInt8, UInt16Le, UInt32Le, String, sizeof, FactoryType, ArrayType, Stream
|
||||||
from rdpy.core.error import InvalidExpectedDataException
|
from rdpy.core.error import InvalidExpectedDataException
|
||||||
import rdpy.core.log as log
|
import rdpy.core.log as log
|
||||||
import sec, gcc
|
import sec
|
||||||
|
from t125 import gcc
|
||||||
from rdpy.security import rc4
|
from rdpy.security import rc4
|
||||||
from rdpy.security import rsa_wrapper as rsa
|
from rdpy.security import rsa_wrapper as rsa
|
||||||
|
|
||||||
@@ -161,7 +162,7 @@ class ServerLicenseRequest(CompositeType):
|
|||||||
|
|
||||||
def __init__(self, readLen = None):
|
def __init__(self, readLen = None):
|
||||||
CompositeType.__init__(self, readLen = readLen)
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
self.serverRandom = String("\x00" * 32, readLen = UInt8(32))
|
self.serverRandom = String("\x00" * 32, readLen = CallableValue(32))
|
||||||
self.productInfo = ProductInformation()
|
self.productInfo = ProductInformation()
|
||||||
self.keyExchangeList = LicenseBinaryBlob(BinaryBlobType.BB_KEY_EXCHG_ALG_BLOB)
|
self.keyExchangeList = LicenseBinaryBlob(BinaryBlobType.BB_KEY_EXCHG_ALG_BLOB)
|
||||||
self.serverCertificate = LicenseBinaryBlob(BinaryBlobType.BB_CERTIFICATE_BLOB)
|
self.serverCertificate = LicenseBinaryBlob(BinaryBlobType.BB_CERTIFICATE_BLOB)
|
||||||
@@ -182,7 +183,7 @@ class ClientNewLicenseRequest(CompositeType):
|
|||||||
#pure microsoft client ;-)
|
#pure microsoft client ;-)
|
||||||
#http://msdn.microsoft.com/en-us/library/1040af38-c733-4fb3-acd1-8db8cc979eda#id10
|
#http://msdn.microsoft.com/en-us/library/1040af38-c733-4fb3-acd1-8db8cc979eda#id10
|
||||||
self.platformId = UInt32Le(0x04000000 | 0x00010000)
|
self.platformId = UInt32Le(0x04000000 | 0x00010000)
|
||||||
self.clientRandom = String("\x00" * 32, readLen = UInt8(32))
|
self.clientRandom = String("\x00" * 32, readLen = CallableValue(32))
|
||||||
self.encryptedPreMasterSecret = LicenseBinaryBlob(BinaryBlobType.BB_RANDOM_BLOB)
|
self.encryptedPreMasterSecret = LicenseBinaryBlob(BinaryBlobType.BB_RANDOM_BLOB)
|
||||||
self.ClientUserName = LicenseBinaryBlob(BinaryBlobType.BB_CLIENT_USER_NAME_BLOB)
|
self.ClientUserName = LicenseBinaryBlob(BinaryBlobType.BB_CLIENT_USER_NAME_BLOB)
|
||||||
self.ClientMachineName = LicenseBinaryBlob(BinaryBlobType.BB_CLIENT_MACHINE_NAME_BLOB)
|
self.ClientMachineName = LicenseBinaryBlob(BinaryBlobType.BB_CLIENT_MACHINE_NAME_BLOB)
|
||||||
@@ -198,7 +199,7 @@ class ServerPlatformChallenge(CompositeType):
|
|||||||
CompositeType.__init__(self, readLen = readLen)
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
self.connectFlags = UInt32Le()
|
self.connectFlags = UInt32Le()
|
||||||
self.encryptedPlatformChallenge = LicenseBinaryBlob(BinaryBlobType.BB_ANY_BLOB)
|
self.encryptedPlatformChallenge = LicenseBinaryBlob(BinaryBlobType.BB_ANY_BLOB)
|
||||||
self.MACData = String(readLen = UInt8(16))
|
self.MACData = String(readLen = CallableValue(16))
|
||||||
|
|
||||||
class ClientPLatformChallengeResponse(CompositeType):
|
class ClientPLatformChallengeResponse(CompositeType):
|
||||||
"""
|
"""
|
||||||
@@ -211,7 +212,7 @@ class ClientPLatformChallengeResponse(CompositeType):
|
|||||||
CompositeType.__init__(self, readLen = readLen)
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
self.encryptedPlatformChallengeResponse = LicenseBinaryBlob(BinaryBlobType.BB_DATA_BLOB)
|
self.encryptedPlatformChallengeResponse = LicenseBinaryBlob(BinaryBlobType.BB_DATA_BLOB)
|
||||||
self.encryptedHWID = LicenseBinaryBlob(BinaryBlobType.BB_DATA_BLOB)
|
self.encryptedHWID = LicenseBinaryBlob(BinaryBlobType.BB_DATA_BLOB)
|
||||||
self.MACData = String(readLen = UInt8(16))
|
self.MACData = String(readLen = CallableValue(16))
|
||||||
|
|
||||||
class LicPacket(CompositeType):
|
class LicPacket(CompositeType):
|
||||||
"""
|
"""
|
||||||
@@ -233,7 +234,7 @@ class LicPacket(CompositeType):
|
|||||||
if self.bMsgtype.value == c._MESSAGE_TYPE_:
|
if self.bMsgtype.value == c._MESSAGE_TYPE_:
|
||||||
return c(readLen = self.wMsgSize - 4)
|
return c(readLen = self.wMsgSize - 4)
|
||||||
log.debug("unknown license message : %s"%self.bMsgtype.value)
|
log.debug("unknown license message : %s"%self.bMsgtype.value)
|
||||||
return String()
|
return String(readLen = self.wMsgSize - 4)
|
||||||
|
|
||||||
if message is None:
|
if message is None:
|
||||||
message = FactoryType(LicensingMessageFactory)
|
message = FactoryType(LicensingMessageFactory)
|
||||||
@@ -302,6 +303,9 @@ class LicenseManager(object):
|
|||||||
"""
|
"""
|
||||||
#get server information
|
#get server information
|
||||||
serverRandom = licenseRequest.serverRandom.value
|
serverRandom = licenseRequest.serverRandom.value
|
||||||
|
if self._transport.getGCCServerSettings().SC_SECURITY.serverCertificate._is_readed:
|
||||||
|
serverCertificate = self._transport.getGCCServerSettings().SC_SECURITY.serverCertificate
|
||||||
|
else:
|
||||||
s = Stream(licenseRequest.serverCertificate.blobData.value)
|
s = Stream(licenseRequest.serverCertificate.blobData.value)
|
||||||
serverCertificate = gcc.ServerCertificate()
|
serverCertificate = gcc.ServerCertificate()
|
||||||
s.readType(serverCertificate)
|
s.readType(serverCertificate)
|
||||||
|
|||||||
0
rdpy/protocol/rdp/nla/__init__.py
Normal file
0
rdpy/protocol/rdp/nla/__init__.py
Normal file
291
rdpy/protocol/rdp/nla/cssp.py
Normal file
291
rdpy/protocol/rdp/nla/cssp.py
Normal file
@@ -0,0 +1,291 @@
|
|||||||
|
#
|
||||||
|
# Copyright (c) 2014-2015 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/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
"""
|
||||||
|
@summary: Credential Security Support Provider
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc226764.aspx
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pyasn1.type import namedtype, univ, tag
|
||||||
|
import pyasn1.codec.der.encoder as der_encoder
|
||||||
|
import pyasn1.codec.der.decoder as der_decoder
|
||||||
|
import pyasn1.codec.ber.encoder as ber_encoder
|
||||||
|
|
||||||
|
from rdpy.core.type import Stream
|
||||||
|
from twisted.internet import protocol
|
||||||
|
from OpenSSL import crypto
|
||||||
|
from rdpy.security import x509
|
||||||
|
from rdpy.core import error
|
||||||
|
|
||||||
|
class NegoToken(univ.Sequence):
|
||||||
|
componentType = namedtype.NamedTypes(
|
||||||
|
namedtype.NamedType('negoToken', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
|
||||||
|
)
|
||||||
|
|
||||||
|
class NegoData(univ.SequenceOf):
|
||||||
|
"""
|
||||||
|
@summary: contain spnego ntlm of kerberos data
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc226781.aspx
|
||||||
|
"""
|
||||||
|
componentType = NegoToken()
|
||||||
|
|
||||||
|
class TSRequest(univ.Sequence):
|
||||||
|
"""
|
||||||
|
@summary: main structure
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc226780.aspx
|
||||||
|
"""
|
||||||
|
componentType = namedtype.NamedTypes(
|
||||||
|
namedtype.NamedType('version', univ.Integer().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
|
||||||
|
namedtype.OptionalNamedType('negoTokens', NegoData().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))),
|
||||||
|
namedtype.OptionalNamedType('authInfo', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2))),
|
||||||
|
namedtype.OptionalNamedType('pubKeyAuth', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3))),
|
||||||
|
namedtype.OptionalNamedType('errorCode', univ.Integer().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 4)))
|
||||||
|
)
|
||||||
|
|
||||||
|
class TSCredentials(univ.Sequence):
|
||||||
|
"""
|
||||||
|
@summary: contain user information
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc226782.aspx
|
||||||
|
"""
|
||||||
|
componentType = namedtype.NamedTypes(
|
||||||
|
namedtype.NamedType('credType', univ.Integer().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
|
||||||
|
namedtype.NamedType('credentials', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1)))
|
||||||
|
)
|
||||||
|
|
||||||
|
class TSPasswordCreds(univ.Sequence):
|
||||||
|
"""
|
||||||
|
@summary: contain username and password
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc226783.aspx
|
||||||
|
"""
|
||||||
|
componentType = namedtype.NamedTypes(
|
||||||
|
namedtype.NamedType('domainName', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
|
||||||
|
namedtype.NamedType('userName', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))),
|
||||||
|
namedtype.NamedType('password', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2)))
|
||||||
|
)
|
||||||
|
|
||||||
|
class TSCspDataDetail(univ.Sequence):
|
||||||
|
"""
|
||||||
|
@summary: smart card credentials
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc226785.aspx
|
||||||
|
"""
|
||||||
|
componentType = namedtype.NamedTypes(
|
||||||
|
namedtype.NamedType('keySpec', univ.Integer().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
|
||||||
|
namedtype.OptionalNamedType('cardName', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))),
|
||||||
|
namedtype.OptionalNamedType('readerName', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2))),
|
||||||
|
namedtype.OptionalNamedType('containerName', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3))),
|
||||||
|
namedtype.OptionalNamedType('cspName', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 4)))
|
||||||
|
)
|
||||||
|
|
||||||
|
class TSSmartCardCreds(univ.Sequence):
|
||||||
|
"""
|
||||||
|
@summary: smart card credentials
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc226784.aspx
|
||||||
|
"""
|
||||||
|
componentType = namedtype.NamedTypes(
|
||||||
|
namedtype.NamedType('pin', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
|
||||||
|
namedtype.NamedType('cspData', TSCspDataDetail().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))),
|
||||||
|
namedtype.OptionalNamedType('userHint', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2))),
|
||||||
|
namedtype.OptionalNamedType('domainHint', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3)))
|
||||||
|
)
|
||||||
|
|
||||||
|
class OpenSSLRSAPublicKey(univ.Sequence):
|
||||||
|
"""
|
||||||
|
@summary: asn1 public rsa key
|
||||||
|
@see: https://tools.ietf.org/html/rfc3447
|
||||||
|
"""
|
||||||
|
componentType = namedtype.NamedTypes(
|
||||||
|
namedtype.NamedType('unknow', univ.Integer()),
|
||||||
|
namedtype.NamedType('modulus', univ.Integer()),
|
||||||
|
namedtype.NamedType('publicExponent', univ.Integer()),
|
||||||
|
)
|
||||||
|
|
||||||
|
def encodeDERTRequest(negoTypes = [], authInfo = None, pubKeyAuth = None):
|
||||||
|
"""
|
||||||
|
@summary: create TSRequest from list of Type
|
||||||
|
@param negoTypes: {list(Type)}
|
||||||
|
@param authInfo: {str} authentication info TSCredentials encrypted with authentication protocol
|
||||||
|
@param pubKeyAuth: {str} public key encrypted with authentication protocol
|
||||||
|
@return: {str} TRequest der encoded
|
||||||
|
"""
|
||||||
|
negoData = NegoData().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))
|
||||||
|
|
||||||
|
#fill nego data tokens
|
||||||
|
i = 0
|
||||||
|
for negoType in negoTypes:
|
||||||
|
s = Stream()
|
||||||
|
s.writeType(negoType)
|
||||||
|
negoToken = NegoToken()
|
||||||
|
negoToken.setComponentByPosition(0, s.getvalue())
|
||||||
|
negoData.setComponentByPosition(i, negoToken)
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
request = TSRequest()
|
||||||
|
request.setComponentByName("version", univ.Integer(2).subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0)))
|
||||||
|
|
||||||
|
if i > 0:
|
||||||
|
request.setComponentByName("negoTokens", negoData)
|
||||||
|
|
||||||
|
if not authInfo is None:
|
||||||
|
request.setComponentByName("authInfo", univ.OctetString(authInfo).subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2)))
|
||||||
|
|
||||||
|
if not pubKeyAuth is None:
|
||||||
|
request.setComponentByName("pubKeyAuth", univ.OctetString(pubKeyAuth).subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3)))
|
||||||
|
|
||||||
|
return der_encoder.encode(request)
|
||||||
|
|
||||||
|
def decodeDERTRequest(s):
|
||||||
|
"""
|
||||||
|
@summary: Decode the stream as
|
||||||
|
@param s: {str}
|
||||||
|
"""
|
||||||
|
return der_decoder.decode(s, asn1Spec=TSRequest())[0]
|
||||||
|
|
||||||
|
def getNegoTokens(tRequest):
|
||||||
|
negoData = tRequest.getComponentByName("negoTokens")
|
||||||
|
return [Stream(negoData.getComponentByPosition(i).getComponentByPosition(0).asOctets()) for i in range(len(negoData))]
|
||||||
|
|
||||||
|
def getPubKeyAuth(tRequest):
|
||||||
|
return tRequest.getComponentByName("pubKeyAuth").asOctets()
|
||||||
|
|
||||||
|
def encodeDERTCredentials(domain, username, password):
|
||||||
|
passwordCred = TSPasswordCreds()
|
||||||
|
passwordCred.setComponentByName("domainName", univ.OctetString(domain).subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0)))
|
||||||
|
passwordCred.setComponentByName("userName", univ.OctetString(username).subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1)))
|
||||||
|
passwordCred.setComponentByName("password", univ.OctetString(password).subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2)))
|
||||||
|
|
||||||
|
credentials = TSCredentials()
|
||||||
|
credentials.setComponentByName("credType", univ.Integer(1).subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0)))
|
||||||
|
credentials.setComponentByName("credentials", univ.OctetString(der_encoder.encode(passwordCred)).subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1)))
|
||||||
|
|
||||||
|
return der_encoder.encode(credentials)
|
||||||
|
|
||||||
|
class CSSP(protocol.Protocol):
|
||||||
|
"""
|
||||||
|
@summary: Handle CSSP connection
|
||||||
|
Proxy class for authentication
|
||||||
|
"""
|
||||||
|
def __init__(self, layer, authenticationProtocol):
|
||||||
|
"""
|
||||||
|
@param layer: {type.Layer.RawLayer}
|
||||||
|
@param authenticationProtocol: {sspi.IAuthenticationProtocol}
|
||||||
|
"""
|
||||||
|
self._layer = layer
|
||||||
|
self._authenticationProtocol = authenticationProtocol
|
||||||
|
#IGenericSecurityService
|
||||||
|
self._interface = None
|
||||||
|
#function call at the end of nego
|
||||||
|
self._callback = None
|
||||||
|
|
||||||
|
def setFactory(self, factory):
|
||||||
|
"""
|
||||||
|
@summary: Call by RawLayer Factory
|
||||||
|
@param param: RawLayerClientFactory or RawLayerFactory
|
||||||
|
"""
|
||||||
|
self._layer.setFactory(factory)
|
||||||
|
|
||||||
|
def dataReceived(self, data):
|
||||||
|
"""
|
||||||
|
@summary: Inherit from twisted.protocol class
|
||||||
|
main event of received data
|
||||||
|
@param data: string data receive from twisted
|
||||||
|
"""
|
||||||
|
self._layer.dataReceived(data)
|
||||||
|
|
||||||
|
def connectionLost(self, reason):
|
||||||
|
"""
|
||||||
|
@summary: Call from twisted engine when protocol is closed
|
||||||
|
@param reason: str represent reason of close connection
|
||||||
|
"""
|
||||||
|
self._layer._factory.connectionLost(self, reason)
|
||||||
|
|
||||||
|
def connectionMade(self):
|
||||||
|
"""
|
||||||
|
@summary: install proxy
|
||||||
|
"""
|
||||||
|
self._layer.transport = self
|
||||||
|
self._layer.getDescriptor = lambda:self.transport
|
||||||
|
self._layer.connectionMade()
|
||||||
|
|
||||||
|
def write(self, data):
|
||||||
|
"""
|
||||||
|
@summary: write data on transport layer
|
||||||
|
@param data: {str}
|
||||||
|
"""
|
||||||
|
self.transport.write(data)
|
||||||
|
|
||||||
|
def startTLS(self, sslContext):
|
||||||
|
"""
|
||||||
|
@summary: start TLS protocol
|
||||||
|
@param sslContext: {ssl.ClientContextFactory | ssl.DefaultOpenSSLContextFactory} context use for TLS protocol
|
||||||
|
"""
|
||||||
|
self.transport.startTLS(sslContext)
|
||||||
|
|
||||||
|
def startNLA(self, sslContext, callback = None):
|
||||||
|
"""
|
||||||
|
@summary: start NLA authentication
|
||||||
|
@param sslContext: {ssl.ClientContextFactory | ssl.DefaultOpenSSLContextFactory} context use for TLS protocol
|
||||||
|
@param callback: {function} function call when cssp layer is read
|
||||||
|
"""
|
||||||
|
self._callback = callback
|
||||||
|
self.startTLS(sslContext)
|
||||||
|
#send negotiate message
|
||||||
|
self.transport.write(encodeDERTRequest( negoTypes = [ self._authenticationProtocol.getNegotiateMessage() ] ))
|
||||||
|
#next state is receive a challenge
|
||||||
|
self.dataReceived = self.recvChallenge
|
||||||
|
|
||||||
|
def recvChallenge(self, data):
|
||||||
|
"""
|
||||||
|
@summary: second state in cssp automata
|
||||||
|
@param data : {str} all data available on buffer
|
||||||
|
"""
|
||||||
|
request = decodeDERTRequest(data)
|
||||||
|
message, self._interface = self._authenticationProtocol.getAuthenticateMessage(getNegoTokens(request)[0])
|
||||||
|
#get back public key
|
||||||
|
#convert from der to ber...
|
||||||
|
pubKeyDer = crypto.dump_privatekey(crypto.FILETYPE_ASN1, self.transport.protocol._tlsConnection.get_peer_certificate().get_pubkey())
|
||||||
|
pubKey = der_decoder.decode(pubKeyDer, asn1Spec=OpenSSLRSAPublicKey())[0]
|
||||||
|
|
||||||
|
rsa = x509.RSAPublicKey()
|
||||||
|
rsa.setComponentByName("modulus", univ.Integer(pubKey.getComponentByName('modulus')._value))
|
||||||
|
rsa.setComponentByName("publicExponent", univ.Integer(pubKey.getComponentByName('publicExponent')._value))
|
||||||
|
self._pubKeyBer = ber_encoder.encode(rsa)
|
||||||
|
|
||||||
|
#send authenticate message with public key encoded
|
||||||
|
self.transport.write(encodeDERTRequest( negoTypes = [ message ], pubKeyAuth = self._interface.GSS_WrapEx(self._pubKeyBer)))
|
||||||
|
#next step is received public key incremented by one
|
||||||
|
self.dataReceived = self.recvPubKeyInc
|
||||||
|
|
||||||
|
def recvPubKeyInc(self, data):
|
||||||
|
"""
|
||||||
|
@summary: the server send the pubKeyBer + 1
|
||||||
|
@param data : {str} all data available on buffer
|
||||||
|
"""
|
||||||
|
request = decodeDERTRequest(data)
|
||||||
|
pubKeyInc = self._interface.GSS_UnWrapEx(getPubKeyAuth(request))
|
||||||
|
#check pubKeyInc = self._pubKeyBer + 1
|
||||||
|
if not (self._pubKeyBer[1:] == pubKeyInc[1:] and ord(self._pubKeyBer[0]) + 1 == ord(pubKeyInc[0])):
|
||||||
|
raise error.InvalidExpectedDataException("CSSP : Invalid public key increment")
|
||||||
|
|
||||||
|
domain, user, password = self._authenticationProtocol.getEncodedCredentials()
|
||||||
|
#send credentials
|
||||||
|
self.transport.write(encodeDERTRequest( authInfo = self._interface.GSS_WrapEx(encodeDERTCredentials(domain, user, password))))
|
||||||
|
#reset state back to normal state
|
||||||
|
self.dataReceived = lambda x: self.__class__.dataReceived(self, x)
|
||||||
|
if not self._callback is None:
|
||||||
|
self._callback()
|
||||||
635
rdpy/protocol/rdp/nla/ntlm.py
Normal file
635
rdpy/protocol/rdp/nla/ntlm.py
Normal file
@@ -0,0 +1,635 @@
|
|||||||
|
#
|
||||||
|
# Copyright (c) 2014-2015 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/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
"""
|
||||||
|
@summary: NTLM Authentication
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc236621.aspx
|
||||||
|
"""
|
||||||
|
|
||||||
|
import hashlib, hmac, struct, datetime
|
||||||
|
import sspi
|
||||||
|
import rdpy.security.pyDes as pyDes
|
||||||
|
import rdpy.security.rc4 as rc4
|
||||||
|
from rdpy.security.rsa_wrapper import random
|
||||||
|
from rdpy.core.type import CompositeType, CallableValue, String, UInt8, UInt16Le, UInt24Le, UInt32Le, sizeof, Stream
|
||||||
|
from rdpy.core import filetimes, error
|
||||||
|
|
||||||
|
class MajorVersion(object):
|
||||||
|
"""
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc236654.aspx
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/a211d894-21bc-4b8b-86ba-b83d0c167b00#id29
|
||||||
|
"""
|
||||||
|
WINDOWS_MAJOR_VERSION_5 = 0x05
|
||||||
|
WINDOWS_MAJOR_VERSION_6 = 0x06
|
||||||
|
|
||||||
|
class MinorVersion(object):
|
||||||
|
"""
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc236654.aspx
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/a211d894-21bc-4b8b-86ba-b83d0c167b00#id30
|
||||||
|
"""
|
||||||
|
WINDOWS_MINOR_VERSION_0 = 0x00
|
||||||
|
WINDOWS_MINOR_VERSION_1 = 0x01
|
||||||
|
WINDOWS_MINOR_VERSION_2 = 0x02
|
||||||
|
WINDOWS_MINOR_VERSION_3 = 0x03
|
||||||
|
|
||||||
|
class NTLMRevision(object):
|
||||||
|
"""
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc236654.aspx
|
||||||
|
"""
|
||||||
|
NTLMSSP_REVISION_W2K3 = 0x0F
|
||||||
|
|
||||||
|
class Negotiate(object):
|
||||||
|
"""
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc236650.aspx
|
||||||
|
"""
|
||||||
|
NTLMSSP_NEGOTIATE_56 = 0x80000000
|
||||||
|
NTLMSSP_NEGOTIATE_KEY_EXCH = 0x40000000
|
||||||
|
NTLMSSP_NEGOTIATE_128 = 0x20000000
|
||||||
|
NTLMSSP_NEGOTIATE_VERSION = 0x02000000
|
||||||
|
NTLMSSP_NEGOTIATE_TARGET_INFO = 0x00800000
|
||||||
|
NTLMSSP_REQUEST_NON_NT_SESSION_KEY = 0x00400000
|
||||||
|
NTLMSSP_NEGOTIATE_IDENTIFY = 0x00100000
|
||||||
|
NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY = 0x00080000
|
||||||
|
NTLMSSP_TARGET_TYPE_SERVER = 0x00020000
|
||||||
|
NTLMSSP_TARGET_TYPE_DOMAIN = 0x00010000
|
||||||
|
NTLMSSP_NEGOTIATE_ALWAYS_SIGN = 0x00008000
|
||||||
|
NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED = 0x00002000
|
||||||
|
NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED = 0x00001000
|
||||||
|
NTLMSSP_NEGOTIATE_NTLM = 0x00000200
|
||||||
|
NTLMSSP_NEGOTIATE_LM_KEY = 0x00000080
|
||||||
|
NTLMSSP_NEGOTIATE_DATAGRAM = 0x00000040
|
||||||
|
NTLMSSP_NEGOTIATE_SEAL = 0x00000020
|
||||||
|
NTLMSSP_NEGOTIATE_SIGN = 0x00000010
|
||||||
|
NTLMSSP_REQUEST_TARGET = 0x00000004
|
||||||
|
NTLM_NEGOTIATE_OEM = 0x00000002
|
||||||
|
NTLMSSP_NEGOTIATE_UNICODE = 0x00000001
|
||||||
|
|
||||||
|
class AvId(object):
|
||||||
|
"""
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc236646.aspx
|
||||||
|
"""
|
||||||
|
MsvAvEOL = 0x0000
|
||||||
|
MsvAvNbComputerName = 0x0001
|
||||||
|
MsvAvNbDomainName = 0x0002
|
||||||
|
MsvAvDnsComputerName = 0x0003
|
||||||
|
MsvAvDnsDomainName = 0x0004
|
||||||
|
MsvAvDnsTreeName = 0x0005
|
||||||
|
MsvAvFlags = 0x0006
|
||||||
|
MsvAvTimestamp = 0x0007
|
||||||
|
MsvAvSingleHost = 0x0008
|
||||||
|
MsvAvTargetName = 0x0009
|
||||||
|
MsvChannelBindings = 0x000A
|
||||||
|
|
||||||
|
def getPayLoadField(message, length, bufferOffset):
|
||||||
|
if length == 0:
|
||||||
|
return None
|
||||||
|
offset = sizeof(message) - sizeof(message.Payload)
|
||||||
|
start = bufferOffset - offset
|
||||||
|
end = start + length
|
||||||
|
return message.Payload.value[start:end]
|
||||||
|
|
||||||
|
class Version(CompositeType):
|
||||||
|
"""
|
||||||
|
@summary: Version structure as describe in NTLM spec
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc236654.aspx
|
||||||
|
"""
|
||||||
|
def __init__(self, conditional):
|
||||||
|
CompositeType.__init__(self, conditional = conditional)
|
||||||
|
self.ProductMajorVersion = UInt8(MajorVersion.WINDOWS_MAJOR_VERSION_6)
|
||||||
|
self.ProductMinorVersion = UInt8(MinorVersion.WINDOWS_MINOR_VERSION_0)
|
||||||
|
self.ProductBuild = UInt16Le(6002)
|
||||||
|
self.Reserved = UInt24Le()
|
||||||
|
self.NTLMRevisionCurrent = UInt8(NTLMRevision.NTLMSSP_REVISION_W2K3)
|
||||||
|
|
||||||
|
class AvPair(CompositeType):
|
||||||
|
"""
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc236646.aspx
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
CompositeType.__init__(self)
|
||||||
|
self.AvId = UInt16Le()
|
||||||
|
self.AvLen = UInt16Le(lambda:sizeof(self.Value))
|
||||||
|
self.Value = String(readLen = self.AvLen)
|
||||||
|
|
||||||
|
class MessageSignatureEx(CompositeType):
|
||||||
|
"""
|
||||||
|
@summary: Signature for message
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc422952.aspx
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
CompositeType.__init__(self)
|
||||||
|
self.Version = UInt32Le(0x00000001, constant = True)
|
||||||
|
self.Checksum = String(readLen = CallableValue(8))
|
||||||
|
self.SeqNum = UInt32Le()
|
||||||
|
|
||||||
|
class NegotiateMessage(CompositeType):
|
||||||
|
"""
|
||||||
|
@summary: Message send from client to server to negotiate capability of NTLM Authentication
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc236641.aspx
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
CompositeType.__init__(self)
|
||||||
|
self.Signature = String("NTLMSSP\x00", readLen = CallableValue(8), constant = True)
|
||||||
|
self.MessageType = UInt32Le(0x00000001, constant = True)
|
||||||
|
|
||||||
|
self.NegotiateFlags = UInt32Le()
|
||||||
|
|
||||||
|
self.DomainNameLen = UInt16Le()
|
||||||
|
self.DomainNameMaxLen = UInt16Le(lambda:self.DomainNameLen.value)
|
||||||
|
self.DomainNameBufferOffset = UInt32Le()
|
||||||
|
|
||||||
|
self.WorkstationLen = UInt16Le()
|
||||||
|
self.WorkstationMaxLen = UInt16Le(lambda:self.WorkstationLen.value)
|
||||||
|
self.WorkstationBufferOffset = UInt32Le()
|
||||||
|
|
||||||
|
self.Version = Version(conditional = lambda:(self.NegotiateFlags.value & Negotiate.NTLMSSP_NEGOTIATE_VERSION))
|
||||||
|
|
||||||
|
self.Payload = String()
|
||||||
|
|
||||||
|
class ChallengeMessage(CompositeType):
|
||||||
|
"""
|
||||||
|
@summary: Message send from server to client contains server challenge
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc236642.aspx
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
CompositeType.__init__(self)
|
||||||
|
self.Signature = String("NTLMSSP\x00", readLen = CallableValue(8), constant = True)
|
||||||
|
self.MessageType = UInt32Le(0x00000002, constant = True)
|
||||||
|
|
||||||
|
self.TargetNameLen = UInt16Le()
|
||||||
|
self.TargetNameMaxLen = UInt16Le(lambda:self.TargetNameLen.value)
|
||||||
|
self.TargetNameBufferOffset = UInt32Le()
|
||||||
|
|
||||||
|
self.NegotiateFlags = UInt32Le()
|
||||||
|
|
||||||
|
self.ServerChallenge = String(readLen = CallableValue(8))
|
||||||
|
self.Reserved = String("\x00" * 8, readLen = CallableValue(8))
|
||||||
|
|
||||||
|
self.TargetInfoLen = UInt16Le()
|
||||||
|
self.TargetInfoMaxLen = UInt16Le(lambda:self.TargetInfoLen.value)
|
||||||
|
self.TargetInfoBufferOffset = UInt32Le()
|
||||||
|
|
||||||
|
self.Version = Version(conditional = lambda:(self.NegotiateFlags.value & Negotiate.NTLMSSP_NEGOTIATE_VERSION))
|
||||||
|
self.Payload = String()
|
||||||
|
|
||||||
|
def getTargetName(self):
|
||||||
|
return getPayLoadField(self, self.TargetNameLen.value, self.TargetNameBufferOffset.value)
|
||||||
|
|
||||||
|
def getTargetInfo(self):
|
||||||
|
return getPayLoadField(self, self.TargetInfoLen.value, self.TargetInfoBufferOffset.value)
|
||||||
|
|
||||||
|
def getTargetInfoAsAvPairArray(self):
|
||||||
|
"""
|
||||||
|
@summary: Parse Target info field to retrieve array of AvPair
|
||||||
|
@return: {map(AvId, str)}
|
||||||
|
"""
|
||||||
|
result = {}
|
||||||
|
s = Stream(self.getTargetInfo())
|
||||||
|
while(True):
|
||||||
|
avPair = AvPair()
|
||||||
|
s.readType(avPair)
|
||||||
|
if avPair.AvId.value == AvId.MsvAvEOL:
|
||||||
|
return result
|
||||||
|
result[avPair.AvId.value] = avPair.Value.value
|
||||||
|
|
||||||
|
|
||||||
|
class AuthenticateMessage(CompositeType):
|
||||||
|
"""
|
||||||
|
@summary: Last message in ntlm authentication
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc236643.aspx
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
CompositeType.__init__(self)
|
||||||
|
self.Signature = String("NTLMSSP\x00", readLen = CallableValue(8), constant = True)
|
||||||
|
self.MessageType = UInt32Le(0x00000003, constant = True)
|
||||||
|
|
||||||
|
self.LmChallengeResponseLen = UInt16Le()
|
||||||
|
self.LmChallengeResponseMaxLen = UInt16Le(lambda:self.LmChallengeResponseLen.value)
|
||||||
|
self.LmChallengeResponseBufferOffset = UInt32Le()
|
||||||
|
|
||||||
|
self.NtChallengeResponseLen = UInt16Le()
|
||||||
|
self.NtChallengeResponseMaxLen = UInt16Le(lambda:self.NtChallengeResponseLen.value)
|
||||||
|
self.NtChallengeResponseBufferOffset = UInt32Le()
|
||||||
|
|
||||||
|
self.DomainNameLen = UInt16Le()
|
||||||
|
self.DomainNameMaxLen = UInt16Le(lambda:self.DomainNameLen.value)
|
||||||
|
self.DomainNameBufferOffset = UInt32Le()
|
||||||
|
|
||||||
|
self.UserNameLen = UInt16Le()
|
||||||
|
self.UserNameMaxLen = UInt16Le(lambda:self.UserNameLen.value)
|
||||||
|
self.UserNameBufferOffset = UInt32Le()
|
||||||
|
|
||||||
|
self.WorkstationLen = UInt16Le()
|
||||||
|
self.WorkstationMaxLen = UInt16Le(lambda:self.WorkstationLen.value)
|
||||||
|
self.WorkstationBufferOffset = UInt32Le()
|
||||||
|
|
||||||
|
self.EncryptedRandomSessionLen = UInt16Le()
|
||||||
|
self.EncryptedRandomSessionMaxLen = UInt16Le(lambda:self.EncryptedRandomSessionLen.value)
|
||||||
|
self.EncryptedRandomSessionBufferOffset = UInt32Le()
|
||||||
|
|
||||||
|
self.NegotiateFlags = UInt32Le()
|
||||||
|
self.Version = Version(conditional = lambda:(self.NegotiateFlags.value & Negotiate.NTLMSSP_NEGOTIATE_VERSION))
|
||||||
|
|
||||||
|
self.MIC = String("\x00" * 16, readLen = CallableValue(16))
|
||||||
|
self.Payload = String()
|
||||||
|
|
||||||
|
def getUserName(self):
|
||||||
|
return getPayLoadField(self, self.UserNameLen.value, self.UserNameBufferOffset.value)
|
||||||
|
|
||||||
|
def getDomainName(self):
|
||||||
|
return getPayLoadField(self, self.DomainNameLen.value, self.DomainNameBufferOffset.value)
|
||||||
|
|
||||||
|
def getLmChallengeResponse(self):
|
||||||
|
return getPayLoadField(self, self.LmChallengeResponseLen.value, self.LmChallengeResponseBufferOffset.value)
|
||||||
|
|
||||||
|
def getNtChallengeResponse(self):
|
||||||
|
return getPayLoadField(self, self.NtChallengeResponseLen.value, self.NtChallengeResponseBufferOffset.value)
|
||||||
|
|
||||||
|
def getEncryptedRandomSession(self):
|
||||||
|
return getPayLoadField(self, self.EncryptedRandomSessionLen.value, self.EncryptedRandomSessionBufferOffset.value)
|
||||||
|
|
||||||
|
def createAuthenticationMessage(NegFlag, domain, user, NtChallengeResponse, LmChallengeResponse, EncryptedRandomSessionKey, Workstation):
|
||||||
|
"""
|
||||||
|
@summary: Create an Authenticate Message
|
||||||
|
@param domain: {str} domain microsoft
|
||||||
|
@param user: {str} user microsoft
|
||||||
|
@param NtChallengeResponse: {str} Challenge response
|
||||||
|
@param LmChallengeResponse: {str} domain microsoft
|
||||||
|
@param EncryptedRandomSessionKey: {str} EncryptedRandomSessionKey
|
||||||
|
"""
|
||||||
|
message = AuthenticateMessage()
|
||||||
|
message.NegotiateFlags.value = NegFlag
|
||||||
|
#fill message
|
||||||
|
offset = sizeof(message)
|
||||||
|
|
||||||
|
message.DomainNameLen.value = len(domain)
|
||||||
|
message.DomainNameBufferOffset.value = offset
|
||||||
|
message.Payload.value += domain
|
||||||
|
offset += len(domain)
|
||||||
|
|
||||||
|
message.UserNameLen.value = len(user)
|
||||||
|
message.UserNameBufferOffset.value = offset
|
||||||
|
message.Payload.value += user
|
||||||
|
offset += len(user)
|
||||||
|
|
||||||
|
message.WorkstationLen.value = len(Workstation)
|
||||||
|
message.WorkstationBufferOffset.value = offset
|
||||||
|
message.Payload.value += Workstation
|
||||||
|
offset += len(Workstation)
|
||||||
|
|
||||||
|
message.LmChallengeResponseLen.value = len(LmChallengeResponse)
|
||||||
|
message.LmChallengeResponseBufferOffset.value = offset
|
||||||
|
message.Payload.value += LmChallengeResponse
|
||||||
|
offset += len(LmChallengeResponse)
|
||||||
|
|
||||||
|
message.NtChallengeResponseLen.value = len(NtChallengeResponse)
|
||||||
|
message.NtChallengeResponseBufferOffset.value = offset
|
||||||
|
message.Payload.value += NtChallengeResponse
|
||||||
|
offset += len(NtChallengeResponse)
|
||||||
|
|
||||||
|
message.EncryptedRandomSessionLen.value = len(EncryptedRandomSessionKey)
|
||||||
|
message.EncryptedRandomSessionBufferOffset.value = offset
|
||||||
|
message.Payload.value += EncryptedRandomSessionKey
|
||||||
|
offset += len(EncryptedRandomSessionKey)
|
||||||
|
|
||||||
|
return message
|
||||||
|
|
||||||
|
def expandDesKey(key):
|
||||||
|
"""
|
||||||
|
@summary: Expand the key from a 7-byte password key into a 8-byte DES key
|
||||||
|
"""
|
||||||
|
s = chr(((ord(key[0]) >> 1) & 0x7f) << 1)
|
||||||
|
s = s + chr(((ord(key[0]) & 0x01) << 6 | ((ord(key[1]) >> 2) & 0x3f)) << 1)
|
||||||
|
s = s + chr(((ord(key[1]) & 0x03) << 5 | ((ord(key[2]) >> 3) & 0x1f)) << 1)
|
||||||
|
s = s + chr(((ord(key[2]) & 0x07) << 4 | ((ord(key[3]) >> 4) & 0x0f)) << 1)
|
||||||
|
s = s + chr(((ord(key[3]) & 0x0f) << 3 | ((ord(key[4]) >> 5) & 0x07)) << 1)
|
||||||
|
s = s + chr(((ord(key[4]) & 0x1f) << 2 | ((ord(key[5]) >> 6) & 0x03)) << 1)
|
||||||
|
s = s + chr(((ord(key[5]) & 0x3f) << 1 | ((ord(key[6]) >> 7) & 0x01)) << 1)
|
||||||
|
s = s + chr((ord(key[6]) & 0x7f) << 1)
|
||||||
|
return s
|
||||||
|
|
||||||
|
def CurrentFileTimes():
|
||||||
|
"""
|
||||||
|
@summary: Current File times in 64 bits
|
||||||
|
@return : {str[8]}
|
||||||
|
"""
|
||||||
|
return struct.pack("Q", filetimes.dt_to_filetime(datetime.datetime.now()))
|
||||||
|
|
||||||
|
def DES(key, data):
|
||||||
|
"""
|
||||||
|
@summary: DES use in microsoft specification
|
||||||
|
@param key: {str} Des key on 56 bits or 7 bytes
|
||||||
|
@param data: {str} data to encrypt
|
||||||
|
"""
|
||||||
|
return pyDes.des(expandDesKey(key)).encrypt(data)
|
||||||
|
|
||||||
|
def DESL(key, data):
|
||||||
|
"""
|
||||||
|
@summary: an krosoft security function (triple des = des + des + des ;-))
|
||||||
|
@param key: {str} Des key
|
||||||
|
@param data: {str} encrypted data
|
||||||
|
"""
|
||||||
|
return DES(key[0:7], data) + DES(key[7:14], data) + DES(key[14:16] + "\x00" * 5, data)
|
||||||
|
|
||||||
|
def UNICODE(s):
|
||||||
|
"""
|
||||||
|
@param s: source
|
||||||
|
@return: {str} encoded in unicode
|
||||||
|
"""
|
||||||
|
return s.encode('utf-16le')
|
||||||
|
|
||||||
|
def MD4(s):
|
||||||
|
"""
|
||||||
|
@summary: compute the md4 sum
|
||||||
|
@param s: {str} input data
|
||||||
|
@return: {str} MD4(s)
|
||||||
|
"""
|
||||||
|
return hashlib.new('md4', s).digest()
|
||||||
|
|
||||||
|
def MD5(s):
|
||||||
|
"""
|
||||||
|
@summary: compute the md5 sum
|
||||||
|
@param s: {str} input data
|
||||||
|
@return: {str} MD5(s)
|
||||||
|
"""
|
||||||
|
return hashlib.new('md5', s).digest()
|
||||||
|
|
||||||
|
def Z(m):
|
||||||
|
"""
|
||||||
|
@summary: fill m zero in string
|
||||||
|
@param m: {int} size of string
|
||||||
|
@return: \x00 * m
|
||||||
|
"""
|
||||||
|
return "\x00" * m
|
||||||
|
|
||||||
|
def RC4K(key, plaintext):
|
||||||
|
"""
|
||||||
|
@summary: Context free of rc4 encoding
|
||||||
|
@param key: {str} key
|
||||||
|
@param plaintext: {str} plaintext
|
||||||
|
@return {str} encrypted text
|
||||||
|
"""
|
||||||
|
return rc4.crypt(rc4.RC4Key(key), plaintext)
|
||||||
|
|
||||||
|
def KXKEYv2(SessionBaseKey, LmChallengeResponse, ServerChallenge):
|
||||||
|
"""
|
||||||
|
@summary: Key eXchange Key for NTLMv2
|
||||||
|
@param SessionBaseKey: {str} computed by NTLMv1Anthentication or NTLMv2Authenticate function
|
||||||
|
@param LmChallengeResponse : {str} computed by NTLMv1Anthentication or NTLMv2Authenticate function
|
||||||
|
@param ServerChallenge : {str} Server chanllenge come from ChallengeMessage
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc236710.aspx
|
||||||
|
"""
|
||||||
|
return SessionBaseKey
|
||||||
|
|
||||||
|
def SEALKEY(ExportedSessionKey, client):
|
||||||
|
if client:
|
||||||
|
return MD5(ExportedSessionKey + "session key to client-to-server sealing key magic constant\0")
|
||||||
|
else:
|
||||||
|
return MD5(ExportedSessionKey + "session key to server-to-client sealing key magic constant\0")
|
||||||
|
|
||||||
|
def SIGNKEY(ExportedSessionKey, client):
|
||||||
|
if client:
|
||||||
|
return MD5(ExportedSessionKey + "session key to client-to-server signing key magic constant\0")
|
||||||
|
else:
|
||||||
|
return MD5(ExportedSessionKey + "session key to server-to-client signing key magic constant\0")
|
||||||
|
|
||||||
|
def HMAC_MD5(key, data):
|
||||||
|
"""
|
||||||
|
@summary: classic HMAC algorithm with MD5 sum
|
||||||
|
@param key: {str} key
|
||||||
|
@param data: {str} data
|
||||||
|
"""
|
||||||
|
return hmac.new(key, data, hashlib.md5).digest()
|
||||||
|
|
||||||
|
def NTOWFv2(Passwd, User, UserDom):
|
||||||
|
"""
|
||||||
|
@summary: Version 2 of NTLM hash function
|
||||||
|
@param Passwd: {str} Password
|
||||||
|
@param User: {str} Username
|
||||||
|
@param UserDom: {str} microsoft domain
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc236700.aspx
|
||||||
|
"""
|
||||||
|
return HMAC_MD5(MD4(UNICODE(Passwd)), UNICODE(User.upper() + UserDom))
|
||||||
|
|
||||||
|
def LMOWFv2(Passwd, User, UserDom):
|
||||||
|
"""
|
||||||
|
@summary: Same as NTOWFv2
|
||||||
|
@param Passwd: {str} Password
|
||||||
|
@param User: {str} Username
|
||||||
|
@param UserDom: {str} microsoft domain
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc236700.aspx
|
||||||
|
"""
|
||||||
|
return NTOWFv2(Passwd, User, UserDom)
|
||||||
|
|
||||||
|
def ComputeResponsev2(ResponseKeyNT, ResponseKeyLM, ServerChallenge, ClientChallenge, Time, ServerName):
|
||||||
|
"""
|
||||||
|
@summary: process NTLMv2 Authenticate hash
|
||||||
|
@param NegFlg: {int} Negotiation flags come from challenge message
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc236700.aspx
|
||||||
|
"""
|
||||||
|
Responserversion = "\x01"
|
||||||
|
HiResponserversion = "\x01"
|
||||||
|
|
||||||
|
temp = Responserversion + HiResponserversion + Z(6) + Time + ClientChallenge + Z(4) + ServerName
|
||||||
|
NTProofStr = HMAC_MD5(ResponseKeyNT, ServerChallenge + temp)
|
||||||
|
NtChallengeResponse = NTProofStr + temp
|
||||||
|
LmChallengeResponse = HMAC_MD5(ResponseKeyLM, ServerChallenge + ClientChallenge) + ClientChallenge
|
||||||
|
|
||||||
|
SessionBaseKey = HMAC_MD5(ResponseKeyNT, NTProofStr)
|
||||||
|
|
||||||
|
return NtChallengeResponse, LmChallengeResponse, SessionBaseKey
|
||||||
|
|
||||||
|
def MAC(handle, SigningKey, SeqNum, Message):
|
||||||
|
"""
|
||||||
|
@summary: generate signature for application message
|
||||||
|
@param handle: {rc4.RC4Key} handle on crypt
|
||||||
|
@param SigningKey: {str} Signing key
|
||||||
|
@param SeqNum: {int} Sequence number
|
||||||
|
@param Message: Message to sign
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc422952.aspx
|
||||||
|
"""
|
||||||
|
signature = MessageSignatureEx()
|
||||||
|
signature.SeqNum.value = SeqNum
|
||||||
|
|
||||||
|
#write the SeqNum
|
||||||
|
s = Stream()
|
||||||
|
s.writeType(signature.SeqNum)
|
||||||
|
|
||||||
|
signature.Checksum.value = rc4.crypt(handle, HMAC_MD5(SigningKey, s.getvalue() + Message)[:8])
|
||||||
|
|
||||||
|
return signature
|
||||||
|
|
||||||
|
def MIC(ExportedSessionKey, negotiateMessage, challengeMessage, authenticateMessage):
|
||||||
|
"""
|
||||||
|
@summary: Compute MIC signature
|
||||||
|
@param negotiateMessage: {NegotiateMessage}
|
||||||
|
@param challengeMessage: {ChallengeMessage}
|
||||||
|
@param authenticateMessage: {AuthenticateMessage}
|
||||||
|
@return: {str} signature
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc236676.aspx
|
||||||
|
"""
|
||||||
|
s = Stream()
|
||||||
|
s.writeType((negotiateMessage, challengeMessage, authenticateMessage))
|
||||||
|
return HMAC_MD5(ExportedSessionKey, s.getvalue())
|
||||||
|
|
||||||
|
class NTLMv2(sspi.IAuthenticationProtocol):
|
||||||
|
"""
|
||||||
|
@summary: Handle NTLMv2 Authentication
|
||||||
|
"""
|
||||||
|
def __init__(self, domain, user, password):
|
||||||
|
self._domain = domain
|
||||||
|
self._user = user
|
||||||
|
self._password = password
|
||||||
|
self._enableUnicode = False
|
||||||
|
#https://msdn.microsoft.com/en-us/library/cc236700.aspx
|
||||||
|
self._ResponseKeyNT = NTOWFv2(password, user, domain)
|
||||||
|
self._ResponseKeyLM = LMOWFv2(password, user, domain)
|
||||||
|
|
||||||
|
#For MIC computation
|
||||||
|
self._negotiateMessage = None
|
||||||
|
self._challengeMessage = None
|
||||||
|
self._authenticateMessage = None
|
||||||
|
|
||||||
|
def getNegotiateMessage(self):
|
||||||
|
"""
|
||||||
|
@summary: generate first handshake messgae
|
||||||
|
"""
|
||||||
|
self._negotiateMessage = NegotiateMessage()
|
||||||
|
self._negotiateMessage.NegotiateFlags.value = (Negotiate.NTLMSSP_NEGOTIATE_KEY_EXCH |
|
||||||
|
Negotiate.NTLMSSP_NEGOTIATE_128 |
|
||||||
|
Negotiate.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY |
|
||||||
|
Negotiate.NTLMSSP_NEGOTIATE_ALWAYS_SIGN |
|
||||||
|
Negotiate.NTLMSSP_NEGOTIATE_NTLM |
|
||||||
|
Negotiate.NTLMSSP_NEGOTIATE_SEAL |
|
||||||
|
Negotiate.NTLMSSP_NEGOTIATE_SIGN |
|
||||||
|
Negotiate.NTLMSSP_REQUEST_TARGET |
|
||||||
|
Negotiate.NTLMSSP_NEGOTIATE_UNICODE)
|
||||||
|
return self._negotiateMessage
|
||||||
|
|
||||||
|
def getAuthenticateMessage(self, s):
|
||||||
|
"""
|
||||||
|
@summary: Client last handshake message
|
||||||
|
@param s: {Stream} challenge message stream
|
||||||
|
@return: {(AuthenticateMessage, NTLMv2SecurityInterface)} Last handshake message and security interface use to encrypt
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc236676.aspx
|
||||||
|
"""
|
||||||
|
self._challengeMessage = ChallengeMessage()
|
||||||
|
s.readType(self._challengeMessage)
|
||||||
|
|
||||||
|
ServerChallenge = self._challengeMessage.ServerChallenge.value
|
||||||
|
ClientChallenge = random(64)
|
||||||
|
|
||||||
|
computeMIC = False
|
||||||
|
ServerName = self._challengeMessage.getTargetInfo()
|
||||||
|
infos = self._challengeMessage.getTargetInfoAsAvPairArray()
|
||||||
|
if infos.has_key(AvId.MsvAvTimestamp):
|
||||||
|
Timestamp = infos[AvId.MsvAvTimestamp]
|
||||||
|
computeMIC = True
|
||||||
|
else:
|
||||||
|
Timestamp = CurrentFileTimes()
|
||||||
|
|
||||||
|
|
||||||
|
NtChallengeResponse, LmChallengeResponse, SessionBaseKey = ComputeResponsev2(self._ResponseKeyNT, self._ResponseKeyLM, ServerChallenge, ClientChallenge, Timestamp, ServerName)
|
||||||
|
KeyExchangeKey = KXKEYv2(SessionBaseKey, LmChallengeResponse, ServerChallenge)
|
||||||
|
ExportedSessionKey = random(128)
|
||||||
|
EncryptedRandomSessionKey = RC4K(KeyExchangeKey, ExportedSessionKey)
|
||||||
|
|
||||||
|
domain, user = self._domain, self._user
|
||||||
|
if self._challengeMessage.NegotiateFlags.value & Negotiate.NTLMSSP_NEGOTIATE_UNICODE:
|
||||||
|
self._enableUnicode = True
|
||||||
|
domain, user = UNICODE(domain), UNICODE(user)
|
||||||
|
self._authenticateMessage = createAuthenticationMessage(self._challengeMessage.NegotiateFlags.value, domain, user, NtChallengeResponse, LmChallengeResponse, EncryptedRandomSessionKey, "")
|
||||||
|
|
||||||
|
if computeMIC:
|
||||||
|
self._authenticateMessage.MIC.value = MIC(ExportedSessionKey, self._negotiateMessage, self._challengeMessage, self._authenticateMessage)
|
||||||
|
else:
|
||||||
|
self._authenticateMessage.MIC._conditional = lambda:False
|
||||||
|
|
||||||
|
ClientSigningKey = SIGNKEY(ExportedSessionKey, True)
|
||||||
|
ServerSigningKey = SIGNKEY(ExportedSessionKey, False)
|
||||||
|
ClientSealingKey = SEALKEY(ExportedSessionKey, True)
|
||||||
|
ServerSealingKey = SEALKEY(ExportedSessionKey, False)
|
||||||
|
|
||||||
|
interface = NTLMv2SecurityInterface(rc4.RC4Key(ClientSealingKey), rc4.RC4Key(ServerSealingKey), ClientSigningKey, ServerSigningKey)
|
||||||
|
|
||||||
|
return self._authenticateMessage, interface
|
||||||
|
|
||||||
|
def getEncodedCredentials(self):
|
||||||
|
"""
|
||||||
|
@summary: return encoded credentials accorded with authentication protocol nego
|
||||||
|
@return: (domain, username, password)
|
||||||
|
"""
|
||||||
|
if self._enableUnicode:
|
||||||
|
return UNICODE(self._domain), UNICODE(self._user), UNICODE(self._password)
|
||||||
|
else:
|
||||||
|
return self._domain, self._user, self._password
|
||||||
|
|
||||||
|
|
||||||
|
class NTLMv2SecurityInterface(sspi.IGenericSecurityService):
|
||||||
|
"""
|
||||||
|
@summary: Generic Security Service for NTLM session
|
||||||
|
"""
|
||||||
|
def __init__(self, encryptHandle, decryptHandle, signingKey, verifyKey):
|
||||||
|
"""
|
||||||
|
@param encryptHandle: {rc4.RC4Key} rc4 keystream for encrypt phase
|
||||||
|
@param decryptHandle: {rc4.RC4Key} rc4 keystream for decrypt phase
|
||||||
|
@param signingKey: {str} signingKey
|
||||||
|
@param verifyKey: {str} verifyKey
|
||||||
|
"""
|
||||||
|
self._encryptHandle = encryptHandle
|
||||||
|
self._decryptHandle = decryptHandle
|
||||||
|
self._signingKey = signingKey
|
||||||
|
self._verifyKey = verifyKey
|
||||||
|
self._seqNum = 0
|
||||||
|
|
||||||
|
def GSS_WrapEx(self, data):
|
||||||
|
"""
|
||||||
|
@summary: Encrypt function for NTLMv2 security service
|
||||||
|
@param data: data to encrypt
|
||||||
|
@return: {str} encrypted data
|
||||||
|
"""
|
||||||
|
encryptedData = rc4.crypt(self._encryptHandle, data)
|
||||||
|
signature = MAC(self._encryptHandle, self._signingKey, self._seqNum, data)
|
||||||
|
self._seqNum += 1
|
||||||
|
s = Stream()
|
||||||
|
s.writeType(signature)
|
||||||
|
return s.getvalue() + encryptedData
|
||||||
|
|
||||||
|
def GSS_UnWrapEx(self, data):
|
||||||
|
"""
|
||||||
|
@summary: decrypt data with key exchange in Authentication protocol
|
||||||
|
@param data: {str}
|
||||||
|
"""
|
||||||
|
signature = MessageSignatureEx()
|
||||||
|
message = String()
|
||||||
|
s = Stream(data)
|
||||||
|
s.readType((signature, message))
|
||||||
|
|
||||||
|
#decrypt message
|
||||||
|
plaintextMessage = rc4.crypt(self._decryptHandle, message.value)
|
||||||
|
checksum = rc4.crypt(self._decryptHandle, signature.Checksum.value)
|
||||||
|
|
||||||
|
#recompute checksum
|
||||||
|
t = Stream()
|
||||||
|
t.writeType(signature.SeqNum)
|
||||||
|
verify = HMAC_MD5(self._verifyKey, t.getvalue() + plaintextMessage)[:8]
|
||||||
|
if verify != checksum:
|
||||||
|
raise error.InvalidExpectedDataException("NTLMv2SecurityInterface : Invalid checksum")
|
||||||
|
|
||||||
|
return plaintextMessage
|
||||||
69
rdpy/protocol/rdp/nla/sspi.py
Normal file
69
rdpy/protocol/rdp/nla/sspi.py
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
#
|
||||||
|
# Copyright (c) 2014-2015 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/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
"""
|
||||||
|
@summary: security service provider interface (Microsoft)
|
||||||
|
"""
|
||||||
|
|
||||||
|
from rdpy.core.error import CallPureVirtualFuntion
|
||||||
|
|
||||||
|
class IAuthenticationProtocol(object):
|
||||||
|
"""
|
||||||
|
@summary: generic class for authentication Protocol (ex: ntlmv2, SPNEGO or kerberos)
|
||||||
|
"""
|
||||||
|
def getNegotiateMessage(self):
|
||||||
|
"""
|
||||||
|
@summary: Client first handshake message for authentication protocol
|
||||||
|
@return: {object} first handshake message
|
||||||
|
"""
|
||||||
|
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "getNegotiateMessage", "IAuthenticationProtocol"))
|
||||||
|
|
||||||
|
def getAuthenticateMessage(self, s):
|
||||||
|
"""
|
||||||
|
@summary: Client last handshake message
|
||||||
|
@param s: {Stream} challenge message stream
|
||||||
|
@return: {(object, IGenericSecurityService)} Last handshake message and interface for application
|
||||||
|
"""
|
||||||
|
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "getAuthenticateMessage", "IAuthenticationProtocol"))
|
||||||
|
|
||||||
|
def getEncodedCredentials(self):
|
||||||
|
"""
|
||||||
|
@summary: return encoded credentials accorded with authentication protocol nego
|
||||||
|
@return: (domain, username, password)
|
||||||
|
"""
|
||||||
|
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "getEncodedCredentials", "IAuthenticationProtocol"))
|
||||||
|
|
||||||
|
class IGenericSecurityService(object):
|
||||||
|
"""
|
||||||
|
@summary: use by application from authentification protocol
|
||||||
|
@see: http://www.rfc-editor.org/rfc/rfc2743.txt
|
||||||
|
"""
|
||||||
|
def GSS_WrapEx(self, data):
|
||||||
|
"""
|
||||||
|
@summary: encrypt data with key exchange in Authentication protocol
|
||||||
|
@param data: {str}
|
||||||
|
"""
|
||||||
|
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "GSS_WrapEx", "IGenericSecurityService"))
|
||||||
|
|
||||||
|
def GSS_UnWrapEx(self, data):
|
||||||
|
"""
|
||||||
|
@summary: decrypt data with key exchange in Authentication protocol
|
||||||
|
@param data: {str}
|
||||||
|
"""
|
||||||
|
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "GSS_UnWrapEx", "IGenericSecurityService"))
|
||||||
@@ -24,7 +24,7 @@ Definition of structure use for capabilities nego
|
|||||||
Use in PDU layer
|
Use in PDU layer
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from rdpy.core.type import CompositeType, String, UInt8, UInt16Le, UInt32Le, sizeof, ArrayType, FactoryType
|
from rdpy.core.type import CompositeType, CallableValue, String, UInt8, UInt16Le, UInt32Le, sizeof, ArrayType, FactoryType
|
||||||
|
|
||||||
class CapsType(object):
|
class CapsType(object):
|
||||||
"""
|
"""
|
||||||
@@ -308,7 +308,7 @@ class OrderCapability(CompositeType):
|
|||||||
|
|
||||||
def __init__(self, readLen = None):
|
def __init__(self, readLen = None):
|
||||||
CompositeType.__init__(self, readLen = readLen)
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
self.terminalDescriptor = String("\x00" * 16, readLen = UInt8(16))
|
self.terminalDescriptor = String("\x00" * 16, readLen = CallableValue(16))
|
||||||
self.pad4octetsA = UInt32Le(0)
|
self.pad4octetsA = UInt32Le(0)
|
||||||
self.desktopSaveXGranularity = UInt16Le(1)
|
self.desktopSaveXGranularity = UInt16Le(1)
|
||||||
self.desktopSaveYGranularity = UInt16Le(20)
|
self.desktopSaveYGranularity = UInt16Le(20)
|
||||||
@@ -316,7 +316,7 @@ class OrderCapability(CompositeType):
|
|||||||
self.maximumOrderLevel = UInt16Le(1)
|
self.maximumOrderLevel = UInt16Le(1)
|
||||||
self.numberFonts = UInt16Le()
|
self.numberFonts = UInt16Le()
|
||||||
self.orderFlags = UInt16Le(OrderFlag.NEGOTIATEORDERSUPPORT)
|
self.orderFlags = UInt16Le(OrderFlag.NEGOTIATEORDERSUPPORT)
|
||||||
self.orderSupport = ArrayType(UInt8, init = [UInt8(0) for _ in range (0, 32)], readLen = UInt8(32))
|
self.orderSupport = ArrayType(UInt8, init = [UInt8(0) for _ in range (0, 32)], readLen = CallableValue(32))
|
||||||
self.textFlags = UInt16Le()
|
self.textFlags = UInt16Le()
|
||||||
self.orderSupportExFlags = UInt16Le()
|
self.orderSupportExFlags = UInt16Le()
|
||||||
self.pad4octetsB = UInt32Le()
|
self.pad4octetsB = UInt32Le()
|
||||||
@@ -388,7 +388,7 @@ class InputCapability(CompositeType):
|
|||||||
#same value as gcc.ClientCoreSettings.keyboardFnKeys
|
#same value as gcc.ClientCoreSettings.keyboardFnKeys
|
||||||
self.keyboardFunctionKey = UInt32Le()
|
self.keyboardFunctionKey = UInt32Le()
|
||||||
#same value as gcc.ClientCoreSettingrrs.imeFileName
|
#same value as gcc.ClientCoreSettingrrs.imeFileName
|
||||||
self.imeFileName = String("\x00" * 64, readLen = UInt8(64))
|
self.imeFileName = String("\x00" * 64, readLen = CallableValue(64))
|
||||||
|
|
||||||
class BrushCapability(CompositeType):
|
class BrushCapability(CompositeType):
|
||||||
"""
|
"""
|
||||||
@@ -412,7 +412,7 @@ class GlyphCapability(CompositeType):
|
|||||||
|
|
||||||
def __init__(self, readLen = None):
|
def __init__(self, readLen = None):
|
||||||
CompositeType.__init__(self, readLen = readLen)
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
self.glyphCache = ArrayType(CacheEntry, init = [CacheEntry() for _ in range(0,10)], readLen = UInt8(10))
|
self.glyphCache = ArrayType(CacheEntry, init = [CacheEntry() for _ in range(0,10)], readLen = CallableValue(10))
|
||||||
self.fragCache = UInt32Le()
|
self.fragCache = UInt32Le()
|
||||||
#all fonts are sent with bitmap format (very expensive)
|
#all fonts are sent with bitmap format (very expensive)
|
||||||
self.glyphSupportLevel = UInt16Le(GlyphSupport.GLYPH_SUPPORT_NONE)
|
self.glyphSupportLevel = UInt16Le(GlyphSupport.GLYPH_SUPPORT_NONE)
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ Implement the main graphic layer
|
|||||||
|
|
||||||
In this layer are managed all mains bitmap update orders end user inputs
|
In this layer are managed all mains bitmap update orders end user inputs
|
||||||
"""
|
"""
|
||||||
from rdpy.core.type import CompositeType, String, UInt8, UInt16Le, UInt32Le, sizeof, ArrayType, FactoryType
|
from rdpy.core.type import CompositeType, CallableValue, String, UInt8, UInt16Le, UInt32Le, sizeof, ArrayType, FactoryType
|
||||||
from rdpy.core.error import InvalidExpectedDataException
|
from rdpy.core.error import InvalidExpectedDataException
|
||||||
import rdpy.core.log as log
|
import rdpy.core.log as log
|
||||||
import caps, order
|
import caps, order
|
||||||
@@ -161,6 +161,15 @@ class PointerFlag(object):
|
|||||||
PTRFLAGS_BUTTON2 = 0x2000
|
PTRFLAGS_BUTTON2 = 0x2000
|
||||||
PTRFLAGS_BUTTON3 = 0x4000
|
PTRFLAGS_BUTTON3 = 0x4000
|
||||||
|
|
||||||
|
class PointerExFlag(object):
|
||||||
|
"""
|
||||||
|
@summary: Use in Pointer event
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc240587.aspx
|
||||||
|
"""
|
||||||
|
PTRXFLAGS_DOWN = 0x8000
|
||||||
|
PTRXFLAGS_BUTTON1 = 0x0001
|
||||||
|
PTRXFLAGS_BUTTON2 = 0x0002
|
||||||
|
|
||||||
class KeyboardFlag(object):
|
class KeyboardFlag(object):
|
||||||
"""
|
"""
|
||||||
@summary: Use in scan code key event
|
@summary: Use in scan code key event
|
||||||
@@ -202,6 +211,16 @@ class Display(object):
|
|||||||
SUPPRESS_DISPLAY_UPDATES = 0x00
|
SUPPRESS_DISPLAY_UPDATES = 0x00
|
||||||
ALLOW_DISPLAY_UPDATES = 0x01
|
ALLOW_DISPLAY_UPDATES = 0x01
|
||||||
|
|
||||||
|
class ToogleFlag(object):
|
||||||
|
"""
|
||||||
|
@summary: Use to known state of keyboard
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc240588.aspx
|
||||||
|
"""
|
||||||
|
TS_SYNC_SCROLL_LOCK = 0x00000001
|
||||||
|
TS_SYNC_NUM_LOCK = 0x00000002
|
||||||
|
TS_SYNC_CAPS_LOCK = 0x00000004
|
||||||
|
TS_SYNC_KANA_LOCK = 0x00000008
|
||||||
|
|
||||||
class ErrorInfo(object):
|
class ErrorInfo(object):
|
||||||
"""
|
"""
|
||||||
@summary: Error code use in Error info PDU
|
@summary: Error code use in Error info PDU
|
||||||
@@ -413,8 +432,6 @@ class ErrorInfo(object):
|
|||||||
ERRINFO_VCDATATOOLONG : "The size of a received Virtual Channel PDU (section 2.2.6.1) exceeds the chunking size specified in the Virtual Channel Capability Set (section 2.2.7.1.10).",
|
ERRINFO_VCDATATOOLONG : "The size of a received Virtual Channel PDU (section 2.2.6.1) exceeds the chunking size specified in the Virtual Channel Capability Set (section 2.2.7.1.10).",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ShareControlHeader(CompositeType):
|
class ShareControlHeader(CompositeType):
|
||||||
"""
|
"""
|
||||||
@summary: PDU share control header
|
@summary: PDU share control header
|
||||||
@@ -461,10 +478,10 @@ class PDU(CompositeType):
|
|||||||
"""
|
"""
|
||||||
for c in [DemandActivePDU, ConfirmActivePDU, DataPDU, DeactiveAllPDU]:
|
for c in [DemandActivePDU, ConfirmActivePDU, DataPDU, DeactiveAllPDU]:
|
||||||
if self.shareControlHeader.pduType.value == c._PDUTYPE_:
|
if self.shareControlHeader.pduType.value == c._PDUTYPE_:
|
||||||
return c()
|
return c(readLen = CallableValue(self.shareControlHeader.totalLength.value - sizeof(self.shareControlHeader)))
|
||||||
log.debug("unknown PDU type : %s"%hex(self.shareControlHeader.pduType.value))
|
log.debug("unknown PDU type : %s"%hex(self.shareControlHeader.pduType.value))
|
||||||
#read entire packet
|
#read entire packet
|
||||||
return String()
|
return String(readLen = CallableValue(self.shareControlHeader.totalLength.value - sizeof(self.shareControlHeader)))
|
||||||
|
|
||||||
if pduMessage is None:
|
if pduMessage is None:
|
||||||
pduMessage = FactoryType(PDUMessageFactory)
|
pduMessage = FactoryType(PDUMessageFactory)
|
||||||
@@ -481,8 +498,8 @@ class DemandActivePDU(CompositeType):
|
|||||||
#may declare the PDU type
|
#may declare the PDU type
|
||||||
_PDUTYPE_ = PDUType.PDUTYPE_DEMANDACTIVEPDU
|
_PDUTYPE_ = PDUType.PDUTYPE_DEMANDACTIVEPDU
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, readLen = None):
|
||||||
CompositeType.__init__(self)
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
self.shareId = UInt32Le()
|
self.shareId = UInt32Le()
|
||||||
self.lengthSourceDescriptor = UInt16Le(lambda:sizeof(self.sourceDescriptor))
|
self.lengthSourceDescriptor = UInt16Le(lambda:sizeof(self.sourceDescriptor))
|
||||||
self.lengthCombinedCapabilities = UInt16Le(lambda:(sizeof(self.numberCapabilities) + sizeof(self.pad2Octets) + sizeof(self.capabilitySets)))
|
self.lengthCombinedCapabilities = UInt16Le(lambda:(sizeof(self.numberCapabilities) + sizeof(self.pad2Octets) + sizeof(self.capabilitySets)))
|
||||||
@@ -500,8 +517,8 @@ class ConfirmActivePDU(CompositeType):
|
|||||||
#may declare the PDU type
|
#may declare the PDU type
|
||||||
_PDUTYPE_ = PDUType.PDUTYPE_CONFIRMACTIVEPDU
|
_PDUTYPE_ = PDUType.PDUTYPE_CONFIRMACTIVEPDU
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, readLen = None):
|
||||||
CompositeType.__init__(self)
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
self.shareId = UInt32Le()
|
self.shareId = UInt32Le()
|
||||||
self.originatorId = UInt16Le(0x03EA, constant = True)
|
self.originatorId = UInt16Le(0x03EA, constant = True)
|
||||||
self.lengthSourceDescriptor = UInt16Le(lambda:sizeof(self.sourceDescriptor))
|
self.lengthSourceDescriptor = UInt16Le(lambda:sizeof(self.sourceDescriptor))
|
||||||
@@ -519,10 +536,10 @@ class DeactiveAllPDU(CompositeType):
|
|||||||
#may declare the PDU type
|
#may declare the PDU type
|
||||||
_PDUTYPE_ = PDUType.PDUTYPE_DEACTIVATEALLPDU
|
_PDUTYPE_ = PDUType.PDUTYPE_DEACTIVATEALLPDU
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, readLen = None):
|
||||||
#in old version this packet is empty i don't know
|
#in old version this packet is empty i don't know
|
||||||
#and not specified
|
#and not specified
|
||||||
CompositeType.__init__(self, optional = True)
|
CompositeType.__init__(self, optional = True, readLen = readLen)
|
||||||
self.shareId = UInt32Le()
|
self.shareId = UInt32Le()
|
||||||
self.lengthSourceDescriptor = UInt16Le(lambda:sizeof(self.sourceDescriptor))
|
self.lengthSourceDescriptor = UInt16Le(lambda:sizeof(self.sourceDescriptor))
|
||||||
self.sourceDescriptor = String("rdpy", readLen = self.lengthSourceDescriptor)
|
self.sourceDescriptor = String("rdpy", readLen = self.lengthSourceDescriptor)
|
||||||
@@ -534,19 +551,19 @@ class DataPDU(CompositeType):
|
|||||||
#may declare the PDU type
|
#may declare the PDU type
|
||||||
_PDUTYPE_ = PDUType.PDUTYPE_DATAPDU
|
_PDUTYPE_ = PDUType.PDUTYPE_DATAPDU
|
||||||
|
|
||||||
def __init__(self, pduData = None, shareId = 0):
|
def __init__(self, pduData = None, shareId = 0, readLen = None):
|
||||||
CompositeType.__init__(self)
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
self.shareDataHeader = ShareDataHeader(lambda:sizeof(self), lambda:self.pduData.__class__._PDUTYPE2_, shareId)
|
self.shareDataHeader = ShareDataHeader(lambda:sizeof(self), lambda:self.pduData.__class__._PDUTYPE2_, shareId)
|
||||||
|
|
||||||
def PDUDataFactory():
|
def PDUDataFactory():
|
||||||
"""
|
"""
|
||||||
@summary: 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]:
|
for c in [UpdateDataPDU, SynchronizeDataPDU, ControlDataPDU, ErrorInfoDataPDU, FontListDataPDU, FontMapDataPDU, PersistentListPDU, ClientInputEventPDU, ShutdownDeniedPDU, ShutdownRequestPDU, SupressOutputDataPDU, SaveSessionInfoPDU]:
|
||||||
if self.shareDataHeader.pduType2.value == c._PDUTYPE2_:
|
if self.shareDataHeader.pduType2.value == c._PDUTYPE2_:
|
||||||
return c()
|
return c(readLen = CallableValue(readLen.value - sizeof(self.shareDataHeader)))
|
||||||
log.debug("unknown PDU data type : %s"%hex(self.shareDataHeader.pduType2.value))
|
log.debug("unknown PDU data type : %s"%hex(self.shareDataHeader.pduType2.value))
|
||||||
return String()
|
return String(readLen = CallableValue(readLen.value - sizeof(self.shareDataHeader)))
|
||||||
|
|
||||||
if pduData is None:
|
if pduData is None:
|
||||||
pduData = FactoryType(PDUDataFactory)
|
pduData = FactoryType(PDUDataFactory)
|
||||||
@@ -655,8 +672,8 @@ class PersistentListPDU(CompositeType):
|
|||||||
"""
|
"""
|
||||||
_PDUTYPE2_ = PDUType2.PDUTYPE2_BITMAPCACHE_PERSISTENT_LIST
|
_PDUTYPE2_ = PDUType2.PDUTYPE2_BITMAPCACHE_PERSISTENT_LIST
|
||||||
|
|
||||||
def __init__(self, userId = 0, shareId = 0):
|
def __init__(self, userId = 0, shareId = 0, readLen = None):
|
||||||
CompositeType.__init__(self)
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
self.numEntriesCache0 = UInt16Le()
|
self.numEntriesCache0 = UInt16Le()
|
||||||
self.numEntriesCache1 = UInt16Le()
|
self.numEntriesCache1 = UInt16Le()
|
||||||
self.numEntriesCache2 = UInt16Le()
|
self.numEntriesCache2 = UInt16Le()
|
||||||
@@ -670,7 +687,7 @@ class PersistentListPDU(CompositeType):
|
|||||||
self.bitMask = UInt8()
|
self.bitMask = UInt8()
|
||||||
self.pad2 = UInt8()
|
self.pad2 = UInt8()
|
||||||
self.pad3 = UInt16Le()
|
self.pad3 = UInt16Le()
|
||||||
self.entries = ArrayType(PersistentListEntry, readLen = UInt16Le(lambda:(self.numEntriesCache0 + self.numEntriesCache1 + self.numEntriesCache2 + self.numEntriesCache3 + self.numEntriesCache4)))
|
self.entries = ArrayType(PersistentListEntry, readLen = CallableValue(lambda:(self.numEntriesCache0 + self.numEntriesCache1 + self.numEntriesCache2 + self.numEntriesCache3 + self.numEntriesCache4)))
|
||||||
|
|
||||||
class ClientInputEventPDU(CompositeType):
|
class ClientInputEventPDU(CompositeType):
|
||||||
"""
|
"""
|
||||||
@@ -679,8 +696,8 @@ class ClientInputEventPDU(CompositeType):
|
|||||||
"""
|
"""
|
||||||
_PDUTYPE2_ = PDUType2.PDUTYPE2_INPUT
|
_PDUTYPE2_ = PDUType2.PDUTYPE2_INPUT
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, readLen = None):
|
||||||
CompositeType.__init__(self)
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
self.numEvents = UInt16Le(lambda:len(self.slowPathInputEvents._array))
|
self.numEvents = UInt16Le(lambda:len(self.slowPathInputEvents._array))
|
||||||
self.pad2Octets = UInt16Le()
|
self.pad2Octets = UInt16Le()
|
||||||
self.slowPathInputEvents = ArrayType(SlowPathInputEvent, readLen = self.numEvents)
|
self.slowPathInputEvents = ArrayType(SlowPathInputEvent, readLen = self.numEvents)
|
||||||
@@ -691,8 +708,8 @@ class ShutdownRequestPDU(CompositeType):
|
|||||||
client -> server
|
client -> server
|
||||||
"""
|
"""
|
||||||
_PDUTYPE2_ = PDUType2.PDUTYPE2_SHUTDOWN_REQUEST
|
_PDUTYPE2_ = PDUType2.PDUTYPE2_SHUTDOWN_REQUEST
|
||||||
def __init__(self):
|
def __init__(self, readLen = None):
|
||||||
CompositeType.__init__(self)
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
|
|
||||||
class ShutdownDeniedPDU(CompositeType):
|
class ShutdownDeniedPDU(CompositeType):
|
||||||
"""
|
"""
|
||||||
@@ -700,8 +717,8 @@ class ShutdownDeniedPDU(CompositeType):
|
|||||||
server -> client
|
server -> client
|
||||||
"""
|
"""
|
||||||
_PDUTYPE2_ = PDUType2.PDUTYPE2_SHUTDOWN_DENIED
|
_PDUTYPE2_ = PDUType2.PDUTYPE2_SHUTDOWN_DENIED
|
||||||
def __init__(self):
|
def __init__(self, readLen = None):
|
||||||
CompositeType.__init__(self)
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
|
|
||||||
class InclusiveRectangle(CompositeType):
|
class InclusiveRectangle(CompositeType):
|
||||||
"""
|
"""
|
||||||
@@ -761,9 +778,9 @@ class UpdateDataPDU(CompositeType):
|
|||||||
"""
|
"""
|
||||||
for c in [BitmapUpdateDataPDU]:
|
for c in [BitmapUpdateDataPDU]:
|
||||||
if self.updateType.value == c._UPDATE_TYPE_:
|
if self.updateType.value == c._UPDATE_TYPE_:
|
||||||
return c()
|
return c(readLen = CallableValue(readLen.value - 2))
|
||||||
log.debug("unknown PDU update data type : %s"%hex(self.updateType.value))
|
log.debug("unknown PDU update data type : %s"%hex(self.updateType.value))
|
||||||
return String()
|
return String(readLen = CallableValue(readLen.value - 2))
|
||||||
|
|
||||||
if updateData is None:
|
if updateData is None:
|
||||||
updateData = FactoryType(UpdateDataFactory, conditional = lambda:(self.updateType.value != UpdateType.UPDATETYPE_SYNCHRONIZE))
|
updateData = FactoryType(UpdateDataFactory, conditional = lambda:(self.updateType.value != UpdateType.UPDATETYPE_SYNCHRONIZE))
|
||||||
@@ -772,6 +789,18 @@ class UpdateDataPDU(CompositeType):
|
|||||||
|
|
||||||
self.updateData = updateData
|
self.updateData = updateData
|
||||||
|
|
||||||
|
class SaveSessionInfoPDU(CompositeType):
|
||||||
|
"""
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc240636.aspx
|
||||||
|
"""
|
||||||
|
_PDUTYPE2_ = PDUType2.PDUTYPE2_SAVE_SESSION_INFO
|
||||||
|
|
||||||
|
def __init__(self, readLen = None):
|
||||||
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
|
self.infoType = UInt32Le()
|
||||||
|
#TODO parse info data
|
||||||
|
self.infoData = String()
|
||||||
|
|
||||||
class FastPathUpdatePDU(CompositeType):
|
class FastPathUpdatePDU(CompositeType):
|
||||||
"""
|
"""
|
||||||
@summary: Fast path update PDU packet
|
@summary: Fast path update PDU packet
|
||||||
@@ -789,9 +818,9 @@ class FastPathUpdatePDU(CompositeType):
|
|||||||
"""
|
"""
|
||||||
for c in [FastPathBitmapUpdateDataPDU]:
|
for c in [FastPathBitmapUpdateDataPDU]:
|
||||||
if (self.updateHeader.value & 0xf) == c._FASTPATH_UPDATE_TYPE_:
|
if (self.updateHeader.value & 0xf) == c._FASTPATH_UPDATE_TYPE_:
|
||||||
return c()
|
return c(readLen = self.size)
|
||||||
log.debug("unknown Fast Path PDU update data type : %s"%hex(self.updateHeader.value & 0xf))
|
log.debug("unknown Fast Path PDU update data type : %s"%hex(self.updateHeader.value & 0xf))
|
||||||
return String()
|
return String(readLen = self.size)
|
||||||
|
|
||||||
if updateData is None:
|
if updateData is None:
|
||||||
updateData = FactoryType(UpdateDataFactory)
|
updateData = FactoryType(UpdateDataFactory)
|
||||||
@@ -821,8 +850,8 @@ class OrderUpdateDataPDU(CompositeType):
|
|||||||
@see: http://msdn.microsoft.com/en-us/library/cc241571.aspx
|
@see: http://msdn.microsoft.com/en-us/library/cc241571.aspx
|
||||||
@todo: not implemented yet but need it
|
@todo: not implemented yet but need it
|
||||||
"""
|
"""
|
||||||
def __init__(self):
|
def __init__(self, readLen = None):
|
||||||
CompositeType.__init__(self)
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
self.pad2OctetsA = UInt16Le()
|
self.pad2OctetsA = UInt16Le()
|
||||||
self.numberOrders = UInt16Le(lambda:len(self.orderData._array))
|
self.numberOrders = UInt16Le(lambda:len(self.orderData._array))
|
||||||
self.pad2OctetsB = UInt16Le()
|
self.pad2OctetsB = UInt16Le()
|
||||||
@@ -873,7 +902,7 @@ class BitmapData(CompositeType):
|
|||||||
self.flags = UInt16Le()
|
self.flags = UInt16Le()
|
||||||
self.bitmapLength = UInt16Le(lambda:(sizeof(self.bitmapComprHdr) + sizeof(self.bitmapDataStream)))
|
self.bitmapLength = UInt16Le(lambda:(sizeof(self.bitmapComprHdr) + sizeof(self.bitmapDataStream)))
|
||||||
self.bitmapComprHdr = BitmapCompressedDataHeader(bodySize = lambda:sizeof(self.bitmapDataStream), scanWidth = lambda:self.width.value, uncompressedSize = lambda:(self.width.value * self.height.value * self.bitsPerPixel.value), conditional = lambda:((self.flags.value & BitmapFlag.BITMAP_COMPRESSION) and not (self.flags.value & BitmapFlag.NO_BITMAP_COMPRESSION_HDR)))
|
self.bitmapComprHdr = BitmapCompressedDataHeader(bodySize = lambda:sizeof(self.bitmapDataStream), scanWidth = lambda:self.width.value, uncompressedSize = lambda:(self.width.value * self.height.value * self.bitsPerPixel.value), conditional = lambda:((self.flags.value & BitmapFlag.BITMAP_COMPRESSION) and not (self.flags.value & BitmapFlag.NO_BITMAP_COMPRESSION_HDR)))
|
||||||
self.bitmapDataStream = String(bitmapDataStream, readLen = UInt16Le(lambda:(self.bitmapLength.value if (not self.flags.value & BitmapFlag.BITMAP_COMPRESSION or self.flags.value & BitmapFlag.NO_BITMAP_COMPRESSION_HDR) else self.bitmapComprHdr.cbCompMainBodySize.value)))
|
self.bitmapDataStream = String(bitmapDataStream, readLen = CallableValue(lambda:(self.bitmapLength.value if (not self.flags.value & BitmapFlag.BITMAP_COMPRESSION or self.flags.value & BitmapFlag.NO_BITMAP_COMPRESSION_HDR) else self.bitmapComprHdr.cbCompMainBodySize.value)))
|
||||||
|
|
||||||
class FastPathBitmapUpdateDataPDU(CompositeType):
|
class FastPathBitmapUpdateDataPDU(CompositeType):
|
||||||
"""
|
"""
|
||||||
@@ -882,8 +911,8 @@ class FastPathBitmapUpdateDataPDU(CompositeType):
|
|||||||
"""
|
"""
|
||||||
_FASTPATH_UPDATE_TYPE_ = FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP
|
_FASTPATH_UPDATE_TYPE_ = FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, readLen = None):
|
||||||
CompositeType.__init__(self)
|
CompositeType.__init__(self, readLen = readLen)
|
||||||
self.header = UInt16Le(FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP, constant = True)
|
self.header = UInt16Le(FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP, constant = True)
|
||||||
self.numberRectangles = UInt16Le(lambda:len(self.rectangles._array))
|
self.numberRectangles = UInt16Le(lambda:len(self.rectangles._array))
|
||||||
self.rectangles = ArrayType(BitmapData, readLen = self.numberRectangles)
|
self.rectangles = ArrayType(BitmapData, readLen = self.numberRectangles)
|
||||||
@@ -899,11 +928,10 @@ class SlowPathInputEvent(CompositeType):
|
|||||||
self.messageType = UInt16Le(lambda:self.slowPathInputData.__class__._INPUT_MESSAGE_TYPE_)
|
self.messageType = UInt16Le(lambda:self.slowPathInputData.__class__._INPUT_MESSAGE_TYPE_)
|
||||||
|
|
||||||
def SlowPathInputDataFactory():
|
def SlowPathInputDataFactory():
|
||||||
for c in [PointerEvent, ScancodeKeyEvent, UnicodeKeyEvent]:
|
for c in [PointerEvent, PointerExEvent, ScancodeKeyEvent, UnicodeKeyEvent, SynchronizeEvent]:
|
||||||
if self.messageType.value == c._INPUT_MESSAGE_TYPE_:
|
if self.messageType.value == c._INPUT_MESSAGE_TYPE_:
|
||||||
return c()
|
return c()
|
||||||
log.debug("unknown slow path input : %s"%hex(self.messageType.value))
|
raise InvalidExpectedDataException("unknown slow path input : %s"%hex(self.messageType.value))
|
||||||
return String()
|
|
||||||
|
|
||||||
if messageData is None:
|
if messageData is None:
|
||||||
messageData = FactoryType(SlowPathInputDataFactory)
|
messageData = FactoryType(SlowPathInputDataFactory)
|
||||||
@@ -912,6 +940,18 @@ class SlowPathInputEvent(CompositeType):
|
|||||||
|
|
||||||
self.slowPathInputData = messageData
|
self.slowPathInputData = messageData
|
||||||
|
|
||||||
|
class SynchronizeEvent(CompositeType):
|
||||||
|
"""
|
||||||
|
@summary: Synchronize keyboard
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc240588.aspx
|
||||||
|
"""
|
||||||
|
_INPUT_MESSAGE_TYPE_ = InputMessageType.INPUT_EVENT_SYNC
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
CompositeType.__init__(self)
|
||||||
|
self.pad2Octets = UInt16Le()
|
||||||
|
self.toggleFlags = UInt32Le()
|
||||||
|
|
||||||
class PointerEvent(CompositeType):
|
class PointerEvent(CompositeType):
|
||||||
"""
|
"""
|
||||||
@summary: Event use to communicate mouse position
|
@summary: Event use to communicate mouse position
|
||||||
@@ -925,6 +965,19 @@ class PointerEvent(CompositeType):
|
|||||||
self.xPos = UInt16Le()
|
self.xPos = UInt16Le()
|
||||||
self.yPos = UInt16Le()
|
self.yPos = UInt16Le()
|
||||||
|
|
||||||
|
class PointerExEvent(CompositeType):
|
||||||
|
"""
|
||||||
|
@summary: Event use to communicate mouse position
|
||||||
|
@see: http://msdn.microsoft.com/en-us/library/cc240587.aspx
|
||||||
|
"""
|
||||||
|
_INPUT_MESSAGE_TYPE_ = InputMessageType.INPUT_EVENT_MOUSEX
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
CompositeType.__init__(self)
|
||||||
|
self.pointerFlags = UInt16Le()
|
||||||
|
self.xPos = UInt16Le()
|
||||||
|
self.yPos = UInt16Le()
|
||||||
|
|
||||||
class ScancodeKeyEvent(CompositeType):
|
class ScancodeKeyEvent(CompositeType):
|
||||||
"""
|
"""
|
||||||
@summary: Event use to communicate keyboard informations
|
@summary: Event use to communicate keyboard informations
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ In this layer are managed all mains bitmap update orders end user inputs
|
|||||||
|
|
||||||
from rdpy.core.layer import LayerAutomata
|
from rdpy.core.layer import LayerAutomata
|
||||||
from rdpy.core.error import CallPureVirtualFuntion
|
from rdpy.core.error import CallPureVirtualFuntion
|
||||||
|
from rdpy.core.type import ArrayType
|
||||||
import rdpy.core.log as log
|
import rdpy.core.log as log
|
||||||
import rdpy.protocol.rdp.tpkt as tpkt
|
import rdpy.protocol.rdp.tpkt as tpkt
|
||||||
import data, caps
|
import data, caps
|
||||||
@@ -39,6 +40,13 @@ class PDUClientListener(object):
|
|||||||
"""
|
"""
|
||||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onReady", "PDUClientListener"))
|
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onReady", "PDUClientListener"))
|
||||||
|
|
||||||
|
def onSessionReady(self):
|
||||||
|
"""
|
||||||
|
@summary: Event call when Windows session is ready
|
||||||
|
"""
|
||||||
|
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onSessionReady", "PDUClientListener"))
|
||||||
|
|
||||||
|
|
||||||
def onUpdate(self, rectangles):
|
def onUpdate(self, rectangles):
|
||||||
"""
|
"""
|
||||||
@summary: call when a bitmap data is received from update PDU
|
@summary: call when a bitmap data is received from update PDU
|
||||||
@@ -259,8 +267,9 @@ class Client(PDULayer):
|
|||||||
@summary: Main receive function after connection sequence
|
@summary: Main receive function after connection sequence
|
||||||
@param s: Stream from transport layer
|
@param s: Stream from transport layer
|
||||||
"""
|
"""
|
||||||
pdu = data.PDU()
|
pdus = ArrayType(data.PDU)
|
||||||
s.readType(pdu)
|
s.readType(pdus)
|
||||||
|
for pdu in pdus:
|
||||||
if pdu.shareControlHeader.pduType.value == data.PDUType.PDUTYPE_DATAPDU:
|
if pdu.shareControlHeader.pduType.value == data.PDUType.PDUTYPE_DATAPDU:
|
||||||
self.readDataPDU(pdu.pduMessage)
|
self.readDataPDU(pdu.pduMessage)
|
||||||
elif pdu.shareControlHeader.pduType.value == data.PDUType.PDUTYPE_DEACTIVATEALLPDU:
|
elif pdu.shareControlHeader.pduType.value == data.PDUType.PDUTYPE_DEACTIVATEALLPDU:
|
||||||
@@ -276,10 +285,11 @@ class Client(PDULayer):
|
|||||||
@param fastPathS: {Stream} that contain fast path data
|
@param fastPathS: {Stream} that contain fast path data
|
||||||
@param secFlag: {SecFlags}
|
@param secFlag: {SecFlags}
|
||||||
"""
|
"""
|
||||||
fastPathPDU = data.FastPathUpdatePDU()
|
updates = ArrayType(data.FastPathUpdatePDU)
|
||||||
fastPathS.readType(fastPathPDU)
|
fastPathS.readType(updates)
|
||||||
if fastPathPDU.updateHeader.value == data.FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP:
|
for update in updates:
|
||||||
self._listener.onUpdate(fastPathPDU.updateData.rectangles._array)
|
if update.updateHeader.value == data.FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP:
|
||||||
|
self._listener.onUpdate(update.updateData.rectangles._array)
|
||||||
|
|
||||||
def readDataPDU(self, dataPDU):
|
def readDataPDU(self, dataPDU):
|
||||||
"""
|
"""
|
||||||
@@ -287,14 +297,20 @@ class Client(PDULayer):
|
|||||||
@param dataPDU: DataPDU object
|
@param dataPDU: DataPDU object
|
||||||
"""
|
"""
|
||||||
if dataPDU.shareDataHeader.pduType2.value == data.PDUType2.PDUTYPE2_SET_ERROR_INFO_PDU:
|
if dataPDU.shareDataHeader.pduType2.value == data.PDUType2.PDUTYPE2_SET_ERROR_INFO_PDU:
|
||||||
|
#ignore 0 error code because is not an error code
|
||||||
|
if dataPDU.pduData.errorInfo.value == 0:
|
||||||
|
return
|
||||||
errorMessage = "Unknown code %s"%hex(dataPDU.pduData.errorInfo.value)
|
errorMessage = "Unknown code %s"%hex(dataPDU.pduData.errorInfo.value)
|
||||||
if data.ErrorInfo._MESSAGES_.has_key(dataPDU.pduData.errorInfo):
|
if data.ErrorInfo._MESSAGES_.has_key(dataPDU.pduData.errorInfo):
|
||||||
errorMessage = data.ErrorInfo._MESSAGES_[dataPDU.pduData.errorInfo]
|
errorMessage = data.ErrorInfo._MESSAGES_[dataPDU.pduData.errorInfo]
|
||||||
|
|
||||||
log.error("INFO PDU : %s"%errorMessage)
|
log.error("INFO PDU : %s"%errorMessage)
|
||||||
|
|
||||||
elif dataPDU.shareDataHeader.pduType2.value == data.PDUType2.PDUTYPE2_SHUTDOWN_DENIED:
|
elif dataPDU.shareDataHeader.pduType2.value == data.PDUType2.PDUTYPE2_SHUTDOWN_DENIED:
|
||||||
#may be an event to ask to user
|
#may be an event to ask to user
|
||||||
self._transport.close()
|
self._transport.close()
|
||||||
|
elif dataPDU.shareDataHeader.pduType2.value == data.PDUType2.PDUTYPE2_SAVE_SESSION_INFO:
|
||||||
|
#handle session event
|
||||||
|
self._listener.onSessionReady()
|
||||||
elif dataPDU.shareDataHeader.pduType2.value == data.PDUType2.PDUTYPE2_UPDATE:
|
elif dataPDU.shareDataHeader.pduType2.value == data.PDUType2.PDUTYPE2_UPDATE:
|
||||||
self.readUpdateDataPDU(dataPDU.pduData)
|
self.readUpdateDataPDU(dataPDU.pduData)
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,17 @@ import pdu.layer
|
|||||||
import pdu.data
|
import pdu.data
|
||||||
import pdu.caps
|
import pdu.caps
|
||||||
import rdpy.core.log as log
|
import rdpy.core.log as log
|
||||||
import tpkt, x224, mcs, gcc, sec
|
import tpkt, x224, sec
|
||||||
|
from t125 import mcs, gcc
|
||||||
|
from nla import cssp, ntlm
|
||||||
|
|
||||||
|
class SecurityLevel(object):
|
||||||
|
"""
|
||||||
|
@summary: RDP security level
|
||||||
|
"""
|
||||||
|
RDP_LEVEL_RDP = 0
|
||||||
|
RDP_LEVEL_SSL = 1
|
||||||
|
RDP_LEVEL_NLA = 2
|
||||||
|
|
||||||
class RDPClientController(pdu.layer.PDUClientListener):
|
class RDPClientController(pdu.layer.PDUClientListener):
|
||||||
"""
|
"""
|
||||||
@@ -57,7 +67,7 @@ class RDPClientController(pdu.layer.PDUClientListener):
|
|||||||
@return: return Protocol layer for twisted
|
@return: return Protocol layer for twisted
|
||||||
In case of RDP TPKT is the Raw layer
|
In case of RDP TPKT is the Raw layer
|
||||||
"""
|
"""
|
||||||
return self._tpktLayer
|
return cssp.CSSP(self._tpktLayer, ntlm.NTLMv2(self._secLayer._info.domain.value, self._secLayer._info.userName.value, self._secLayer._info.password.value))
|
||||||
|
|
||||||
def getColorDepth(self):
|
def getColorDepth(self):
|
||||||
"""
|
"""
|
||||||
@@ -90,7 +100,7 @@ class RDPClientController(pdu.layer.PDUClientListener):
|
|||||||
def setUsername(self, username):
|
def setUsername(self, username):
|
||||||
"""
|
"""
|
||||||
@summary: Set the username for session
|
@summary: Set the username for session
|
||||||
@param username: username of session
|
@param username: {string} username of session
|
||||||
"""
|
"""
|
||||||
#username in PDU info packet
|
#username in PDU info packet
|
||||||
self._secLayer._info.userName.value = username
|
self._secLayer._info.userName.value = username
|
||||||
@@ -99,7 +109,7 @@ class RDPClientController(pdu.layer.PDUClientListener):
|
|||||||
def setPassword(self, password):
|
def setPassword(self, password):
|
||||||
"""
|
"""
|
||||||
@summary: Set password for session
|
@summary: Set password for session
|
||||||
@param password: password of session
|
@param password: {string} password of session
|
||||||
"""
|
"""
|
||||||
self.setAutologon()
|
self.setAutologon()
|
||||||
self._secLayer._info.password.value = password
|
self._secLayer._info.password.value = password
|
||||||
@@ -107,7 +117,7 @@ class RDPClientController(pdu.layer.PDUClientListener):
|
|||||||
def setDomain(self, domain):
|
def setDomain(self, domain):
|
||||||
"""
|
"""
|
||||||
@summary: Set the windows domain of session
|
@summary: Set the windows domain of session
|
||||||
@param domain: domain of session
|
@param domain: {string} domain of session
|
||||||
"""
|
"""
|
||||||
self._secLayer._info.domain.value = domain
|
self._secLayer._info.domain.value = domain
|
||||||
|
|
||||||
@@ -117,6 +127,13 @@ class RDPClientController(pdu.layer.PDUClientListener):
|
|||||||
"""
|
"""
|
||||||
self._secLayer._info.flag |= sec.InfoFlag.INFO_AUTOLOGON
|
self._secLayer._info.flag |= sec.InfoFlag.INFO_AUTOLOGON
|
||||||
|
|
||||||
|
def setAlternateShell(self, appName):
|
||||||
|
"""
|
||||||
|
@summary: set application name of app which start at the begining of session
|
||||||
|
@param appName: {string} application name
|
||||||
|
"""
|
||||||
|
self._secLayer._info.alternateShell.value = appName
|
||||||
|
|
||||||
def setKeyboardLayout(self, layout):
|
def setKeyboardLayout(self, layout):
|
||||||
"""
|
"""
|
||||||
@summary: keyboard layout
|
@summary: keyboard layout
|
||||||
@@ -137,12 +154,14 @@ class RDPClientController(pdu.layer.PDUClientListener):
|
|||||||
def setSecurityLevel(self, level):
|
def setSecurityLevel(self, level):
|
||||||
"""
|
"""
|
||||||
@summary: Request basic security
|
@summary: Request basic security
|
||||||
@param level: {str} (ssl | rdp)
|
@param level: {SecurityLevel}
|
||||||
"""
|
"""
|
||||||
if level == "rdp":
|
if level == SecurityLevel.RDP_LEVEL_RDP:
|
||||||
self._x224Layer._requestedProtocol = x224.Protocols.PROTOCOL_RDP
|
self._x224Layer._requestedProtocol = x224.Protocols.PROTOCOL_RDP
|
||||||
elif level == "ssl":
|
elif level == SecurityLevel.RDP_LEVEL_SSL:
|
||||||
self._x224Layer._requestedProtocol = x224.Protocols.PROTOCOL_SSL
|
self._x224Layer._requestedProtocol = x224.Protocols.PROTOCOL_SSL
|
||||||
|
elif level == SecurityLevel.RDP_LEVEL_NLA:
|
||||||
|
self._x224Layer._requestedProtocol = x224.Protocols.PROTOCOL_SSL | x224.Protocols.PROTOCOL_HYBRID
|
||||||
|
|
||||||
def addClientObserver(self, observer):
|
def addClientObserver(self, observer):
|
||||||
"""
|
"""
|
||||||
@@ -180,6 +199,15 @@ class RDPClientController(pdu.layer.PDUClientListener):
|
|||||||
for observer in self._clientObserver:
|
for observer in self._clientObserver:
|
||||||
observer.onReady()
|
observer.onReady()
|
||||||
|
|
||||||
|
def onSessionReady(self):
|
||||||
|
"""
|
||||||
|
@summary: Call when Windows session is ready (connected)
|
||||||
|
"""
|
||||||
|
self._isReady = True
|
||||||
|
#signal all listener
|
||||||
|
for observer in self._clientObserver:
|
||||||
|
observer.onSessionReady()
|
||||||
|
|
||||||
def onClose(self):
|
def onClose(self):
|
||||||
"""
|
"""
|
||||||
@summary: Event call when RDP stack is closed
|
@summary: Event call when RDP stack is closed
|
||||||
@@ -200,6 +228,17 @@ class RDPClientController(pdu.layer.PDUClientListener):
|
|||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
if button == 4 or button == 5:
|
||||||
|
event = pdu.data.PointerExEvent()
|
||||||
|
if isPressed:
|
||||||
|
event.pointerFlags.value |= pdu.data.PointerExFlag.PTRXFLAGS_DOWN
|
||||||
|
|
||||||
|
if button == 4:
|
||||||
|
event.pointerFlags.value |= pdu.data.PointerExFlag.PTRXFLAGS_BUTTON1
|
||||||
|
elif button == 5:
|
||||||
|
event.pointerFlags.value |= pdu.data.PointerExFlag.PTRXFLAGS_BUTTON2
|
||||||
|
|
||||||
|
else:
|
||||||
event = pdu.data.PointerEvent()
|
event = pdu.data.PointerEvent()
|
||||||
if isPressed:
|
if isPressed:
|
||||||
event.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_DOWN
|
event.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_DOWN
|
||||||
@@ -213,11 +252,11 @@ class RDPClientController(pdu.layer.PDUClientListener):
|
|||||||
else:
|
else:
|
||||||
event.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_MOVE
|
event.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_MOVE
|
||||||
|
|
||||||
#position
|
# position
|
||||||
event.xPos.value = x
|
event.xPos.value = x
|
||||||
event.yPos.value = y
|
event.yPos.value = y
|
||||||
|
|
||||||
#send proper event
|
# send proper event
|
||||||
self._pduLayer.sendInputEvents([event])
|
self._pduLayer.sendInputEvents([event])
|
||||||
|
|
||||||
except InvalidValue:
|
except InvalidValue:
|
||||||
@@ -257,11 +296,12 @@ class RDPClientController(pdu.layer.PDUClientListener):
|
|||||||
except InvalidValue:
|
except InvalidValue:
|
||||||
log.info("try send wheel event with incorrect position")
|
log.info("try send wheel event with incorrect position")
|
||||||
|
|
||||||
def sendKeyEventScancode(self, code, isPressed):
|
def sendKeyEventScancode(self, code, isPressed, extended = False):
|
||||||
"""
|
"""
|
||||||
@summary: Send a scan code to RDP stack
|
@summary: Send a scan code to RDP stack
|
||||||
@param code: scan code
|
@param code: scan code
|
||||||
@param isPressed: True if key is pressed and false if it's released
|
@param isPressed: True if key is pressed and false if it's released
|
||||||
|
@param extended: {boolean} extended scancode like ctr or win button
|
||||||
"""
|
"""
|
||||||
if not self._isReady:
|
if not self._isReady:
|
||||||
return
|
return
|
||||||
@@ -269,11 +309,12 @@ class RDPClientController(pdu.layer.PDUClientListener):
|
|||||||
try:
|
try:
|
||||||
event = pdu.data.ScancodeKeyEvent()
|
event = pdu.data.ScancodeKeyEvent()
|
||||||
event.keyCode.value = code
|
event.keyCode.value = code
|
||||||
if isPressed:
|
if not isPressed:
|
||||||
event.keyboardFlags.value |= pdu.data.KeyboardFlag.KBDFLAGS_DOWN
|
|
||||||
else:
|
|
||||||
event.keyboardFlags.value |= pdu.data.KeyboardFlag.KBDFLAGS_RELEASE
|
event.keyboardFlags.value |= pdu.data.KeyboardFlag.KBDFLAGS_RELEASE
|
||||||
|
|
||||||
|
if extended:
|
||||||
|
event.keyboardFlags.value |= pdu.data.KeyboardFlag.KBDFLAGS_EXTENDED
|
||||||
|
|
||||||
#send event
|
#send event
|
||||||
self._pduLayer.sendInputEvents([event])
|
self._pduLayer.sendInputEvents([event])
|
||||||
|
|
||||||
@@ -347,6 +388,7 @@ class RDPServerController(pdu.layer.PDUServerListener):
|
|||||||
self._x224Layer = x224.Server(self._mcsLayer, privateKeyFileName, certificateFileName, False)
|
self._x224Layer = x224.Server(self._mcsLayer, privateKeyFileName, certificateFileName, False)
|
||||||
#transport packet (protocol layer)
|
#transport packet (protocol layer)
|
||||||
self._tpktLayer = tpkt.TPKT(self._x224Layer)
|
self._tpktLayer = tpkt.TPKT(self._x224Layer)
|
||||||
|
|
||||||
#fastpath stack
|
#fastpath stack
|
||||||
self._pduLayer.initFastPath(self._secLayer)
|
self._pduLayer.initFastPath(self._secLayer)
|
||||||
self._secLayer.initFastPath(self._tpktLayer)
|
self._secLayer.initFastPath(self._tpktLayer)
|
||||||
@@ -465,11 +507,11 @@ class RDPServerController(pdu.layer.PDUServerListener):
|
|||||||
for event in slowPathInputEvents:
|
for event in slowPathInputEvents:
|
||||||
#scan code
|
#scan code
|
||||||
if event.messageType.value == pdu.data.InputMessageType.INPUT_EVENT_SCANCODE:
|
if event.messageType.value == pdu.data.InputMessageType.INPUT_EVENT_SCANCODE:
|
||||||
observer.onKeyEventScancode(event.slowPathInputData.keyCode.value, not (event.slowPathInputData.keyboardFlags.value & pdu.data.KeyboardFlag.KBDFLAGS_RELEASE))
|
observer.onKeyEventScancode(event.slowPathInputData.keyCode.value, not (event.slowPathInputData.keyboardFlags.value & pdu.data.KeyboardFlag.KBDFLAGS_RELEASE), bool(event.slowPathInputData.keyboardFlags.value & pdu.data.KeyboardFlag.KBDFLAGS_EXTENDED))
|
||||||
#unicode
|
#unicode
|
||||||
elif event.messageType.value == pdu.data.InputMessageType.INPUT_EVENT_UNICODE:
|
elif event.messageType.value == pdu.data.InputMessageType.INPUT_EVENT_UNICODE:
|
||||||
observer.onKeyEventUnicode(event.slowPathInputData.unicode.value, not (event.slowPathInputData.keyboardFlags.value & pdu.data.KeyboardFlag.KBDFLAGS_RELEASE))
|
observer.onKeyEventUnicode(event.slowPathInputData.unicode.value, not (event.slowPathInputData.keyboardFlags.value & pdu.data.KeyboardFlag.KBDFLAGS_RELEASE))
|
||||||
#mouse event
|
#mouse events
|
||||||
elif event.messageType.value == pdu.data.InputMessageType.INPUT_EVENT_MOUSE:
|
elif event.messageType.value == pdu.data.InputMessageType.INPUT_EVENT_MOUSE:
|
||||||
isPressed = event.slowPathInputData.pointerFlags.value & pdu.data.PointerFlag.PTRFLAGS_DOWN
|
isPressed = event.slowPathInputData.pointerFlags.value & pdu.data.PointerFlag.PTRFLAGS_DOWN
|
||||||
button = 0
|
button = 0
|
||||||
@@ -480,6 +522,15 @@ class RDPServerController(pdu.layer.PDUServerListener):
|
|||||||
elif event.slowPathInputData.pointerFlags.value & pdu.data.PointerFlag.PTRFLAGS_BUTTON3:
|
elif event.slowPathInputData.pointerFlags.value & pdu.data.PointerFlag.PTRFLAGS_BUTTON3:
|
||||||
button = 3
|
button = 3
|
||||||
observer.onPointerEvent(event.slowPathInputData.xPos.value, event.slowPathInputData.yPos.value, button, isPressed)
|
observer.onPointerEvent(event.slowPathInputData.xPos.value, event.slowPathInputData.yPos.value, button, isPressed)
|
||||||
|
elif event.messageType.value == pdu.data.InputMessageType.INPUT_EVENT_MOUSEX:
|
||||||
|
isPressed = event.slowPathInputData.pointerFlags.value & pdu.data.PointerExFlag.PTRXFLAGS_DOWN
|
||||||
|
button = 0
|
||||||
|
if event.slowPathInputData.pointerFlags.value & pdu.data.PointerExFlag.PTRXFLAGS_BUTTON1:
|
||||||
|
button = 4
|
||||||
|
elif event.slowPathInputData.pointerFlags.value & pdu.data.PointerExFlag.PTRXFLAGS_BUTTON2:
|
||||||
|
button = 5
|
||||||
|
observer.onPointerEvent(event.slowPathInputData.xPos.value, event.slowPathInputData.yPos.value, button, isPressed)
|
||||||
|
|
||||||
|
|
||||||
def sendUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
|
def sendUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
|
||||||
"""
|
"""
|
||||||
@@ -507,8 +558,9 @@ class ClientFactory(layer.RawLayerClientFactory):
|
|||||||
@summary: Factory of Client RDP protocol
|
@summary: Factory of Client RDP protocol
|
||||||
@param reason: twisted reason
|
@param reason: twisted reason
|
||||||
"""
|
"""
|
||||||
def connectionLost(self, tpktLayer, reason):
|
def connectionLost(self, csspLayer, reason):
|
||||||
#retrieve controller
|
#retrieve controller
|
||||||
|
tpktLayer = csspLayer._layer
|
||||||
x224Layer = tpktLayer._presentation
|
x224Layer = tpktLayer._presentation
|
||||||
mcsLayer = x224Layer._presentation
|
mcsLayer = x224Layer._presentation
|
||||||
secLayer = mcsLayer._channels[mcs.Channel.MCS_GLOBAL_CHANNEL]
|
secLayer = mcsLayer._channels[mcs.Channel.MCS_GLOBAL_CHANNEL]
|
||||||
@@ -593,6 +645,12 @@ class RDPClientObserver(object):
|
|||||||
"""
|
"""
|
||||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onReady", "RDPClientObserver"))
|
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onReady", "RDPClientObserver"))
|
||||||
|
|
||||||
|
def onSessionReady(self):
|
||||||
|
"""
|
||||||
|
@summary: Windows session is ready
|
||||||
|
"""
|
||||||
|
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onSessionReady", "RDPClientObserver"))
|
||||||
|
|
||||||
def onClose(self):
|
def onClose(self):
|
||||||
"""
|
"""
|
||||||
@summary: Stack is closes
|
@summary: Stack is closes
|
||||||
@@ -638,11 +696,12 @@ class RDPServerObserver(object):
|
|||||||
"""
|
"""
|
||||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onClose", "RDPClientObserver"))
|
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onClose", "RDPClientObserver"))
|
||||||
|
|
||||||
def onKeyEventScancode(self, code, isPressed):
|
def onKeyEventScancode(self, code, isPressed, isExtended):
|
||||||
"""
|
"""
|
||||||
@summary: Event call when a keyboard event is catch in scan code format
|
@summary: Event call when a keyboard event is catch in scan code format
|
||||||
@param code: scan code of key
|
@param code: {integer} scan code of key
|
||||||
@param isPressed: True if key is down
|
@param isPressed: {boolean} True if key is down
|
||||||
|
@param isExtended: {boolean} True if a special key
|
||||||
"""
|
"""
|
||||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onKeyEventScanCode", "RDPServerObserver"))
|
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onKeyEventScanCode", "RDPServerObserver"))
|
||||||
|
|
||||||
@@ -659,7 +718,7 @@ class RDPServerObserver(object):
|
|||||||
@summary: Event call on mouse event
|
@summary: Event call on mouse event
|
||||||
@param x: x position
|
@param x: x position
|
||||||
@param y: y position
|
@param y: y position
|
||||||
@param button: 1, 2 or 3 button
|
@param button: 1, 2, 3, 4 or 5 button
|
||||||
@param isPressed: True if mouse button is pressed
|
@param isPressed: True if mouse button is pressed
|
||||||
"""
|
"""
|
||||||
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onPointerEvent", "RDPServerObserver"))
|
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onPointerEvent", "RDPServerObserver"))
|
||||||
@@ -22,8 +22,9 @@ RDP Standard security layer
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import sha, md5
|
import sha, md5
|
||||||
import gcc, lic, tpkt, mcs
|
import lic, tpkt
|
||||||
from rdpy.core.type import CompositeType, Stream, UInt32Le, UInt16Le, String, sizeof, UInt8
|
from t125 import gcc, mcs
|
||||||
|
from rdpy.core.type import CompositeType, CallableValue, Stream, UInt32Le, UInt16Le, String, sizeof, UInt8
|
||||||
from rdpy.core.layer import LayerAutomata, IStreamSender
|
from rdpy.core.layer import LayerAutomata, IStreamSender
|
||||||
from rdpy.core.error import InvalidExpectedDataException
|
from rdpy.core.error import InvalidExpectedDataException
|
||||||
from rdpy.core import log
|
from rdpy.core import log
|
||||||
@@ -308,8 +309,8 @@ class ClientSecurityExchangePDU(CompositeType):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
CompositeType.__init__(self)
|
CompositeType.__init__(self)
|
||||||
self.length = UInt32Le(lambda:(sizeof(self) - 4))
|
self.length = UInt32Le(lambda:(sizeof(self) - 4))
|
||||||
self.encryptedClientRandom = String(readLen = UInt8(lambda:(self.length.value - 8)))
|
self.encryptedClientRandom = String(readLen = CallableValue(lambda:(self.length.value - 8)))
|
||||||
self.padding = String("\x00" * 8, readLen = UInt8(8))
|
self.padding = String("\x00" * 8, readLen = CallableValue(8))
|
||||||
|
|
||||||
class RDPInfo(CompositeType):
|
class RDPInfo(CompositeType):
|
||||||
"""
|
"""
|
||||||
@@ -322,20 +323,20 @@ class RDPInfo(CompositeType):
|
|||||||
#code page
|
#code page
|
||||||
self.codePage = UInt32Le()
|
self.codePage = UInt32Le()
|
||||||
#support flag
|
#support flag
|
||||||
self.flag = UInt32Le(InfoFlag.INFO_MOUSE | InfoFlag.INFO_UNICODE | InfoFlag.INFO_LOGONNOTIFY | InfoFlag.INFO_LOGONERRORS | InfoFlag.INFO_DISABLECTRLALTDEL)
|
self.flag = UInt32Le(InfoFlag.INFO_MOUSE | InfoFlag.INFO_UNICODE | InfoFlag.INFO_LOGONNOTIFY | InfoFlag.INFO_LOGONERRORS | InfoFlag.INFO_DISABLECTRLALTDEL | InfoFlag.INFO_ENABLEWINDOWSKEY)
|
||||||
self.cbDomain = UInt16Le(lambda:sizeof(self.domain) - 2)
|
self.cbDomain = UInt16Le(lambda:sizeof(self.domain) - 2)
|
||||||
self.cbUserName = UInt16Le(lambda:sizeof(self.userName) - 2)
|
self.cbUserName = UInt16Le(lambda:sizeof(self.userName) - 2)
|
||||||
self.cbPassword = UInt16Le(lambda:sizeof(self.password) - 2)
|
self.cbPassword = UInt16Le(lambda:sizeof(self.password) - 2)
|
||||||
self.cbAlternateShell = UInt16Le(lambda:sizeof(self.alternateShell) - 2)
|
self.cbAlternateShell = UInt16Le(lambda:sizeof(self.alternateShell) - 2)
|
||||||
self.cbWorkingDir = UInt16Le(lambda:sizeof(self.workingDir) - 2)
|
self.cbWorkingDir = UInt16Le(lambda:sizeof(self.workingDir) - 2)
|
||||||
#microsoft domain
|
#microsoft domain
|
||||||
self.domain = String(readLen = UInt16Le(lambda:self.cbDomain.value + 2), unicode = True)
|
self.domain = String(readLen = CallableValue(lambda:self.cbDomain.value + 2), unicode = True)
|
||||||
self.userName = String(readLen = UInt16Le(lambda:self.cbUserName.value + 2), unicode = True)
|
self.userName = String(readLen = CallableValue(lambda:self.cbUserName.value + 2), unicode = True)
|
||||||
self.password = String(readLen = UInt16Le(lambda:self.cbPassword.value + 2), unicode = True)
|
self.password = String(readLen = CallableValue(lambda:self.cbPassword.value + 2), unicode = True)
|
||||||
#shell execute at start of session
|
#shell execute at start of session
|
||||||
self.alternateShell = String(readLen = UInt16Le(lambda:self.cbAlternateShell.value + 2), unicode = True)
|
self.alternateShell = String(readLen = CallableValue(lambda:self.cbAlternateShell.value + 2), unicode = True)
|
||||||
#working directory for session
|
#working directory for session
|
||||||
self.workingDir = String(readLen = UInt16Le(lambda:self.cbWorkingDir.value + 2), unicode = True)
|
self.workingDir = String(readLen = CallableValue(lambda:self.cbWorkingDir.value + 2), unicode = True)
|
||||||
self.extendedInfo = RDPExtendedInfo(conditional = extendedInfoConditional)
|
self.extendedInfo = RDPExtendedInfo(conditional = extendedInfoConditional)
|
||||||
|
|
||||||
class RDPExtendedInfo(CompositeType):
|
class RDPExtendedInfo(CompositeType):
|
||||||
@@ -408,7 +409,7 @@ class SecLayer(LayerAutomata, IStreamSender, tpkt.IFastPathListener, tpkt.IFastP
|
|||||||
self._decryptRc4 = rc4.RC4Key(self._currentDecrytKey)
|
self._decryptRc4 = rc4.RC4Key(self._currentDecrytKey)
|
||||||
self._nbDecryptedPacket = 0
|
self._nbDecryptedPacket = 0
|
||||||
|
|
||||||
signature = String(readLen = UInt8(8))
|
signature = String(readLen = CallableValue(8))
|
||||||
encryptedPayload = String()
|
encryptedPayload = String()
|
||||||
s.readType((signature, encryptedPayload))
|
s.readType((signature, encryptedPayload))
|
||||||
decrypted = rc4.crypt(self._decryptRc4, encryptedPayload.value)
|
decrypted = rc4.crypt(self._decryptRc4, encryptedPayload.value)
|
||||||
|
|||||||
0
rdpy/protocol/rdp/t125/__init__.py
Normal file
0
rdpy/protocol/rdp/t125/__init__.py
Normal file
@@ -23,7 +23,7 @@ http://msdn.microsoft.com/en-us/library/cc240508.aspx
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import md5
|
import md5
|
||||||
from rdpy.core.type import UInt8, UInt16Le, UInt32Le, CompositeType, String, Stream, sizeof, FactoryType, ArrayType
|
from rdpy.core.type import UInt8, UInt16Le, UInt32Le, CompositeType, CallableValue, String, Stream, sizeof, FactoryType, ArrayType
|
||||||
import per, mcs
|
import per, mcs
|
||||||
from rdpy.core.error import InvalidExpectedDataException
|
from rdpy.core.error import InvalidExpectedDataException
|
||||||
from rdpy.core import log
|
from rdpy.core import log
|
||||||
@@ -252,18 +252,18 @@ class ClientCoreData(CompositeType):
|
|||||||
self.sasSequence = UInt16Le(Sequence.RNS_UD_SAS_DEL)
|
self.sasSequence = UInt16Le(Sequence.RNS_UD_SAS_DEL)
|
||||||
self.kbdLayout = UInt32Le(KeyboardLayout.US)
|
self.kbdLayout = UInt32Le(KeyboardLayout.US)
|
||||||
self.clientBuild = UInt32Le(3790)
|
self.clientBuild = UInt32Le(3790)
|
||||||
self.clientName = String("rdpy" + "\x00"*11, readLen = UInt8(32), unicode = True)
|
self.clientName = String("rdpy" + "\x00"*11, readLen = CallableValue(32), unicode = True)
|
||||||
self.keyboardType = UInt32Le(KeyboardType.IBM_101_102_KEYS)
|
self.keyboardType = UInt32Le(KeyboardType.IBM_101_102_KEYS)
|
||||||
self.keyboardSubType = UInt32Le(0)
|
self.keyboardSubType = UInt32Le(0)
|
||||||
self.keyboardFnKeys = UInt32Le(12)
|
self.keyboardFnKeys = UInt32Le(12)
|
||||||
self.imeFileName = String("\x00"*64, readLen = UInt8(64), optional = True)
|
self.imeFileName = String("\x00"*64, readLen = CallableValue(64), optional = True)
|
||||||
self.postBeta2ColorDepth = UInt16Le(ColorDepth.RNS_UD_COLOR_8BPP, optional = True)
|
self.postBeta2ColorDepth = UInt16Le(ColorDepth.RNS_UD_COLOR_8BPP, optional = True)
|
||||||
self.clientProductId = UInt16Le(1, optional = True)
|
self.clientProductId = UInt16Le(1, optional = True)
|
||||||
self.serialNumber = UInt32Le(0, optional = True)
|
self.serialNumber = UInt32Le(0, optional = True)
|
||||||
self.highColorDepth = UInt16Le(HighColor.HIGH_COLOR_24BPP, optional = True)
|
self.highColorDepth = UInt16Le(HighColor.HIGH_COLOR_24BPP, optional = True)
|
||||||
self.supportedColorDepths = UInt16Le(Support.RNS_UD_15BPP_SUPPORT | Support.RNS_UD_16BPP_SUPPORT | Support.RNS_UD_24BPP_SUPPORT | Support.RNS_UD_32BPP_SUPPORT, optional = True)
|
self.supportedColorDepths = UInt16Le(Support.RNS_UD_15BPP_SUPPORT | Support.RNS_UD_16BPP_SUPPORT | Support.RNS_UD_24BPP_SUPPORT | Support.RNS_UD_32BPP_SUPPORT, optional = True)
|
||||||
self.earlyCapabilityFlags = UInt16Le(CapabilityFlags.RNS_UD_CS_SUPPORT_ERRINFO_PDU, optional = True)
|
self.earlyCapabilityFlags = UInt16Le(CapabilityFlags.RNS_UD_CS_SUPPORT_ERRINFO_PDU, optional = True)
|
||||||
self.clientDigProductId = String("\x00"*64, readLen = UInt8(64), optional = True)
|
self.clientDigProductId = String("\x00"*64, readLen = CallableValue(64), optional = True)
|
||||||
self.connectionType = UInt8(optional = True)
|
self.connectionType = UInt8(optional = True)
|
||||||
self.pad1octet = UInt8(optional = True)
|
self.pad1octet = UInt8(optional = True)
|
||||||
self.serverSelectedProtocol = UInt32Le(optional = True)
|
self.serverSelectedProtocol = UInt32Le(optional = True)
|
||||||
@@ -355,8 +355,8 @@ class ProprietaryServerCertificate(CompositeType):
|
|||||||
self.PublicKeyBlob = RSAPublicKey(readLen = self.wPublicKeyBlobLen)
|
self.PublicKeyBlob = RSAPublicKey(readLen = self.wPublicKeyBlobLen)
|
||||||
self.wSignatureBlobType = UInt16Le(0x0008, constant = True)
|
self.wSignatureBlobType = UInt16Le(0x0008, constant = True)
|
||||||
self.wSignatureBlobLen = UInt16Le(lambda:(sizeof(self.SignatureBlob) + sizeof(self.padding)))
|
self.wSignatureBlobLen = UInt16Le(lambda:(sizeof(self.SignatureBlob) + sizeof(self.padding)))
|
||||||
self.SignatureBlob = String(readLen = UInt16Le(lambda:(self.wSignatureBlobLen.value - sizeof(self.padding))))
|
self.SignatureBlob = String(readLen = CallableValue(lambda:(self.wSignatureBlobLen.value - sizeof(self.padding))))
|
||||||
self.padding = String(b"\x00" * 8, readLen = UInt8(8))
|
self.padding = String(b"\x00" * 8, readLen = CallableValue(8))
|
||||||
|
|
||||||
def getPublicKey(self):
|
def getPublicKey(self):
|
||||||
"""
|
"""
|
||||||
@@ -418,7 +418,7 @@ class X509CertificateChain(CompositeType):
|
|||||||
CompositeType.__init__(self)
|
CompositeType.__init__(self)
|
||||||
self.NumCertBlobs = UInt32Le()
|
self.NumCertBlobs = UInt32Le()
|
||||||
self.CertBlobArray = ArrayType(CertBlob, readLen = self.NumCertBlobs)
|
self.CertBlobArray = ArrayType(CertBlob, readLen = self.NumCertBlobs)
|
||||||
self.padding = String(readLen = UInt8(lambda:(8 + 4 * self.NumCertBlobs.value)))
|
self.padding = String(readLen = CallableValue(lambda:(8 + 4 * self.NumCertBlobs.value)))
|
||||||
|
|
||||||
def getPublicKey(self):
|
def getPublicKey(self):
|
||||||
"""
|
"""
|
||||||
@@ -447,8 +447,8 @@ class RSAPublicKey(CompositeType):
|
|||||||
self.bitlen = UInt32Le(lambda:((self.keylen.value - 8) * 8))
|
self.bitlen = UInt32Le(lambda:((self.keylen.value - 8) * 8))
|
||||||
self.datalen = UInt32Le(lambda:((self.bitlen.value / 8) - 1))
|
self.datalen = UInt32Le(lambda:((self.bitlen.value / 8) - 1))
|
||||||
self.pubExp = UInt32Le()
|
self.pubExp = UInt32Le()
|
||||||
self.modulus = String(readLen = UInt16Le(lambda:(self.keylen.value - 8)))
|
self.modulus = String(readLen = CallableValue(lambda:(self.keylen.value - 8)))
|
||||||
self.padding = String("\x00" * 8, readLen = UInt8(8))
|
self.padding = String("\x00" * 8, readLen = CallableValue(8))
|
||||||
|
|
||||||
class ChannelDef(CompositeType):
|
class ChannelDef(CompositeType):
|
||||||
"""
|
"""
|
||||||
@@ -458,7 +458,7 @@ class ChannelDef(CompositeType):
|
|||||||
def __init__(self, name = "", options = 0):
|
def __init__(self, name = "", options = 0):
|
||||||
CompositeType.__init__(self)
|
CompositeType.__init__(self)
|
||||||
#name of channel
|
#name of channel
|
||||||
self.name = String(name[0:8] + "\x00" * (8 - len(name)), readLen = UInt8(8))
|
self.name = String(name[0:8] + "\x00" * (8 - len(name)), readLen = CallableValue(8))
|
||||||
#unknown
|
#unknown
|
||||||
self.options = UInt32Le()
|
self.options = UInt32Le()
|
||||||
|
|
||||||
@@ -554,7 +554,7 @@ def readConferenceCreateRequest(s):
|
|||||||
|
|
||||||
per.readOctetStream(s, h221_cs_key, 4)
|
per.readOctetStream(s, h221_cs_key, 4)
|
||||||
length = per.readLength(s)
|
length = per.readLength(s)
|
||||||
clientSettings = Settings(readLen = UInt32Le(length))
|
clientSettings = Settings(readLen = CallableValue(length))
|
||||||
s.readType(clientSettings)
|
s.readType(clientSettings)
|
||||||
return clientSettings
|
return clientSettings
|
||||||
|
|
||||||
@@ -578,7 +578,7 @@ def readConferenceCreateResponse(s):
|
|||||||
raise InvalidExpectedDataException("cannot read h221_sc_key")
|
raise InvalidExpectedDataException("cannot read h221_sc_key")
|
||||||
|
|
||||||
length = per.readLength(s)
|
length = per.readLength(s)
|
||||||
serverSettings = Settings(readLen = UInt32Le(length))
|
serverSettings = Settings(readLen = CallableValue(length))
|
||||||
s.readType(serverSettings)
|
s.readType(serverSettings)
|
||||||
return serverSettings
|
return serverSettings
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ It exist channel for file system order, audio channel, clipboard etc...
|
|||||||
from rdpy.core.layer import LayerAutomata, IStreamSender, Layer
|
from rdpy.core.layer import LayerAutomata, IStreamSender, Layer
|
||||||
from rdpy.core.type import sizeof, Stream, UInt8, UInt16Le, String
|
from rdpy.core.type import sizeof, Stream, UInt8, UInt16Le, String
|
||||||
from rdpy.core.error import InvalidExpectedDataException, InvalidValue, InvalidSize, CallPureVirtualFuntion
|
from rdpy.core.error import InvalidExpectedDataException, InvalidValue, InvalidSize, CallPureVirtualFuntion
|
||||||
from rdpy.protocol.rdp.ber import writeLength
|
from ber import writeLength
|
||||||
import rdpy.core.log as log
|
import rdpy.core.log as log
|
||||||
|
|
||||||
import ber, gcc, per
|
import ber, gcc, per
|
||||||
@@ -204,7 +204,21 @@ class TPKT(RawLayer, IFastPathSender):
|
|||||||
|
|
||||||
def sendFastPath(self, secFlag, fastPathS):
|
def sendFastPath(self, secFlag, fastPathS):
|
||||||
"""
|
"""
|
||||||
@param fastPathS: type transform to stream and send as fastpath
|
@param fastPathS: {Type | Tuple} type transform to stream and send as fastpath
|
||||||
@param secFlag: {integer} Security flag for fastpath packet
|
@param secFlag: {integer} Security flag for fastpath packet
|
||||||
"""
|
"""
|
||||||
RawLayer.send(self, (UInt8(Action.FASTPATH_ACTION_FASTPATH | ((secFlag & 0x3) << 6)), UInt16Be((sizeof(fastPathS) + 3) | 0x8000), fastPathS))
|
RawLayer.send(self, (UInt8(Action.FASTPATH_ACTION_FASTPATH | ((secFlag & 0x3) << 6)), UInt16Be((sizeof(fastPathS) + 3) | 0x8000), fastPathS))
|
||||||
|
|
||||||
|
def startTLS(self, sslContext):
|
||||||
|
"""
|
||||||
|
@summary: start TLS protocol
|
||||||
|
@param sslContext: {ssl.ClientContextFactory | ssl.DefaultOpenSSLContextFactory} context use for TLS protocol
|
||||||
|
"""
|
||||||
|
self.transport.startTLS(sslContext)
|
||||||
|
|
||||||
|
def startNLA(self, sslContext, callback):
|
||||||
|
"""
|
||||||
|
@summary: use to start NLA (NTLM over SSL) protocol
|
||||||
|
must be called after startTLS function
|
||||||
|
"""
|
||||||
|
self.transport.startNLA(sslContext, callback)
|
||||||
@@ -50,6 +50,7 @@ class NegociationType(object):
|
|||||||
class Protocols(object):
|
class Protocols(object):
|
||||||
"""
|
"""
|
||||||
@summary: Protocols available for x224 layer
|
@summary: Protocols available for x224 layer
|
||||||
|
@see: https://msdn.microsoft.com/en-us/library/cc240500.aspx
|
||||||
"""
|
"""
|
||||||
PROTOCOL_RDP = 0x00000000
|
PROTOCOL_RDP = 0x00000000
|
||||||
PROTOCOL_SSL = 0x00000001
|
PROTOCOL_SSL = 0x00000001
|
||||||
@@ -132,7 +133,7 @@ class X224Layer(LayerAutomata, IStreamSender):
|
|||||||
"""
|
"""
|
||||||
LayerAutomata.__init__(self, presentation)
|
LayerAutomata.__init__(self, presentation)
|
||||||
#client requested selectedProtocol
|
#client requested selectedProtocol
|
||||||
self._requestedProtocol = Protocols.PROTOCOL_SSL
|
self._requestedProtocol = Protocols.PROTOCOL_SSL | Protocols.PROTOCOL_HYBRID
|
||||||
#server selected selectedProtocol
|
#server selected selectedProtocol
|
||||||
self._selectedProtocol = Protocols.PROTOCOL_SSL
|
self._selectedProtocol = Protocols.PROTOCOL_SSL
|
||||||
|
|
||||||
@@ -204,20 +205,33 @@ class Client(X224Layer):
|
|||||||
self._selectedProtocol = Protocols.PROTOCOL_RDP
|
self._selectedProtocol = Protocols.PROTOCOL_RDP
|
||||||
|
|
||||||
#NLA protocol doesn't support in actual version of RDPY
|
#NLA protocol doesn't support in actual version of RDPY
|
||||||
if self._selectedProtocol in [ Protocols.PROTOCOL_HYBRID, Protocols.PROTOCOL_HYBRID_EX ]:
|
if self._selectedProtocol in [ Protocols.PROTOCOL_HYBRID_EX ]:
|
||||||
raise InvalidExpectedDataException("RDPY doesn't support NLA security Layer")
|
raise InvalidExpectedDataException("RDPY doesn't support PROTOCOL_HYBRID_EX security Layer")
|
||||||
|
|
||||||
if self._selectedProtocol == Protocols.PROTOCOL_SSL:
|
|
||||||
log.debug("*" * 10 + " select SSL layer " + "*" * 10)
|
|
||||||
#_transport is TPKT and transport is TCP layer of twisted
|
|
||||||
self._transport.transport.startTLS(ClientTLSContext())
|
|
||||||
|
|
||||||
#now i'm ready to receive data
|
#now i'm ready to receive data
|
||||||
self.setNextState(self.recvData)
|
self.setNextState(self.recvData)
|
||||||
|
|
||||||
|
if self._selectedProtocol == Protocols.PROTOCOL_RDP:
|
||||||
|
log.warning("*" * 43)
|
||||||
|
log.warning("*" + " " * 10 + "RDP Security selected" + " " * 10 + "*")
|
||||||
|
log.warning("*" * 43)
|
||||||
#connection is done send to presentation
|
#connection is done send to presentation
|
||||||
self._presentation.connect()
|
self._presentation.connect()
|
||||||
|
|
||||||
|
elif self._selectedProtocol == Protocols.PROTOCOL_SSL:
|
||||||
|
log.info("*" * 43)
|
||||||
|
log.info("*" + " " * 10 + "SSL Security selected" + " " * 10 + "*")
|
||||||
|
log.info("*" * 43)
|
||||||
|
self._transport.startTLS(ClientTLSContext())
|
||||||
|
#connection is done send to presentation
|
||||||
|
self._presentation.connect()
|
||||||
|
|
||||||
|
elif self._selectedProtocol == Protocols.PROTOCOL_HYBRID:
|
||||||
|
log.info("*" * 43)
|
||||||
|
log.info("*" + " " * 10 + "NLA Security selected" + " " * 10 + "*")
|
||||||
|
log.info("*" * 43)
|
||||||
|
self._transport.startNLA(ClientTLSContext(), lambda:self._presentation.connect())
|
||||||
|
|
||||||
class Server(X224Layer):
|
class Server(X224Layer):
|
||||||
"""
|
"""
|
||||||
@summary: Server automata of X224 layer
|
@summary: Server automata of X224 layer
|
||||||
@@ -289,7 +303,7 @@ class Server(X224Layer):
|
|||||||
if self._selectedProtocol == Protocols.PROTOCOL_SSL:
|
if self._selectedProtocol == Protocols.PROTOCOL_SSL:
|
||||||
log.debug("*" * 10 + " select SSL layer " + "*" * 10)
|
log.debug("*" * 10 + " select SSL layer " + "*" * 10)
|
||||||
#_transport is TPKT and transport is TCP layer of twisted
|
#_transport is TPKT and transport is TCP layer of twisted
|
||||||
self._transport.transport.startTLS(ServerTLSContext(self._serverPrivateKeyFileName, self._serverCertificateFileName))
|
self._transport.startTLS(ServerTLSContext(self._serverPrivateKeyFileName, self._serverCertificateFileName))
|
||||||
|
|
||||||
#connection is done send to presentation
|
#connection is done send to presentation
|
||||||
self.setNextState(self.recvData)
|
self.setNextState(self.recvData)
|
||||||
|
|||||||
@@ -148,13 +148,10 @@ def extractRSAKey(certificate):
|
|||||||
"""
|
"""
|
||||||
#http://www.alvestrand.no/objectid/1.2.840.113549.1.1.1.html
|
#http://www.alvestrand.no/objectid/1.2.840.113549.1.1.1.html
|
||||||
|
|
||||||
#extract binary data
|
binaryTuple = certificate.getComponentByName('tbsCertificate').getComponentByName('subjectPublicKeyInfo').getComponentByName('subjectPublicKey')
|
||||||
l = 0L
|
l = int("".join([str(i) for i in binaryTuple]), 2)
|
||||||
for b in certificate.getComponentByName('tbsCertificate').getComponentByName('subjectPublicKeyInfo').getComponentByName('subjectPublicKey'):
|
return extractRSAKeyFromASN1(hex(l)[2:-1].decode('hex'))
|
||||||
l = (l << 1) | b
|
|
||||||
|
|
||||||
rsaKey = decoder.decode(hex(l)[2:-1].decode('hex'), asn1Spec=RSAPublicKey())[0]
|
def extractRSAKeyFromASN1(subjectPublicKey):
|
||||||
|
rsaKey = decoder.decode(subjectPublicKey, asn1Spec=RSAPublicKey())[0]
|
||||||
return rsaKey.getComponentByName('modulus')._value , rsaKey.getComponentByName('publicExponent')._value
|
return rsaKey.getComponentByName('modulus')._value , rsaKey.getComponentByName('publicExponent')._value
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -303,7 +303,7 @@ class RDPClientQt(RDPClientObserver, QAdaptor):
|
|||||||
@param isCompress: {bool} use RLE compression
|
@param isCompress: {bool} use RLE compression
|
||||||
@param data: {str} bitmap data
|
@param data: {str} bitmap data
|
||||||
"""
|
"""
|
||||||
image = RDPBitmapToQtImage(width, height, bitsPerPixel, isCompress, data);
|
image = RDPBitmapToQtImage(width, height, bitsPerPixel, isCompress, data)
|
||||||
#if image need to be cut
|
#if image need to be cut
|
||||||
#For bit alignement server may send more than image pixel
|
#For bit alignement server may send more than image pixel
|
||||||
self._widget.notifyImage(destLeft, destTop, image, destRight - destLeft + 1, destBottom - destTop + 1)
|
self._widget.notifyImage(destLeft, destTop, image, destRight - destLeft + 1, destBottom - destTop + 1)
|
||||||
@@ -311,12 +311,21 @@ class RDPClientQt(RDPClientObserver, QAdaptor):
|
|||||||
def onReady(self):
|
def onReady(self):
|
||||||
"""
|
"""
|
||||||
@summary: Call when stack is ready
|
@summary: Call when stack is ready
|
||||||
|
@see: rdp.RDPClientObserver.onReady
|
||||||
"""
|
"""
|
||||||
#do something maybe a loader
|
#do something maybe a loader
|
||||||
|
|
||||||
|
def onSessionReady(self):
|
||||||
|
"""
|
||||||
|
@summary: Windows session is ready
|
||||||
|
@see: rdp.RDPClientObserver.onSessionReady
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
def onClose(self):
|
def onClose(self):
|
||||||
"""
|
"""
|
||||||
@summary: Call when stack is close
|
@summary: Call when stack is close
|
||||||
|
@see: rdp.RDPClientObserver.onClose
|
||||||
"""
|
"""
|
||||||
#do something maybe a message
|
#do something maybe a message
|
||||||
|
|
||||||
@@ -336,12 +345,6 @@ class QRemoteDesktop(QtGui.QWidget):
|
|||||||
self._adaptor = adaptor
|
self._adaptor = adaptor
|
||||||
#set correct size
|
#set correct size
|
||||||
self.resize(width, height)
|
self.resize(width, height)
|
||||||
#refresh stack of image
|
|
||||||
#because we can update image only in paint
|
|
||||||
#event function. When protocol receive image
|
|
||||||
#we will stock into refresh list
|
|
||||||
#and in paint event paint list of all refresh images
|
|
||||||
self._refresh = []
|
|
||||||
#bind mouse event
|
#bind mouse event
|
||||||
self.setMouseTracking(True)
|
self.setMouseTracking(True)
|
||||||
#buffer image
|
#buffer image
|
||||||
@@ -354,8 +357,9 @@ class QRemoteDesktop(QtGui.QWidget):
|
|||||||
@param y: y position of new image
|
@param y: y position of new image
|
||||||
@param qimage: new QImage
|
@param qimage: new QImage
|
||||||
"""
|
"""
|
||||||
#save in refresh list (order is important)
|
#fill buffer image
|
||||||
self._refresh.append((x, y, qimage, width, height))
|
with QtGui.QPainter(self._buffer) as qp:
|
||||||
|
qp.drawImage(x, y, qimage, 0, 0, width, height)
|
||||||
#force update
|
#force update
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
@@ -373,17 +377,10 @@ class QRemoteDesktop(QtGui.QWidget):
|
|||||||
@summary: Call when Qt renderer engine estimate that is needed
|
@summary: Call when Qt renderer engine estimate that is needed
|
||||||
@param e: QEvent
|
@param e: QEvent
|
||||||
"""
|
"""
|
||||||
#fill buffer image
|
|
||||||
with QtGui.QPainter(self._buffer) as qp:
|
|
||||||
#draw image
|
|
||||||
for (x, y, image, width, height) in self._refresh:
|
|
||||||
qp.drawImage(x, y, image, 0, 0, width, height)
|
|
||||||
#draw in widget
|
#draw in widget
|
||||||
with QtGui.QPainter(self) as qp:
|
with QtGui.QPainter(self) as qp:
|
||||||
qp.drawImage(0, 0, self._buffer)
|
qp.drawImage(0, 0, self._buffer)
|
||||||
|
|
||||||
self._refresh = []
|
|
||||||
|
|
||||||
def mouseMoveEvent(self, event):
|
def mouseMoveEvent(self, event):
|
||||||
"""
|
"""
|
||||||
@summary: Call when mouse move
|
@summary: Call when mouse move
|
||||||
|
|||||||
18
setup.py
18
setup.py
@@ -4,20 +4,12 @@ import setuptools
|
|||||||
from distutils.core import setup, Extension
|
from distutils.core import setup, Extension
|
||||||
|
|
||||||
setup(name='rdpy',
|
setup(name='rdpy',
|
||||||
version='1.2.2',
|
version='1.3.2',
|
||||||
description='Remote Desktop Protocol in Python',
|
description='Remote Desktop Protocol in Python',
|
||||||
long_description="""
|
long_description="""
|
||||||
RDPY is a pure Python implementation of the Microsoft RDP (Remote Desktop Protocol) protocol (Client and Server side).
|
RDPY is a pure Python implementation of the Microsoft RDP (Remote Desktop Protocol) protocol (Client and Server side). RDPY is built over the event driven network engine Twisted.
|
||||||
RDPY is built over the event driven network engine Twisted.
|
|
||||||
|
|
||||||
RDPY provide RDP and VNC binaries :
|
RDPY provide RDP and VNC binaries : RDP Man In The Middle proxy which record session, RDP Honeypot, RDP screenshoter, RDP client, VNC client, VNC screenshoter, RSS Player
|
||||||
- RDP Man In The Middle proxy which record session
|
|
||||||
- RDP Honeypot
|
|
||||||
- RDP screenshoter
|
|
||||||
- RDP client
|
|
||||||
- VNC client
|
|
||||||
- VNC screenshoter
|
|
||||||
- RSS Player
|
|
||||||
""",
|
""",
|
||||||
author='Sylvain Peyrefitte',
|
author='Sylvain Peyrefitte',
|
||||||
author_email='citronneur@gmail.com',
|
author_email='citronneur@gmail.com',
|
||||||
@@ -29,6 +21,8 @@ setup(name='rdpy',
|
|||||||
'rdpy.protocol',
|
'rdpy.protocol',
|
||||||
'rdpy.protocol.rdp',
|
'rdpy.protocol.rdp',
|
||||||
'rdpy.protocol.rdp.pdu',
|
'rdpy.protocol.rdp.pdu',
|
||||||
|
'rdpy.protocol.rdp.nla',
|
||||||
|
'rdpy.protocol.rdp.t125',
|
||||||
'rdpy.protocol.rfb',
|
'rdpy.protocol.rfb',
|
||||||
'rdpy.ui'
|
'rdpy.ui'
|
||||||
],
|
],
|
||||||
@@ -48,6 +42,6 @@ setup(name='rdpy',
|
|||||||
'service_identity',
|
'service_identity',
|
||||||
'qt4reactor',
|
'qt4reactor',
|
||||||
'rsa',
|
'rsa',
|
||||||
'pyasn1',
|
'pyasn1'
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import os, sys
|
|||||||
sys.path.insert(1, os.path.join(sys.path[0], '..'))
|
sys.path.insert(1, os.path.join(sys.path[0], '..'))
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
import rdpy.protocol.rdp.ber as ber
|
import rdpy.protocol.rdp.t125.ber as ber
|
||||||
import rdpy.core.type as type
|
import rdpy.core.type as type
|
||||||
import rdpy.core.error as error
|
import rdpy.core.error as error
|
||||||
|
|
||||||
|
|||||||
128
test/test_protocol_rdp_cssp_ntlm.py
Normal file
128
test/test_protocol_rdp_cssp_ntlm.py
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
#
|
||||||
|
# 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/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
"""
|
||||||
|
unit test for rdpy.protocol.rdp.nla.cssp and ntlm module
|
||||||
|
"""
|
||||||
|
import unittest
|
||||||
|
import os, sys
|
||||||
|
# Change path so we find rdpy
|
||||||
|
sys.path.insert(1, os.path.join(sys.path[0], '..'))
|
||||||
|
|
||||||
|
from rdpy.protocol.rdp.nla import cssp, ntlm
|
||||||
|
from rdpy.security import rc4
|
||||||
|
|
||||||
|
pubKeyHex = """
|
||||||
|
MIGJAoGBAJ6VtUEDxTPqKWUrZe8wcd1zuzA77Mpyz73g+C
|
||||||
|
H/ppd2oQi10saVgdK6cRBKrCU0N6DD\nV/DqH4yE63vmbF
|
||||||
|
AmH7dBCljTgIc9C0HZvFQ6D3cUefW5pDjrEwg1rr+zF1ri
|
||||||
|
WIk5xCJ/FleQCK+R\nO5XIU9DAjhmK8xC8yMdC+xLeLV6D
|
||||||
|
AgMBAAE=
|
||||||
|
"""
|
||||||
|
peer0_0 = """
|
||||||
|
MC+gAwIBAqEoMCYwJKAiBCBOVExNU1NQAAEAAAA1gghgAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAA==
|
||||||
|
"""
|
||||||
|
peer1_0 = """
|
||||||
|
MIIBCaADAgECoYIBADCB/TCB+qCB9wSB9E5UTE1TU1AAAg
|
||||||
|
AAAA4ADgA4AAAANYKJYnPHQ6nn/Lv8\nAAAAAAAAAACuAK
|
||||||
|
4ARgAAAAYBsR0AAAAPUwBJAFIAQQBEAEUATAACAA4AUwBJ
|
||||||
|
AFIAQQBEAEUATAAB\nABYAVwBBAFYALQBHAEwAVwAtADAA
|
||||||
|
MAA5AAQAGgBTAGkAcgBhAGQAZQBsAC4AbABvAGMAYQBsAA
|
||||||
|
MA\nMgB3AGEAdgAtAGcAbAB3AC0AMAAwADkALgBTAGkAcg
|
||||||
|
BhAGQAZQBsAC4AbABvAGMAYQBsAAUAGgBT\nAGkAcgBhAG
|
||||||
|
QAZQBsAC4AbABvAGMAYQBsAAcACABWkzQyx1XQAQAAAAA=
|
||||||
|
"""
|
||||||
|
peer0_1 = """
|
||||||
|
MIICD6ADAgECoYIBYjCCAV4wggFaoIIBVgSCAVJOVExNU1
|
||||||
|
NQAAMAAAAYABgAUAAAANoA2gBoAAAA\nCAAIAEAAAAAIAA
|
||||||
|
gASAAAAAAAAABQAAAAEAAQAEIBAAA1gghgYwBvAGMAbwB0
|
||||||
|
AG8AdABvABqKwrxk\n2sAom6gUCFFt1rgpCdKZGTNwnlGg
|
||||||
|
bsU5R/OelmrD/LLrx+ABAQAAAAAAAABFCDLHVdABKQnSmR
|
||||||
|
kz\ncJ4AAAAAAgAOAFMASQBSAEEARABFAEwAAQAWAFcAQQ
|
||||||
|
BWAC0ARwBMAFcALQAwADAAOQAEABoAUwBp\nAHIAYQBkAG
|
||||||
|
UAbAAuAGwAbwBjAGEAbAADADIAdwBhAHYALQBnAGwAdwAt
|
||||||
|
ADAAMAA5AC4AUwBpAHIA\nYQBkAGUAbAAuAGwAbwBjAGEA
|
||||||
|
bAAFABoAUwBpAHIAYQBkAGUAbAAuAGwAbwBjAGEAbAAHAA
|
||||||
|
gAVpM0\nMsdV0AEAAAAAv+z19mkBOu9b0Kv+P991MKOCAK
|
||||||
|
AEggCcAQAAAMQOzZaPZ8DdAAAAAM+IvTDiU0pL\njUnU6a
|
||||||
|
NjH+gZWeaIlqpQNYECmpElixwPj8aRRFVfTtkbw66U3gmo
|
||||||
|
3YBkUoVK8tfHESkivuWtV2tP\n3KGuAFv/6GzbFYQYlA7r
|
||||||
|
zZ1Bw072ps8s9cWeoNmAX6oiZmFW3j7LX3xkr7+nJoOoXI
|
||||||
|
jzvorm5kz3\nldCo8Iwh+IZ3SSnj0/h4H1GR
|
||||||
|
"""
|
||||||
|
|
||||||
|
class TestCsspNtlm(unittest.TestCase):
|
||||||
|
"""
|
||||||
|
@summary: test generate ntlmv2 over cssp authentication protocol
|
||||||
|
"""
|
||||||
|
def testCSSPNTLMAuthentication(self):
|
||||||
|
negotiate_data_request = cssp.decodeDERTRequest(peer0_0.decode('base64'))
|
||||||
|
challenge_data_request = cssp.decodeDERTRequest(peer1_0.decode('base64'))
|
||||||
|
authenticate_data_request = cssp.decodeDERTRequest(peer0_1.decode('base64'))
|
||||||
|
|
||||||
|
negotiate_data = cssp.getNegoTokens(negotiate_data_request)[0]
|
||||||
|
challenge_data = cssp.getNegoTokens(challenge_data_request)[0]
|
||||||
|
authenticate_data = cssp.getNegoTokens(authenticate_data_request)[0]
|
||||||
|
|
||||||
|
negotiate = ntlm.NegotiateMessage()
|
||||||
|
negotiate_data.readType(negotiate)
|
||||||
|
|
||||||
|
challenge = ntlm.ChallengeMessage()
|
||||||
|
challenge_data.readType(challenge)
|
||||||
|
|
||||||
|
ServerChallenge = challenge.ServerChallenge.value
|
||||||
|
ServerName = challenge.getTargetInfo()
|
||||||
|
|
||||||
|
authenticate = ntlm.AuthenticateMessage()
|
||||||
|
authenticate_data.readType(authenticate)
|
||||||
|
|
||||||
|
NtChallengeResponseTemp = authenticate.getNtChallengeResponse()
|
||||||
|
NTProofStr = NtChallengeResponseTemp[:16]
|
||||||
|
temp = NtChallengeResponseTemp[16:]
|
||||||
|
Timestamp = temp[8:16]
|
||||||
|
ClientChallenge = temp[16:24]
|
||||||
|
|
||||||
|
EncryptedRandomSessionKey = authenticate.getEncryptedRandomSession()
|
||||||
|
domain = "coco"
|
||||||
|
user = "toto"
|
||||||
|
password = "lolo"
|
||||||
|
|
||||||
|
ResponseKeyNT = ntlm.NTOWFv2(password, user, domain)
|
||||||
|
ResponseKeyLM = ntlm.LMOWFv2(password, user, domain)
|
||||||
|
NtChallengeResponse, LmChallengeResponse, SessionBaseKey = ntlm.ComputeResponsev2(ResponseKeyNT, ResponseKeyLM, ServerChallenge, ClientChallenge, Timestamp, ServerName)
|
||||||
|
KeyExchangeKey = ntlm.KXKEYv2(SessionBaseKey, LmChallengeResponse, ServerChallenge)
|
||||||
|
ExportedSessionKey = ntlm.RC4K(KeyExchangeKey, EncryptedRandomSessionKey)
|
||||||
|
|
||||||
|
domain, user = domain, user
|
||||||
|
if challenge.NegotiateFlags.value & ntlm.Negotiate.NTLMSSP_NEGOTIATE_UNICODE:
|
||||||
|
domain, user = ntlm.UNICODE(domain), ntlm.UNICODE(user)
|
||||||
|
|
||||||
|
ClientSigningKey = ntlm.SIGNKEY(ExportedSessionKey, True)
|
||||||
|
ServerSigningKey = ntlm.SIGNKEY(ExportedSessionKey, False)
|
||||||
|
ClientSealingKey = ntlm.SEALKEY(ExportedSessionKey, True)
|
||||||
|
ServerSealingKey = ntlm.SEALKEY(ExportedSessionKey, False)
|
||||||
|
|
||||||
|
interface = ntlm.NTLMv2SecurityInterface(rc4.RC4Key(ClientSealingKey), rc4.RC4Key(ServerSealingKey), ClientSigningKey, ServerSigningKey)
|
||||||
|
|
||||||
|
EncryptedPubKeySrc = cssp.getPubKeyAuth(authenticate_data_request)
|
||||||
|
EncryptedPubKeyDst = interface.GSS_WrapEx(pubKeyHex.decode('base64'))
|
||||||
|
|
||||||
|
self.assertTrue(EncryptedPubKeySrc == EncryptedPubKeyDst, "Public key must be equals")
|
||||||
|
|
||||||
@@ -109,6 +109,17 @@ class TestLic(unittest.TestCase):
|
|||||||
s.pos = 0
|
s.pos = 0
|
||||||
s.readType(lic.LicPacket(lic.ClientNewLicenseRequest()))
|
s.readType(lic.LicPacket(lic.ClientNewLicenseRequest()))
|
||||||
self._state = True
|
self._state = True
|
||||||
|
def getGCCServerSettings(self):
|
||||||
|
class A:
|
||||||
|
def __init__(self):
|
||||||
|
self._is_readed = False
|
||||||
|
class B:
|
||||||
|
def __init__(self):
|
||||||
|
self.serverCertificate = A()
|
||||||
|
class C:
|
||||||
|
def __init__(self):
|
||||||
|
self.SC_SECURITY = B()
|
||||||
|
return C()
|
||||||
|
|
||||||
t = Transport()
|
t = Transport()
|
||||||
l = lic.LicenseManager(t)
|
l = lic.LicenseManager(t)
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import os, sys
|
|||||||
sys.path.insert(1, os.path.join(sys.path[0], '..'))
|
sys.path.insert(1, os.path.join(sys.path[0], '..'))
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
import rdpy.protocol.rdp.per as per
|
import rdpy.protocol.rdp.t125.per as per
|
||||||
import rdpy.core.type as type
|
import rdpy.core.type as type
|
||||||
import rdpy.core.error as error
|
import rdpy.core.error as error
|
||||||
|
|
||||||
|
|||||||
@@ -107,19 +107,6 @@ class X224Test(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertRaises(X224Test.X224_PASS, layer.recv, type.String('\x01\x02'))
|
self.assertRaises(X224Test.X224_PASS, layer.recv, type.String('\x01\x02'))
|
||||||
|
|
||||||
def test_x224_client_recvConnectionConfirm_negotiation_bad_protocol(self):
|
|
||||||
"""
|
|
||||||
@summary: unit test for X224Client.recvConnectionConfirm and sendConnectionRequest function
|
|
||||||
Server ask another protocol than SSL or RDP
|
|
||||||
"""
|
|
||||||
message = x224.ServerConnectionConfirm()
|
|
||||||
message.protocolNeg.selectedProtocol.value = x224.Protocols.PROTOCOL_HYBRID
|
|
||||||
s = type.Stream()
|
|
||||||
s.writeType(message)
|
|
||||||
s.pos = 0
|
|
||||||
layer = x224.Client(None)
|
|
||||||
self.assertRaises(error.InvalidExpectedDataException, layer.recvConnectionConfirm, s)
|
|
||||||
|
|
||||||
def test_x224_client_recvConnectionConfirm_negotiation_failure(self):
|
def test_x224_client_recvConnectionConfirm_negotiation_failure(self):
|
||||||
"""
|
"""
|
||||||
@summary: unit test for X224Client.recvConnectionConfirm and sendConnectionRequest function
|
@summary: unit test for X224Client.recvConnectionConfirm and sendConnectionRequest function
|
||||||
@@ -141,12 +128,10 @@ class X224Test(unittest.TestCase):
|
|||||||
tls_begin = False
|
tls_begin = False
|
||||||
presentation_connect = False
|
presentation_connect = False
|
||||||
class Transport(object):
|
class Transport(object):
|
||||||
def __init__(self):
|
|
||||||
class TLSTransport(object):
|
|
||||||
def startTLS(self, context):
|
def startTLS(self, context):
|
||||||
global tls_begin
|
global tls_begin
|
||||||
tls_begin = True
|
tls_begin = True
|
||||||
self.transport = TLSTransport()
|
|
||||||
|
|
||||||
class Presentation(object):
|
class Presentation(object):
|
||||||
def connect(self):
|
def connect(self):
|
||||||
@@ -214,12 +199,9 @@ class X224Test(unittest.TestCase):
|
|||||||
x224.ServerTLSContext = ServerTLSContext
|
x224.ServerTLSContext = ServerTLSContext
|
||||||
|
|
||||||
class Transport(object):
|
class Transport(object):
|
||||||
def __init__(self):
|
|
||||||
class TLS(object):
|
|
||||||
def startTLS(self, context):
|
def startTLS(self, context):
|
||||||
global tls
|
global tls
|
||||||
tls = True
|
tls = True
|
||||||
self.transport = TLS()
|
|
||||||
|
|
||||||
def send(self, data):
|
def send(self, data):
|
||||||
if not isinstance(data, x224.ServerConnectionConfirm):
|
if not isinstance(data, x224.ServerConnectionConfirm):
|
||||||
|
|||||||
Reference in New Issue
Block a user