Merged PR 504: Command and Control Full PR
## Summary Implements the Command and Control applications to the quality and capability needed for the TAP001 expansion and lays the foundations for all the features required for TAP002 (Next Release). The C2C suite contains three new applications: **1. Abstract C2** Base class for the C2 Server and the C2 Beacon. Controls the main internal logic of both applications but with a couple of abstract methods which each class defines differently. **2. C2 Server** The C2 Server takes red agent actions and converts the action options into C2 Commands which are then passed to the C2 Beacon. The output of these commands is sent back to the C2 Server and then returned back to the red agent. **3. C2 Beacon** The C2 beacon uses the Terminal and the Ransomware Applications to perform different commands which it receives these commands and executes them and returns the output. The C2 beacon can also be configured by the Red Agent to configure the current networking behaviour. For a much more detailed description please refer to the .rst documentation and the notebook which demonstrate and describe the functionality very explicitly. Lastly the wiki page also provides more information around the design work for this feature. [Command and Control](/Welcome-to-PrimAITE!/Design/[~In-Progress~]/Command-and-Control) Worth noting that some changes were needed that were unseen during the design page but the overall goals of the feature have been accomplished. ## Test process Tested via notebooks and a series of e2e tests. ## Checklist - [x] PR is linked to a **work item** - [x] **acceptance criteria** of linked ticket are met - [x] performed **self-review** of the code - [x] written **tests** for any new functionality added with this PR - [x] updated the **documentation** if this PR changes or adds functionality - [x] written/updated **design docs** if this PR implements new functionality - [x] updated the **change log** - [x] ran **pre-commit** checks for code style - [x] attended to any **TO-DOs** left in the code (One remaining but unsure if it should be handled in this PR) Related work items: #2689, #2720, #2721, #2779
This commit is contained in:
@@ -0,0 +1,203 @@
|
||||
# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK
|
||||
from ipaddress import IPv4Address
|
||||
from typing import Tuple
|
||||
|
||||
import pytest
|
||||
|
||||
from primaite.game.agent.interface import ProxyAgent
|
||||
from primaite.game.game import PrimaiteGame
|
||||
from primaite.simulator.file_system.file_system_item_abc import FileSystemItemHealthStatus
|
||||
from primaite.simulator.network.hardware.base import UserManager
|
||||
from primaite.simulator.network.hardware.nodes.host.computer import Computer
|
||||
from primaite.simulator.network.hardware.nodes.host.server import Server
|
||||
from primaite.simulator.network.hardware.nodes.network.router import ACLAction
|
||||
from primaite.simulator.network.transmission.transport_layer import Port
|
||||
from primaite.simulator.system.applications.red_applications.c2.c2_beacon import C2Beacon
|
||||
from primaite.simulator.system.applications.red_applications.c2.c2_server import C2Command, C2Server
|
||||
from primaite.simulator.system.services.database.database_service import DatabaseService
|
||||
from primaite.simulator.system.services.ftp.ftp_client import FTPClient
|
||||
from primaite.simulator.system.services.ftp.ftp_server import FTPServer
|
||||
from primaite.simulator.system.services.service import ServiceOperatingState
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def game_and_agent_fixture(game_and_agent):
|
||||
"""Create a game with a simple agent that can be controlled by the tests."""
|
||||
game, agent = game_and_agent
|
||||
|
||||
router = game.simulation.network.get_node_by_hostname("router")
|
||||
router.acl.add_rule(action=ACLAction.PERMIT, src_port=Port.HTTP, dst_port=Port.HTTP, position=4)
|
||||
router.acl.add_rule(action=ACLAction.PERMIT, src_port=Port.DNS, dst_port=Port.DNS, position=5)
|
||||
router.acl.add_rule(action=ACLAction.PERMIT, src_port=Port.FTP, dst_port=Port.FTP, position=6)
|
||||
|
||||
c2_server_host = game.simulation.network.get_node_by_hostname("client_1")
|
||||
c2_server_host.software_manager.install(software_class=C2Server)
|
||||
c2_server: C2Server = c2_server_host.software_manager.software["C2Server"]
|
||||
c2_server.run()
|
||||
|
||||
return (game, agent)
|
||||
|
||||
|
||||
def test_c2_beacon_default(game_and_agent_fixture: Tuple[PrimaiteGame, ProxyAgent]):
|
||||
"""Tests that a Red Agent can install, configure and establish a C2 Beacon (default params)."""
|
||||
game, agent = game_and_agent_fixture
|
||||
|
||||
# Installing C2 Beacon on Server_1
|
||||
server_1: Server = game.simulation.network.get_node_by_hostname("server_1")
|
||||
|
||||
action = (
|
||||
"NODE_APPLICATION_INSTALL",
|
||||
{"node_id": 1, "application_name": "C2Beacon"},
|
||||
)
|
||||
agent.store_action(action)
|
||||
game.step()
|
||||
assert agent.history[-1].response.status == "success"
|
||||
|
||||
action = (
|
||||
"CONFIGURE_C2_BEACON",
|
||||
{
|
||||
"node_id": 1,
|
||||
"config": {
|
||||
"c2_server_ip_address": "10.0.1.2",
|
||||
"keep_alive_frequency": 5,
|
||||
"masquerade_protocol": "TCP",
|
||||
"masquerade_port": "HTTP",
|
||||
},
|
||||
},
|
||||
)
|
||||
agent.store_action(action)
|
||||
game.step()
|
||||
assert agent.history[-1].response.status == "success"
|
||||
|
||||
action = (
|
||||
"NODE_APPLICATION_EXECUTE",
|
||||
{"node_id": 1, "application_id": 0},
|
||||
)
|
||||
agent.store_action(action)
|
||||
game.step()
|
||||
assert agent.history[-1].response.status == "success"
|
||||
|
||||
# Asserting that we've confirmed our connection
|
||||
c2_beacon: C2Beacon = server_1.software_manager.software["C2Beacon"]
|
||||
assert c2_beacon.c2_connection_active == True
|
||||
|
||||
|
||||
def test_c2_server_ransomware(game_and_agent_fixture: Tuple[PrimaiteGame, ProxyAgent]):
|
||||
"""Tests that a Red Agent can install a RansomwareScript, Configure and launch all via C2 Server actions."""
|
||||
game, agent = game_and_agent_fixture
|
||||
|
||||
# Installing a C2 Beacon on server_1
|
||||
server_1: Server = game.simulation.network.get_node_by_hostname("server_1")
|
||||
server_1.software_manager.install(C2Beacon)
|
||||
|
||||
# Installing a database on Server_2 for the ransomware to attack
|
||||
server_2: Server = game.simulation.network.get_node_by_hostname("server_2")
|
||||
server_2.software_manager.install(DatabaseService)
|
||||
server_2.software_manager.software["DatabaseService"].start()
|
||||
# Configuring the C2 to connect to client 1 (C2 Server)
|
||||
c2_beacon: C2Beacon = server_1.software_manager.software["C2Beacon"]
|
||||
c2_beacon.configure(c2_server_ip_address=IPv4Address("10.0.1.2"))
|
||||
c2_beacon.establish()
|
||||
assert c2_beacon.c2_connection_active == True
|
||||
|
||||
# C2 Action 1: Installing the RansomwareScript & Database client via Terminal
|
||||
|
||||
action = (
|
||||
"C2_SERVER_TERMINAL_COMMAND",
|
||||
{
|
||||
"node_id": 0,
|
||||
"ip_address": None,
|
||||
"account": {
|
||||
"username": "admin",
|
||||
"password": "admin",
|
||||
},
|
||||
"commands": [
|
||||
["software_manager", "application", "install", "RansomwareScript"],
|
||||
["software_manager", "application", "install", "DatabaseClient"],
|
||||
],
|
||||
},
|
||||
)
|
||||
agent.store_action(action)
|
||||
game.step()
|
||||
assert agent.history[-1].response.status == "success"
|
||||
|
||||
action = (
|
||||
"C2_SERVER_RANSOMWARE_CONFIGURE",
|
||||
{
|
||||
"node_id": 0,
|
||||
"config": {"server_ip_address": "10.0.2.3", "payload": "ENCRYPT"},
|
||||
},
|
||||
)
|
||||
agent.store_action(action)
|
||||
game.step()
|
||||
assert agent.history[-1].response.status == "success"
|
||||
|
||||
# Stepping a few timesteps to allow for the RansowmareScript to finish installing.
|
||||
|
||||
action = ("DONOTHING", {})
|
||||
agent.store_action(action)
|
||||
game.step()
|
||||
game.step()
|
||||
game.step()
|
||||
|
||||
action = (
|
||||
"C2_SERVER_RANSOMWARE_LAUNCH",
|
||||
{
|
||||
"node_id": 0,
|
||||
},
|
||||
)
|
||||
agent.store_action(action)
|
||||
game.step()
|
||||
assert agent.history[-1].response.status == "success"
|
||||
|
||||
database_file = server_2.software_manager.file_system.get_file("database", "database.db")
|
||||
assert database_file.health_status == FileSystemItemHealthStatus.CORRUPT
|
||||
|
||||
|
||||
def test_c2_server_data_exfiltration(game_and_agent_fixture: Tuple[PrimaiteGame, ProxyAgent]):
|
||||
"""Tests that a Red Agent can extract a database.db file via C2 Server actions."""
|
||||
game, agent = game_and_agent_fixture
|
||||
|
||||
# Installing a C2 Beacon on server_1
|
||||
server_1: Server = game.simulation.network.get_node_by_hostname("server_1")
|
||||
server_1.software_manager.install(C2Beacon)
|
||||
|
||||
# Installing a database on Server_2 (creates a database.db file.)
|
||||
server_2: Server = game.simulation.network.get_node_by_hostname("server_2")
|
||||
server_2.software_manager.install(DatabaseService)
|
||||
server_2.software_manager.software["DatabaseService"].start()
|
||||
|
||||
# Configuring the C2 to connect to client 1 (C2 Server)
|
||||
c2_beacon: C2Beacon = server_1.software_manager.software["C2Beacon"]
|
||||
c2_beacon.configure(c2_server_ip_address=IPv4Address("10.0.1.2"))
|
||||
c2_beacon.establish()
|
||||
assert c2_beacon.c2_connection_active == True
|
||||
|
||||
# Selecting a target file to steal: database.db
|
||||
# Server 2 ip : 10.0.2.3
|
||||
database_file = server_2.software_manager.file_system.get_file(folder_name="database", file_name="database.db")
|
||||
assert database_file is not None
|
||||
|
||||
# C2 Action: Data exfiltrate.
|
||||
|
||||
action = (
|
||||
"C2_SERVER_DATA_EXFILTRATE",
|
||||
{
|
||||
"node_id": 0,
|
||||
"target_file_name": "database.db",
|
||||
"target_folder_name": "database",
|
||||
"exfiltration_folder_name": "spoils",
|
||||
"target_ip_address": "10.0.2.3",
|
||||
"account": {
|
||||
"username": "admin",
|
||||
"password": "admin",
|
||||
},
|
||||
},
|
||||
)
|
||||
agent.store_action(action)
|
||||
game.step()
|
||||
|
||||
assert server_1.file_system.access_file(folder_name="spoils", file_name="database.db")
|
||||
|
||||
client_1 = game.simulation.network.get_node_by_hostname("client_1")
|
||||
assert client_1.file_system.access_file(folder_name="spoils", file_name="database.db")
|
||||
Reference in New Issue
Block a user