Trace following mode (#513)
* WIP New Policy class * WIP pubsub * Update Signal tests * small fixes from github comments * Fix event decode_instruction signature * Good merge * Good good merge * WIP manticore refactor * Fix default old-style initial state * add -> enqueue * @m.init * Fix workspace url * Some test skipped * Ad Fixme to platform specific stuff in State * add -> enqueue * Enqueue created state * Fix m.init Use a messy hack to adhere to the spec (callback func receive 1 state argument) * Add _coverage_file ivar to Manticore * Fix symbolic files * remove extra enqueue * Fixing __main__ * comments * Experimental plugin system * tests fixed * Fix plugins * Some reporting moved to plugin * Fix assertions test * Add published events to classes that publish them * Update how we verify callbacks * Update Eventful._publish * Yet another flavor for event name checking * really it's a bunch of minimal bugfixes * Remove get_all_event_names from Plugin * Update where we get all events * Use new metaclass-based event registry * Initial concrete trace follower * Add extended (json) trace support * More stubs for condition gather * Update trace saving to new format * Produce trace regardless of contents * Record register deltas in trace * Move initialization to _start_run so we can call run() multiple times * Re-fix multiple workspace bug * Fix it correctly * Add extended trace and accessors * make sure did_execute_instruction is always published * Produce a consistent version * move extended tracing to separate class * Reorg of extended tracing plugins * Add concolic tracing script * Clean up old hooks * Sync memory as well; cleanups * Revert binary tests * simplify concolic follower * Move trace follower to plugin.py * Simplify follower * Add skip ranges to Follower * Update concolic.py * Remove redundant state init * Clean up driver script * Update header line * Move trace follower * Move Follower to follow_trace.py * rm unused import * Remove unnecessary property * rm ConcreteTraceFollower * Revert start_run behavior
This commit is contained in:
parent
88b213de7e
commit
3c7d92bfcd
@ -1,12 +1,32 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from capstone import CS_GRP_JUMP
|
||||||
|
|
||||||
from ..utils.helpers import issymbolic
|
from ..utils.helpers import issymbolic
|
||||||
from ..utils.event import Eventful
|
|
||||||
logger = logging.getLogger('MANTICORE')
|
logger = logging.getLogger('MANTICORE')
|
||||||
|
|
||||||
|
|
||||||
class Plugin(object):
|
class Plugin(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.manticore = None
|
self.manticore = None
|
||||||
|
self.last_reg_state = {}
|
||||||
|
|
||||||
|
def _dict_diff(d1, d2):
|
||||||
|
'''
|
||||||
|
Produce a dict that includes all the keys in d2 that represent different values in d1, as well as values that
|
||||||
|
aren't in d1.
|
||||||
|
|
||||||
|
:param dict d1: First dict
|
||||||
|
:param dict d2: Dict to compare with
|
||||||
|
:rtype: dict
|
||||||
|
'''
|
||||||
|
d = {}
|
||||||
|
for key in set(d1).intersection(set(d2)):
|
||||||
|
if d2[key] != d1[key]:
|
||||||
|
d[key] = d2[key]
|
||||||
|
for key in set(d2).difference(set(d1)):
|
||||||
|
d[key] = d2[key]
|
||||||
|
return d
|
||||||
|
|
||||||
class Tracer(Plugin):
|
class Tracer(Plugin):
|
||||||
def will_start_run_callback(self, state):
|
def will_start_run_callback(self, state):
|
||||||
@ -15,6 +35,69 @@ class Tracer(Plugin):
|
|||||||
def did_execute_instruction_callback(self, state, pc, target_pc, instruction):
|
def did_execute_instruction_callback(self, state, pc, target_pc, instruction):
|
||||||
state.context['trace'].append(pc)
|
state.context['trace'].append(pc)
|
||||||
|
|
||||||
|
class ExtendedTracer(Plugin):
|
||||||
|
def __init__(self):
|
||||||
|
'''
|
||||||
|
Record a detailed execution trace
|
||||||
|
'''
|
||||||
|
super(ExtendedTracer, self).__init__()
|
||||||
|
self.last_dict = {}
|
||||||
|
self.current_pc = None
|
||||||
|
self.context_key = 'e_trace'
|
||||||
|
|
||||||
|
def will_start_run_callback(self, state):
|
||||||
|
state.context[self.context_key] = []
|
||||||
|
|
||||||
|
def register_state_to_dict(self, cpu):
|
||||||
|
d = {}
|
||||||
|
for reg in cpu.canonical_registers:
|
||||||
|
val = cpu.read_register(reg)
|
||||||
|
d[reg] = val if not issymbolic(val) else '<sym>'
|
||||||
|
return d
|
||||||
|
|
||||||
|
def will_execute_instruction_callback(self, state, pc, instruction):
|
||||||
|
self.current_pc = pc
|
||||||
|
|
||||||
|
def did_execute_instruction_callback(self, state, pc, target_pc, instruction):
|
||||||
|
reg_state = self.register_state_to_dict(state.cpu)
|
||||||
|
entry = {
|
||||||
|
'type': 'regs',
|
||||||
|
'values': _dict_diff(self.last_dict, reg_state)
|
||||||
|
}
|
||||||
|
self.last_dict = reg_state
|
||||||
|
state.context[self.context_key].append(entry)
|
||||||
|
|
||||||
|
def will_read_memory_callback(self, state, where, size):
|
||||||
|
if self.current_pc == where:
|
||||||
|
return
|
||||||
|
|
||||||
|
#print 'will_read_memory %x %r, current_pc %x'%(where, size, self.current_pc)
|
||||||
|
|
||||||
|
def did_read_memory_callback(self, state, where, value, size):
|
||||||
|
if self.current_pc == where:
|
||||||
|
return
|
||||||
|
|
||||||
|
#print 'did_read_memory %x %r %r, current_pc %x'%(where, value, size, self.current_pc)
|
||||||
|
|
||||||
|
def will_write_memory_callback(self, state, where, value, size):
|
||||||
|
if self.current_pc == where:
|
||||||
|
return
|
||||||
|
|
||||||
|
#print 'will_write_memory %x %r %r, current_pc %x'%(where, value, size, self.current_pc)
|
||||||
|
|
||||||
|
def did_write_memory_callback(self, state, where, value, size):
|
||||||
|
if self.current_pc == where:
|
||||||
|
raise Exception
|
||||||
|
return
|
||||||
|
|
||||||
|
entry = {
|
||||||
|
'type': 'mem_write',
|
||||||
|
'where': where,
|
||||||
|
'value': value,
|
||||||
|
'size': size
|
||||||
|
}
|
||||||
|
state.context[self.context_key].append(entry)
|
||||||
|
|
||||||
class RecordSymbolicBranches(Plugin):
|
class RecordSymbolicBranches(Plugin):
|
||||||
def will_start_run_callback(self, state):
|
def will_start_run_callback(self, state):
|
||||||
state.context['branches'] = {}
|
state.context['branches'] = {}
|
||||||
@ -91,7 +174,6 @@ class Visited(Plugin):
|
|||||||
logger.info('Coverage: %d different instructions executed', len(executor_visited))
|
logger.info('Coverage: %d different instructions executed', len(executor_visited))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#TODO document all callbacks
|
#TODO document all callbacks
|
||||||
class ExamplePlugin(Plugin):
|
class ExamplePlugin(Plugin):
|
||||||
def will_decode_instruction_callback(self, state, pc):
|
def will_decode_instruction_callback(self, state, pc):
|
||||||
|
|||||||
@ -517,8 +517,8 @@ class ManticoreOutput(object):
|
|||||||
with self._named_stream('trace') as f:
|
with self._named_stream('trace') as f:
|
||||||
if 'trace' not in state.context:
|
if 'trace' not in state.context:
|
||||||
return
|
return
|
||||||
for pc in state.context['trace']:
|
for entry in state.context['trace']:
|
||||||
f.write('0x{:08x}\n'.format(pc))
|
f.write('0x{:x}'.format(entry))
|
||||||
|
|
||||||
def save_constraints(self, state):
|
def save_constraints(self, state):
|
||||||
# XXX(yan): We want to conditionally enable this check
|
# XXX(yan): We want to conditionally enable this check
|
||||||
|
|||||||
@ -181,6 +181,7 @@ class Manticore(Eventful):
|
|||||||
#Move the folowing into a plugin
|
#Move the folowing into a plugin
|
||||||
self._assertions = {}
|
self._assertions = {}
|
||||||
self._coverage_file = None
|
self._coverage_file = None
|
||||||
|
self.trace = None
|
||||||
|
|
||||||
#FIXME move the folowing to aplugin
|
#FIXME move the folowing to aplugin
|
||||||
self.subscribe('will_generate_testcase', self._generate_testcase_callback)
|
self.subscribe('will_generate_testcase', self._generate_testcase_callback)
|
||||||
|
|||||||
@ -1238,7 +1238,7 @@ class Linux(Platform):
|
|||||||
def pad(s):
|
def pad(s):
|
||||||
return s +'\x00'*(65-len(s))
|
return s +'\x00'*(65-len(s))
|
||||||
|
|
||||||
now = datetime.now().strftime("%a %b %d %H:%M:%S ART %Y")
|
now = datetime(2017, 8, 01).strftime("%a %b %d %H:%M:%S ART %Y")
|
||||||
info = (('sysname', 'Linux'),
|
info = (('sysname', 'Linux'),
|
||||||
('nodename', 'ubuntu'),
|
('nodename', 'ubuntu'),
|
||||||
('release', '4.4.0-77-generic'),
|
('release', '4.4.0-77-generic'),
|
||||||
|
|||||||
118
scripts/follow_trace.py
Executable file
118
scripts/follow_trace.py
Executable file
@ -0,0 +1,118 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
'''
|
||||||
|
A simple trace following execution driver script. Only supports passing symbolic arguments via argv.
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import argparse
|
||||||
|
import itertools
|
||||||
|
|
||||||
|
from manticore import Manticore, issymbolic
|
||||||
|
from manticore.core.plugin import ExtendedTracer, Plugin
|
||||||
|
|
||||||
|
def _partition(pred, iterable):
|
||||||
|
t1, t2 = itertools.tee(iterable)
|
||||||
|
return (list(itertools.ifilterfalse(pred, t1)), filter(pred, t2))
|
||||||
|
|
||||||
|
|
||||||
|
class TraceReceiver(Plugin):
|
||||||
|
def __init__(self, tracer):
|
||||||
|
self._trace = None
|
||||||
|
self._tracer = tracer
|
||||||
|
super(self.__class__, self).__init__()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def trace(self):
|
||||||
|
return self._trace
|
||||||
|
|
||||||
|
def will_generate_testcase_callback(self, state, test_id, msg):
|
||||||
|
self._trace = state.context[self._tracer.context_key]
|
||||||
|
|
||||||
|
instructions, writes = _partition(lambda x: x['type'] == 'regs', self._trace)
|
||||||
|
total = len(self._trace)
|
||||||
|
print 'Recorded concrete trace: {}/{} instructions, {}/{} writes'.format(
|
||||||
|
len(instructions), total, len(writes), total)
|
||||||
|
|
||||||
|
|
||||||
|
class Follower(Plugin):
|
||||||
|
def __init__(self, trace):
|
||||||
|
self.index = 0
|
||||||
|
self.trace = trace
|
||||||
|
self.last_instruction = None
|
||||||
|
self.symbolic_ranges = []
|
||||||
|
self.active = True
|
||||||
|
super(self.__class__, self).__init__()
|
||||||
|
|
||||||
|
def add_symbolic_range(self, pc_start, pc_end):
|
||||||
|
self.symbolic_ranges.append((pc_start,pc_end))
|
||||||
|
|
||||||
|
def get_next(self, type):
|
||||||
|
event = self.trace[self.index]
|
||||||
|
assert event['type'] == type
|
||||||
|
self.index += 1
|
||||||
|
return event
|
||||||
|
|
||||||
|
def did_write_memory_callback(self, state, where, value, size):
|
||||||
|
if not self.active:
|
||||||
|
return
|
||||||
|
write = self.get_next('mem_write')
|
||||||
|
|
||||||
|
if not issymbolic(value):
|
||||||
|
return
|
||||||
|
|
||||||
|
assert write['where'] == where and write['size'] == size
|
||||||
|
state.constrain(value == write['value'])
|
||||||
|
|
||||||
|
def did_execute_instruction_callback(self, state, last_pc, pc, insn):
|
||||||
|
if not self.active:
|
||||||
|
return
|
||||||
|
event = self.get_next('regs')
|
||||||
|
self.last_instruction = event['values']
|
||||||
|
if issymbolic(pc):
|
||||||
|
state.constrain(state.cpu.RIP == self.last_instruction['RIP'])
|
||||||
|
else:
|
||||||
|
for start, stop in self.symbolic_ranges:
|
||||||
|
if start <= pc <= stop:
|
||||||
|
self.active = False
|
||||||
|
|
||||||
|
|
||||||
|
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.maxint,
|
||||||
|
help="Value of PC until which to explore symbolically. (Probably don't want this set)")
|
||||||
|
parser.add_argument('--verbose', '-v', action='count', 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()
|
||||||
|
|
||||||
|
if __name__=='__main__':
|
||||||
|
main()
|
||||||
Loading…
x
Reference in New Issue
Block a user