def test_save_discover_seeded_fact_not_in_command(self, event_loop, ability, executor, operation, knowledge_svc): test_executor = executor(name='psh', platform='windows') test_ability = ability(ability_id='123', executors=[test_executor]) fact1 = Fact(trait='remote.host.fqdn', value='dc') fact2 = Fact(trait='domain.user.name', value='Bob') relationship = Relationship(source=fact1, edge='has_user', target=fact2) link = Link(command='net user', paw='123456', ability=test_ability, id='111111', executor=test_executor) operation = operation(name='test-op', agents=[], adversary=Adversary(name='sample', adversary_id='XYZ', atomic_ordering=[], description='test'), source=Source(id='test-source', facts=[fact1, fact2])) event_loop.run_until_complete(operation._init_source()) event_loop.run_until_complete( link.save_fact(operation, fact2, 1, relationship)) assert fact2.origin_type == OriginType.SEEDED assert '123456' in fact2.collected_by
def test_link_knowledge_svc_synchronization(self, loop, executor, ability, knowledge_svc): test_executor = executor(name='psh', platform='windows') test_ability = ability(ability_id='123', executors=[test_executor]) fact = Fact(trait='remote.host.fqdn', value='dc') fact2 = Fact(trait='domain.user.name', value='Bob') relationship = Relationship(source=fact, edge='has_admin', target=fact2) test_link = Link(command='echo "this was a triumph"', paw='123456', ability=test_ability, id=111111, executor=test_executor) loop.run_until_complete( test_link._create_relationships([relationship], None)) checkable = [(x.trait, x.value) for x in test_link.facts] assert (fact.trait, fact.value) in checkable assert (fact2.trait, fact2.value) in checkable knowledge_base_f = loop.run_until_complete( knowledge_svc.get_facts(dict(source=test_link.id))) assert len(knowledge_base_f) == 2 assert test_link.id in knowledge_base_f[0].links knowledge_base_r = loop.run_until_complete( knowledge_svc.get_relationships(dict(edge='has_admin'))) assert len(knowledge_base_r) == 1
def test_no_status_change_event_fired_when_setting_same_status( self, mock_emit_status_change_method, ability, executor): executor = executor('psh', 'windows') ability = ability(executor=executor) link = Link(command='net user a', paw='123456', ability=ability, executor=executor, status=-3) link.status = link.status mock_emit_status_change_method.assert_not_called()
def test_status_change_event_fired_on_status_change( self, mock_emit_status_change_method, ability, executor): executor = executor('psh', 'windows') ability = ability(executor=executor) link = Link(command='net user a', paw='123456', ability=ability, executor=executor, status=-3) link.status = -5 mock_emit_status_change_method.assert_called_with(from_status=-3, to_status=-5)
async def _generate_cleanup_links(self, operation, agent, link_status): links = [] for link in [l for l in operation.chain if l.paw == agent.paw]: ability = (await self.get_service('data_svc').locate('abilities', match=dict(unique=link.ability.unique)))[0] for cleanup in ability.cleanup: decoded_cmd = agent.replace(cleanup) variant, _, _ = await self._build_single_test_variant(decoded_cmd, link.used, link.ability.executor) lnk = Link(operation=operation.id, command=self.encode_string(variant), paw=agent.paw, cleanup=1, ability=ability, score=0, jitter=2, status=link_status) if lnk.command not in [l.command for l in links]: lnk.apply_id(agent.host) links.append(lnk) return links
async def test_trim_links(self, setup_planning_test, planning_svc): """ This test covers both remove_links_with_unset_variables and remove_links_missing_requirements. It uses a fact set that causes add_test_variants to create three links. One of which is the original that has not been populated with facts, this one gets pruned off by remove_links_with_unset_variables. Of the remaining two links that are populated, one is pruned off by a requirement that requires that the character 0 is in the link's command. The tests show that only one link is returned by trim_links and that the returned link is the one that is populated and adheres to the requirement. """ ability, agent, operation, _ = setup_planning_test link = Link.load(dict(command=BaseWorld.encode_string(test_string), paw=agent.paw, ability=ability, executor=next(ability.executors), status=0)) facts = [ Fact(trait='1_2_3', value='0'), Fact(trait='1_2_3', value='4'), Fact(trait='a.b.c', value='1'), Fact(trait='a.b.d', value='2'), Fact(trait='a.b.e', value='3'), ] operation.all_facts = async_wrapper(facts) operation.planner = MagicMock() planning_svc.load_module = async_wrapper(RequirementFake()) link.ability.requirements = [Requirement('fake_requirement', [{'fake': 'relationship'}])] trimmed_links = await planning_svc.trim_links(operation, [link], agent) assert len(trimmed_links) == 1 assert BaseWorld.decode_bytes(trimmed_links[0].display['command']) == target_string
async def create_potential_link(self, operation_id: str, data: dict, access: BaseWorld.Access): self.validate_link_data(data) operation = await self.get_operation_object(operation_id, access) agent = await self.get_agent(operation, data) if data['executor']['name'] not in agent.executors: raise JsonHttpBadRequest( f'Agent {agent.paw} missing specified executor') encoded_command = self._encode_string( agent.replace(self._encode_string(data['executor']['command']), file_svc=self.services['file_svc'])) executor = self.build_executor(data=data.pop('executor', {}), agent=agent) ability = self.build_ability(data=data.pop('ability', {}), executor=executor) link = Link.load( dict(command=encoded_command, paw=agent.paw, ability=ability, executor=executor, status=operation.link_status(), score=data.get('score', 0), jitter=data.get('jitter', 0), cleanup=data.get('cleanup', 0), pin=data.get('pin', 0), host=agent.host, deadman=data.get('deadman', False), used=data.get('used', []), relationships=data.get('relationships', []))) link.apply_id(agent.host) await operation.apply(link) return link.display
def setUp(self): # for those that are curious -- when abilities are created, commands are b64 encoded # by default. self.command = 'whoami' self.encoded_command = b64encode( self.command.strip().encode('utf-8')).decode() self.dummy_ability = Ability(ability_id=None, tactic=None, technique_id=None, technique=None, name=None, test=None, description=None, cleanup=None, executor='sh', platform=None, payload=None, variations=[], parsers=None, requirements=None, privilege=None) self.dummy_agent = Agent(paw='123', platform='linux', executors=['sh'], server='http://localhost:8888', sleep_min=0, sleep_max=0, watchdog=0) self.dummy_link = Link(id='abc', operation='123', command=self.encoded_command, paw='123', ability=self.dummy_ability)
async def _generate_new_links(self, operation, agent, abilities, link_status): """Generate links with given status :param operation: Operation to generate links on :type operation: Operation :param agent: Agent to generate links on :type agent: Agent :param agent: Abilities to generate links for :type agent: list(Ability) :param link_status: Link status, referencing link state dict :type link_status: int :return: Links for agent :rtype: list(Link) """ links = [] for ability in await agent.capabilities(abilities): executor = await agent.get_preferred_executor(ability) if not executor: continue if executor.HOOKS and executor.language and executor.language in executor.HOOKS: await executor.HOOKS[executor.language](ability, executor) if executor.command: link = Link.load( dict(command=self.encode_string(executor.test), paw=agent.paw, score=0, ability=ability, executor=executor, status=link_status, jitter=self.jitter(operation.jitter))) links.append(link) return links
def test_filter_bs(self, loop, setup_planning_test, planning_svc): _, agent, operation, ability = setup_planning_test link = Link.load( dict(command=BaseWorld.encode_string(test_string), paw=agent.paw, ability=ability, executor=next(ability.executors), status=0)) f0 = Fact(trait='1_2_3', value='0') f1 = Fact(trait='a.b.c', value='1') f2 = Fact(trait='a.b.d', value='2') f3 = Fact(trait='a.b.e', value='3') f4 = Fact(trait='a.b.e', value='4') f5 = Fact(trait='a.b.e', value='5') f6 = Fact(trait='a.b.e', value='6') gen = loop.run_until_complete( planning_svc.add_test_variants([link], agent, facts=[f0, f1, f2, f3, f4, f5, f6])) assert len(gen) == 4 assert BaseWorld.decode_bytes( gen[1].display['command']) == target_string
async def test_trait_with_multiple_variations_of_parts( self, setup_planning_test, planning_svc): _, agent, operation, ability = setup_planning_test encoded_command = BaseWorld.encode_string('#{a} #{a.b} #{a.b.c}') link = Link.load( dict(command=encoded_command, paw=agent.paw, ability=ability, executor=next(ability.executors), status=0)) input_facts = [ Fact(trait='a', value='1'), Fact(trait='a.b', value='2'), Fact(trait='a.b.c', value='3'), Fact(trait='server', value='5') ] new_links = await planning_svc.add_test_variants([link], agent, facts=input_facts) assert len(new_links) == 2 found_commands = set(x.command for x in new_links) assert len(found_commands) == 2 # the original and the replaced assert encoded_command in found_commands assert BaseWorld.encode_string('1 2 3') in found_commands
def test_get_links(self, loop, setup_planning_test, planning_svc, data_svc): # PART A: Don't fill in facts for "cability" so only "tability" # is returned in "links" tability, agent, operation, cability = setup_planning_test operation.adversary.atomic_ordering = ["123", "321"] links = loop.run_until_complete(planning_svc.get_links (operation=operation, buckets=None, agent=agent)) assert links[0].ability.ability_id == tability.ability_id # PART B: Fill in facts to allow "cability" to be returned in "links" # in addition to "tability" operation.add_link(Link.load( dict(command='', paw=agent.paw, ability=tability, status=0))) operation.chain[0].facts.append(Fact(trait='1_2_3', value='0')) operation.chain[0].facts.append(Fact(trait='a.b.c', value='1')) operation.chain[0].facts.append(Fact(trait='a.b.d', value='2')) operation.chain[0].facts.append(Fact(trait='a.b.e', value='3')) links = loop.run_until_complete(planning_svc.get_links (operation=operation, buckets=None, agent=agent)) assert links[0].ability.ability_id == cability.ability_id assert links[1].ability.ability_id == tability.ability_id assert base64.b64decode(links[0].command).decode('utf-8') == target_string
async def _generate_cleanup_links(self, operation, agent, link_status): """Generate cleanup links with given status :param operation: Operation to generate cleanup links for :type operation: Operation :param agent: Agent to generate cleanup links for :type agent: Agent :param link_status: Link status, referencing link state dict :type link_status: int :return: Cleanup links for agent :rtype: list(Link) """ links = [] for link in [l for l in operation.chain if l.paw == agent.paw]: matched_abilities = await self.get_service('data_svc').locate('abilities', match=dict(unique=link.ability.unique)) if matched_abilities: ability = matched_abilities[0] for cleanup in ability.cleanup: decoded_cmd = agent.replace(cleanup, file_svc=self.get_service('file_svc')) variant, _, _ = await self._build_single_test_variant(decoded_cmd, link.used, link.ability.executor) lnk = Link.load(dict(command=self.encode_string(variant), paw=agent.paw, cleanup=1, ability=ability, score=0, jitter=2, status=link_status)) if lnk.command not in [l.command for l in links]: lnk.apply_id(agent.host) links.append(lnk) return links
def test_wait_for_links_and_monitor(self, loop, planning_svc, fact, setup_planning_test): # PART A: ability, agent, operation, _ = setup_planning_test # Add a link to operation.chain operation.add_link(Link.load( dict(command='', paw=agent.paw, ability=ability, status=0))) # Set id to match planner.operation.chain[0].id operation.chain[0].id = "123" planner = PlannerFake(operation) # Create a list containing only the id used above link_ids = ["123"] # Make sure program doesn't hang in wait_for_links_completion() planner.operation.chain[0].finish = True assert loop.run_until_complete(planning_svc.wait_for_links_and_monitor( planner, operation, link_ids, condition_stop=True)) is False # PART B: # Make sure program hangs in wait_for_links_completion() planner.operation.chain[0].finish = False timeout = False try: loop.run_until_complete(asyncio.wait_for( planning_svc.wait_for_links_and_monitor(planner, operation, link_ids, condition_stop=True), timeout=5.0)) except asyncio.TimeoutError: timeout = True assert timeout is True
async def add_manual_command(self, access, data): for parameter in ['operation', 'agent', 'executor', 'command']: if parameter not in data.keys(): return dict(error='Missing parameter: %s' % parameter) operation_search = {'id': data['operation'], **access} operation = next(iter(await self.get_service('data_svc').locate('operations', match=operation_search)), None) if not operation: return dict(error='Operation not found') agent_search = {'paw': data['agent'], **access} agent = next(iter(await self.get_service('data_svc').locate('agents', match=agent_search)), None) if not agent: return dict(error='Agent not found') if data['executor'] not in agent.executors: return dict(error='Agent missing specified executor') encoded_command = self.encode_string(data['command']) ability_id = str(uuid.uuid4()) ability = Ability(ability_id=ability_id, tactic='auto-generated', technique_id='auto-generated', technique='auto-generated', name='Manual Command', description='Manual command ability', cleanup='', test=encoded_command, executor=data['executor'], platform=agent.platform, payloads=[], parsers=[], requirements=[], privilege=None, variations=[]) link = Link.load(dict(command=encoded_command, paw=agent.paw, cleanup=0, ability=ability, score=0, jitter=2, status=operation.link_status())) link.apply_id(agent.host) operation.add_link(link) return dict(link=link.unique)
async def test_global_variables_not_replaced_with_facts( self, setup_planning_test, planning_svc): _, agent, operation, ability = setup_planning_test encoded_command = BaseWorld.encode_string( '#{server} #{origin_link_id}') link = Link.load( dict(command=encoded_command, paw=agent.paw, ability=ability, executor=next(ability.executors), status=0)) input_facts = [ Fact(trait='server', value='bad.server'), Fact(trait='origin_link_id', value='bad.origin_link_id') ] planning_svc.add_global_variable_owner(Agent) # handles #{server} planning_svc.add_global_variable_owner( Link) # handles #{origin_link_id} new_links = await planning_svc.add_test_variants([link], agent, facts=input_facts) assert len(new_links) == 1 assert new_links[0].raw_command == f'{agent.server} {link.id}'
def _generate_link(operation, command, paw, ability, *args, **kwargs): return Link(operation=operation, ability=ability, command=command, paw=paw, *args, **kwargs)
def test_link_agent_reported_time_not_present_when_none_roundtrip( self, ability, executor): test_executor = executor(name='psh', platform='windows') test_ability = ability(ability_id='123') test_link = Link( command= 'sc.exe \\dc create sandsvc binpath= "s4ndc4t.exe -originLinkID 111111"', paw='123456', ability=test_ability, executor=test_executor, id=111111) serialized_link = test_link.display loaded_link = Link.load(serialized_link) assert 'agent_reported_time' not in serialized_link assert loaded_link.agent_reported_time is None
async def _generate_new_links(self, operation, agent, abilities, link_status): """Generate links with given status :param operation: Operation to generate links on :type operation: Operation :param agent: Agent to generate links on :type agent: Agent :param agent: Abilities to generate links for :type agent: list(Ability) :param link_status: Link status, referencing link state dict :type link_status: int :return: Links for agent :rtype: list(Link) """ links = [] for a in await agent.capabilities(abilities): if a.code and a.HOOKS: await a.HOOKS[a.language](a) if a.test: links.append( Link.load( dict(command=a.test, paw=agent.paw, score=0, ability=a, status=link_status, jitter=self.jitter(operation.jitter)))) return links
async def _generate_new_links(self, operation, agent, abilities, link_status): links = [] for a in await agent.capabilities(abilities): links.append( Link(operation=operation.id, command=a.test, paw=agent.paw, score=0, ability=a, status=link_status, jitter=self.jitter(operation.jitter)) ) return links
def _create_and_setup_verification_link(self, ability, link_pid): link = Link(command=None, paw='gameboard_detection', ability=ability, executor=next(ability.executors)) fact = Fact(trait='host.process.id', value=link_pid) link.used.append(fact) link.facts.append(fact) link.command = self.encode_string( 'The link with the PID shown below was successfully detected.') link.collect = datetime.now() link.finish = self.data_svc.get_current_timestamp() link.output = True link.status = 0 link.id = link.generate_number() link.pin = link_pid return link
async def test_get_cleanup_links(self, setup_planning_test, planning_svc): ability, agent, operation, _ = setup_planning_test executor = next(ability.executors) operation.add_link(Link.load(dict(command='', paw=agent.paw, ability=ability, executor=executor, status=0))) links = await planning_svc.get_cleanup_links(operation=operation, agent=agent) link_list = list(links) assert len(link_list) == 1 assert BaseWorld.decode_bytes(link_list[0].command) == executor.cleanup[0]
def _generate_link(command, paw, ability, executor, *args, **kwargs): return Link.load( dict(ability=ability, executor=executor, command=command, paw=paw, *args, **kwargs))
async def handle_potential_links(self, request): data = dict(await request.json()) options = dict( PUT=dict(func=lambda d: self.rest_svc.apply_potential_link( Link.from_json(d))), POST=dict(func=lambda d: self.rest_svc.get_potential_links(**d))) resp = await options[request.method]['func'](data) return web.json_response(resp)
def test_get_cleanup_links(self, loop, setup_planning_test, planning_svc): ability, agent, operation, _ = setup_planning_test operation.add_link(Link.load(dict(command='', paw=agent.paw, ability=ability, status=0))) links = loop.run_until_complete( planning_svc.get_cleanup_links(operation=operation, agent=agent) ) link_list = list(links) assert len(link_list) == 1 assert link_list[0].command == ability.cleanup[0]
async def test_remove_links_does_not_ignore_global_variables(self, planning_svc, ability, executor): cmd = 'a -b --foo=#{server} --bar=#{origin_link_id}' links = [Link(command=BaseWorld.encode_string(cmd), paw='1', ability=ability(), executor=executor())] planning_svc.add_global_variable_owner(Agent) # handles #{server} planning_svc.add_global_variable_owner(Link) # handles #{origin_link_id} await planning_svc.remove_links_with_unset_variables(links) assert len(links) == 0
def test_emit_status_change_event(self, loop, fake_event_svc, ability): link = Link(command='net user a', paw='123456', ability=ability(), status=-3) fake_event_svc.reset() loop.run_until_complete( link._emit_status_change_event( from_status=-3, to_status=-5 ) ) expected_key = (Link.EVENT_EXCHANGE, Link.EVENT_QUEUE_STATUS_CHANGED) assert expected_key in fake_event_svc.fired event_kwargs = fake_event_svc.fired[expected_key] assert event_kwargs['link'] == link.id assert event_kwargs['from_status'] == -3 assert event_kwargs['to_status'] == -5
def test_no_status_change_event_on_instantiation( self, mock_emit_status_change_method, ability, executor): executor = executor('psh', 'windows') ability = ability(executor=executor) Link(command='net user a', paw='123456', ability=ability, executor=executor) mock_emit_status_change_method.assert_not_called()
async def task(self, abilities, obfuscator, facts=(), deadman=False): bps = BasePlanningService() potential_links = [Link.load(dict(command=i.test, paw=self.paw, ability=i, deadman=deadman)) for i in await self.capabilities(abilities)] links = [] for valid in await bps.remove_links_missing_facts( await bps.add_test_variants(links=potential_links, agent=self, facts=facts)): links.append(valid) links = await bps.obfuscate_commands(self, obfuscator, links) self.links.extend(links) return links
def test_link_agent_reported_time_present_when_set_roundtrip( self, ability, executor): test_executor = executor(name='psh', platform='windows') test_ability = ability(ability_id='123') test_link = Link( command= 'sc.exe \\dc create sandsvc binpath= "s4ndc4t.exe -originLinkID 111111"', paw='123456', ability=test_ability, executor=test_executor, id=111111, agent_reported_time=BaseService.get_timestamp_from_string( '2021-02-23 11:50:16')) serialized_link = test_link.display loaded_link = Link.load(serialized_link) assert serialized_link['agent_reported_time'] == '2021-02-23 11:50:16' assert loaded_link.agent_reported_time == BaseService.get_timestamp_from_string( '2021-02-23 11:50:16')