63 Commits

Author SHA1 Message Date
speyrefitte
1e2cf2ef88 add missing links 2015-06-09 18:11:36 +02:00
speyrefitte
040ac6185d Merge branch 'dev' of https://github.com/citronneur/rdpy into dev 2015-06-01 18:16:39 +02:00
speyrefitte
0f30de7d20 synthax 2015-06-01 18:15:57 +02:00
speyrefitte
a23ae25a1f fix issue on unhandle upadte 2015-05-21 10:29:32 +02:00
speyrefitte
763ed2e3ee fix major bug on update handle 2015-05-20 17:51:43 +02:00
speyrefitte
11d66a4818 add onSessionReady event -> user session is ready 2015-05-19 17:53:15 +02:00
speyrefitte
9b99365f80 fix bug on lwin key activation 2015-05-19 16:42:22 +02:00
speyrefitte
bd7c708bf3 update version 2015-05-04 11:49:37 +02:00
speyrefitte
1deb2d69ea bug fixing 2015-05-04 11:47:25 +02:00
speyrefitte
0a5a1fd12c add keylogger on rss player and file format 2015-04-29 12:13:24 +02:00
speyrefitte
c97b451ce3 Merge branch 'hotfix' of https://github.com/citronneur/rdpy into dev 2015-04-29 09:32:50 +02:00
speyrefitte
80f989a804 Merge branch 'dev' of https://github.com/citronneur/rdpy into dev 2015-04-29 09:32:44 +02:00
speyrefitte
c6e100f9a6 Merge branch 'master' of https://github.com/citronneur/rdpy into dev 2015-04-29 09:31:05 +02:00
Sylvain Peyrefitte
15df00ec20 Merge pull request #24 from citronneur/master
Update readme with pypi version
2015-03-25 14:48:18 +01:00
Sylvain Peyrefitte
342349cf41 Merge pull request #23 from citronneur/master
Just add pypi package
2015-03-25 14:47:14 +01:00
Sylvain Peyrefitte
d6043106e3 Update README.md 2015-03-25 12:15:57 +01:00
speyrefitte
bd7e73a6e7 change log format 2015-03-20 17:54:48 +01:00
Sylvain Peyrefitte
fc1685e652 Merge pull request #21 from ojosdegris/patch-1
Added OS X install example
2015-03-18 10:47:01 +01:00
vittore
4320824aae Fixed example to follow existing style. 2015-03-13 12:11:41 -04:00
vittore
5a438174b9 Added OS X install example 2015-03-13 12:07:22 -04:00
speyrefitte
bb9483e7e1 update setup.py 2015-03-13 10:38:26 +01:00
speyrefitte
bd362263f7 Correct bug from cssp security layer 2015-03-13 10:17:13 +01:00
citronneur
20de5f6f82 Add tests for cssp ntlm authentication protocol 2015-03-09 22:31:08 +01:00
citronneur
95052a323f remove pycrypto dependancies, bug fixing 2015-03-08 21:04:26 +01:00
speyrefitte
0abf18d130 NLA Security Layer is AVAILABLE 2015-03-06 18:19:42 +01:00
Sylvain Peyrefitte
b57b3d7398 Update ntlm.py 2015-03-05 22:53:13 +01:00
citronneur
0695825d98 cssp protocol proxy 2015-03-05 22:28:33 +01:00
speyrefitte
8fb4893b6f almost finish ntlmv2... 2015-03-05 18:26:36 +01:00
citronneur
30a16fbb7a add some usefull methods 2015-03-03 21:57:40 +01:00
speyrefitte
98494d0e73 try to understand key in ntlm 2015-03-03 18:27:45 +01:00
speyrefitte
a7058f1c54 not handle correctly the ntlmv2 auth message 2015-03-02 18:37:18 +01:00
citronneur
1e2f284e97 Parse chanllenge response from server 2015-02-24 22:35:16 +01:00
citronneur
8cd789480f Fix immediatly automata 2015-02-22 14:04:09 +01:00
citronneur
e9a93d117b automata modification for NLA 2015-02-21 14:29:17 +01:00
citronneur
3fe16130d8 NTLM pbs ... 2015-02-21 10:07:19 +01:00
citronneur
31b0920a87 Some changes + NTLM challenge message 2015-02-20 22:13:50 +01:00
speyrefitte
36c05faa11 NTLM Negotiate message embended in cssp request 2015-02-20 10:13:24 +01:00
citronneur
1c3119cffd ASN.1 Tag correcr 2015-02-19 23:05:21 +01:00
speyrefitte
d6bb21565d start spneg 2015-02-19 18:26:50 +01:00
citronneur
d6428430eb Start NTLM support 2015-02-18 22:01:58 +01:00
citronneur
a4f4d71929 Merge branch 'dev' of https://github.com/citronneur/rdpy into dev 2015-02-18 21:10:18 +01:00
citronneur
9e211c0199 Merge branch 'master' of https://github.com/citronneur/rdpy into dev 2015-02-18 21:10:11 +01:00
Sylvain Peyrefitte
5bd78cc012 Merge pull request #18 from manuteleco/master
Fix typos and tabs in README
2015-02-17 23:10:36 +01:00
Manuel Rodríguez Guimeráns
e139a2c7eb Replace tabs with spaces in code snippets. 2015-02-17 16:14:06 +01:00
Manuel Rodríguez Guimeráns
349a8a7227 Fix typos in documentation. 2015-02-17 16:12:58 +01:00
speyrefitte
35514a2849 Fix build issue 2015-02-17 13:45:22 +01:00
citronneur
222ee76c91 Merge branch 'dev' of https://github.com/citronneur/rdpy into dev 2015-02-16 22:29:42 +01:00
citronneur
30c3611bb9 add credssp grammar 2015-02-16 22:29:12 +01:00
citronneur
6c93ca17b0 release 1.2.2 2015-02-16 11:35:01 +01:00
speyrefitte
a01eb57cef Add support for salted mac generation, fix issue 17 2015-02-10 11:39:26 +01:00
speyrefitte
9e50c2292d fix certuficate signing 2015-02-09 15:43:19 +01:00
speyrefitte
82d7798255 fix issue 16 2015-02-06 18:07:02 +01:00
speyrefitte
e9db7d720f fix issue 16 2015-02-06 15:23:44 +01:00
speyrefitte
1d5b15a310 fix minor bug to work with rdesktop but finally there is a bug in rdesktop 2015-02-06 14:35:41 +01:00
speyrefitte
a8ddaa77ff fix issue 14 on xp sp3 + server side effect for honeypot 2015-02-05 16:06:06 +01:00
citronneur
4c56f55266 fix issue 14 2015-02-04 21:47:32 +01:00
citronneur
1039e014c1 fix issue 14 2015-02-04 21:04:58 +01:00
speyrefitte
84ac320e82 add multiple file for honeypot 2015-01-29 17:56:21 +01:00
speyrefitte
02dfe8f46e fix issue 13 2015-01-23 17:13:14 +01:00
speyrefitte
8b159e668f merge jaredhaight branch 2015-01-19 11:23:46 +01:00
Sylvain Peyrefitte
b22a7d5dce Merge pull request #11 from r04r/patch-1
Fix typos in README.md
2015-01-19 11:06:39 +01:00
Jared Haight
1b1dfa06c8 Fixed some typos 2015-01-16 22:40:31 -05:00
r04r
8e7e6cdcb4 Fix typos in README.md 2015-01-16 18:08:19 +01:00
38 changed files with 2034 additions and 437 deletions

View File

@@ -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

286
README.md
View File

@@ -1,25 +1,25 @@
# RDPY [![Build Status](https://travis-ci.org/citronneur/rdpy.svg?branch=dev)](https://travis-ci.org/citronneur/rdpy) # RDPY [![Build Status](https://travis-ci.org/citronneur/rdpy.svg?branch=dev)](https://travis-ci.org/citronneur/rdpy) [![PyPI version](https://badge.fury.io/py/rdpy.png)](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 provide 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
* RDP Honeypot * RDP Honeypot
* RDP screen shooter * RDP screenshoter
* RDP client * RDP client
* VNC client * VNC client
* VNC screen shooter * VNC screenshoter
* RSS Player * RSS Player
## Build ## Build
RDPY is fully implemented in python, except the bitmap uncompression algorithm which is implemented in C for performance purposes. RDPY is fully implemented in python, except the bitmap decompression algorithm which is implemented in C for performance purposes.
### Depends ### Dependencies
Depends are only needed for pyqt4 binaries : Dependencies are only needed for pyqt4 binaries :
* rdpy-rdpclient * rdpy-rdpclient
* rdpy-rdpscreenshot * rdpy-rdpscreenshot
* rdpy-vncclient * rdpy-vncclient
@@ -28,11 +28,17 @@ Depends are only needed for pyqt4 binaries :
#### Linux #### Linux
Exemple from Debian based system : 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
``` ```
@@ -53,7 +59,7 @@ Or use PIP:
$ pip install rdpy $ pip install rdpy
``` ```
For virtualenv, you need to link qt4 library to it: For virtualenv, you will need to link the qt4 library to it:
``` ```
$ 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/
@@ -65,13 +71,13 @@ RDPY comes with some very useful binaries. These binaries are linux and windows
### rdpy-rdpclient ### rdpy-rdpclient
rdpy-rdpclient is a simple RDP Qt4 client . rdpy-rdpclient is a simple RDP Qt4 client.
``` ```
$ rdpy-rdpclient.py [-u username] [-p password] [-d domain] [-r rss_ouput_file] [...] XXX.XXX.XXX.XXX[:3389] $ rdpy-rdpclient.py [-u username] [-p password] [-d domain] [-r rss_ouput_file] [...] XXX.XXX.XXX.XXX[:3389]
``` ```
You can use rdpy-rdpclient as Recorder Session Scenario, used in rdpy-rdphoneypot. You can use rdpy-rdpclient in a Recorder Session Scenario, used in rdpy-rdphoneypot.
### rdpy-vncclient ### rdpy-vncclient
@@ -83,7 +89,7 @@ $ rdpy-vncclient.py [-p password] XXX.XXX.XXX.XXX[:5900]
### rdpy-rdpscreenshot ### rdpy-rdpscreenshot
rdpy-rdpscreenshot save login screen in file. rdpy-rdpscreenshot saves login screen in file.
``` ```
$ rdpy-rdpscreenshot.py [-w width] [-l height] [-o output_file_path] XXX.XXX.XXX.XXX[:3389] $ rdpy-rdpscreenshot.py [-w width] [-l height] [-o output_file_path] XXX.XXX.XXX.XXX[:3389]
@@ -91,7 +97,7 @@ $ rdpy-rdpscreenshot.py [-w width] [-l height] [-o output_file_path] XXX.XXX.XXX
### rdpy-vncscreenshot ### rdpy-vncscreenshot
rdpy-vncscreenshot save first screen update in file. rdpy-vncscreenshot saves the first screen update in file.
``` ```
$ rdpy-vncscreenshot.py [-p password] [-o output_file_path] XXX.XXX.XXX.XXX[:5900] $ rdpy-vncscreenshot.py [-p password] [-o output_file_path] XXX.XXX.XXX.XXX[:5900]
@@ -100,24 +106,25 @@ $ rdpy-vncscreenshot.py [-p password] [-o output_file_path] XXX.XXX.XXX.XXX[:590
### rdpy-rdpmitm ### rdpy-rdpmitm
rdpy-rdpmitm is a RDP proxy allows you to do a Man In The Middle attack on RDP protocol. rdpy-rdpmitm is a RDP proxy allows you to do a Man In The Middle attack on RDP protocol.
Record Session Scenario into rss file which can be replay by rdpy-rssplayer. Record Session Scenario into rss file which can be replayed by rdpy-rssplayer.
``` ```
$ rdpy-rdpmitm.py -o output_dir [-l listen_port] [-k private_key_file_path] [-c certificate_file_path] [-r (for XP or server 2003 client)] target_host[:target_port] $ rdpy-rdpmitm.py -o output_dir [-l listen_port] [-k private_key_file_path] [-c certificate_file_path] [-r (for XP or server 2003 client)] target_host[:target_port]
``` ```
Output directory is use to save 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
rdpy-rdphoneypot is a RDP honey Pot. Use Recorded Session Scenario to replay scenario through RDP Protocol. rdpy-rdphoneypot is an RDP honey Pot. Use Recorded Session Scenario to replay scenario through RDP Protocol.
``` ```
$ rdpy-rdphoneypot.py [-l listen_port] [-k private_key_file_path] [-c certificate_file_path] rss_file_path $ 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.
### rdpy-rssplayer ### rdpy-rssplayer
@@ -129,11 +136,11 @@ $ rdpy-rssplayer.py rss_file_path
## RDPY Qt Widget ## RDPY Qt Widget
RDPY can also be used as Qt widget throw rdpy.ui.qt4.QRemoteDesktop class. It can be embedded in your own Qt application. qt4reactor must be used in your app for Twisted and Qt to work together. For more details, see sources of rdpy-rdpclient. RDPY can also be used as Qt widget through rdpy.ui.qt4.QRemoteDesktop class. It can be embedded in your own Qt application. qt4reactor must be used in your app for Twisted and Qt to work together. For more details, see sources of rdpy-rdpclient.
## RDPY library ## RDPY library
In a nutshell the RDPY can be used as a protocol library with a twisted engine. In a nutshell RDPY can be used as a protocol library with a twisted engine.
### Simple RDP Client ### Simple RDP Client
@@ -142,48 +149,53 @@ from rdpy.protocol.rdp import rdp
class MyRDPFactory(rdp.ClientFactory): class MyRDPFactory(rdp.ClientFactory):
def clientConnectionLost(self, connector, reason): def clientConnectionLost(self, connector, reason):
reactor.stop() reactor.stop()
def clientConnectionFailed(self, connector, reason): def clientConnectionFailed(self, connector, reason):
reactor.stop() reactor.stop()
def buildObserver(self, controller, addr): def buildObserver(self, controller, addr):
class MyObserver(rdp.RDPClientObserver) class MyObserver(rdp.RDPClientObserver):
def onReady(self): def onReady(self):
"""
@summary: Call when stack is ready
"""
#send 'r' key
self._controller.sendKeyEventUnicode(ord(unicode("r".toUtf8(), encoding="UTF-8")), True)
#mouse move and click at pixel 200x200
self._controller.sendPointerEvent(200, 200, 1, true)
def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
"""
@summary: Notify bitmap update
@param destLeft: xmin position
@param destTop: ymin position
@param destRight: xmax position because RDP can send bitmap with padding
@param destBottom: ymax position because RDP can send bitmap with padding
@param width: width of bitmap
@param height: height of bitmap
@param bitsPerPixel: number of bit per pixel
@param isCompress: use RLE compression
@param data: bitmap data
"""
def onSessionReady(self):
""" """
@summary: Call when stack is ready @summary: Windows session is ready
"""
#send 'r' key
self._controller.sendKeyEventUnicode(ord(unicode("r".toUtf8(), encoding="UTF-8")), True)
#mouse move and click at pixel 200x200
self._controller.sendPointerEvent(200, 200, 1, true)
def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
"""
@summary: Notify bitmap update
@param destLeft: xmin position
@param destTop: ymin position
@param destRight: xmax position because RDP can send bitmap with padding
@param destBottom: ymax position because RDP can send bitmap with padding
@param width: width of bitmap
@param height: height of bitmap
@param bitsPerPixel: number of bit per pixel
@param isCompress: use RLE compression
@param data: bitmap data
"""
def onClose(self):
"""
@summary: Call when stack is close
""" """
return MyObserver(controller) def onClose(self):
"""
@summary: Call when stack is close
"""
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()
``` ```
@@ -194,48 +206,48 @@ from rdpy.protocol.rdp import rdp
class MyRDPFactory(rdp.ServerFactory): class MyRDPFactory(rdp.ServerFactory):
def buildObserver(self, controller, addr): def buildObserver(self, controller, addr):
class MyObserver(rdp.RDPServerObserver)
def onReady(self):
"""
@summary: Call when server is ready
to send and receive messages
"""
def onKeyEventScancode(self, code, isPressed):
"""
@summary: Event call when a keyboard event is catch in scan code format
@param code: scan code of key
@param isPressed: True if key is down
@see: rdp.RDPServerObserver.onKeyEventScancode
"""
def onKeyEventUnicode(self, code, isPressed):
"""
@summary: Event call when a keyboard event is catch in unicode format
@param code: unicode of key
@param isPressed: True if key is down
@see: rdp.RDPServerObserver.onKeyEventUnicode
"""
def onPointerEvent(self, x, y, button, isPressed):
"""
@summary: Event call on mouse event
@param x: x position
@param y: y position
@param button: 1, 2 or 3 button
@param isPressed: True if mouse button is pressed
@see: rdp.RDPServerObserver.onPointerEvent
"""
def onClose(self):
"""
@summary: Call when human client close connection
@see: rdp.RDPServerObserver.onClose
"""
return MyObserver(controller) class MyObserver(rdp.RDPServerObserver):
def onReady(self):
"""
@summary: Call when server is ready
to send and receive messages
"""
def onKeyEventScancode(self, code, isPressed):
"""
@summary: Event call when a keyboard event is catch in scan code format
@param code: scan code of key
@param isPressed: True if key is down
@see: rdp.RDPServerObserver.onKeyEventScancode
"""
def onKeyEventUnicode(self, code, isPressed):
"""
@summary: Event call when a keyboard event is catch in unicode format
@param code: unicode of key
@param isPressed: True if key is down
@see: rdp.RDPServerObserver.onKeyEventUnicode
"""
def onPointerEvent(self, x, y, button, isPressed):
"""
@summary: Event call on mouse event
@param x: x position
@param y: y position
@param button: 1, 2 or 3 button
@param isPressed: True if mouse button is pressed
@see: rdp.RDPServerObserver.onPointerEvent
"""
def onClose(self):
"""
@summary: Call when human client close connection
@see: rdp.RDPServerObserver.onClose
"""
return MyObserver(controller)
from twisted.internet import reactor from twisted.internet import reactor
reactor.listenTCP(3389, MyRDPFactory()) reactor.listenTCP(3389, MyRDPFactory())
@@ -244,55 +256,55 @@ reactor.run()
### Simple VNC Client ### Simple VNC Client
```python ```python
from rdpy.protocol.rfb import rdp from rdpy.protocol.rfb import rfb
class MyRFBFactory(rfb.ClientFactory): class MyRFBFactory(rfb.ClientFactory):
def clientConnectionLost(self, connector, reason): def clientConnectionLost(self, connector, reason):
reactor.stop() reactor.stop()
def clientConnectionFailed(self, connector, reason): def clientConnectionFailed(self, connector, reason):
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):
"""
@summary: Event when network stack is ready to receive or send event
"""
def onUpdate(self, width, height, x, y, pixelFormat, encoding, data): def onReady(self):
""" """
@summary: Implement RFBClientObserver interface @summary: Event when network stack is ready to receive or send event
@param width: width of new image """
@param height: height of new image
@param x: x position of new image
@param y: y position of new image
@param pixelFormat: pixefFormat structure in rfb.message.PixelFormat
@param encoding: encoding type rfb.message.Encoding
@param data: image data in accordance with pixel format and encoding
"""
def onCutText(self, text):
"""
@summary: event when server send cut text event
@param text: text received
"""
def onBell(self):
"""
@summary: event when server send biiip
"""
def onClose(self):
"""
@summary: Call when stack is close
"""
return MyObserver(controller) def onUpdate(self, width, height, x, y, pixelFormat, encoding, data):
"""
@summary: Implement RFBClientObserver interface
@param width: width of new image
@param height: height of new image
@param x: x position of new image
@param y: y position of new image
@param pixelFormat: pixefFormat structure in rfb.message.PixelFormat
@param encoding: encoding type rfb.message.Encoding
@param data: image data in accordance with pixel format and encoding
"""
def onCutText(self, text):
"""
@summary: event when server send cut text event
@param text: text received
"""
def onBell(self):
"""
@summary: event when server send biiip
"""
def onClose(self):
"""
@summary: Call when stack is close
"""
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()
``` ```

View File

@@ -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

View File

@@ -31,13 +31,15 @@ from twisted.internet import reactor
log._LOG_LEVEL = log.Level.INFO log._LOG_LEVEL = log.Level.INFO
class HoneyPotServer(rdp.RDPServerObserver): class HoneyPotServer(rdp.RDPServerObserver):
def __init__(self, controller, rssFile): def __init__(self, controller, rssFileSizeList):
""" """
@param controller: {RDPServerController} @param controller: {RDPServerController}
@param rssFileSizeList: {Tuple} Tuple(Tuple(width, height), rssFilePath)
""" """
rdp.RDPServerObserver.__init__(self, controller) rdp.RDPServerObserver.__init__(self, controller)
self._rssFile = rssFile self._rssFileSizeList = rssFileSizeList
self._dx, self._dy = 0, 0 self._dx, self._dy = 0, 0
self._rssFile = None
def onReady(self): def onReady(self):
""" """
@@ -47,6 +49,14 @@ class HoneyPotServer(rdp.RDPServerObserver):
restart a connection sequence restart a connection sequence
@see: rdp.RDPServerObserver.onReady @see: rdp.RDPServerObserver.onReady
""" """
if self._rssFile is None:
#compute which RSS file to keep
width, height = self._controller.getScreen()
size = width * height
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))
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("""Credentials:
@@ -60,7 +70,7 @@ class HoneyPotServer(rdp.RDPServerObserver):
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):
@@ -89,7 +99,7 @@ class HoneyPotServer(rdp.RDPServerObserver):
clientSize = nextEvent.event.width.value, nextEvent.event.height.value clientSize = nextEvent.event.width.value, nextEvent.event.height.value
serverSize = self._controller.getScreen() serverSize = self._controller.getScreen()
self._dx, self._dy = (serverSize[0] - clientSize[0]) / 2, (serverSize[1] - clientSize[1]) / 2 self._dx, self._dy = (max(0, serverSize[0] - clientSize[0]) / 2), max(0, (serverSize[1] - clientSize[1]) / 2)
#restart connection sequence #restart connection sequence
return return
@@ -100,14 +110,14 @@ class HoneyPotServerFactory(rdp.ServerFactory):
""" """
@summary: Factory on listening events @summary: Factory on listening events
""" """
def __init__(self, rssFilePath, privateKeyFilePath, certificateFilePath): def __init__(self, rssFileSizeList, privateKeyFilePath, certificateFilePath):
""" """
@param rssFilePath: Recorded Session Scenario File path @param rssFileSizeList: {Tuple} Tuple(Tuple(width, height), rssFilePath)
@param privateKeyFilePath: {str} file contain server private key (if none -> back to standard RDP security) @param privateKeyFilePath: {str} file contain server private key (if none -> back to standard RDP security)
@param certificateFilePath: {str} file contain server certificate (if none -> back to standard RDP security) @param certificateFilePath: {str} file contain server certificate (if none -> back to standard RDP security)
""" """
rdp.ServerFactory.__init__(self, 16, privateKeyFilePath, certificateFilePath) rdp.ServerFactory.__init__(self, 16, privateKeyFilePath, certificateFilePath)
self._rssFilePath = rssFilePath self._rssFileSizeList = rssFileSizeList
def buildObserver(self, controller, addr): def buildObserver(self, controller, addr):
""" """
@@ -116,14 +126,27 @@ class HoneyPotServerFactory(rdp.ServerFactory):
@see: rdp.ServerFactory.buildObserver @see: rdp.ServerFactory.buildObserver
""" """
log.info("Connection from %s:%s"%(addr.host, addr.port)) log.info("Connection from %s:%s"%(addr.host, addr.port))
return HoneyPotServer(controller, rss.createReader(self._rssFilePath)) return HoneyPotServer(controller, self._rssFileSizeList)
def readSize(filePath):
"""
@summary: read size event in rss file
@param filePath: path of rss file
"""
r = rss.createReader(filePath)
while True:
e = r.nextEvent()
if e is None:
return None
elif e.type.value == rss.EventType.SCREEN:
return e.event.width.value, e.event.height.value
def help(): def help():
""" """
@summary: Print help in console @summary: Print help in console
""" """
print """ print """
Usage: rdpy-rdphoneypot.py rss_filepath Usage: rdpy-rdphoneypot.py rss_filepath(1..n)
[-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)]
@@ -133,6 +156,7 @@ if __name__ == '__main__':
listen = "3389" listen = "3389"
privateKeyFilePath = None privateKeyFilePath = None
certificateFilePath = None certificateFilePath = None
rssFileSizeList = []
try: try:
opts, args = getopt.getopt(sys.argv[1:], "hl:k:c:") opts, args = getopt.getopt(sys.argv[1:], "hl:k:c:")
@@ -149,5 +173,12 @@ if __name__ == '__main__':
elif opt == "-c": elif opt == "-c":
certificateFilePath = arg certificateFilePath = arg
reactor.listenTCP(int(listen), HoneyPotServerFactory(args[0], privateKeyFilePath, certificateFilePath)) #build size map
log.info("Build size map")
for arg in args:
size = readSize(arg)
rssFileSizeList.append((size, arg))
log.info("(%s, %s) -> %s"%(size[0], size[1], arg))
reactor.listenTCP(int(listen), HoneyPotServerFactory(rssFileSizeList, privateKeyFilePath, certificateFilePath))
reactor.run() reactor.run()

View File

@@ -92,16 +92,18 @@ class ProxyServer(rdp.RDPServerObserver):
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,6 +115,7 @@ 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):
""" """
@@ -176,6 +179,13 @@ class ProxyClient(rdp.RDPClientObserver):
#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
@@ -251,7 +261,9 @@ def help():
[-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)]
[-o output directory for recoded files]
[-r RDP standard security (XP or server 2003 client or older)] [-r RDP standard security (XP or server 2003 client or older)]
[-n For NLA Client authentication (need to provide credentials)]
""" """
def parseIpPort(interface, defaultPort = "3389"): def parseIpPort(interface, defaultPort = "3389"):
@@ -265,10 +277,11 @@ if __name__ == '__main__':
privateKeyFilePath = None privateKeyFilePath = None
certificateFilePath = None certificateFilePath = None
ouputDirectory = None ouputDirectory = None
clientSecurity = "ssl" #for anonymous authentication
clientSecurity = rdp.SecurityLevel.RDP_LEVEL_SSL
try: try:
opts, args = getopt.getopt(sys.argv[1:], "hl:k:c:o:r") opts, args = getopt.getopt(sys.argv[1:], "hl:k:c:o:rn")
except getopt.GetoptError: except getopt.GetoptError:
help() help()
for opt, arg in opts: for opt, arg in opts:
@@ -284,7 +297,9 @@ if __name__ == '__main__':
elif opt == "-o": elif opt == "-o":
ouputDirectory = arg ouputDirectory = arg
elif opt == "-r": elif opt == "-r":
clientSecurity = "rdp" clientSecurity = rdp.SecurityLevel.RDP_LEVEL_RDP
elif opt == "-n":
clientSecurity = rdp.SecurityLevel.RDP_LEVEL_NLA
if ouputDirectory is None or not os.path.dirname(ouputDirectory): if ouputDirectory is None or not os.path.dirname(ouputDirectory):
log.error("%s is an invalid output directory"%ouputDirectory) log.error("%s is an invalid output directory"%ouputDirectory)

View File

@@ -56,7 +56,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,7 +67,7 @@ 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
@@ -121,7 +122,7 @@ class RDPScreenShotFactory(rdp.ClientFactory):
""" """
@summary: callback use when bitmap is received @summary: callback use when bitmap is received
""" """
image = RDPBitmapToQtImage(destLeft, 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)
@@ -135,6 +136,13 @@ class RDPScreenShotFactory(rdp.ClientFactory):
""" """
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

View File

@@ -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
View 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

View File

@@ -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):
""" """

View File

@@ -39,7 +39,7 @@ def log(message):
@summary: Main log function @summary: Main log function
@param message: string to print @param message: string to print
""" """
print message print "[*] %s"%message
def error(message): def error(message):
""" """
@@ -48,7 +48,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 +57,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 +66,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 +75,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)

View File

@@ -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
@@ -211,6 +233,28 @@ class FileRecorder(object):
infoEvent.domain.value = domain infoEvent.domain.value = domain
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):
""" """
@@ -252,4 +296,5 @@ def createReader(path):
@param path: {str} path of input file @param path: {str} path of input file
@return: {FileReader} @return: {FileReader}
""" """
return FileReader(open(path, "rb")) with open(path, "rb") as f:
return FileReader(f)

60
rdpy/core/scancode.py Normal file
View 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];

View File

@@ -257,7 +257,7 @@ class SimpleType(Type, CallableValue):
@raise InvalidSize: if there is not enough data in stream @raise InvalidSize: if there is not enough data in stream
""" """
if s.dataLen() < self._typeSize: if s.dataLen() < self._typeSize:
raise InvalidSize("Stream is too small to read expected Simple") raise InvalidSize("Stream is too small to read expected SimpleType")
self.value = struct.unpack(self._structFormat, s.read(self._typeSize))[0] self.value = struct.unpack(self._structFormat, s.read(self._typeSize))[0]
def mask(self): def mask(self):
@@ -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):
@@ -498,6 +498,9 @@ class CompositeType(Type):
@summary: Call sizeof on each sub type @summary: Call sizeof on each sub type
@return: sum of sizeof of each Type attributes @return: sum of sizeof of each Type attributes
""" """
if self._is_readed and not self._readLen is None:
return self._readLen.value
size = 0 size = 0
for name in self._typeName: for name in self._typeName:
size += sizeof(self.__dict__[name]) size += sizeof(self.__dict__[name])
@@ -808,7 +811,7 @@ class String(Type, CallableValue):
self.value = s.getvalue()[s.pos:] self.value = s.getvalue()[s.pos:]
else: else:
self.value = "" self.value = ""
while self.value[-len(self._until):] != self._until or s.dataLen() == 0: while self.value[-len(self._until):] != self._until and s.dataLen() != 0:
self.value += s.read(1) self.value += s.read(1)
else: else:
self.value = s.read(self._readLen.value) self.value = s.read(self._readLen.value)

View File

@@ -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
@@ -97,8 +98,8 @@ class LicenseBinaryBlob(CompositeType):
@summary: Blob use by license manager to exchange security data @summary: Blob use by license manager to exchange security data
@see: http://msdn.microsoft.com/en-us/library/cc240481.aspx @see: http://msdn.microsoft.com/en-us/library/cc240481.aspx
""" """
def __init__(self, blobType = BinaryBlobType.BB_ANY_BLOB): def __init__(self, blobType = BinaryBlobType.BB_ANY_BLOB, optional = False):
CompositeType.__init__(self) CompositeType.__init__(self, optional = optional)
self.wBlobType = UInt16Le(blobType, constant = True if blobType != BinaryBlobType.BB_ANY_BLOB else False) self.wBlobType = UInt16Le(blobType, constant = True if blobType != BinaryBlobType.BB_ANY_BLOB else False)
self.wBlobLen = UInt16Le(lambda:sizeof(self.blobData)) self.wBlobLen = UInt16Le(lambda:sizeof(self.blobData))
self.blobData = String(readLen = self.wBlobLen) self.blobData = String(readLen = self.wBlobLen)
@@ -110,11 +111,11 @@ class LicensingErrorMessage(CompositeType):
""" """
_MESSAGE_TYPE_ = MessageType.ERROR_ALERT _MESSAGE_TYPE_ = MessageType.ERROR_ALERT
def __init__(self): def __init__(self, readLen = None):
CompositeType.__init__(self) CompositeType.__init__(self, readLen = readLen)
self.dwErrorCode = UInt32Le() self.dwErrorCode = UInt32Le()
self.dwStateTransition = UInt32Le() self.dwStateTransition = UInt32Le()
self.blob = LicenseBinaryBlob(BinaryBlobType.BB_ERROR_BLOB) self.blob = LicenseBinaryBlob(BinaryBlobType.BB_ANY_BLOB)
class ProductInformation(CompositeType): class ProductInformation(CompositeType):
""" """
@@ -159,9 +160,9 @@ class ServerLicenseRequest(CompositeType):
""" """
_MESSAGE_TYPE_ = MessageType.LICENSE_REQUEST _MESSAGE_TYPE_ = MessageType.LICENSE_REQUEST
def __init__(self): def __init__(self, readLen = None):
CompositeType.__init__(self) 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)
@@ -175,14 +176,14 @@ class ClientNewLicenseRequest(CompositeType):
""" """
_MESSAGE_TYPE_ = MessageType.NEW_LICENSE_REQUEST _MESSAGE_TYPE_ = MessageType.NEW_LICENSE_REQUEST
def __init__(self): def __init__(self, readLen = None):
CompositeType.__init__(self) CompositeType.__init__(self, readLen = readLen)
#RSA and must be only RSA #RSA and must be only RSA
self.preferredKeyExchangeAlg = UInt32Le(0x00000001, constant = True) self.preferredKeyExchangeAlg = UInt32Le(0x00000001, constant = True)
#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)
@@ -194,11 +195,11 @@ class ServerPlatformChallenge(CompositeType):
""" """
_MESSAGE_TYPE_ = MessageType.PLATFORM_CHALLENGE _MESSAGE_TYPE_ = MessageType.PLATFORM_CHALLENGE
def __init__(self): def __init__(self, readLen = None):
CompositeType.__init__(self) 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):
""" """
@@ -207,11 +208,11 @@ class ClientPLatformChallengeResponse(CompositeType):
""" """
_MESSAGE_TYPE_ = MessageType.PLATFORM_CHALLENGE_RESPONSE _MESSAGE_TYPE_ = MessageType.PLATFORM_CHALLENGE_RESPONSE
def __init__(self): def __init__(self, readLen = None):
CompositeType.__init__(self) 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):
""" """
@@ -231,9 +232,9 @@ class LicPacket(CompositeType):
""" """
for c in [LicensingErrorMessage, ServerLicenseRequest, ClientNewLicenseRequest, ServerPlatformChallenge, ClientPLatformChallengeResponse]: for c in [LicensingErrorMessage, ServerLicenseRequest, ClientNewLicenseRequest, ServerPlatformChallenge, ClientPLatformChallengeResponse]:
if self.bMsgtype.value == c._MESSAGE_TYPE_: if self.bMsgtype.value == c._MESSAGE_TYPE_:
return c() 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,9 +303,12 @@ class LicenseManager(object):
""" """
#get server information #get server information
serverRandom = licenseRequest.serverRandom.value serverRandom = licenseRequest.serverRandom.value
s = Stream(licenseRequest.serverCertificate.blobData.value) if self._transport.getGCCServerSettings().SC_SECURITY.serverCertificate._is_readed:
serverCertificate = gcc.ServerCertificate() serverCertificate = self._transport.getGCCServerSettings().SC_SECURITY.serverCertificate
s.readType(serverCertificate) else:
s = Stream(licenseRequest.serverCertificate.blobData.value)
serverCertificate = gcc.ServerCertificate()
s.readType(serverCertificate)
#generate crypto values #generate crypto values
clientRandom = rsa.random(256) clientRandom = rsa.random(256)

View File

View 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()

View 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

View 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"))

View File

@@ -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)

View File

@@ -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
@@ -202,6 +202,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 +423,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
@@ -429,7 +437,8 @@ class ShareControlHeader(CompositeType):
#share control header #share control header
self.totalLength = UInt16Le(totalLength) self.totalLength = UInt16Le(totalLength)
self.pduType = UInt16Le(pduType) self.pduType = UInt16Le(pduType)
self.PDUSource = UInt16Le(userId) #for xp sp3 and deactiveallpdu PDUSource may not be present
self.PDUSource = UInt16Le(userId, optional = True)
class ShareDataHeader(CompositeType): class ShareDataHeader(CompositeType):
""" """
@@ -460,10 +469,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)
@@ -480,8 +489,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)))
@@ -499,8 +508,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))
@@ -518,8 +527,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):
CompositeType.__init__(self) #in old version this packet is empty i don't know
#and not specified
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)
@@ -531,19 +542,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)
@@ -652,8 +663,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()
@@ -667,7 +678,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):
""" """
@@ -676,8 +687,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)
@@ -688,8 +699,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):
""" """
@@ -697,8 +708,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):
""" """
@@ -758,9 +769,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))
@@ -768,7 +779,19 @@ class UpdateDataPDU(CompositeType):
raise InvalidExpectedDataException("Try to send an invalid data update PDU") raise InvalidExpectedDataException("Try to send an invalid data update PDU")
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
@@ -786,9 +809,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)
@@ -818,8 +841,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()
@@ -870,7 +893,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):
""" """
@@ -879,8 +902,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)
@@ -896,11 +919,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, 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)
@@ -908,7 +930,19 @@ class SlowPathInputEvent(CompositeType):
raise InvalidExpectedDataException("try to send an invalid Slow Path Input Event") raise InvalidExpectedDataException("try to send an invalid Slow Path Input Event")
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

View File

@@ -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
@@ -179,6 +187,9 @@ class Client(PDULayer):
for cap in pdu.pduMessage.capabilitySets._array: for cap in pdu.pduMessage.capabilitySets._array:
self._serverCapabilities[cap.capabilitySetType] = cap self._serverCapabilities[cap.capabilitySetType] = cap
#secure checksum cap here maybe protocol (another) design error
self._transport._enableSecureCheckSum = bool(self._serverCapabilities[caps.CapsType.CAPSTYPE_GENERAL].capability.extraFlags & caps.GeneralExtraFlag.ENC_SALTED_CHECKSUM)
self.sendConfirmActivePDU() self.sendConfirmActivePDU()
#send synchronize #send synchronize
@@ -256,15 +267,16 @@ 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)
if pdu.shareControlHeader.pduType.value == data.PDUType.PDUTYPE_DATAPDU: for pdu in pdus:
self.readDataPDU(pdu.pduMessage) if pdu.shareControlHeader.pduType.value == data.PDUType.PDUTYPE_DATAPDU:
elif pdu.shareControlHeader.pduType.value == data.PDUType.PDUTYPE_DEACTIVATEALLPDU: self.readDataPDU(pdu.pduMessage)
#use in deactivation-reactivation sequence elif pdu.shareControlHeader.pduType.value == data.PDUType.PDUTYPE_DEACTIVATEALLPDU:
#next state is either a capabilities re exchange or disconnection #use in deactivation-reactivation sequence
#http://msdn.microsoft.com/en-us/library/cc240454.aspx #next state is either a capabilities re exchange or disconnection
self.setNextState(self.recvDemandActivePDU) #http://msdn.microsoft.com/en-us/library/cc240454.aspx
self.setNextState(self.recvDemandActivePDU)
def recvFastPath(self, secFlag, fastPathS): def recvFastPath(self, secFlag, fastPathS):
""" """
@@ -273,25 +285,32 @@ 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):
""" """
@summary: read a data PDU object @summary: read a data PDU object
@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)
@@ -312,7 +331,7 @@ class Client(PDULayer):
generalCapability = self._clientCapabilities[caps.CapsType.CAPSTYPE_GENERAL].capability generalCapability = self._clientCapabilities[caps.CapsType.CAPSTYPE_GENERAL].capability
generalCapability.osMajorType.value = caps.MajorType.OSMAJORTYPE_WINDOWS generalCapability.osMajorType.value = caps.MajorType.OSMAJORTYPE_WINDOWS
generalCapability.osMinorType.value = caps.MinorType.OSMINORTYPE_WINDOWS_NT generalCapability.osMinorType.value = caps.MinorType.OSMINORTYPE_WINDOWS_NT
generalCapability.extraFlags.value = caps.GeneralExtraFlag.LONG_CREDENTIALS_SUPPORTED | caps.GeneralExtraFlag.NO_BITMAP_COMPRESSION_HDR generalCapability.extraFlags.value = caps.GeneralExtraFlag.LONG_CREDENTIALS_SUPPORTED | caps.GeneralExtraFlag.NO_BITMAP_COMPRESSION_HDR | caps.GeneralExtraFlag.ENC_SALTED_CHECKSUM
if not self._fastPathSender is None: if not self._fastPathSender is None:
generalCapability.extraFlags.value |= caps.GeneralExtraFlag.FASTPATH_OUTPUT_SUPPORTED generalCapability.extraFlags.value |= caps.GeneralExtraFlag.FASTPATH_OUTPUT_SUPPORTED
@@ -413,6 +432,9 @@ class Server(PDULayer):
#find use full flag #find use full flag
self._clientFastPathSupported = bool(self._clientCapabilities[caps.CapsType.CAPSTYPE_GENERAL].capability.extraFlags.value & caps.GeneralExtraFlag.FASTPATH_OUTPUT_SUPPORTED) self._clientFastPathSupported = bool(self._clientCapabilities[caps.CapsType.CAPSTYPE_GENERAL].capability.extraFlags.value & caps.GeneralExtraFlag.FASTPATH_OUTPUT_SUPPORTED)
#secure checksum cap here maybe protocol (another) design error
self._transport._enableSecureCheckSum = bool(self._clientCapabilities[caps.CapsType.CAPSTYPE_GENERAL].capability.extraFlags & caps.GeneralExtraFlag.ENC_SALTED_CHECKSUM)
self.setNextState(self.recvClientSynchronizePDU) self.setNextState(self.recvClientSynchronizePDU)
def recvClientSynchronizePDU(self, s): def recvClientSynchronizePDU(self, s):
@@ -525,7 +547,7 @@ class Server(PDULayer):
generalCapability = self._serverCapabilities[caps.CapsType.CAPSTYPE_GENERAL].capability generalCapability = self._serverCapabilities[caps.CapsType.CAPSTYPE_GENERAL].capability
generalCapability.osMajorType.value = caps.MajorType.OSMAJORTYPE_WINDOWS generalCapability.osMajorType.value = caps.MajorType.OSMAJORTYPE_WINDOWS
generalCapability.osMinorType.value = caps.MinorType.OSMINORTYPE_WINDOWS_NT generalCapability.osMinorType.value = caps.MinorType.OSMINORTYPE_WINDOWS_NT
generalCapability.extraFlags.value = caps.GeneralExtraFlag.LONG_CREDENTIALS_SUPPORTED | caps.GeneralExtraFlag.NO_BITMAP_COMPRESSION_HDR | caps.GeneralExtraFlag.FASTPATH_OUTPUT_SUPPORTED generalCapability.extraFlags.value = caps.GeneralExtraFlag.LONG_CREDENTIALS_SUPPORTED | caps.GeneralExtraFlag.NO_BITMAP_COMPRESSION_HDR | caps.GeneralExtraFlag.FASTPATH_OUTPUT_SUPPORTED | caps.GeneralExtraFlag.ENC_SALTED_CHECKSUM
inputCapability = self._serverCapabilities[caps.CapsType.CAPSTYPE_INPUT].capability inputCapability = self._serverCapabilities[caps.CapsType.CAPSTYPE_INPUT].capability
inputCapability.inputFlags.value = caps.InputFlags.INPUT_FLAG_SCANCODES | caps.InputFlags.INPUT_FLAG_MOUSEX inputCapability.inputFlags.value = caps.InputFlags.INPUT_FLAG_SCANCODES | caps.InputFlags.INPUT_FLAG_MOUSEX

View File

@@ -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
@@ -257,11 +285,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 +298,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 +377,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,7 +496,7 @@ 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))
@@ -507,8 +538,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 +625,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 +676,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"))

View File

@@ -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
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
@@ -55,6 +56,7 @@ class SecurityFlag(object):
class InfoFlag(object): class InfoFlag(object):
""" """
Client capabilities informations Client capabilities informations
@see: https://msdn.microsoft.com/en-us/library/cc240475.aspx
""" """
INFO_MOUSE = 0x00000001 INFO_MOUSE = 0x00000001
INFO_DISABLECTRLALTDEL = 0x00000002 INFO_DISABLECTRLALTDEL = 0x00000002
@@ -79,6 +81,7 @@ class InfoFlag(object):
class PerfFlag(object): class PerfFlag(object):
""" """
Network performances flag Network performances flag
@see: https://msdn.microsoft.com/en-us/library/cc240476.aspx
""" """
PERF_DISABLE_WALLPAPER = 0x00000001 PERF_DISABLE_WALLPAPER = 0x00000001
PERF_DISABLE_FULLWINDOWDRAG = 0x00000002 PERF_DISABLE_FULLWINDOWDRAG = 0x00000002
@@ -164,12 +167,12 @@ def macData(macSaltKey, data):
md5Digest = md5.new() md5Digest = md5.new()
#encode length #encode length
s = Stream() dataLength = Stream()
s.writeType(UInt32Le(len(data))) dataLength.writeType(UInt32Le(len(data)))
sha1Digest.update(macSaltKey) sha1Digest.update(macSaltKey)
sha1Digest.update("\x36" * 40) sha1Digest.update("\x36" * 40)
sha1Digest.update(s.getvalue()) sha1Digest.update(dataLength.getvalue())
sha1Digest.update(data) sha1Digest.update(data)
sha1Sig = sha1Digest.digest() sha1Sig = sha1Digest.digest()
@@ -180,6 +183,38 @@ def macData(macSaltKey, data):
return md5Digest.digest() return md5Digest.digest()
def macSaltedData(macSaltKey, data, encryptionCount):
"""
@see: https://msdn.microsoft.com/en-us/library/cc240789.aspx
@param macSaltKey: {str} mac key
@param data: {str} data to sign
@param encryptionCount: nb encrypted packet
@return: {str} signature
"""
sha1Digest = sha.new()
md5Digest = md5.new()
#encode length
dataLengthS = Stream()
dataLengthS.writeType(UInt32Le(len(data)))
encryptionCountS = Stream()
encryptionCountS.writeType(UInt32Le(encryptionCount))
sha1Digest.update(macSaltKey)
sha1Digest.update("\x36" * 40)
sha1Digest.update(dataLengthS.getvalue())
sha1Digest.update(data)
sha1Digest.update(encryptionCountS.getvalue())
sha1Sig = sha1Digest.digest()
md5Digest.update(macSaltKey)
md5Digest.update("\x5c" * 48)
md5Digest.update(sha1Sig)
return md5Digest.digest()
def tempKey(initialKey, currentKey): def tempKey(initialKey, currentKey):
""" """
@see: http://msdn.microsoft.com/en-us/library/cc240792.aspx @see: http://msdn.microsoft.com/en-us/library/cc240792.aspx
@@ -276,8 +311,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):
""" """
@@ -290,20 +325,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):
@@ -342,6 +377,9 @@ class SecLayer(LayerAutomata, IStreamSender, tpkt.IFastPathListener, tpkt.IFastP
#True if classic encryption is enable #True if classic encryption is enable
self._enableEncryption = False self._enableEncryption = False
#Enable Secure Mac generation
self._enableSecureCheckSum = False
#initialise decrypt and encrypt keys #initialise decrypt and encrypt keys
self._macKey = None self._macKey = None
self._initialDecrytKey = None self._initialDecrytKey = None
@@ -358,10 +396,11 @@ class SecLayer(LayerAutomata, IStreamSender, tpkt.IFastPathListener, tpkt.IFastP
self._encryptRc4 = None self._encryptRc4 = None
def readEncryptedPayload(self, s): def readEncryptedPayload(self, s, saltedMacGeneration):
""" """
@summary: decrypt basic RDP security payload @summary: decrypt basic RDP security payload
@param s: {Stream} encrypted stream @param s: {Stream} encrypted stream
@param saltedMacGeneration: {bool} use salted mac generation
@return: {Stream} decrypted @return: {Stream} decrypted
""" """
#if update is needed #if update is needed
@@ -372,24 +411,28 @@ 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)
#ckeck signature #ckeck signature
if macData(self._macKey, decrypted)[:8] != signature.value: if not saltedMacGeneration and macData(self._macKey, decrypted)[:8] != signature.value:
raise InvalidExpectedDataException("Bad packet signature") raise InvalidExpectedDataException("bad signature")
if saltedMacGeneration and macSaltedData(self._macKey, decrypted, self._nbDecryptedPacket)[:8] != signature.value:
raise InvalidExpectedDataException("bad signature")
#count #count
self._nbDecryptedPacket += 1 self._nbDecryptedPacket += 1
return Stream(decrypted) return Stream(decrypted)
def writeEncryptedPayload(self, data): def writeEncryptedPayload(self, data, saltedMacGeneration):
""" """
@summary: sign and crypt data @summary: sign and crypt data
@param s: {Stream} raw stream @param data: {Type} raw stream
@param saltedMacGeneration: {bool} use salted mac generation
@return: {Tuple} (signature, encryptedData) @return: {Tuple} (signature, encryptedData)
""" """
if self._nbEncryptedPacket == 4096: if self._nbEncryptedPacket == 4096:
@@ -400,9 +443,14 @@ class SecLayer(LayerAutomata, IStreamSender, tpkt.IFastPathListener, tpkt.IFastP
self._nbEncryptedPacket = 0 self._nbEncryptedPacket = 0
self._nbEncryptedPacket += 1 self._nbEncryptedPacket += 1
s = Stream() s = Stream()
s.writeType(data) s.writeType(data)
return (String(macData(self._macKey, s.getvalue())[:8]), String(rc4.crypt(self._encryptRc4, s.getvalue())))
if saltedMacGeneration:
return (String(macSaltedData(self._macKey, s.getvalue(), self._nbEncryptedPacket - 1)[:8]), String(rc4.crypt(self._encryptRc4, s.getvalue())))
else:
return (String(macData(self._macKey, s.getvalue())[:8]), String(rc4.crypt(self._encryptRc4, s.getvalue())))
def recv(self, data): def recv(self, data):
""" """
@@ -419,7 +467,7 @@ class SecLayer(LayerAutomata, IStreamSender, tpkt.IFastPathListener, tpkt.IFastP
data.readType((securityFlag, securityFlagHi)) data.readType((securityFlag, securityFlagHi))
if securityFlag.value & SecurityFlag.SEC_ENCRYPT: if securityFlag.value & SecurityFlag.SEC_ENCRYPT:
data = self.readEncryptedPayload(data) data = self.readEncryptedPayload(data, securityFlag.value & SecurityFlag.SEC_SECURE_CHECKSUM)
self._presentation.recv(data) self._presentation.recv(data)
@@ -433,7 +481,12 @@ class SecLayer(LayerAutomata, IStreamSender, tpkt.IFastPathListener, tpkt.IFastP
self._transport.send(data) self._transport.send(data)
return return
self.sendFlagged(SecurityFlag.SEC_ENCRYPT, data) flag = SecurityFlag.SEC_ENCRYPT
if self._enableSecureCheckSum:
flag |= SecurityFlag.SEC_SECURE_CHECKSUM
self.sendFlagged(flag, data)
def sendFlagged(self, flag, data): def sendFlagged(self, flag, data):
""" """
@@ -444,7 +497,7 @@ class SecLayer(LayerAutomata, IStreamSender, tpkt.IFastPathListener, tpkt.IFastP
@param data: {Type | Tuple} @param data: {Type | Tuple}
""" """
if flag & SecurityFlag.SEC_ENCRYPT: if flag & SecurityFlag.SEC_ENCRYPT:
data = self.writeEncryptedPayload(data) data = self.writeEncryptedPayload(data, flag & SecurityFlag.SEC_SECURE_CHECKSUM)
self._transport.send((UInt16Le(flag), UInt16Le(), data)) self._transport.send((UInt16Le(flag), UInt16Le(), data))
def recvFastPath(self, secFlag, fastPathS): def recvFastPath(self, secFlag, fastPathS):
@@ -454,7 +507,7 @@ class SecLayer(LayerAutomata, IStreamSender, tpkt.IFastPathListener, tpkt.IFastP
@param fastPathS: {Stream} @param fastPathS: {Stream}
""" """
if self._enableEncryption and secFlag & tpkt.SecFlags.FASTPATH_OUTPUT_ENCRYPTED: if self._enableEncryption and secFlag & tpkt.SecFlags.FASTPATH_OUTPUT_ENCRYPTED:
fastPathS = self.readEncryptedPayload(fastPathS) fastPathS = self.readEncryptedPayload(fastPathS, secFlag & tpkt.SecFlags.FASTPATH_OUTPUT_SECURE_CHECKSUM)
self._fastPathPresentation.recvFastPath(secFlag, fastPathS) self._fastPathPresentation.recvFastPath(secFlag, fastPathS)
@@ -472,7 +525,11 @@ class SecLayer(LayerAutomata, IStreamSender, tpkt.IFastPathListener, tpkt.IFastP
""" """
if self._enableEncryption: if self._enableEncryption:
secFlag |= tpkt.SecFlags.FASTPATH_OUTPUT_ENCRYPTED secFlag |= tpkt.SecFlags.FASTPATH_OUTPUT_ENCRYPTED
fastPathS = self.writeEncryptedPayload(fastPathS)
if self._enableSecureCheckSum:
secFlag |= tpkt.SecFlags.FASTPATH_OUTPUT_SECURE_CHECKSUM
fastPathS = self.writeEncryptedPayload(fastPathS, self._enableSecureCheckSum)
self._fastPathTransport.sendFastPath(secFlag, fastPathS) self._fastPathTransport.sendFastPath(secFlag, fastPathS)
@@ -661,7 +718,7 @@ class Server(SecLayer):
raise InvalidExpectedDataException("Waiting info packet") raise InvalidExpectedDataException("Waiting info packet")
if securityFlag.value & SecurityFlag.SEC_ENCRYPT: if securityFlag.value & SecurityFlag.SEC_ENCRYPT:
s = self.readEncryptedPayload(s) s = self.readEncryptedPayload(s, securityFlag.value & SecurityFlag.SEC_SECURE_CHECKSUM)
s.readType(self._info) s.readType(self._info)
#next state send error license #next state send error license

View File

View 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,21 +252,21 @@ 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)) self.imeFileName = String("\x00"*64, readLen = CallableValue(64), optional = True)
self.postBeta2ColorDepth = UInt16Le(ColorDepth.RNS_UD_COLOR_8BPP) self.postBeta2ColorDepth = UInt16Le(ColorDepth.RNS_UD_COLOR_8BPP, optional = True)
self.clientProductId = UInt16Le(1) self.clientProductId = UInt16Le(1, optional = True)
self.serialNumber = UInt32Le(0) self.serialNumber = UInt32Le(0, optional = True)
self.highColorDepth = UInt16Le(HighColor.HIGH_COLOR_24BPP) 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) 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) self.earlyCapabilityFlags = UInt16Le(CapabilityFlags.RNS_UD_CS_SUPPORT_ERRINFO_PDU, optional = True)
self.clientDigProductId = String("\x00"*64, readLen = UInt8(64)) self.clientDigProductId = String("\x00"*64, readLen = CallableValue(64), optional = True)
self.connectionType = UInt8() self.connectionType = UInt8(optional = True)
self.pad1octet = UInt8() self.pad1octet = UInt8(optional = True)
self.serverSelectedProtocol = UInt32Le() self.serverSelectedProtocol = UInt32Le(optional = True)
class ServerCoreData(CompositeType): class ServerCoreData(CompositeType):
""" """
@@ -278,7 +278,8 @@ class ServerCoreData(CompositeType):
def __init__(self, readLen = None): def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen) CompositeType.__init__(self, readLen = readLen)
self.rdpVersion = UInt32Le(Version.RDP_VERSION_5_PLUS) self.rdpVersion = UInt32Le(Version.RDP_VERSION_5_PLUS)
self.clientRequestedProtocol = UInt32Le() self.clientRequestedProtocol = UInt32Le(optional = True)
self.earlyCapabilityFlags = UInt32Le(optional = True)
class ClientSecurityData(CompositeType): class ClientSecurityData(CompositeType):
""" """
@@ -353,9 +354,9 @@ class ProprietaryServerCertificate(CompositeType):
self.wPublicKeyBlobLen = UInt16Le(lambda:sizeof(self.PublicKeyBlob)) self.wPublicKeyBlobLen = UInt16Le(lambda:sizeof(self.PublicKeyBlob))
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) - 8)) self.wSignatureBlobLen = UInt16Le(lambda:(sizeof(self.SignatureBlob) + sizeof(self.padding)))
self.SignatureBlob = String(readLen = self.wSignatureBlobLen) 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):
""" """
@@ -380,7 +381,7 @@ class ProprietaryServerCertificate(CompositeType):
md5Digest = md5.new() md5Digest = md5.new()
md5Digest.update(s.getvalue()) md5Digest.update(s.getvalue())
return md5Digest.digest() + "\x00" + "\xff" * 46 + "\x01" return md5Digest.digest() + "\x00" + "\xff" * 45 + "\x01"
def sign(self): def sign(self):
""" """
@@ -417,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):
""" """
@@ -446,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):
""" """
@@ -457,13 +458,13 @@ 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()
class ClientNetworkData(CompositeType): class ClientNetworkData(CompositeType):
""" """
GCC client network block @summary: GCC client network block
All channels asked by client are listed here All channels asked by client are listed here
@see: http://msdn.microsoft.com/en-us/library/cc240512.aspx @see: http://msdn.microsoft.com/en-us/library/cc240512.aspx
""" """
@@ -476,7 +477,7 @@ class ClientNetworkData(CompositeType):
class ServerNetworkData(CompositeType): class ServerNetworkData(CompositeType):
""" """
GCC server network block @summary: GCC server network block
All channels asked by client are listed here All channels asked by client are listed here
@see: All channels asked by client are listed here @see: All channels asked by client are listed here
""" """
@@ -491,7 +492,7 @@ class ServerNetworkData(CompositeType):
class Settings(CompositeType): class Settings(CompositeType):
""" """
Class which group all clients settings supported by RDPY @summary: Class which group all clients settings supported by RDPY
""" """
def __init__(self, init = [], readLen = None): def __init__(self, init = [], readLen = None):
CompositeType.__init__(self, readLen = readLen) CompositeType.__init__(self, readLen = readLen)
@@ -518,21 +519,21 @@ class Settings(CompositeType):
def clientSettings(): def clientSettings():
""" """
Build settings for client @summary: Build settings for client
@return: Settings @return: Settings
""" """
return Settings([ClientCoreData(), ClientNetworkData(), ClientSecurityData()]) return Settings([ClientCoreData(), ClientNetworkData(), ClientSecurityData()])
def serverSettings(): def serverSettings():
""" """
Build settings for server @summary: Build settings for server
@return Settings @return Settings
""" """
return Settings([ServerCoreData(), ServerSecurityData(), ServerNetworkData()]) return Settings([ServerCoreData(), ServerSecurityData(), ServerNetworkData()])
def readConferenceCreateRequest(s): def readConferenceCreateRequest(s):
""" """
Read a response from client @summary: Read a response from client
GCC create request GCC create request
@param s: Stream @param s: Stream
@param client settings (Settings) @param client settings (Settings)
@@ -553,13 +554,13 @@ 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
def readConferenceCreateResponse(s): def readConferenceCreateResponse(s):
""" """
Read response from server @summary: Read response from server
and return server settings read from this response and return server settings read from this response
@param s: Stream @param s: Stream
@return: ServerSettings @return: ServerSettings
@@ -577,13 +578,13 @@ 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
def writeConferenceCreateRequest(userData): def writeConferenceCreateRequest(userData):
""" """
Write conference create request structure @summary: Write conference create request structure
@param userData: Settings for client @param userData: Settings for client
@return: GCC packet @return: GCC packet
""" """
@@ -598,7 +599,7 @@ def writeConferenceCreateRequest(userData):
def writeConferenceCreateResponse(serverData): def writeConferenceCreateResponse(serverData):
""" """
Write a conference create response packet @summary: Write a conference create response packet
@param serverData: Settings for server @param serverData: Settings for server
@return: gcc packet @return: gcc packet
""" """
@@ -607,6 +608,6 @@ def writeConferenceCreateResponse(serverData):
return (per.writeChoice(0), per.writeObjectIdentifier(t124_02_98_oid), return (per.writeChoice(0), per.writeObjectIdentifier(t124_02_98_oid),
per.writeLength(len(serverDataStream.getvalue()) + 14), per.writeChoice(0x14), per.writeLength(len(serverDataStream.getvalue()) + 14), per.writeChoice(0x14),
per.writeInteger16(0x79F3, 1001), per.writeInteger(1), per.writeEnumerates(16), per.writeInteger16(0x79F3, 1001), per.writeInteger(1), per.writeEnumerates(0),
per.writeNumberOfSet(1), per.writeChoice(0xc0), per.writeNumberOfSet(1), per.writeChoice(0xc0),
per.writeOctetStream(h221_sc_key, 4), per.writeOctetStream(serverDataStream.getvalue())) per.writeOctetStream(h221_sc_key, 4), per.writeOctetStream(serverDataStream.getvalue()))

View File

@@ -27,7 +27,6 @@ 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
import rdpy.core.log as log import rdpy.core.log as log
import ber, gcc, per import ber, gcc, per
@@ -255,7 +254,7 @@ class MCSLayer(LayerAutomata):
domainParam = (ber.writeInteger(maxChannels), ber.writeInteger(maxUsers), ber.writeInteger(maxTokens), domainParam = (ber.writeInteger(maxChannels), ber.writeInteger(maxUsers), ber.writeInteger(maxTokens),
ber.writeInteger(1), ber.writeInteger(0), ber.writeInteger(1), ber.writeInteger(1), ber.writeInteger(0), ber.writeInteger(1),
ber.writeInteger(maxPduSize), ber.writeInteger(2)) ber.writeInteger(maxPduSize), ber.writeInteger(2))
return (ber.writeUniversalTag(ber.Tag.BER_TAG_SEQUENCE, True), writeLength(sizeof(domainParam)), domainParam) return (ber.writeUniversalTag(ber.Tag.BER_TAG_SEQUENCE, True), ber.writeLength(sizeof(domainParam)), domainParam)
def writeMCSPDUHeader(self, mcsPdu, options = 0): def writeMCSPDUHeader(self, mcsPdu, options = 0):
""" """
@@ -515,15 +514,16 @@ class Server(MCSLayer):
self.readDomainParams(data) self.readDomainParams(data)
self.readDomainParams(data) self.readDomainParams(data)
self._clientSettings = gcc.readConferenceCreateRequest(Stream(ber.readOctetString(data))) self._clientSettings = gcc.readConferenceCreateRequest(Stream(ber.readOctetString(data)))
i = 1 if not self._clientSettings.CS_NET is None:
for channelDef in self._clientSettings.getBlock(gcc.MessageType.CS_NET).channelDefArray._array: i = 1
self._serverSettings.getBlock(gcc.MessageType.SC_NET).channelIdArray._array.append(UInt16Le(i + Channel.MCS_GLOBAL_CHANNEL)) for channelDef in self._clientSettings.CS_NET.channelDefArray._array:
#if channel can be handle by serve add it self._serverSettings.SC_NET.channelIdArray._array.append(UInt16Le(i + Channel.MCS_GLOBAL_CHANNEL))
for serverChannelDef, layer in self._virtualChannels: #if channel can be handle by serve add it
if channelDef.name == serverChannelDef.name: for serverChannelDef, layer in self._virtualChannels:
self._channels[i + Channel.MCS_GLOBAL_CHANNEL] = layer if channelDef.name == serverChannelDef.name:
i += 1 self._channels[i + Channel.MCS_GLOBAL_CHANNEL] = layer
i += 1
self.sendConnectResponse() self.sendConnectResponse()
self.setNextState(self.recvErectDomainRequest) self.setNextState(self.recvErectDomainRequest)

View File

@@ -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)

View File

@@ -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,19 +205,32 @@ 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)
#connection is done send to presentation if self._selectedProtocol == Protocols.PROTOCOL_RDP:
self._presentation.connect() log.warning("*" * 43)
log.warning("*" + " " * 10 + "RDP Security selected" + " " * 10 + "*")
log.warning("*" * 43)
#connection is done send to presentation
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):
""" """
@@ -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)

View File

@@ -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]
return rsaKey.getComponentByName('modulus')._value , rsaKey.getComponentByName('publicExponent')._value
def extractRSAKeyFromASN1(subjectPublicKey):
rsaKey = decoder.decode(subjectPublicKey, asn1Spec=RSAPublicKey())[0]
return rsaKey.getComponentByName('modulus')._value , rsaKey.getComponentByName('publicExponent')._value

View File

@@ -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,16 +377,9 @@ 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):
""" """

View File

@@ -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.1', 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
\t-RDP Man In The Middle proxy which record session
\t-RDP Honeypot
\t-RDP screenshoter
\t-RDP client
\t-VNC client
\t-VNC screenshoter
\t-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'
], ],

View File

@@ -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

View 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")

View File

@@ -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)

View File

@@ -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

View File

@@ -106,19 +106,6 @@ class X224Test(unittest.TestCase):
layer.connect() layer.connect()
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):
""" """
@@ -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): def startTLS(self, context):
class TLS(object): global tls
def startTLS(self, context): tls = True
global tls
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):