def test_ran_ability_id(self, ability, adversary): op = Operation(name='test', agents=[], adversary=adversary) mock_link = MagicMock(spec=Link, ability=ability(ability_id='123'), finish='2021-01-01 08:00:00') op.chain = [mock_link] assert op.ran_ability_id('123')
class TestPlanningService(TestBase): def setUp(self): self.initialize() self.ability = Ability(ability_id='123', executor='sh', test=BaseWorld.encode_string('mkdir test'), cleanup=BaseWorld.encode_string('rm -rf test')) self.agent = Agent(sleep_min=1, sleep_max=2, watchdog=0) self.operation = Operation(name='test1', agents=self.agent, adversary='hunter') self.run_async(self.data_svc.store(self.ability)) def test_get_cleanup_links(self): self.operation.add_link( Link(operation=self.operation, command='', paw=self.agent.paw, ability=self.ability, status=0)) links = self.run_async( self.planning_svc.get_cleanup_links(operation=self.operation, agent=self.agent)) link_list = list(links) self.assertEqual(len(link_list), 1) self.assertEqual(link_list[0].command, self.ability.cleanup)
def op_for_event_logs(operation_agent, operation_adversary, executor, ability, operation_link, encoded_command): op = Operation(name='test', agents=[operation_agent], adversary=operation_adversary) op.set_start_details() command_1 = 'whoami' command_2 = 'hostname' executor_1 = executor(name='psh', platform='windows', command=command_1) executor_2 = executor(name='psh', platform='windows', command=command_2) ability_1 = ability(ability_id='123', tactic='test tactic', technique_id='T0000', technique_name='test technique', name='test ability', description='test ability desc', executors=[executor_1]) ability_2 = ability(ability_id='456', tactic='test tactic', technique_id='T0000', technique_name='test technique', name='test ability 2', description='test ability 2 desc', executors=[executor_2]) link_1 = operation_link(ability=ability_1, paw=operation_agent.paw, executor=executor_1, command=encoded_command(command_1), status=0, host=operation_agent.host, pid=789, decide=datetime.strptime('2021-01-01 08:00:00', '%Y-%m-%d %H:%M:%S'), collect=datetime.strptime('2021-01-01 08:01:00', '%Y-%m-%d %H:%M:%S'), finish='2021-01-01 08:02:00') link_2 = operation_link(ability=ability_2, paw=operation_agent.paw, executor=executor_2, command=encoded_command(command_2), status=0, host=operation_agent.host, pid=7890, decide=datetime.strptime('2021-01-01 09:00:00', '%Y-%m-%d %H:%M:%S'), collect=datetime.strptime('2021-01-01 09:01:00', '%Y-%m-%d %H:%M:%S'), finish='2021-01-01 09:02:00') discarded_link = operation_link(ability=ability_2, paw=operation_agent.paw, executor=executor_2, command=encoded_command(command_2), status=-2, host=operation_agent.host, pid=7891, decide=datetime.strptime( '2021-01-01 10:00:00', '%Y-%m-%d %H:%M:%S')) op.chain = [link_1, link_2, discarded_link] return op
def test_ran_ability_id(self, ability, adversary): op = Operation(name='test', agents=[], adversary=adversary) mock_link = MagicMock(spec=Link, ability=ability(ability_id='123'), finish=MOCK_LINK_FINISH_TIME) op.chain = [mock_link] assert op.ran_ability_id('123')
def test_no_state_change_event_fired_when_setting_same_state( self, mock_emit_state_change_method, fake_event_svc, adversary): initial_state = 'running' op = Operation(name='test', agents=[], adversary=adversary, state=initial_state) op.state = initial_state mock_emit_state_change_method.assert_not_called()
def test_state_change_event_fired_on_state_change( self, mock_emit_state_change_method, fake_event_svc, adversary): op = Operation(name='test', agents=[], adversary=adversary, state='running') op.state = 'finished' mock_emit_state_change_method.assert_called_with(from_state='running', to_state='finished')
def op_for_event_logs(operation_agent, operation_adversary, executor, ability, operation_link, encoded_command, parse_datestring): op = Operation(name='test', agents=[operation_agent], adversary=operation_adversary) op.set_start_details() command_1 = 'whoami' command_2 = 'hostname' executor_1 = executor(name='psh', platform='windows', command=command_1) executor_2 = executor(name='psh', platform='windows', command=command_2) ability_1 = ability(ability_id='123', tactic='test tactic', technique_id='T0000', technique_name='test technique', name='test ability', description='test ability desc', executors=[executor_1]) ability_2 = ability(ability_id='456', tactic='test tactic', technique_id='T0000', technique_name='test technique', name='test ability 2', description='test ability 2 desc', executors=[executor_2]) link_1 = operation_link(ability=ability_1, paw=operation_agent.paw, executor=executor_1, command=encoded_command(command_1), status=0, host=operation_agent.host, pid=789, decide=parse_datestring(LINK1_DECIDE_TIME), collect=parse_datestring(LINK1_COLLECT_TIME), finish=LINK1_FINISH_TIME) link_2 = operation_link(ability=ability_2, paw=operation_agent.paw, executor=executor_2, command=encoded_command(command_2), status=0, host=operation_agent.host, pid=7890, decide=parse_datestring(LINK2_DECIDE_TIME), collect=parse_datestring(LINK2_COLLECT_TIME), finish=LINK2_FINISH_TIME) discarded_link = operation_link( ability=ability_2, paw=operation_agent.paw, executor=executor_2, command=encoded_command(command_2), status=-2, host=operation_agent.host, pid=7891, decide=parse_datestring('2021-01-01T10:00:00Z')) op.chain = [link_1, link_2, discarded_link] return op
class TestLearningSvc(TestBase): def setUp(self): self.initialize() self.ability = self.run_async( self.data_svc.store( Ability(ability_id='123', tactic='discovery', technique_id='T1033', technique='Find', name='test', test='d2hvYW1pCg==', description='find active user', cleanup='', executor='sh', platform='darwin', payload='wifi.sh', parsers=[], requirements=[], privilege=None))) self.operation = Operation(name='sample', agents=None, adversary=None) self.run_async(self.data_svc.store(self.operation)) def test_learn(self): link = Link(operation=self.operation.id, ability=self.ability, command=None, paw=None) self.operation.add_link(link) self.run_async( self.learning_svc.learn( link=link, blob=BaseWorld.encode_string( 'i contain 1 ip address 192.168.0.1 and one file /etc/host.txt. that is all.' ))) self.assertEqual(2, len(link.facts)) def test_build_relationships(self): self.learning_svc.model.add( frozenset({'host.user.name', 'target.org.name'})) self.learning_svc.model.add( frozenset( {'host.file.extension', 'host.user.name', 'domain.user.name'})) facts = [ Fact(trait='target.org.name', value='something'), Fact(trait='host.user.name', value='admin'), Fact(trait='host.user.name', value='root'), Fact(trait='domain.user.name', value='user'), Fact(trait='not.really.here', value='should never be found') ] link = Link(operation=self.operation.id, ability=self.ability, command=None, paw=None) self.run_async(self.learning_svc._build_relationships(link, facts)) self.assertEqual(4, len(link.relationships))
def setUp(self): self.initialize() self.ability = Ability(ability_id='123', executor='sh', test=BaseWorld.encode_string('mkdir test'), cleanup=BaseWorld.encode_string('rm -rf test')) self.agent = Agent(sleep_min=1, sleep_max=2, watchdog=0) self.operation = Operation(name='test1', agents=self.agent, adversary='hunter') self.run_async(self.data_svc.store(self.ability))
async def create_operation(self, links, source): planner = (await self.get_service('data_svc').locate( 'planners', match=dict(name='sequential')))[0] await self.get_service('data_svc').store(source) self.op = Operation(name=BLUE_OP_NAME, agents=self.agents, adversary=self.adversary, source=source, access=self.Access.BLUE, planner=planner, state='running', auto_close=False, jitter='1/4') self.op.set_start_details() await self.update_operation(links)
def op_without_learning_parser(ability, adversary): op = Operation(id='54321', name='testB', agents=[], adversary=adversary, use_learning_parsers=False) return op
def op_with_learning_parser(ability, adversary): op = Operation(id='12345', name='testA', agents=[], adversary=adversary, use_learning_parsers=True) return op
async def _build_operation_object(self, data): name = data.pop('name') group = data.pop('group') planner = await self.get_service('data_svc').locate( 'planners', match=dict(name=data.pop('planner'))) adversary = await self._construct_adversary_for_op( data.pop('adversary_id')) agents = await self._construct_agents_for_group(group) sources = await self.get_service('data_svc').locate( 'sources', match=dict(name=data.pop('source'))) return Operation(name=name, planner=planner[0], agents=agents, adversary=adversary, group=group, jitter=data.pop('jitter'), source=next(iter(sources), None), state=data.pop('state'), allow_untrusted=int(data.pop('allow_untrusted')), autonomous=int(data.pop('autonomous')), phases_enabled=bool(int(data.pop('phases_enabled'))), obfuscator=data.pop('obfuscator'), max_time=int(data.pop('max_time')), auto_close=bool(int(data.pop('auto_close'))))
async def _build_operation_object(self, access, data): name = data.pop('name') group = data.pop('group', '') planner = await self.get_service('data_svc').locate( 'planners', match=dict(name=data.get('planner', 'atomic'))) adversary = await self._construct_adversary_for_op( data.pop('adversary_id', '')) agents = await self.construct_agents_for_group(group) sources = await self.get_service('data_svc').locate( 'sources', match=dict(name=data.pop('source', 'basic'))) allowed = self._get_allowed_from_access(access) return Operation(name=name, planner=planner[0], agents=agents, adversary=adversary, group=group, jitter=data.pop('jitter', '2/8'), source=next(iter(sources), None), state=data.pop('state', 'running'), autonomous=int(data.pop('autonomous', 1)), access=allowed, obfuscator=data.pop('obfuscator', 'plain-text'), auto_close=bool(int(data.pop('auto_close', 0))), visibility=int(data.pop('visibility', '50')))
async def create_operation(self, links, source): planner = (await self.get_service('data_svc').locate( 'planners', match=dict(name='batch')))[0] await self.get_service('data_svc').store(source) blue_op_name = self.get_config(prop='op_name', name='response') self.op = Operation(name=blue_op_name, agents=self.agents, adversary=self.adversary, source=source, access=self.Access.BLUE, planner=planner, state='running', auto_close=False, jitter='1/4') self.op.set_start_details() await self.update_operation(links)
async def _build_operation_object(self, access, data): name = data.pop('name') group = data.pop('group') planner = await self.get_service('data_svc').locate( 'planners', match=dict(name=data.pop('planner'))) adversary = await self._construct_adversary_for_op( data.pop('adversary_id')) agents = await self.construct_agents_for_group(group) sources = await self.get_service('data_svc').locate( 'sources', match=dict(name=data.pop('source'))) allowed = self.Access.BLUE if self.Access.BLUE in access[ 'access'] else self.Access.RED return Operation(name=name, planner=planner[0], agents=agents, adversary=adversary, group=group, jitter=data.pop('jitter'), source=next(iter(sources), None), state=data.pop('state'), autonomous=int(data.pop('autonomous')), access=allowed, phases_enabled=bool(int(data.pop('phases_enabled'))), obfuscator=data.pop('obfuscator'), auto_close=bool(int(data.pop('auto_close'))), visibility=int(data.pop('visibility')))
async def validate_operation_state(self, data: dict, existing: Operation = None): if not existing: if data.get('state') in Operation.get_finished_states(): raise JsonHttpBadRequest('Cannot create a finished operation.') elif data.get('state') not in Operation.get_states(): raise JsonHttpBadRequest('state must be one of {}'.format( Operation.get_states())) else: if await existing.is_finished(): raise JsonHttpBadRequest( 'This operation has already finished.') elif 'state' in data and data.get( 'state') not in Operation.get_states(): raise JsonHttpBadRequest('state must be one of {}'.format( Operation.get_states()))
def test_emit_state_change_event(self, loop, fake_event_svc, adversary): op = Operation(name='test', agents=[], adversary=adversary, state='running') fake_event_svc.reset() loop.run_until_complete( op._emit_state_change_event( from_state='running', to_state='finished' ) ) expected_key = (Operation.EVENT_EXCHANGE, Operation.EVENT_QUEUE_STATE_CHANGED) assert expected_key in fake_event_svc.fired event_kwargs = fake_event_svc.fired[expected_key] assert event_kwargs['op'] == op.id assert event_kwargs['from_state'] == 'running' assert event_kwargs['to_state'] == 'finished'
def setup_rest_svc_test(loop, data_svc): BaseWorld.apply_config(name='main', config={ 'app.contact.http': '0.0.0.0', 'plugins': ['sandcat', 'stockpile'], 'crypt_salt': 'BLAH', 'api_key': 'ADMIN123', 'encryption_key': 'ADMIN123', 'exfil_dir': '/tmp' }) loop.run_until_complete( data_svc.store( Ability(ability_id='123', test=BaseWorld.encode_string('curl #{app.contact.http}'), variations=[], executor='psh', platform='windows'))) adversary = Adversary(adversary_id='123', name='test', description='test', atomic_ordering=[]) loop.run_until_complete(data_svc.store(adversary)) agent = Agent(paw='123', sleep_min=2, sleep_max=8, watchdog=0, executors=['pwsh', 'psh'], platform='windows') loop.run_until_complete(data_svc.store(agent)) loop.run_until_complete( data_svc.store( Planner(planner_id='123', name='test', module='test', params=dict()))) source = Source(id='123', name='test', facts=[], adjustments=[]) loop.run_until_complete(data_svc.store(source)) loop.run_until_complete( data_svc.store( Operation(name='test', agents=[agent], adversary=adversary, id='123', source=source))) loop.run_until_complete( data_svc.store( Obfuscator( name='plain-text', description= 'Does no obfuscation to any command, instead running it in plain text', module='plugins.stockpile.app.obfuscators.plain_text')))
def test_operation(self): adversary = self.run_async(self.data_svc.store( Adversary(adversary_id='123', name='test', description='test adversary', phases=dict()) )) self.run_async(self.data_svc.store(Operation(name='my first op', agents=[], adversary=adversary))) operations = self.run_async(self.data_svc.locate('operations')) self.assertEqual(1, len(operations)) for x in operations: json.dumps(x.display)
def setUp(self): self.initialize() self.ability = self.run_async( self.data_svc.store( Ability(ability_id='123', tactic='discovery', technique_id='T1033', technique='Find', name='test', test='d2hvYW1pCg==', description='find active user', cleanup='', executor='sh', platform='darwin', payload='wifi.sh', parsers=[], requirements=[], privilege=None))) self.operation = Operation(name='sample', agents=None, adversary=None) self.run_async(self.data_svc.store(self.operation))
async def create_operation(self, source, op_type): planner = (await self.get_service('data_svc').locate('planners', match=dict(name='batch')))[0] await self.get_service('data_svc').store(source) blue_op_name = self.get_config(prop='op_name', name='response') access = self.Access.BLUE if op_type == 'visible' else self.Access.HIDDEN self.ops[op_type] = Operation(name=blue_op_name, agents=self.agents, adversary=self.adversary, source=source, access=access, planner=planner, state='running', auto_close=False, jitter='1/4') obj = await self.get_service('data_svc').locate('objectives', match=dict(name='default')) self.ops[op_type].objective = deepcopy(obj[0]) self.ops[op_type].set_start_details()
async def _create_detection_operation(self, red_op_name, red_op_id, red_op_access): planner = (await self.get_service('data_svc').locate( 'planners', match=dict(name='atomic')))[0] adversary_id = self.get_config(prop='adversary', name='gameboard') adversary = (await self.data_svc.locate( 'adversaries', match=dict(adversary_id=adversary_id)))[0] obj = (await self.data_svc.locate('objectives', match=dict(name='default')))[0] agent = Agent(0, 0, 0, paw='gameboard_detection') access = self.Access.BLUE detection_operation = Operation(name=red_op_name + '_' + str(red_op_id) + self.blue_op_name_modifier, agents=[agent], adversary=adversary, access=access, planner=planner, group='blue') detection_operation.objective = obj detection_operation.set_start_details() return [await self.data_svc.store(detection_operation)]
def test_operation(self, loop, data_svc): adversary = loop.run_until_complete( data_svc.store( Adversary(adversary_id='123', name='test', description='test adversary', phases=dict()))) loop.run_until_complete( data_svc.store( Operation(name='my first op', agents=[], adversary=adversary))) operations = loop.run_until_complete(data_svc.locate('operations')) assert len(operations) == 1 for x in operations: json.dumps(x.display)
def setup_rest_svc_test(loop, data_svc): BaseWorld.apply_config(name='main', config={'app.contact.http': '0.0.0.0', 'plugins': ['sandcat', 'stockpile'], 'crypt_salt': 'BLAH', 'api_key': 'ADMIN123', 'encryption_key': 'ADMIN123', 'exfil_dir': '/tmp'}) loop.run_until_complete(data_svc.store( Ability(ability_id='123', name='testA', executors=[ Executor(name='psh', platform='windows', command='curl #{app.contact.http}') ]) )) loop.run_until_complete(data_svc.store( Ability(ability_id='456', name='testB', executors=[ Executor(name='sh', platform='linux', command='whoami') ]) )) loop.run_until_complete(data_svc.store( Ability(ability_id='789', name='testC', executors=[ Executor(name='sh', platform='linux', command='hostname') ]) )) adversary = Adversary(adversary_id='123', name='test', description='test', atomic_ordering=[]) loop.run_until_complete(data_svc.store(adversary)) agent = Agent(paw='123', sleep_min=2, sleep_max=8, watchdog=0, executors=['pwsh', 'psh'], platform='windows') loop.run_until_complete(data_svc.store(agent)) loop.run_until_complete(data_svc.store( Objective(id='495a9828-cab1-44dd-a0ca-66e58177d8cc', name='default', goals=[Goal()]) )) loop.run_until_complete(data_svc.store( Planner(planner_id='123', name='test', module='test', params=dict()) )) source = Source(id='123', name='test', facts=[], adjustments=[]) loop.run_until_complete(data_svc.store(source)) loop.run_until_complete(data_svc.store( Operation(name='test', agents=[agent], adversary=adversary, id='123', source=source) )) loop.run_until_complete(data_svc.store( Obfuscator(name='plain-text', description='Does no obfuscation to any command, instead running it in plain text', module='plugins.stockpile.app.obfuscators.plain_text') ))
async def _build_operation_object(self, data): name = data.pop('name') planner = await self.get_service('data_svc').locate( 'planners', match=dict(name=data.pop('planner'))) adversary = await self.get_service('data_svc').locate( 'adversaries', match=dict(adversary_id=data.pop('adversary_id'))) agents = await self.get_service('data_svc').locate( 'agents', match=dict(group=data.pop('group'))) sources = await self.get_service('data_svc').locate( 'sources', match=dict(name=data.pop('source'))) return Operation(name=name, planner=planner[0], agents=agents, adversary=adversary[0], jitter=data.pop('jitter'), source=next(iter(sources), None), state=data.pop('state'), allow_untrusted=int(data.pop('allow_untrusted')), autonomous=int(data.pop('autonomous')))
def op_with_learning_and_seeded(ability, adversary, operation_agent, parse_datestring): sc = Source(id='3124', name='test', facts=[Fact(trait='domain.user.name', value='bob')]) op = Operation(id='6789', name='testC', agents=[], adversary=adversary, source=sc, use_learning_parsers=True) # patch operation to make it 'realistic' op.start = parse_datestring(OP_START_TIME) op.adversary = op.adversary() op.planner = Planner(planner_id='12345', name='test_planner', module='not.an.actual.planner', params=None) op.objective = Objective(id='6428', name='not_an_objective') t_operation_agent = operation_agent t_operation_agent.paw = '123456' op.agents = [t_operation_agent] return op
def test_no_state_change_event_on_instantiation( self, mock_emit_state_change_method, fake_event_svc, adversary): Operation(name='test', agents=[], adversary=adversary) mock_emit_state_change_method.assert_not_called()
class ResponseService(BaseService): def __init__(self, services): self.log = self.add_service('response_svc', self) self.data_svc = services.get('data_svc') self.rest_svc = services.get('rest_svc') self.agents = [] self.adversary = None self.abilities = [] self.op = None @template('response.html') async def splash(self, request): abilities = [ a for a in await self.data_svc.locate('abilities') if await a.which_plugin() == 'response' ] adversaries = [ a for a in await self.data_svc.locate('adversaries') if await a.which_plugin() == 'response' ] return dict(abilities=abilities, adversaries=adversaries) @staticmethod async def register_handler(event_svc): await event_svc.observe_event('link/completed', handle_link_completed) async def respond_to_pid(self, pid, agent): available_agents = await self.get_available_agents(agent) if not available_agents: return total_facts = [] total_links = [] for blue_agent in available_agents: agent_facts, agent_links = await self.run_abilities_on_agent( blue_agent, pid) total_facts.extend(agent_facts) total_links.extend(agent_links) await self.save_to_operation(total_facts, total_links) async def get_available_agents(self, agent_to_match): await self.refresh_blue_agents_abilities() available_agents = [ a for a in self.agents if a.host == agent_to_match.host ] if not available_agents: self.log.debug('No available blue agents to respond to red action') return [] return available_agents async def refresh_blue_agents_abilities(self): self.agents = await self.data_svc.locate( 'agents', match=dict(access=self.Access.BLUE)) self.adversary = (await self.data_svc.locate( 'adversaries', match=dict(adversary_id=BLUE_ADVERSARY)))[0] self.abilities = [] for a in self.adversary.atomic_ordering: if a not in self.abilities: self.abilities.append(a) async def run_abilities_on_agent(self, blue_agent, original_pid): facts = [Fact(trait='host.process.id', value=original_pid)] links = [] for ability_id in self.abilities: ability_facts, ability_links = await self.run_ability_on_agent( blue_agent, ability_id, facts, original_pid) links.extend(ability_links) facts.extend(ability_facts) return facts, links async def run_ability_on_agent(self, blue_agent, ability_id, agent_facts, original_pid): links = await self.rest_svc.task_agent_with_ability( blue_agent.paw, ability_id, agent_facts) await self.wait_for_link_completion(links, blue_agent) ability_facts = [] for link in links: link.pin = int(original_pid) unique_facts = link.facts[1:] ability_facts.extend(unique_facts) return ability_facts, links @staticmethod async def wait_for_link_completion(links, agent): for link in links: while not link.finish or link.can_ignore(): await asyncio.sleep(3) if not agent.trusted: break async def create_fact_source(self, facts): source_id = str(uuid.uuid4()) source_name = 'blue-pid-{}'.format(source_id) return Source(id=source_id, name=source_name, facts=facts) async def save_to_operation(self, facts, links): if not self.op or await self.op.is_finished(): source = await self.create_fact_source(facts) await self.create_operation(links=links, source=source) else: await self.update_operation(links) await self.get_service('data_svc').store(self.op) async def create_operation(self, links, source): planner = (await self.get_service('data_svc').locate( 'planners', match=dict(name='sequential')))[0] await self.get_service('data_svc').store(source) self.op = Operation(name=BLUE_OP_NAME, agents=self.agents, adversary=self.adversary, source=source, access=self.Access.BLUE, planner=planner, state='running', auto_close=False, jitter='1/4') self.op.set_start_details() await self.update_operation(links) async def update_operation(self, links): for link in links: link.operation = self.op.id self.op.add_link(link)
def _generate_operation(name, agents, adversary, *args, **kwargs): return Operation(name=name, agents=agent, adversary=adversary, *args, **kwargs)