├── .github └── workflows │ └── python-windows-exe.yml ├── .gitignore ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.md ├── aardwolfgui ├── __init__.py ├── _version.py ├── aardpclient.py └── aardpclient_simple.py ├── builder └── pyinstaller │ └── build.bat ├── pyproject.toml └── setup.py /.github/workflows/python-windows-exe.yml: -------------------------------------------------------------------------------- 1 | name: Build Windows Executable - PyInstaller 2 | # Description: 3 | # Most of my projects come with a build.bat script that uses PyInstaller to freeze the examples 4 | # to an executable file. This Action will set up the envrionment and run this build.bat script, 5 | # then upload the resulting executables to a google cloud bucket. 6 | # Additionally the executables will be compressed and encrypted using 7z 7 | 8 | on: 9 | push: 10 | branches: 11 | - main # Trigger on push to master branch 12 | 13 | jobs: 14 | build: 15 | runs-on: windows-latest # Use a Windows runner 16 | permissions: 17 | contents: 'read' 18 | id-token: 'write' 19 | 20 | steps: 21 | - uses: 'actions/checkout@v4' 22 | with: 23 | fetch-depth: 0 24 | - id: 'auth' 25 | uses: 'google-github-actions/auth@v1' 26 | with: 27 | credentials_json: '${{ secrets.GCLOUD_BUCKET_SERVICE_USER_SECRET }}' 28 | 29 | - name: Set up Python 30 | uses: actions/setup-python@v2 31 | with: 32 | python-version: '3.9' 33 | 34 | - name: Install Dependencies 35 | run: | 36 | python -m pip install --upgrade pip 37 | pip install pyinstaller virtualenv 38 | 39 | - name: Run Batch File to Build Executable 40 | run: builder\pyinstaller\build.bat 41 | 42 | - name: Compress executables 43 | run: | 44 | "C:\Program Files\7-Zip\7z.exe" a secure.7z *.exe -pprotected 45 | working-directory: ${{ github.workspace }}/builder/pyinstaller 46 | shell: cmd 47 | 48 | #- name: Upload Executable 49 | # uses: actions/upload-artifact@v2 50 | # with: 51 | # name: executable 52 | # path: builder\pyinstaller\*.exe 53 | 54 | - name: 'Set up Cloud SDK' 55 | uses: 'google-github-actions/setup-gcloud@v1' 56 | with: 57 | version: '>= 390.0.0' 58 | 59 | - name: Upload Executables to GCS 60 | run: | 61 | $PROJVERSION = python -c "import sys; sys.path.append('${{ github.event.repository.name }}'); import _version; print(_version.__version__)" 62 | Write-Host "Detected Version: $PROJVERSION" 63 | gsutil cp builder\pyinstaller\*.exe gs://skelsec-github-foss/${{ github.event.repository.name }}/$PROJVERSION/ 64 | gsutil cp builder\pyinstaller\*.7z gs://skelsec-github-foss/${{ github.event.repository.name }}/$PROJVERSION/ 65 | shell: powershell 66 | 67 | - uses: sarisia/actions-status-discord@v1 68 | if: always() 69 | with: 70 | webhook: ${{ secrets.DISCORD_WEBHOOK }} 71 | status: ${{ job.status }} 72 | content: | 73 | ${{ github.event_name == 'push' && format('Hey all! A new commit was pushed to {0}!', github.repository) || '' }} 74 | ${{ github.event_name == 'pull_request' && format('Hey all! A new pull request has been opened on {0}!', github.repository) || '' }} 75 | ${{ github.event_name == 'release' && format('Hey all! A new release was created for project {0}!', github.event.repository.name) || '' }} 76 | title: | 77 | ${{ github.event_name == 'push' && 'Push Notification' || '' }} 78 | ${{ github.event_name == 'pull_request' && 'Pull Request Notification' || '' }} 79 | ${{ github.event_name == 'release' && 'Release Notification' || '' }} 80 | color: | 81 | ${{ github.event_name == 'push' && '0x00ff00' || '' }} 82 | ${{ github.event_name == 'pull_request' && '0xff0000' || '' }} 83 | ${{ github.event_name == 'release' && '0x0000ff' || '' }} 84 | url: "${{ github.server_url }}/${{ github.repository }}" 85 | username: GitHub Actions 86 | avatar_url: "https://avatars.githubusercontent.com/u/19204702" 87 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 105 | __pypackages__/ 106 | 107 | # Celery stuff 108 | celerybeat-schedule 109 | celerybeat.pid 110 | 111 | # SageMath parsed files 112 | *.sage.py 113 | 114 | # Environments 115 | .env 116 | .venv 117 | env/ 118 | venv/ 119 | ENV/ 120 | env.bak/ 121 | venv.bak/ 122 | 123 | # Spyder project settings 124 | .spyderproject 125 | .spyproject 126 | 127 | # Rope project settings 128 | .ropeproject 129 | 130 | # mkdocs documentation 131 | /site 132 | 133 | # mypy 134 | .mypy_cache/ 135 | .dmypy.json 136 | dmypy.json 137 | 138 | # Pyre type checker 139 | .pyre/ 140 | 141 | # pytype static type analyzer 142 | .pytype/ 143 | 144 | # Cython debug symbols 145 | cython_debug/ 146 | 147 | # PyCharm 148 | # JetBrains specific template is maintainted in a separate JetBrains.gitignore that can 149 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 150 | # and can be added to the global gitignore or merged into this file. For a more nuclear 151 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 152 | #.idea/ 153 | *.tsv 154 | *.png 155 | *.exe -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Tamas Jos (@skelsec) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE README.md 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | clean: 2 | rm -f -r build/ 3 | rm -f -r dist/ 4 | rm -f -r *.egg-info 5 | find . -name '*.pyc' -exec rm -f {} + 6 | find . -name '*.pyo' -exec rm -f {} + 7 | find . -name '*~' -exec rm -f {} + 8 | 9 | publish: clean 10 | python3 setup.py sdist bdist_wheel 11 | python3 -m twine upload dist/* 12 | 13 | rebuild: clean 14 | python3 setup.py install 15 | 16 | build: 17 | python3 setup.py install 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Supported Python versions](https://img.shields.io/badge/python-3.7+-blue.svg) [![Twitter](https://img.shields.io/twitter/follow/skelsec?label=skelsec&style=social)](https://twitter.com/intent/follow?screen_name=skelsec) 2 | 3 | ## :triangular_flag_on_post: Sponsors 4 | 5 | If you like this project, consider purchasing licenses of [OctoPwn](https://octopwn.com/), our full pentesting suite that runs in your browser! 6 | For notifications on new builds/releases and other info, hop on to our [Discord](https://discord.gg/PM8utcNxMS) 7 | 8 | 9 | # AARDWOLFGUI - Asynchronous RDP client in Python (GUI) 10 | Qt5 based GUI for aardwolf RDP/VNC client 11 | 12 | ## :triangular_flag_on_post: Runs in the browser 13 | 14 | This project, alongside with many other pentester tools runs in the browser with the power of OctoPwn! 15 | Check out the community version at [OctoPwn - Live](https://live.octopwn.com/) 16 | 17 | # Example scripts 18 | - `aardpclient` Basic RDP/VNC client running on top of Qt5. Can copy-paste text, handles keyboard and mouse. 19 | 20 | # URL format 21 | As usual the scripts take the target/scredentials in URL format. Below some examples 22 | - `rdp+kerberos-password://TEST\Administrator:Passw0rd!1@win2016ad.test.corp/?dc=10.10.10.2&proxytype=socks5&proxyhost=127.0.0.1&proxyport=1080` 23 | 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` 24 | - `rdp+ntlm-password://TEST\Administrator:Passw0rd!1@10.10.10.103` 25 | CredSSP (aka `HYBRID`) auth using NTLM auth + password connecting to RDP server `10.10.10.103` 26 | - `rdp+ntlm-password://TEST\Administrator:@10.10.10.103` 27 | CredSSP (aka `HYBRID`) auth using Pass-the-Hash (NTLM) auth connecting to RDP server `10.10.10.103` 28 | - `rdp+plain://Administrator:Passw0rd!1@10.10.10.103` 29 | Plain authentication (No SSL, encryption is RC4) using password connecting to RDP server `10.10.10.103` 30 | - `vnc+plain://Passw0rd!1@10.10.10.103` 31 | VNC client with VNC authentication using password connecting to RDP server `10.10.10.103` 32 | - `vnc+plain://Passw0rd!1@10.10.10.103` 33 | VNC client with VNC authentication using password connecting to RDP server `10.10.10.103` 34 | - `vnc+plain://:admin:aaa@10.10.10.103` 35 | VNC client with VNC authentication using password `admin:aa` connecting to RDP server `10.10.10.103`. Note that if the password contains `:` char you will have to prepend the password with `:` 36 | 37 | 38 | # Additional info for Qt install. 39 | - installing in venv will require installing Qt6 outside of venv, then installing 'wheel' and 'vext.pyqt6' in the venv via pip first. then install pyqt5 in the venv 40 | - installing Qt6 can be a nightmare 41 | - On ubuntu you can use `apt install python3-pyqt6` before installing `aardwolf` and it will (should) work 42 | 43 | # Kudos 44 | - Citronneur's ([@awakecoding](https://twitter.com/citronneur)) [`rdpy`](https://github.com/citronneur/rdpy). The decompression code and the QT image magic was really valuable. 45 | - Marc-André Moreau ([@awakecoding](https://twitter.com/awakecoding)) for providing suggestions on fixes 46 | 47 | 48 | -------------------------------------------------------------------------------- /aardwolfgui/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skelsec/aardwolfgui/8741a77afbaf052d755b0959f500ba60262712bf/aardwolfgui/__init__.py -------------------------------------------------------------------------------- /aardwolfgui/_version.py: -------------------------------------------------------------------------------- 1 | 2 | __version__ = "0.0.8" 3 | __banner__ = \ 4 | """ 5 | # aardpwolfgui %s 6 | # Author: Tamas Jos @skelsec (info@skelsecprojects.com) 7 | """ % __version__ 8 | -------------------------------------------------------------------------------- /aardwolfgui/aardpclient.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import asyncio 3 | import traceback 4 | import queue 5 | import threading 6 | import time 7 | 8 | from aardwolf import logger 9 | from aardwolf.keyboard import VK_MODIFIERS 10 | from aardwolf.commons.factory import RDPConnectionFactory 11 | from aardwolf.commons.iosettings import RDPIOSettings 12 | from aardwolf.commons.queuedata import RDPDATATYPE 13 | from aardwolf.commons.queuedata.keyboard import RDP_KEYBOARD_SCANCODE, RDP_KEYBOARD_UNICODE 14 | from aardwolf.commons.queuedata.mouse import RDP_MOUSE 15 | from aardwolf.extensions.RDPECLIP.protocol.formatlist import CLIPBRD_FORMAT 16 | from aardwolf.commons.queuedata.clipboard import RDP_CLIPBOARD_DATA_TXT 17 | from aardwolf.commons.queuedata.constants import MOUSEBUTTON, VIDEO_FORMAT 18 | from aardwolf.commons.target import RDPConnectionDialect 19 | 20 | from PIL.ImageQt import ImageQt 21 | 22 | from PyQt6.QtWidgets import QApplication, QMainWindow, QLabel #qApp, 23 | from PyQt6.QtCore import QObject, pyqtSignal, pyqtSlot, QThread, Qt 24 | from PyQt6.QtGui import QPainter, QImage, QPixmap 25 | 26 | import pyperclip 27 | 28 | 29 | # with the help of 30 | # https://gist.github.com/jazzycamel/8abd37bf2d60cce6e01d 31 | 32 | class RDPClientConsoleSettings: 33 | def __init__(self, url:str, iosettings:RDPIOSettings): 34 | self.mhover:int = True 35 | self.keyboard:int = True 36 | self.url:str = url 37 | self.iosettings:RDPIOSettings = iosettings 38 | # file path of the ducky file (if used) 39 | self.ducky_file = None 40 | # ducky script start delay, None means that typing will not start automatically 41 | self.ducky_autostart_delay = 5 42 | 43 | class RDPImage: 44 | def __init__(self,x,y,image,width,height): 45 | self.x = x 46 | self.y = y 47 | self.image = image 48 | self.width = width 49 | self.height = height 50 | 51 | class RDPInterfaceThread(QObject): 52 | result=pyqtSignal(RDPImage) 53 | connection_terminated=pyqtSignal() 54 | 55 | def __init__(self, parent=None, **kwargs): 56 | super().__init__(parent, **kwargs) 57 | self.settings:RDPClientConsoleSettings = None 58 | self.conn = None 59 | self.input_evt = None 60 | self.in_q = None 61 | self.loop_started_evt = threading.Event() 62 | self.gui_stopped_evt = threading.Event() 63 | self.input_handler_thread = None 64 | self.asyncthread:threading.Thread = None 65 | 66 | def set_settings(self, settings, in_q): 67 | self.settings = settings 68 | self.in_q = in_q 69 | 70 | def inputhandler(self, loop:asyncio.AbstractEventLoop): 71 | while not self.conn.disconnected_evt.is_set(): 72 | data = self.in_q.get() 73 | loop.call_soon_threadsafe(self.conn.ext_in_queue.put_nowait, data) 74 | if data is None: 75 | break 76 | logger.debug('inputhandler terminating') 77 | 78 | async def ducky_keyboard_sender(self, scancode, is_pressed, as_char = False): 79 | ### Callback function for the duckyexecutor to dispatch scancodes/characters to the remote end 80 | try: 81 | #print('SCANCODE: %s' % scancode) 82 | #print('is_pressed: %s' % is_pressed) 83 | #print('as_char: %s' % as_char) 84 | if as_char is False: 85 | ki = RDP_KEYBOARD_SCANCODE() 86 | ki.keyCode = scancode 87 | ki.is_pressed = is_pressed 88 | ki.modifiers = VK_MODIFIERS(0) 89 | await self.conn.ext_in_queue.put(ki) 90 | else: 91 | ki = RDP_KEYBOARD_UNICODE() 92 | ki.char = scancode 93 | ki.is_pressed = is_pressed 94 | await self.conn.ext_in_queue.put(ki) 95 | except Exception as e: 96 | traceback.print_exc() 97 | 98 | async def ducky_exec(self, bypass_delay = False): 99 | try: 100 | if self.settings.ducky_file is None: 101 | return 102 | from aardwolf.keyboard.layoutmanager import KeyboardLayoutManager 103 | from aardwolf.utils.ducky import DuckyExecutorBase, DuckyReaderFile 104 | if bypass_delay is False: 105 | if self.settings.ducky_autostart_delay is not None: 106 | await asyncio.sleep(self.settings.ducky_autostart_delay) 107 | else: 108 | return 109 | 110 | layout = KeyboardLayoutManager().get_layout_by_shortname(self.settings.iosettings.client_keyboard) 111 | executor = DuckyExecutorBase(layout, self.ducky_keyboard_sender, send_as_char = True if self.conn.target.dialect == RDPConnectionDialect.VNC else False) 112 | reader = DuckyReaderFile.from_file(self.settings.ducky_file, executor) 113 | await reader.parse() 114 | except Exception as e: 115 | traceback.print_exc() 116 | 117 | async def rdpconnection(self): 118 | input_handler_thread = None 119 | 120 | try: 121 | rdpurl = RDPConnectionFactory.from_url(self.settings.url, self.settings.iosettings) 122 | self.conn = rdpurl.get_connection(self.settings.iosettings) 123 | _, err = await self.conn.connect() 124 | if err is not None: 125 | raise err 126 | 127 | #asyncio.create_task(self.inputhandler()) 128 | input_handler_thread = asyncio.get_event_loop().run_in_executor(None, self.inputhandler, asyncio.get_event_loop()) 129 | self.loop_started_evt.set() 130 | if self.settings.ducky_file is not None: 131 | x = asyncio.create_task(self.ducky_exec()) 132 | while not self.gui_stopped_evt.is_set(): 133 | data = await self.conn.ext_out_queue.get() 134 | if data is None: 135 | return 136 | if data.type == RDPDATATYPE.VIDEO: 137 | ri = RDPImage(data.x, data.y, data.data, data.width, data.height) 138 | if not self.gui_stopped_evt.is_set(): 139 | self.result.emit(ri) 140 | else: 141 | return 142 | elif data.type == RDPDATATYPE.CLIPBOARD_READY: 143 | continue 144 | elif data.type == RDPDATATYPE.CLIPBOARD_NEW_DATA_AVAILABLE: 145 | continue 146 | elif data.type == RDPDATATYPE.CLIPBOARD_CONSUMED: 147 | continue 148 | elif data.type == RDPDATATYPE.CLIPBOARD_DATA_TXT: 149 | continue 150 | else: 151 | logger.debug('Unknown incoming data: %s'% data) 152 | 153 | except asyncio.CancelledError: 154 | return 155 | 156 | except Exception as e: 157 | traceback.print_exc() 158 | finally: 159 | if self.conn is not None: 160 | await self.conn.terminate() 161 | if input_handler_thread is not None: 162 | input_handler_thread.cancel() 163 | if not self.gui_stopped_evt.is_set(): 164 | self.connection_terminated.emit() 165 | 166 | def starter(self): 167 | self.loop = asyncio.new_event_loop() 168 | asyncio.set_event_loop(self.loop) 169 | try: 170 | self.rdp_connection_task = self.loop.create_task(self.rdpconnection()) 171 | self.loop.run_until_complete(self.rdp_connection_task) 172 | self.loop.close() 173 | except Exception as e: 174 | pass 175 | 176 | 177 | @pyqtSlot() 178 | def start(self): 179 | # creating separate thread for async otherwise this will not return 180 | # and then there will be no events sent back from application 181 | self.asyncthread = threading.Thread(target=self.starter, args=()) 182 | self.asyncthread.start() 183 | 184 | @pyqtSlot() 185 | def stop(self): 186 | self.gui_stopped_evt.set() 187 | if self.conn is not None and self.loop.is_running(): 188 | try: 189 | asyncio.run_coroutine_threadsafe(self.conn.terminate(), self.loop) 190 | except: 191 | pass 192 | time.sleep(0.1) # waiting connection to terminate 193 | self.rdp_connection_task.cancel() 194 | self.loop.stop() 195 | 196 | @pyqtSlot() 197 | def startducky(self): 198 | time.sleep(0.1) # waiting for keyboard flush 199 | asyncio.run_coroutine_threadsafe(self.ducky_exec(bypass_delay = True), self.loop) 200 | 201 | @pyqtSlot() 202 | def clipboard_send_files(self, files): 203 | asyncio.run_coroutine_threadsafe(self.conn.set_current_clipboard_files(files), self.loop) 204 | 205 | class RDPClientQTGUI(QMainWindow): 206 | #inputevent=pyqtSignal() 207 | 208 | def __init__(self, settings:RDPClientConsoleSettings): 209 | super().__init__() 210 | self.setAcceptDrops(True) 211 | self.settings = settings 212 | self.ducky_key_ctr = 0 213 | 214 | # enabling this will singificantly increase the bandwith 215 | self.mhover = settings.mhover 216 | # enabling keyboard tracking 217 | self.keyboard = settings.keyboard 218 | self.is_rdp = True if settings.url.lower().startswith('rdp') is True else False 219 | 220 | # setting up the main window with the requested resolution 221 | self.setGeometry(0, 0, self.settings.iosettings.video_width, self.settings.iosettings.video_height) 222 | # this buffer will hold the current frame and will be contantly updated 223 | # as new rectangle info comes in from the server 224 | self._buffer = QImage(self.settings.iosettings.video_width, self.settings.iosettings.video_height, QImage.Format.Format_RGB32) 225 | 226 | 227 | # setting up worker thread in a qthread 228 | # the worker recieves the video updates from the connection object 229 | # and then dispatches it to updateImage 230 | # this is needed as the RDPConnection class uses async queues 231 | # and QT is not async so an interface between the two worlds 232 | # had to be created 233 | self.in_q = queue.Queue() 234 | self._thread=QThread() 235 | self._threaded=RDPInterfaceThread(result=self.updateImage, connection_terminated=self.connectionClosed) 236 | self._threaded.set_settings(self.settings, self.in_q) 237 | self._thread.started.connect(self._threaded.start) 238 | self._threaded.moveToThread(self._thread) 239 | QApplication.instance().aboutToQuit.connect(self._thread.quit) 240 | self._thread.start() 241 | 242 | # setting up the canvas (qlabel) which will display the image data 243 | self._label_imageDisplay = QLabel() 244 | self._label_imageDisplay.setFixedSize(self.settings.iosettings.video_width, self.settings.iosettings.video_height) 245 | 246 | self.setCentralWidget(self._label_imageDisplay) 247 | 248 | # enabling mouse tracking 249 | self.setMouseTracking(True) 250 | self._label_imageDisplay.setMouseTracking(True) 251 | self.__extended_rdp_keys = { 252 | Qt.Key.Key_End : 'VK_END', 253 | Qt.Key.Key_Down : 'VK_DOWN', 254 | Qt.Key.Key_PageDown : 'VK_NEXT', 255 | Qt.Key.Key_Insert : 'VK_INSERT', 256 | Qt.Key.Key_Delete : 'VK_DELETE', 257 | Qt.Key.Key_Print : 'VK_SNAPSHOT', 258 | Qt.Key.Key_Home : 'VK_HOME', 259 | Qt.Key.Key_Up : 'VK_UP', 260 | Qt.Key.Key_PageUp : 'VK_PRIOR', 261 | Qt.Key.Key_Left : 'VK_LEFT', 262 | Qt.Key.Key_Right : 'VK_RIGHT', 263 | Qt.Key.Key_Meta : 'VK_LWIN', 264 | Qt.Key.Key_Enter : 'VK_RETURN', 265 | Qt.Key.Key_Menu : 'VK_LMENU', 266 | Qt.Key.Key_Pause : 'VK_PAUSE', 267 | Qt.Key.Key_Slash: 'VK_DIVIDE', 268 | Qt.Key.Key_Period: 'VK_DECIMAL', 269 | 270 | #Qt.Key.Key_Shift: 'VK_LSHIFT', 271 | #Qt.Key.Key_Tab: 'VK_TAB', 272 | #Qt.Key.Key_0 : 'VK_NUMPAD0', 273 | #Qt.Key.Key_1 : 'VK_NUMPAD1', 274 | #Qt.Key.Key_2 : 'VK_NUMPAD2', 275 | #Qt.Key.Key_3 : 'VK_NUMPAD3', 276 | #Qt.Key.Key_4 : 'VK_NUMPAD4', 277 | #Qt.Key.Key_5 : 'VK_NUMPAD5', 278 | #Qt.Key.Key_6 : 'VK_NUMPAD6', 279 | #Qt.Key.Key_7 : 'VK_NUMPAD7', 280 | #Qt.Key.Key_8 : 'VK_NUMPAD8', 281 | #Qt.Key.Key_9 : 'VK_NUMPAD9', 282 | } 283 | 284 | self.__qtbutton_to_rdp = { 285 | Qt.MouseButton.LeftButton : MOUSEBUTTON.MOUSEBUTTON_LEFT, 286 | Qt.MouseButton.RightButton : MOUSEBUTTON.MOUSEBUTTON_RIGHT, 287 | Qt.MouseButton.MiddleButton : MOUSEBUTTON.MOUSEBUTTON_MIDDLE, 288 | Qt.MouseButton.ExtraButton1 : MOUSEBUTTON.MOUSEBUTTON_5, 289 | Qt.MouseButton.ExtraButton2 : MOUSEBUTTON.MOUSEBUTTON_6, 290 | Qt.MouseButton.ExtraButton3 : MOUSEBUTTON.MOUSEBUTTON_7, 291 | Qt.MouseButton.ExtraButton4 : MOUSEBUTTON.MOUSEBUTTON_8, 292 | Qt.MouseButton.ExtraButton5 : MOUSEBUTTON.MOUSEBUTTON_9, 293 | Qt.MouseButton.ExtraButton6 : MOUSEBUTTON.MOUSEBUTTON_10, 294 | } 295 | 296 | def closeEvent(self, event): 297 | self.connectionClosed() 298 | event.accept() 299 | 300 | def connectionClosed(self): 301 | self.in_q.put(None) 302 | self._threaded.stop() 303 | self._thread.quit() 304 | self.close() 305 | 306 | def updateImage(self, event): 307 | rect = ImageQt(event.image) 308 | if event.width == self.settings.iosettings.video_width and event.height == self.settings.iosettings.video_height: 309 | self._buffer = rect 310 | else: 311 | with QPainter(self._buffer) as qp: 312 | qp.drawImage(event.x, event.y, rect, 0, 0, event.width, event.height) 313 | 314 | pixmap01 = QPixmap.fromImage(self._buffer) 315 | pixmap_image = QPixmap(pixmap01) 316 | self._label_imageDisplay.setPixmap(pixmap_image) 317 | self._label_imageDisplay.setAlignment(Qt.AlignmentFlag.AlignCenter) 318 | self._label_imageDisplay.setScaledContents(True) 319 | self._label_imageDisplay.setMinimumSize(1,1) 320 | self._label_imageDisplay.show() 321 | 322 | ## this is for testing! 323 | #def keyevent_to_string(self, event): 324 | # keymap = {} 325 | # for key, value in vars(Qt).items(): 326 | # if isinstance(value, Qt.Key): 327 | # keymap[value] = key.partition('_')[2] 328 | # modmap = { 329 | # Qt.ControlModifier: keymap[Qt.Key.Key_Control], 330 | # Qt.AltModifier: keymap[Qt.Key.Key_Alt], 331 | # Qt.ShiftModifier: keymap[Qt.Key.Key_Shift], 332 | # Qt.MetaModifier: keymap[Qt.Key.Key_Meta], 333 | # Qt.GroupSwitchModifier: keymap[Qt.Key.Key_AltGr], 334 | # Qt.KeypadModifier: keymap[Qt.Key.Key_NumLock], 335 | # } 336 | # sequence = [] 337 | # for modifier, text in modmap.items(): 338 | # if event.modifiers() & modifier: 339 | # sequence.append(text) 340 | # key = keymap.get(event.key(), event.text()) 341 | # if key not in sequence: 342 | # sequence.append(key) 343 | # return '+'.join(sequence) 344 | 345 | def send_key(self, e, is_pressed): 346 | # https://doc.qt.io/qt-5/qt.html#Key-enum 347 | 348 | # ducky script starter 349 | if is_pressed is True: 350 | if e.key()==Qt.Key.Key_Escape: 351 | self.ducky_key_ctr += 1 352 | if self.ducky_key_ctr == 3: 353 | self.ducky_key_ctr = 0 354 | self._threaded.startducky() 355 | else: 356 | self.ducky_key_ctr = 0 357 | 358 | if self.keyboard is False: 359 | return 360 | #print(self.keyevent_to_string(e)) 361 | 362 | if e.key()==(Qt.Key.Key_Control and Qt.Key.Key_V): 363 | ki = RDP_CLIPBOARD_DATA_TXT() 364 | ki.datatype = CLIPBRD_FORMAT.CF_UNICODETEXT 365 | ki.data = pyperclip.paste() 366 | self.in_q.put(ki) 367 | 368 | modifiers = VK_MODIFIERS(0) 369 | qt_modifiers = QApplication.keyboardModifiers() 370 | if bool(qt_modifiers & Qt.KeyboardModifier.ShiftModifier) is True and e.key() != Qt.Key.Key_Shift: 371 | modifiers |= VK_MODIFIERS.VK_SHIFT 372 | if bool(qt_modifiers & Qt.KeyboardModifier.ControlModifier) is True and e.key() != Qt.Key.Key_Control: 373 | modifiers |= VK_MODIFIERS.VK_CONTROL 374 | if bool(qt_modifiers & Qt.KeyboardModifier.AltModifier) is True and e.key() != Qt.Key.Key_Alt: 375 | modifiers |= VK_MODIFIERS.VK_MENU 376 | if bool(qt_modifiers & Qt.KeyboardModifier.KeypadModifier) is True and e.key() != Qt.Key.Key_NumLock: 377 | modifiers |= VK_MODIFIERS.VK_NUMLOCK 378 | if bool(qt_modifiers & Qt.KeyboardModifier.MetaModifier) is True and e.key() != Qt.Key.Key_Meta: 379 | modifiers |= VK_MODIFIERS.VK_WIN 380 | 381 | ki = RDP_KEYBOARD_SCANCODE() 382 | ki.keyCode = e.nativeScanCode() 383 | ki.is_pressed = is_pressed 384 | if sys.platform == "linux": 385 | #why tho? 386 | ki.keyCode -= 8 387 | ki.modifiers = modifiers 388 | 389 | if e.key() in self.__extended_rdp_keys.keys(): 390 | ki.vk_code = self.__extended_rdp_keys[e.key()] 391 | 392 | #print('SCANCODE: %s' % ki.keyCode) 393 | #print('VK CODE : %s' % ki.vk_code) 394 | #print('TEXT : %s' % repr(e.text())) 395 | self.in_q.put(ki) 396 | 397 | def send_mouse(self, e, is_pressed, is_hover = False): 398 | if is_hover is True and self.settings.mhover is False: 399 | # is hovering is disabled we return immediately 400 | return 401 | buttonNumber = MOUSEBUTTON.MOUSEBUTTON_HOVER 402 | if is_hover is False: 403 | buttonNumber = self.__qtbutton_to_rdp[e.button()] 404 | 405 | mi = RDP_MOUSE() 406 | mi.xPos = e.pos().x() 407 | mi.yPos = e.pos().y() 408 | mi.button = buttonNumber 409 | mi.is_pressed = is_pressed if is_hover is False else False 410 | 411 | self.in_q.put(mi) 412 | 413 | def keyPressEvent(self, e): 414 | self.send_key(e, True) 415 | 416 | def keyReleaseEvent(self, e): 417 | self.send_key(e, False) 418 | 419 | def mouseMoveEvent(self, e): 420 | self.send_mouse(e, False, True) 421 | 422 | def mouseReleaseEvent(self, e): 423 | self.send_mouse(e, False) 424 | 425 | def mousePressEvent(self, e): 426 | self.send_mouse(e, True) 427 | 428 | def dragEnterEvent(self, event): 429 | if event.mimeData().hasUrls(): 430 | event.accept() 431 | else: 432 | event.ignore() 433 | 434 | def dropEvent(self, event): 435 | files = [u.toLocalFile() for u in event.mimeData().urls()] 436 | if len(files) == 0: 437 | return 438 | self._threaded.clipboard_send_files(files) 439 | 440 | def get_help(): 441 | from asysocks.unicomm.common.target import UniTarget 442 | from asyauth.common.credentials import UniCredential 443 | 444 | protocols = """RDP : RDP protocol 445 | VNC: VNC protocol""" 446 | authprotos = """ntlm : CREDSSP+NTLM authentication 447 | kerberos : CREDSSP+Kerberos authentication 448 | sspi-ntlm: CREDSSP+NTLM authentication using current user's creds (Windows only, restricted admin mode only) 449 | sspi-kerberos: CREDSSP+KERBEROS authentication using current user's creds (Windows only, restricted admin mode only) 450 | plain : Old username and password authentication (only works when NLA is disabled on the server) 451 | none : No authentication (same as plain, but no provided credentials needed) 452 | """ 453 | usage = UniCredential.get_help(protocols, authprotos, '') 454 | usage += UniTarget.get_help() 455 | usage += """ 456 | RDP Examples: 457 | Login with no credentials (only works when NLA is disabled on the server): 458 | rdp://10.10.10.2 459 | Login with username and password (only works when NLA is disabled on the server): 460 | rdp://TEST\Administrator:Passw0rd!1@10.10.10.2 461 | Login via CREDSSP+NTLM: 462 | rdp+ntlm-password://TEST\Administrator:Passw0rd!1@10.10.10.2 463 | Login via CREDSSP+Kerberos: 464 | rdp+kerberos-password://TEST\Administrator:Passw0rd!1@win2019ad.test.corp/?dc=10.10.10.2 465 | Login via CREDSSP+NTLM using current user's creds (Windows only, restricted admin mode only): 466 | rdp+sspi-ntlm://win2019ad.test.corp 467 | Login via CREDSSP+Kerberos using current user's creds (Windows only, restricted admin mode only): 468 | rdp+sspi-kerberos://win2019ad.test.corp/ 469 | ... 470 | 471 | VNC examples: 472 | Login with no credentials: 473 | vnc://10.10.10.2 474 | Login with password (the short way): 475 | vnc://Passw0rd!1@10.10.10.2 476 | Login with password: 477 | vnc+plain-password://Passw0rd!1@10.10.10.2 478 | """ 479 | return usage 480 | 481 | def main(): 482 | from aardwolf.extensions.RDPEDYC.vchannels.socksoverrdp import SocksOverRDPChannel 483 | 484 | import logging 485 | import argparse 486 | parser = argparse.ArgumentParser(description='Async RDP Client. Duckyscript will be executed by pressing ESC 3 times', usage=get_help()) 487 | parser.add_argument('-v', '--verbose', action='count', default=0, help='Verbosity, can be stacked') 488 | parser.add_argument('--no-mouse-hover', action='store_false', help='Disables sending mouse hovering data. (saves bandwith)') 489 | parser.add_argument('--no-keyboard', action='store_false', help='Disables keyboard input. (whatever)') 490 | parser.add_argument('--res', default = '1024x768', help='Resolution in "WIDTHxHEIGHT" format. Default: "1024x768"') 491 | parser.add_argument('--bpp', choices = [15, 16, 24, 32], default = 32, type=int, help='Bits per pixel.') 492 | parser.add_argument('--keyboard', default = 'enus', help='Keyboard on the client side. Used for VNC and duckyscript') 493 | parser.add_argument('--ducky', help='Ducky script to be executed') 494 | parser.add_argument('--duckydelay', type=int, default=-1, help='Ducky script autostart delayed') 495 | parser.add_argument('--sockschannel', default = 'SocksChannel', help='-extra- The virtual channel name of the remote SOCKS proxy') 496 | parser.add_argument('--socksip', default = '127.0.0.1', help='-extra- Listen IP for SOCKS server') 497 | parser.add_argument('--socksport', default = 1080, help='-extra- Listen port for SOCKS server') 498 | parser.add_argument('url', help="RDP connection url") 499 | 500 | args = parser.parse_args() 501 | 502 | if args.verbose == 1: 503 | logger.setLevel(logging.INFO) 504 | elif args.verbose == 2: 505 | logger.setLevel(logging.DEBUG) 506 | elif args.verbose > 2: 507 | logger.setLevel(1) 508 | 509 | duckydelay = args.duckydelay 510 | if args.duckydelay == -1: 511 | duckydelay = None 512 | 513 | width, height = args.res.upper().split('X') 514 | height = int(height) 515 | width = int(width) 516 | iosettings = RDPIOSettings() 517 | iosettings.video_width = width 518 | iosettings.video_height = height 519 | iosettings.video_bpp_min = 15 #servers dont support 8 any more :/ 520 | iosettings.video_bpp_max = args.bpp 521 | iosettings.video_out_format = VIDEO_FORMAT.PIL 522 | iosettings.client_keyboard = args.keyboard 523 | iosettings.vchannels[args.sockschannel] = SocksOverRDPChannel(args.sockschannel, args.socksip, args.socksport) 524 | 525 | settings = RDPClientConsoleSettings(args.url, iosettings) 526 | settings.mhover = args.no_mouse_hover 527 | settings.keyboard = args.no_keyboard 528 | settings.ducky_file = args.ducky 529 | settings.ducky_autostart_delay = duckydelay 530 | 531 | 532 | app = QApplication(sys.argv) 533 | qtclient = RDPClientQTGUI(settings) 534 | qtclient.show() 535 | #app.exec_() 536 | app.exec() 537 | app.quit() 538 | 539 | if __name__ == '__main__': 540 | main() 541 | -------------------------------------------------------------------------------- /aardwolfgui/aardpclient_simple.py: -------------------------------------------------------------------------------- 1 | from aardwolf.extensions.RDPECLIP.protocol.formatlist import CLIPBRD_FORMAT 2 | from aardwolf.commons.queuedata.clipboard import RDP_CLIPBOARD_DATA_TXT 3 | import sys 4 | import asyncio 5 | import traceback 6 | import queue 7 | from threading import Thread 8 | 9 | from aardwolf import logger 10 | from aardwolf.commons.url import RDPConnectionURL 11 | from aardwolf.commons.iosettings import RDPIOSettings 12 | from aardwolf.commons.queuedata import RDPDATATYPE 13 | from aardwolf.commons.queuedata.keyboard import RDP_KEYBOARD_SCANCODE 14 | from aardwolf.commons.queuedata.mouse import RDP_MOUSE 15 | 16 | import PySimpleGUI as sg 17 | from PIL import Image, ImageTk 18 | import pyperclip 19 | 20 | 21 | # with the help of 22 | # https://gist.github.com/jazzycamel/8abd37bf2d60cce6e01d 23 | 24 | class RDPClientConsoleSettings: 25 | def __init__(self, url:str, iosettings:RDPIOSettings): 26 | self.mhover:int = True 27 | self.keyboard:int = True 28 | self.url:str = url 29 | self.iosettings:RDPIOSettings = iosettings 30 | 31 | class RDPImage: 32 | def __init__(self,x,y,image,width,height): 33 | self.x = x 34 | self.y = y 35 | self.image = image 36 | self.width = width 37 | self.height = height 38 | """ 39 | try: 40 | rdpurl = RDPConnectionURL(self.settings.url) 41 | self.conn = rdpurl.get_connection(self.settings.iosettings) 42 | _, err = await self.conn.connect() 43 | if err is not None: 44 | raise err 45 | 46 | asyncio.create_task(self.inputhandler()) 47 | self.loop_started_evt.set() 48 | while True: 49 | data = await self.conn.ext_out_queue.get() 50 | if data is None: 51 | return 52 | if data.type == RDPDATATYPE.VIDEO: 53 | ri = RDPImage(data.x, data.y, data.data, data.height, data.width) 54 | self.result.emit(ri) 55 | elif data.type == RDPDATATYPE.CLIPBOARD_READY: 56 | continue 57 | else: 58 | print('Unknown incoming data: %s'% data) 59 | 60 | 61 | except Exception as e: 62 | traceback.print_exc() 63 | finally: 64 | self.connection_terminated.emit() 65 | """ 66 | 67 | async def rdp_run(settings:RDPClientConsoleSettings, video_in_q, rdp_out_q): 68 | # the loop running this should be in a separate thread than the windows manager 69 | pass 70 | 71 | def asyncloop(loop, settings:RDPClientConsoleSettings, video_in_q, rdp_out_q): 72 | # Set loop as the active event loop for this thread 73 | asyncio.set_event_loop(loop) 74 | # We will get our tasks from the main thread so just run an empty loop 75 | loop.run_until_complete(rdp_run(settings, video_in_q, rdp_out_q)) 76 | 77 | 78 | async def window_read(window): 79 | while True: 80 | event, res = window.read(timeout=1) 81 | if event == sg.TIMEOUT_EVENT: 82 | continue 83 | return ('window', event, res) 84 | 85 | async def video_read(q): 86 | data = await q.get() 87 | if data.type == RDPDATATYPE.VIDEO: 88 | return 'video', data 89 | else: 90 | return 'notvideo', data 91 | 92 | async def main(): 93 | print('This is not yet implemented!!!') 94 | return 95 | 96 | 97 | 98 | import logging 99 | import argparse 100 | parser = argparse.ArgumentParser(description='Async RDP Client') 101 | parser.add_argument('-v', '--verbose', action='count', default=0, help='Verbosity, can be stacked') 102 | parser.add_argument('--no-mouse-hover', action='store_false', help='Disables sending mouse hovering data. (saves bandwith)') 103 | parser.add_argument('--no-keyboard', action='store_false', help='Disables keyboard input. (whatever)') 104 | parser.add_argument('--res', default = '1024x768', help='Resolution in "WIDTHxHEIGHT" format. Default: "1024x768"') 105 | parser.add_argument('--bpp', choices = [15, 16, 24, 32], default = 32, type=int, help='Bits per pixel.') 106 | parser.add_argument('url', help="RDP connection url") 107 | 108 | args = parser.parse_args() 109 | 110 | if args.verbose == 1: 111 | logger.setLevel(logging.INFO) 112 | elif args.verbose == 2: 113 | logger.setLevel(logging.DEBUG) 114 | elif args.verbose > 2: 115 | logger.setLevel(1) 116 | 117 | width, height = args.res.upper().split('X') 118 | height = int(height) 119 | width = int(width) 120 | 121 | iosettings = RDPIOSettings() 122 | iosettings.video_width = width 123 | iosettings.video_height = height 124 | iosettings.video_bpp_min = 15 #servers dont support 8 any more :/ 125 | iosettings.video_bpp_max = args.bpp 126 | iosettings.video_out_format = VIDEO_FORMAT.PNG 127 | 128 | settings = RDPClientConsoleSettings(args.url, iosettings) 129 | settings.mhover = args.no_mouse_hover 130 | settings.keyboard = args.no_keyboard 131 | 132 | loop = asyncio.new_event_loop() 133 | video_in_q = queue.Queue() 134 | rdp_out_q = queue.Queue() 135 | #t = Thread(target=asyncloop, args=(loop, settings, video_in_q, rdp_out_q)) 136 | #t.start() 137 | 138 | elements = [ 139 | [sg.Graph(key="image", canvas_size=(width, height), graph_bottom_left=(0, 0), graph_top_right=(width, height), drag_submits=True, enable_events=True)], 140 | #[sg.Text("", size=(1, 1), key='text')], 141 | ] 142 | window = sg.Window("AARDWOLF RDP", elements, size=(width, height)) 143 | 144 | try: 145 | rdpurl = RDPConnectionURL(settings.url) 146 | conn = rdpurl.get_connection(settings.iosettings) 147 | _, err = await conn.connect() 148 | if err is not None: 149 | raise err 150 | except Exception as e: 151 | traceback.print_exc() 152 | return 153 | 154 | #await conn. 155 | 156 | desktop_image = Image.new("RGBA", [settings.iosettings.video_width, settings.iosettings.video_height]) 157 | terminated = False 158 | while not terminated: 159 | window_read_task = window_read(window) 160 | video_q_read = video_read(conn.ext_out_queue) 161 | tasks = [window_read_task, video_q_read] 162 | finished, unfinished = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED) 163 | for x in finished: 164 | result = x.result() 165 | if result[0] == 'window': 166 | event = result[1] 167 | values = result[2] 168 | if event == sg.TIMEOUT_EVENT: 169 | continue 170 | print(event) 171 | if event == "Exit" or event == sg.WIN_CLOSED: 172 | terminated = True 173 | break 174 | elif result[0] == 'video': 175 | #desktop_image.paste(result[1].data, (result[1].x, result[1].y)) 176 | #window["image"].draw_image(data=ImageTk.PhotoImage(desktop_image)) 177 | window["image"].draw_image(data=result[1].data, location=(result[1].x, settings.iosettings.video_height - result[1].y + result[1].height)) 178 | 179 | print('video') 180 | #print(result) 181 | 182 | # cancel the other tasks, we have a result. We need to wait for the cancellations 183 | # to propagate. 184 | for task in unfinished: 185 | task.cancel() 186 | if len(unfinished) > 0: 187 | await asyncio.wait(unfinished) 188 | 189 | #if event == sg.TIMEOUT_EVENT: 190 | # continue 191 | #if event == "Exit" or event == sg.WIN_CLOSED: 192 | # break 193 | 194 | 195 | if __name__ == '__main__': 196 | asyncio.run(main()) -------------------------------------------------------------------------------- /builder/pyinstaller/build.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | set projectname=aardwolfgui 3 | 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 4 | set root=%~dp0 5 | set repo=%root%..\..\%projectname% 6 | 7 | IF NOT DEFINED __BUILDALL_VENV__ (GOTO :CREATEVENV) 8 | GOTO :BUILD 9 | 10 | :CREATEVENV 11 | python -m venv %root%\env 12 | CALL %root%\env\Scripts\activate.bat 13 | pip install pyinstaller 14 | GOTO :BUILD 15 | 16 | :BUILD 17 | cd %repo%\..\ &^ 18 | pip install . &^ 19 | cd %repo% &^ 20 | pyinstaller -F aardpclient.py %hiddenimports% &^ 21 | copy dist\aardpclient.exe %root%\ardpclient.exe &^ 22 | GOTO :CLEANUP 23 | 24 | :CLEANUP 25 | IF NOT DEFINED __BUILDALL_VENV__ (deactivate) 26 | cd %root% 27 | EXIT /B -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=61.0.0"] 3 | build-backend = "setuptools.build_meta" -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | from distutils.core import setup 3 | import re 4 | 5 | VERSIONFILE="aardwolfgui/_version.py" 6 | verstrline = open(VERSIONFILE, "rt").read() 7 | VSRE = r"^__version__ = ['\"]([^'\"]*)['\"]" 8 | mo = re.search(VSRE, verstrline, re.M) 9 | if mo: 10 | verstr = mo.group(1) 11 | else: 12 | raise RuntimeError("Unable to find version string in %s." % (VERSIONFILE,)) 13 | 14 | setup( 15 | # Application name: 16 | name="aardwolfgui", 17 | 18 | # Version number (initial): 19 | version=verstr, 20 | 21 | # Application author details: 22 | author="Tamas Jos", 23 | author_email="info@skelsecprojects.com", 24 | 25 | # Packages 26 | packages=find_packages(), 27 | 28 | # Include additional files into the package 29 | include_package_data=True, 30 | 31 | 32 | # Details 33 | url="https://github.com/skelsec/aardwolfgui", 34 | 35 | zip_safe = False, 36 | # 37 | # license="LICENSE.txt", 38 | description="GUI for aardwolf RD/VNC client", 39 | 40 | # long_description=open("README.txt").read(), 41 | python_requires='>=3.7', 42 | 43 | install_requires=[ 44 | 'aardwolf>=0.2.7', 45 | 'pyperclip', 46 | 'Pillow', 47 | 'pyqt6', 48 | #'pyqt6-sip', 49 | ], 50 | 51 | 52 | classifiers=[ 53 | "Programming Language :: Python :: 3.8", 54 | "Operating System :: OS Independent", 55 | ], 56 | entry_points={ 57 | 'console_scripts': [ 58 | 'ardpclient = aardwolfgui.aardpclient:main', 59 | ], 60 | 61 | } 62 | ) 63 | --------------------------------------------------------------------------------