def main(): parser = argparse.ArgumentParser(description="Follow a concrete trace") parser.add_argument("-f", "--explore_from", help="Value of PC from which to explore symbolically", type=str) parser.add_argument( "-t", "--explore_to", type=str, default=sys.maxsize, help= "Value of PC until which to explore symbolically. (Probably don't want this set)", ) parser.add_argument("--verbose", "-v", action="count", default=0, help="Increase verbosity") parser.add_argument( "cmd", type=str, nargs="+", help= 'Program and arguments. Use "--" to separate script arguments from target arguments', ) args = parser.parse_args(sys.argv[1:]) range = None if args.explore_from: range = (args.explore_from, args.explore_to) # Create a concrete Manticore and record it m1 = Manticore.linux(args.cmd[0], args.cmd[1:]) t = ExtendedTracer() r = TraceReceiver(t) m1.verbosity(args.verbose) m1.register_plugin(t) m1.register_plugin(r) m1.run(procs=1) time.sleep(3) # Create a symbolic Manticore and follow last trace symbolic_args = ["+" * len(arg) for arg in args.cmd[1:]] m2 = Manticore.linux(args.cmd[0], symbolic_args) f = Follower(r.trace) if range: f.add_symbolic_range(*range) m2.verbosity(args.verbose) m2.register_plugin(f) m2.run()
def test_thumb_mode_entrypoint(self): # thumb_mode_entrypoint is a binary with only one instruction # 0x1000: add.w r0, r1, r2 # which is a Thumb instruction, so the entrypoint is set to 0x1001 m = Manticore.linux( os.path.join(os.path.dirname(__file__), "binaries", "thumb_mode_entrypoint")) m.context["success"] = False @m.init def init(m, ready_states): for state in ready_states: state.platform.current.regfile.write("R0", 0) state.platform.current.regfile.write("R1", 0x1234) state.platform.current.regfile.write("R2", 0x5678) @m.hook(0x1001) def pre(state): # If the wrong PC value was used by the loader (0x1001 instead of 0x1000), # the wrong instruction bytes will have been fetched from memory state.abandon() @m.hook(0x1004) def post(state): # If the wrong execution mode was set by the loader, the wrong instruction # will have been executed, so the register value will be incorrect with m.locked_context() as ctx: ctx["success"] = state.cpu.regfile.read("R0") == 0x68AC state.abandon() m.run() self.assertTrue(m.context["success"])
def symbolic_run_get_cons(trace): """ Execute a symbolic run that follows a concrete run; return constraints generated and the stdin data produced """ # mem: has no concurrency support. Manticore should be 'Single' process m2 = Manticore.linux(prog, workspace_url="mem:") f = Follower(trace) set_verbosity(VERBOSITY) m2.register_plugin(f) def on_term_testcase(mm, state, err): with m2.locked_context() as ctx: readdata = [] for name, fd, data in state.platform.syscall_trace: if name in ("_receive", "_read") and fd == 0: readdata.append(data) ctx["readdata"] = readdata ctx["constraints"] = list(state.constraints.constraints) m2.subscribe("will_terminate_state", on_term_testcase) m2.run() constraints = m2.context["constraints"] datas = m2.context["readdata"] return constraints, datas
def symbolic_run_get_cons(trace): ''' Execute a symbolic run that follows a concrete run; return constraints generated and the stdin data produced ''' m2 = Manticore.linux(prog, workspace_url='mem:') f = Follower(trace) m2.verbosity(VERBOSITY) m2.register_plugin(f) def on_term_testcase(mcore, state, stateid, err): with m2.locked_context() as ctx: readdata = [] for name, fd, data in state.platform.syscall_trace: if name in ('_receive', '_read') and fd == 0: readdata.append(data) ctx['readdata'] = readdata ctx['constraints'] = list(state.constraints.constraints) m2.subscribe('will_terminate_state', on_term_testcase) m2.run() constraints = m2.context['constraints'] datas = m2.context['readdata'] return constraints, datas
def test_thumb_mode_entrypoint(self): # thumb_mode_entrypoint is a binary with only one instruction # 0x1000: add.w r0, r1, r2 # which is a Thumb instruction, so the entrypoint is set to 0x1001 m = Manticore.linux( os.path.join(os.path.dirname(__file__), 'binaries', 'thumb_mode_entrypoint')) m.success = False @m.init def init(state): state.cpu.regfile.write('R0', 0) state.cpu.regfile.write('R1', 0x1234) state.cpu.regfile.write('R2', 0x5678) @m.hook(0x1001) def pre(state): # If the wrong PC value was used by the loader (0x1001 instead of 0x1000), # the wrong instruction bytes will have been fetched from memory state.abandon() @m.hook(0x1004) def post(state): # If the wrong execution mode was set by the loader, the wrong instruction # will have been executed, so the register value will be incorrect m.success = state.cpu.regfile.read('R0') == 0x68ac state.abandon() m.run() self.assertTrue(m.success)
def concrete_run_get_trace(inp): m1 = Manticore.linux(prog, concrete_start=inp, workspace_url='mem:') t = ExtendedTracer() r = TraceReceiver(t) m1.verbosity(VERBOSITY) m1.register_plugin(t) m1.register_plugin(r) m1.run(procs=1) return r.trace
def concrete_run_get_trace(inp): consts = config.get_group("core") consts.mprocessing = consts.mprocessing.single m1 = Manticore.linux(prog, concrete_start=inp, workspace_url="mem:") t = ExtendedTracer() # r = TraceReceiver(t) set_verbosity(VERBOSITY) m1.register_plugin(t) # m1.register_plugin(r) m1.run() for st in m1.all_states: return t.get_trace(st)
def test_symbolic_argv_envp(self): dirname = os.path.dirname(__file__) self.m = Manticore.linux(os.path.join(dirname, 'binaries', 'arguments_linux_amd64'), argv=['+'], envp={'TEST': '+'}) for state in self.m.all_states: ptr = state.cpu.read_int(state.cpu.RSP + (8 * 2)) # get argv[1] mem = state.cpu.read_bytes(ptr, 2) self.assertTrue(issymbolic(mem[0])) self.assertEqual(mem[1], b'\0') ptr = state.cpu.read_int(state.cpu.RSP + (8 * 4)) # get envp[0] mem = state.cpu.read_bytes(ptr, 7) self.assertEqual(b''.join(mem[:5]), b'TEST=') self.assertEqual(mem[6], b'\0') self.assertTrue(issymbolic(mem[5]))
def test_symbolic_argv_envp(self) -> None: dirname = os.path.dirname(__file__) self.m = Manticore.linux( os.path.join(dirname, "binaries", "arguments_linux_amd64"), argv=["+"], envp={"TEST": "+"}, ) for state in self.m.all_states: ptr = state.cpu.read_int(state.cpu.RSP + (8 * 2)) # get argv[1] mem = state.cpu.read_bytes(ptr, 2) self.assertTrue(issymbolic(mem[0])) self.assertEqual(mem[1], b"\0") ptr = state.cpu.read_int(state.cpu.RSP + (8 * 4)) # get envp[0] mem = state.cpu.read_bytes(ptr, 7) self.assertEqual(b"".join(mem[:5]), b"TEST=") self.assertEqual(mem[6], b"\0") self.assertTrue(issymbolic(mem[5]))
def main(): parser = argparse.ArgumentParser(prog="sandshrew") # required arg group for help display required = parser.add_argument_group("required arguments") required.add_argument("-t", "--test", dest="test", required=True, help="Target binary for sandshrew analysis") # constraint configuration parser.add_argument( "-c", "--constraint", dest="constraint", required=False, help= "Constraint to apply to symbolic input. Includes ascii, alpha, num, or alphanum", ) # debugging options parser.add_argument( "--debug", dest="debug", action="store_true", required=False, help="If set, turns on debugging output for sandshrew", ) parser.add_argument( "--trace", dest="trace", action="store_true", required=False, help="If set, trace instruction recording will be outputted to logger", ) # other configuration settings parser.add_argument( "--cmpsym", dest="cmp_sym", default="__strcmp_ssse3", required=False, help= "Overrides comparison function used to test for equivalence (default is strcmp)", ) # parse or print help args = parser.parse_args() if args is None: parser.print_help() return 0 # initialize verbosity if args.debug: logging.basicConfig(level=logging.DEBUG) # check binary arch support for x86_64 if not binary_arch(args.test): raise NotImplementedError( "sandshrew only supports x86_64 binary concretization") # initialize Manticore m = Manticore.linux(args.test, ["+" * BUFFER_SIZE]) m.verbosity(2) # initialize mcore context manager m.context["syms"] = binary_symbols(args.test) m.context["exec_flag"] = False m.context["argv1"] = None logging.debug(f"Functions for concretization: {m.context['syms']}") # add record trace hook throughout execution m.context["trace"] = [] # initialize state by checking and storing symbolic argv @m.init def init(m, ready_states): logging.debug(f"Checking for symbolic ARGV") # determine argv[1] from state.input_symbols by label name argv1 = next(sym for sym in next(ready_states).input_symbols if sym.name == "ARGV1") if argv1 is None: raise RuntimeException( "ARGV was not provided and/or made symbolic") # store argv1 in global state with m.locked_context() as context: context["argv1"] = argv1 # store a trace counter, and output if arg was set @m.hook(None) def record(state): pc = state.cpu.PC if args.trace: print(f"{hex(pc)}") with m.locked_context() as context: context["trace"] += [pc] for sym in m.context["syms"]: @m.hook(m.resolve("SANDSHREW_" + sym)) def concrete_checker(state): """ initial checker hook for SANDSHREW_sym that checks for the presence of symbolic input. If so, an unconstrained hook is attached to the memory location to restore symbolic state after concretization """ cpu = state.cpu with m.locked_context() as context: logging.debug( f"Entering target function SANDSHREW_{sym} at {hex(state.cpu.PC)}" ) # check if RSI, the assumed input arg, is symbolic data = cpu.read_int(cpu.RSI) if issymbolic(data): logging.debug( f"Symbolic input parameter to function {sym}() detected" ) # store instruction after `call SANDSHREW_*` return_pc = context["trace"][-1] + 5 # attach a hook to the return_pc, as this is where we will perform concolic execution @m.hook(return_pc) def unconstrain_hook(state): """ unconstrain_hook writes unconstrained symbolic data to the memory location of the output. """ with m.locked_context() as context: # output param is RDI, symbolicate RAX context["return_addr"] = cpu.RAX logging.debug( f"Writing unconstrained buffer to output memory location" ) # initialize unconstrained symbolic input return_buf = state.new_symbolic_buffer(BUFFER_SIZE) # apply charset constraints based on user input for i in range(BUFFER_SIZE): if args.constraint == "alpha": state.constrain( operators.OR( operators.AND( ord("A") <= return_buf[i], return_buf[i] <= ord("Z")), operators.AND( ord("a") <= return_buf[i], return_buf[i] <= ord("z")), )) elif args.constraint == "num": state.constrain( operators.AND( ord("0") <= return_buf[i], return_buf[i] <= ord("9"))) elif args.constraint == "alphanum": raise NotImplementedError( "alphanum constraint set not yet implemented" ) elif args.constraint == "ascii": state.constrain( operators.AND( ord(" ") <= return_buf[i], return_buf[i] <= ord("}"))) # write to address state.cpu.write_bytes(context["return_addr"], return_buf) @m.hook(m.resolve(sym)) def concolic_hook(state): """ hook used in order to concretize the execution of a `call <sym>` instruction """ cpu = state.cpu with m.locked_context() as context: # store `call sym` instruction and ret val instruction call_pc = context["trace"][-1] # we are currently in the function prologue of `sym`. Let's go back to `call sym`. state.cpu.PC = call_pc # use the fallback emulator to concretely execute call instruction. logging.debug( f"Concretely executing `call <{sym}>` at {hex(call_pc)}") state.cpu.decode_instruction(state.cpu.PC) emu = UnicornEmulator(state.cpu) emu.emulate(state.cpu.instruction) logging.debug("Continuing with Manticore symbolic execution") # TODO(alan): resolve ifunc (different archs use different optimized implementations) @m.hook(m.resolve(args.cmp_sym)) def cmp_model(state): """ used in order to invoke Manticore function model for strcmp and/or other comparison operation calls. While a developer can write a test case using a crypto library's built in constant-time comparison operation, it is preferable to use strcmp(). """ logging.debug("Invoking model for comparsion call") state.invoke_model(strcmp) @m.hook(m.resolve("abort")) def fail_state(state): """ hook attached at fail state signified by abort call, which indicates that an edge case input is provided and the abort() call is made """ logging.debug("Entering edge case path") # solve for the symbolic argv input with m.locked_context() as context: solution = state.solve_one(context["return_addr"], BUFFER_SIZE) print(f"Solution found: {solution}") # write solution to individual test case to workspace rand_str = lambda n: "".join( [random.choice(string.ascii_lowercase) for i in range(n)]) with open(m.workspace + "/" + "sandshrew_" + rand_str(4), "w") as fd: fd.write(str(solution)) m.terminate() # run manticore m.run() print( f"Total instructions: {len(m.context['trace'])}\nLast instruction: {hex(m.context['trace'][-1])}" ) return 0
def setUp(self): self.m = Manticore.linux(self.BIN_PATH)