
Twisted Pythonにおけるリモートデスクトッププロトコル

twisted Python によるリモートデスクトッププロトコル
RDPY は、Microsoft RDP(Remote Desktop Protocol)プロトコルの純粋な Python 実装です(クライアント側およびサーバー側)。RDPY はイベント駆動型ネットワークエンジン Twisted 上に構築されています。RDPY は標準の RDP セキュリティレイヤー、SSL 上の RDP、および NLA 認証(ntlmv2 認証プロトコル経由)をサポートしています。
RDPY は以下の RDP および VNC バイナリを提供します:
RDPY は、ビットマップ解凍アルゴリズムを除き、完全に Python で実装されています。このアルゴリズムはパフォーマンス上の理由から C で実装されています。
依存関係は pyqt4 バイナリにのみ必要です:
Debian ベースのシステムの例:
sudo apt-get install python-qt4
Homebrew で PyQt をインストールする OS X の例
$ brew install qt sip pyqt
$ git clone https://github.com/citronneur/rdpy.git rdpy
$ pip install twisted pyopenssl qt4reactor service_identity rsa pyasn1
$ python rdpy/setup.py install
または PIP を使用:
$ pip install rdpy
virtualenv の場合は、qt4 ライブラリをリンクする必要があります:
$ ln -s /usr/lib/python2.7/dist-packages/PyQt4/ $VIRTUAL_ENV/lib/python2.7/site-packages/
$ ln -s /usr/lib/python2.7/dist-packages/sip.so $VIRTUAL_ENV/lib/python2.7/site-packages/
RDPY には非常に便利なバイナリがいくつか含まれています。これらのバイナリは Linux および Windows と互換性があります。
rdpy-rdpclient はシンプルな RDP Qt4 クライアントです。
$ rdpy-rdpclient.py [-u username] [-p password] [-d domain] [-r rss_ouput_file] [...] XXX.XXX.XXX.XXX[:3389]
rdpy-rdphoneypot で使用される Recorder Session シナリオで rdpy-rdpclient を使用できます。
rdpy-vncclient はシンプルな VNC Qt4 クライアントです。
$ rdpy-vncclient.py [-p password] XXX.XXX.XXX.XXX[:5900]
rdpy-rdpscreenshot はログイン画面をファイルに保存します。
$ rdpy-rdpscreenshot.py [-w width] [-l height] [-o output_file_path] XXX.XXX.XXX.XXX[:3389]
rdpy-vncscreenshot は最初の画面更新をファイルに保存します。
$ rdpy-vncscreenshot.py [-p password] [-o output_file_path] XXX.XXX.XXX.XXX[:5900]
rdpy-rdpmitm は RDP プロキシで、RDP プロトコルに対する Man In The Middle 攻撃を可能にします。セッションシナリオを rss ファイルに記録し、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]
出力ディレクトリは、次の形式で rss ファイルを保存するために使用されます(YYYYMMDDHHMMSS_ip_index.rss)。 秘密鍵ファイルと証明書ファイルは、SSL 接続のための標準的な暗号ファイルです。RDP プロトコルは独自のセキュリティレイヤーをネゴシエートできます。両方のパラメータのいずれかが省略された場合、サーバーは標準の RDP をセキュリティレイヤーとして使用します。
rdpy-rdphoneypot は RDP ハニーポットです。記録されたセッションシナリオを使用して、RDP プロトコル経由でシナリオを再生します。
$ rdpy-rdphoneypot.py [-l listen_port] [-k private_key_file_path] [-c certificate_file_path] rss_file_path_1 ... rss_file_path_N
秘密鍵ファイルと証明書ファイルは、SSL 接続のための標準的な暗号ファイルです。RDP プロトコルは独自のセキュリティレイヤーをネゴシエートできます。両方のパラメータのいずれかが省略された場合、サーバーは標準の RDP をセキュリティレイヤーとして使用します。 より一般的な画面サイズに一致させるために、複数のファイルを指定できます。
rdpy-rssplayer は、rdpy-rdpmitm または rdpy-rdpclient バイナリによって生成された Record Session Scenario(rss)ファイルを再生するために使用されます。
$ rdpy-rssplayer.py rss_file_path
RDPY は、rdpy.ui.qt4.QRemoteDesktop クラスを介して Qt ウィジェットとしても使用できます。独自の Qt アプリケーションに埋め込むことができます。Twisted と Qt を連携させるには、アプリケーションで qt4reactor を使用する必要があります。詳細については、rdpy-rdpclient のソースを参照してください。
簡単に言うと、RDPY は twisted エンジンを使用するプロトコルライブラリとして使用できます。
from rdpy.protocol.rdp import rdp
class MyRDPFactory(rdp.ClientFactory):
def clientConnectionLost(self, connector, reason):
reactor.stop()
def clientConnectionFailed(self, connector, reason):
reactor.stop()
def buildObserver(self, controller, addr):
class MyObserver(rdp.RDPClientObserver):
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: Windows session is ready
"""
def onClose(self):
"""
@summary: Call when stack is close
"""
return MyObserver(controller)
from twisted.internet import reactor
reactor.connectTCP("XXX.XXX.XXX.XXX", 3389, MyRDPFactory())
reactor.run()
from rdpy.protocol.rdp import rdp
class MyRDPFactory(rdp.ServerFactory):
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, 3, 4 or 5 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
reactor.listenTCP(3389, MyRDPFactory())
reactor.run()
from rdpy.protocol.rfb import rfb
class MyRFBFactory(rfb.ClientFactory):
def clientConnectionLost(self, connector, reason):
reactor.stop()
def clientConnectionFailed(self, connector, reason):
reactor.stop()
def buildObserver(self, controller, addr):
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):
"""
@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
reactor.connectTCP("XXX.XXX.XXX.XXX", 3389, MyRFBFactory())
reactor.run()