def test_transitive_closure_of_a_node_on_a_graph_with_loops_should_still_terminate( self): dep_graph = DepGraph() # A -> B, B -> C, C -> D, D -> A A = _a_mock_definition() B = _a_mock_definition() C = _a_mock_definition() D = _a_mock_definition() uses = [ (A, B), (B, C), (C, D), (D, A), ] for use in uses: dep_graph.add_edge(*use) result = dep_graph.transitive_closure(C) result_nodes = set(result.nodes) result_edges = set(result.edges) self.assertSetEqual(result_nodes, {A, B, C, D}) self.assertSetEqual(result_edges, {(A, B), (B, C), (C, D), (D, A)})
def test_delegate_nodes_to_the_underlying_graph_object(self): with mock.patch.object(networkx.DiGraph, 'nodes') as digraph_nodes_mock: dep_graph = DepGraph() dep_graph.nodes() digraph_nodes_mock.assert_called_once()
def test_dep_graph(self): project = angr.Project(_binary_path('true'), auto_load_libs=False) cfg = project.analyses.CFGFast() main = cfg.functions['main'] # build a def-use graph for main() of /bin/true without tmps. # check that the only dependency of the first block's # guard is the four cc registers rda = project.analyses.ReachingDefinitions(subject=main, track_tmps=False, dep_graph=DepGraph()) guard_use = list( filter( lambda def_: type(def_.atom) is GuardUse and def_.codeloc. block_addr == main.addr, rda.dep_graph._graph.nodes()))[0] preds = list(rda.dep_graph._graph.pred[guard_use]) self.assertEqual(len(preds), 1) self.assertIsInstance(preds[0].atom, Register) self.assertEqual( preds[0].atom.reg_offset, project.arch.registers['rdi'][0], ) # build a def-use graph for main() of /bin/true. check that t7 in the first block is only used by the guard rda = project.analyses.ReachingDefinitions(subject=main, track_tmps=True, dep_graph=DepGraph()) tmp_7 = list( filter( lambda def_: type(def_.atom) is Tmp and def_.atom.tmp_idx == 7 and def_.codeloc.block_addr == main.addr, rda.dep_graph._graph.nodes()))[0] self.assertEqual(len(rda.dep_graph._graph.succ[tmp_7]), 1) self.assertEqual(type(list(rda.dep_graph._graph.succ[tmp_7])[0].atom), GuardUse)
def test_delegate_add_node_to_the_underlying_graph_object( digraph_add_node_mock): definition = _a_mock_definition() dep_graph = DepGraph() dep_graph.add_node(definition) digraph_add_node_mock.assert_called_once_with(definition)
def test_delegate_add_edge_to_the_underlying_graph_object(digraph_add_edge_mock): use = (_a_mock_definition(), _a_mock_definition()) labels = { 'attribute1': 'value1', 'attribute2': 'value2' } dep_graph = DepGraph() dep_graph.add_edge(*use, **labels) digraph_add_edge_mock.assert_called_once_with(*use, **labels)
def test_delegate_add_node_to_the_underlying_graph_object(self): with mock.patch.object(networkx.DiGraph, 'add_node') as digraph_add_node_mock: definition = _a_mock_definition() dep_graph = DepGraph() dep_graph.add_node(definition) digraph_add_node_mock.assert_called_once_with(definition)
def test_delegate_predecessors_to_the_underlying_graph_object(self): with mock.patch.object(networkx.DiGraph, 'predecessors') as digraph_predecessors_mock: definition = _a_mock_definition() dep_graph = DepGraph() dep_graph.predecessors(definition) digraph_predecessors_mock.assert_called_once_with(definition)
def test_delegate_add_edge_to_the_underlying_graph_object(self): with mock.patch.object(networkx.DiGraph, 'add_edge') as digraph_add_edge_mock: use = (_a_mock_definition(), _a_mock_definition()) labels = {'attribute1': 'value1', 'attribute2': 'value2'} dep_graph = DepGraph() dep_graph.add_edge(*use, **labels) digraph_add_edge_mock.assert_called_once_with(*use, **labels)
def test_transitive_closure_of_a_node_should_copy_labels_from_original_graph(): dep_graph = DepGraph() # A -> B A = _a_mock_definition() B = _a_mock_definition() uses = [(A, B)] for use in uses: dep_graph.add_edge(*use, label='some data') result = dep_graph.transitive_closure(B).get_edge_data(A, B)['label'] nose.tools.assert_equals(result, 'some data')
def test_add_dependencies_for_concrete_pointers_of_fails_if_the_given_definition_is_not_in_the_graph( self): dependency_graph = DepGraph() definition = Definition(Register(0, 4), CodeLocation(0x42, 0), DataSet(UNDEFINED, 4)) with self.assertRaises(AssertionError) as cm: dependency_graph.add_dependencies_for_concrete_pointers_of( definition, None, None) ex = cm.exception self.assertEqual( str(ex), 'The given Definition must be present in the given graph.')
def test_contains_atom_returns_false_if_the_dependency_graph_does_not_contain_a_definition_of_the_given_atom( self): dep_graph = DepGraph() # A -> B A = _a_mock_definition() B = _a_mock_definition() uses = [(A, B)] for use in uses: dep_graph.add_edge(*use) result = dep_graph.contains_atom(Register(8, 4)) self.assertFalse(result)
def test_contains_atom_returns_true_if_the_dependency_graph_contains_a_definition_of_the_given_atom( self): dep_graph = DepGraph() r0 = Register(8, 4) # A -> B A = _a_mock_definition(r0) B = _a_mock_definition() uses = [(A, B)] for use in uses: dep_graph.add_edge(*use) result = dep_graph.contains_atom(r0) self.assertTrue(result)
def test_add_dependencies_for_concrete_pointers_of_adds_a_definition_for_data_pointed_to_by_given_definition( self): arch = self.ArchMock() loader = self.LoaderMock(self.MainObjectMock(self.SectionMock(True))) memory_datum = self.MemoryDataMock(self.memory_address, str.encode(self.string_in_memory), len(self.string_in_memory), 'string') cfg = self.CFGMock({self.memory_address: memory_datum}) register_definition = Definition( Register(0, 4), None, ) dependency_graph = DepGraph() dependency_graph.add_node(register_definition) dependency_graph.add_dependencies_for_concrete_pointers_of( [claripy.BVV(self.memory_address, arch.bits)], register_definition, cfg, loader) memory_definition = Definition( MemoryLocation(self.memory_address, self.string_in_memory_length), ExternalCodeLocation(), ) nodes = list(dependency_graph.nodes()) predecessors = list( dependency_graph.graph.predecessors(register_definition)) self.assertEqual(nodes, [register_definition, memory_definition]) self.assertListEqual(predecessors, [memory_definition])
def test_add_dependencies_for_concrete_pointers_of_create_memory_location_with_undefined_data_if_data_pointed_to_by_definition_is_not_known( self): arch = self.ArchMock() cfg = self.CFGMock({}) loader = self.LoaderMock(self.MainObjectMock(self.SectionMock(True))) datum_content = None datum_size = 0x4242 memory_datum = self.MemoryDataMock(self.memory_address, datum_content, datum_size, 'unknown') cfg = self.CFGMock({self.memory_address: memory_datum}) memory_definition = Definition( MemoryLocation(self.memory_address, datum_size), ExternalCodeLocation(), DataSet(UNDEFINED, datum_size * 8)) register_definition = Definition( Register(0, 4), CodeLocation(0x42, 0), DataSet(self.memory_address, arch.bits)) dependency_graph = DepGraph() dependency_graph.add_node(register_definition) dependency_graph.add_dependencies_for_concrete_pointers_of( register_definition, cfg, loader) nodes = list(dependency_graph.nodes()) predecessors = list( dependency_graph.graph.predecessors(register_definition)) self.assertEqual(nodes, [register_definition, memory_definition]) self.assertListEqual(predecessors, [memory_definition])
def _dependencies(self, subject, sink_atoms: List[Tuple['Atom',SimType]], kb, project, max_depth: int, excluded_funtions: Set[int]) -> Generator[Tuple[int,int,'ReachingDefinitionsAnalysis'], None, None]: Handler = handler_factory([ StdioHandlers, StdlibHandlers, StringHandlers, ]) if isinstance(subject, Function): sink = subject else: raise TypeError('Unsupported type of subject %s.' % type(subject)) # peek into the callgraph and discover all functions reaching the sink within N layers of calls, which is determined # by the depth parameter queue: List[Tuple[CallTrace, int]] = [(CallTrace(sink.addr), 0)] starts: Set[CallTrace] = set() encountered: Set[int] = set(excluded_funtions) while queue: trace, curr_depth = queue.pop(0) if trace.current_function_address() in starts: continue caller_func_addr = trace.current_function_address() callers: Set[int] = set(kb.functions.callgraph.predecessors(caller_func_addr)) # remove the functions that we already came across - essentially bypassing recursive function calls callers = set(addr for addr in callers if addr not in encountered) caller_depth = curr_depth + 1 if caller_depth >= max_depth: # reached the depth limit. add them to potential analysis starts starts |= set(map(lambda caller_addr: trace.step_back(caller_addr, None, caller_func_addr), callers)) else: # add them to the queue for item in map(lambda caller_addr: (trace.step_back(caller_addr, None, caller_func_addr), caller_depth), callers ): queue.append(item) encountered |= callers l.info("Discovered %d function starts at call-depth %d for sink %r.", len(starts), max_depth, sink ) for idx, start in enumerate(starts): handler = Handler(project, False, sink_function=sink, sink_atoms=sink_atoms) rda = project.analyses.ReachingDefinitions( subject=CallTraceSubject(start, kb.functions[start.current_function_address()]), observe_all=True, function_handler=handler, kb=kb, dep_graph=DepGraph() ) yield idx, len(starts), rda
def test_top_predecessors(): dep_graph = DepGraph() # A -> B, B -> D, C -> D A = _a_mock_definition() B = _a_mock_definition() C = _a_mock_definition() D = _a_mock_definition() uses = [ (A, B), (B, D), (C, D), ] for use in uses: dep_graph.add_edge(*use) result = dep_graph.top_predecessors(D) nose.tools.assert_list_equal(result, [A, C])
def test_add_dependencies_for_concrete_pointers_of_does_nothing_if_data_pointed_to_by_definition_is_already_in_dependency_graph( self): arch = self.ArchMock() loader = self.LoaderMock(self.MainObjectMock(self.SectionMock(True))) memory_datum = self.MemoryDataMock(self.memory_address, str.encode(self.string_in_memory), len(self.string_in_memory), 'string') cfg = self.CFGMock({self.memory_address: memory_datum}) memory_location_definition = Definition( MemoryLocation(self.memory_address, self.string_in_memory_length), CodeLocation(0, 0), ) register_definition = Definition( Register(0, 4), CodeLocation(0x42, 0), ) dependency_graph = DepGraph( networkx.DiGraph([(memory_location_definition, register_definition) ])) nodes_before_call = dependency_graph.nodes() dependency_graph.add_dependencies_for_concrete_pointers_of( [claripy.BVV(self.memory_address, arch.bits)], register_definition, cfg, loader) self.assertEqual(nodes_before_call, dependency_graph.nodes())
def test_add_dependencies_for_concrete_pointers_of_adds_a_definition_with_codelocation_in_binary_if_data_in_readonly_memory( self): arch = self.ArchMock() writable = False loader = self.LoaderMock( self.MainObjectMock(self.SectionMock(writable))) memory_datum = self.MemoryDataMock(self.memory_address, str.encode(self.string_in_memory), len(self.string_in_memory), 'string') cfg = self.CFGMock({self.memory_address: memory_datum}) register_definition = Definition( Register(0, 4), CodeLocation(0x42, 0), ) dependency_graph = DepGraph() dependency_graph.add_node(register_definition) dependency_graph.add_dependencies_for_concrete_pointers_of( [claripy.BVV(self.memory_address, arch.bits)], register_definition, cfg, loader, ) origin_codelocation = CodeLocation(0, 0, info={'readonly': True}) predecessor = list( dependency_graph.graph.predecessors(register_definition))[0] self.assertEqual(predecessor.codeloc, origin_codelocation)
def test_transitive_closure_of_a_node(self): dep_graph = DepGraph() # A -> B, B -> D, C -> D A = _a_mock_definition() B = _a_mock_definition() C = _a_mock_definition() D = _a_mock_definition() uses = [ (A, B), (B, D), (C, D), ] for use in uses: dep_graph.add_edge(*use) result = dep_graph.transitive_closure(D) result_nodes = set(result.nodes) result_edges = set(result.edges) self.assertSetEqual(result_nodes, {D, B, C, A}) self.assertSetEqual(result_edges, {(B, D), (C, D), (A, B)})
def test_add_dependencies_for_concrete_pointers_of_does_nothing_if_pointer_is_not_concrete( self): arch = self.ArchMock() cfg = self.CFGMock({}) loader = self.LoaderMock(self.MainObjectMock(self.SectionMock(True))) register_definition = Definition(Register(0, 4), CodeLocation(0x42, 0), DataSet(UNDEFINED, arch.bits)) dependency_graph = DepGraph() dependency_graph.add_node(register_definition) nodes_before_call = dependency_graph.nodes() dependency_graph.add_dependencies_for_concrete_pointers_of( register_definition, cfg, loader) self.assertEqual(nodes_before_call, dependency_graph.nodes())
def test_dep_graph_stack_variables(self): bin_path = _binary_path('fauxware') project = angr.Project(bin_path, auto_load_libs=False) arch = project.arch cfg = project.analyses.CFGFast() main = cfg.functions['authenticate'] rda = project.analyses.ReachingDefinitions(subject=main, track_tmps=False, dep_graph=DepGraph()) dep_graph = rda.dep_graph open_rdi = next( iter( filter( lambda def_: isinstance(def_.atom, Register) and def_.atom. reg_offset == arch.registers['rdi'][ 0] and def_.codeloc.ins_addr == 0x4006a2, dep_graph._graph.nodes()))) # 4006A2 mov rdi, rax preds = list(dep_graph._graph.predecessors(open_rdi)) self.assertEqual(len(preds), 1) rax: Definition = preds[0] self.assertIsInstance(rax.atom, Register) self.assertEqual(rax.atom.reg_offset, arch.registers['rax'][0]) self.assertEqual(rax.codeloc.ins_addr, 0x400699) # 400699 mov rax, [rbp+file] preds = list(dep_graph._graph.predecessors(rax)) self.assertEqual(len(preds), 1) file_var: Definition = preds[0] self.assertIsInstance(file_var.atom, MemoryLocation) self.assertIsInstance(file_var.atom.addr, SpOffset) self.assertEqual(file_var.atom.addr.offset, -32) self.assertEqual(file_var.codeloc.ins_addr, 0x40066c) # 40066C mov [rbp+file], rdi preds = list(dep_graph._graph.predecessors(file_var)) self.assertEqual(len(preds), 1) rdi: Definition = preds[0] self.assertIsInstance(rdi.atom, Register) self.assertEqual(rdi.atom.reg_offset, arch.registers['rdi'][0]) self.assertIsInstance(rdi.codeloc, ExternalCodeLocation)
def test_dep_graph_stack_variables(): bin_path = os.path.join(TESTS_LOCATION, 'x86_64', 'fauxware') project = angr.Project(bin_path, auto_load_libs=False) arch = project.arch cfg = project.analyses.CFGFast() main = cfg.functions['authenticate'] rda = project.analyses.ReachingDefinitions(subject=main, track_tmps=False, dep_graph=DepGraph()) dep_graph = rda.dep_graph open_rdi = next( iter( filter( lambda def_: isinstance(def_.atom, Register) and def_.atom. reg_offset == arch.registers['rdi'][ 0] and def_.codeloc.ins_addr == 0x4006a2, dep_graph._graph.nodes()))) # 4006A2 mov rdi, rax preds = list(dep_graph._graph.predecessors(open_rdi)) assert len(preds) == 1 rax: Definition = preds[0] assert isinstance(rax.atom, Register) assert rax.atom.reg_offset == arch.registers['rax'][0] assert rax.codeloc.ins_addr == 0x400699 # 400699 mov rax, [rbp+file] preds = list(dep_graph._graph.predecessors(rax)) assert len(preds) == 1 file_var: Definition = preds[0] assert isinstance(file_var.atom, MemoryLocation) assert isinstance(file_var.atom.addr, SpOffset) assert file_var.atom.addr.offset == -32 assert file_var.codeloc.ins_addr == 0x40066c # 40066C mov [rbp+file], rdi preds = list(dep_graph._graph.predecessors(file_var)) assert len(preds) == 1 rdi: Definition = preds[0] assert isinstance(rdi.atom, Register) assert rdi.atom.reg_offset == arch.registers['rdi'][0] assert isinstance(rdi.codeloc, ExternalCodeLocation)
def test_transitive_closure_includes_beginning_node_with_memoized_content( self): dep_graph = DepGraph() # A -> B # B -> C # C -> D A = _a_mock_definition() B = _a_mock_definition() C = _a_mock_definition() D = _a_mock_definition() uses = [(A, B), (B, C), (C, D)] for use in uses: dep_graph.add_edge(*use) closure_0 = dep_graph.transitive_closure(C) self.assertNotIn(D, closure_0) closure_1 = dep_graph.transitive_closure(D) self.assertIn(D, closure_1) self.assertTrue(closure_1.has_edge(A, B)) self.assertTrue(closure_1.has_edge(B, C)) self.assertTrue(closure_1.has_edge(C, D))
def test_refuses_to_add_edge_between_non_definition_nodes(self): dep_graph = DepGraph() self.assertRaises(TypeError, dep_graph.add_edge, 1, 2)
def test_dep_graph_has_a_default_graph(): dep_graph = DepGraph() nose.tools.assert_equal(isinstance(dep_graph.graph, networkx.DiGraph), True)
def test_refuses_to_add_edge_between_non_definition_nodes(): dep_graph = DepGraph() nose.tools.assert_raises(TypeError, dep_graph.add_edge, 1, 2)
def test_dep_graph(): project = angr.Project(os.path.join(TESTS_LOCATION, 'x86_64', 'true'), auto_load_libs=False) cfg = project.analyses.CFGFast() main = cfg.functions['main'] # build a def-use graph for main() of /bin/true without tmps. check that the only dependency of the first block's guard is the four cc registers rda = project.analyses.ReachingDefinitions(subject=main, track_tmps=False, dep_graph=DepGraph()) guard_use = list(filter( lambda def_: type(def_.atom) is GuardUse and def_.codeloc.block_addr == main.addr, rda.dep_graph._graph.nodes() ))[0] nose.tools.assert_equal( len(rda.dep_graph._graph.pred[guard_use]), 4 ) nose.tools.assert_equal( all(type(def_.atom) is Register for def_ in rda.dep_graph._graph.pred[guard_use]), True ) nose.tools.assert_equal( set(def_.atom.reg_offset for def_ in rda.dep_graph._graph.pred[guard_use]), {reg.vex_offset for reg in project.arch.register_list if reg.name.startswith('cc_')} ) # build a def-use graph for main() of /bin/true. check that t7 in the first block is only used by the guard rda = project.analyses.ReachingDefinitions(subject=main, track_tmps=True, dep_graph=DepGraph()) tmp_7 = list(filter( lambda def_: type(def_.atom) is Tmp and def_.atom.tmp_idx == 7 and def_.codeloc.block_addr == main.addr, rda.dep_graph._graph.nodes() ))[0] nose.tools.assert_equal( len(rda.dep_graph._graph.succ[tmp_7]), 1 ) nose.tools.assert_equal( type(list(rda.dep_graph._graph.succ[tmp_7])[0].atom), GuardUse )
def test_dep_graph_has_a_default_graph(self): dep_graph = DepGraph() self.assertEqual(isinstance(dep_graph.graph, networkx.DiGraph), True)