Got Manticore (with some minor unpublished fixes) working for McTest, based on Yan's initial work. Most of the angr code directly ports over, which is convenient. Haven't yet implemented McTest_Log (which isn't working for angr, curiously).

This commit is contained in:
Peter Goodman 2017-10-29 20:35:50 -04:00
parent 0ec7047b0f
commit 4b786adc70
3 changed files with 170 additions and 73 deletions

View File

@ -14,37 +14,80 @@
# limitations under the License.
import argparse
import collections
import logging
import manticore
import multiprocessing
import sys
from manticore.core.state import TerminateState
from manticore.utils.helpers import issymbolic
L = logging.getLogger("mctest")
L.setLevel(logging.INFO)
class EntryPointPlugin(manticore.core.plugin.Plugin):
"""Interpose on system calls. When we come across McTest's special system
call that is invoked at the beginnning of McTest_Run, then we stop execution
there and take over."""
def on_syscall_callback(self, state, index):
if 0x41414141 == index:
print 'here!!!!!'
state_id = self.manticore._executor._workspace.save_state(state)
self.manticore._executor.put(state_id)
raise manticore.TerminateState("Canceled", testcase=False)
def read_c_string(state, ea):
"""Read a concrete NUL-terminated string from `ea`."""
return state.cpu.read_string(ea)
def read_uintptr_t(state, ea):
"""Read a uint64_t value from memory."""
next_ea = ea + (state.cpu.address_bit_size // 8)
val = state.cpu.read_int(ea)
"""Read a uintptr_t value from memory."""
addr_size_bits = state.cpu.address_bit_size
next_ea = ea + (addr_size_bits // 8)
val = state.cpu.read_int(ea, size=addr_size_bits)
if issymbolic(val):
val = state.solve_one(val)
return val, next_ea
def read_c_string(state, ea):
return state.cpu.read_string(ea)
def read_uint32_t(state, ea):
"""Read a uint32_t value from memory."""
next_ea = ea + 4
val = state.cpu.read_int(ea, size=32)
if issymbolic(val):
val = state.solve_one(val)
return val, next_ea
TestInfo = collections.namedtuple(
'TestInfo', 'ea name file_name line_number')
def read_test_info(state, ea):
"""Read in a `McTest_TestInfo` info structure from memory."""
prev_test_ea, ea = read_uintptr_t(state, ea)
test_func_ea, ea = read_uintptr_t(state, ea)
test_name_ea, ea = read_uintptr_t(state, ea)
file_name_ea, ea = read_uintptr_t(state, ea)
file_line_num, _ = read_uint32_t(state, ea)
if not test_func_ea or \
not test_name_ea or \
not file_name_ea or \
not file_line_num: # `__LINE__` in C always starts at `1` ;-)
return None, prev_test_ea
test_name = read_c_string(state, test_name_ea)
file_name = read_c_string(state, file_name_ea)
info = TestInfo(test_func_ea, test_name, file_name, file_line_num)
return info, prev_test_ea
def find_test_cases(state, info_ea):
"""Find the test case descriptors."""
tests = []
info_ea, _ = read_uintptr_t(state, info_ea)
while info_ea:
test, info_ea = read_test_info(state, info_ea)
if test:
tests.append(test)
tests.sort(key=lambda t: (t.file_name, t.line_number))
return tests
def read_api_table(state, ea):
"""Reads in the API table."""
@ -58,16 +101,16 @@ def read_api_table(state, ea):
apis[api_name] = api_ea
return apis
def make_symbolic_input(state, input_begin_ea, input_end_ea):
"""Fill in the input data array with symbolic data."""
input_size = input_end_ea - input_begin_ea
data = state.new_symbolic_buffer(nbytes=input_size, name='MCTEST_INPUT')
state.cpu.write_bytes(input_begin_ea, data)
state.cpu.write_bytes(input_begin_ea, data)
return data
def IsSymbolicUInt(state, arg):
def hook_IsSymbolicUInt(state, arg):
"""Implements McTest_IsSymblicUInt, which returns 1 if its input argument
has more then one solutions, and zero otherwise."""
solutions = state.solve_n(arg, 2)
@ -81,28 +124,101 @@ def IsSymbolicUInt(state, arg):
return 1
def Assume(state, arg):
"""Implements _McTest_CanAssume, which tries to inject a constraint."""
def hook_Assume(state, arg):
"""Implements _McTest_Assume, which tries to inject a constraint."""
constraint = arg != 0
state.constrain(constraint)
if len(state.concretize(constraint, 'ONE')) == 0:
L.error("Failed to assert assumption {}".format(constraint))
self.exit(2)
if issymbolic(constraint):
state.constrain(constraint)
def Pass(self):
def hook_Pass(state):
"""Implements McTest_Pass, which notifies us of a passing test."""
L.info("Passed test case")
self.exit(0)
if state.context['failed']:
raise TerminateState("Got to end of failing test case.")
else:
raise TerminateState("Passed test case")
def Fail(self):
def hook_Fail(state):
"""Implements McTest_Fail, which notifies us of a passing test."""
L.error("Failed test case")
self.exit(1)
state.context['failed'] = 1
raise TerminateState("Failed test case")
def hook_SoftFail(state):
"""Implements McTest_Fail, which notifies us of a passing test."""
L.error("Soft failure in test case, continuing")
state.context['failed'] = 1
LEVEL_TO_LOGGER = {
0: L.debug,
1: L.info,
2: L.warning,
3: L.error,
4: L.critical
}
def hook_Log(state):
"""Implements McTest_Log, which lets Manticore intercept and handle the
printing of log messages from the simulated tests."""
pass
def hook(func):
return lambda state: state.invoke_model(func)
def run_test(state, apis, test):
"""Run an individual test case."""
state.cpu.PC = test.ea
m = manticore.Manticore(state, sys.argv[1:])
state = m.initial_state
state.context['failed'] = 0
state.context['log_messages'] = []
state.context['input'] = make_symbolic_input(
state, apis['InputBegin'], apis['InputEnd'])
m.add_hook(apis['IsSymbolicUInt'], hook(hook_IsSymbolicUInt))
m.add_hook(apis['Assume'], hook(hook_Assume))
m.add_hook(apis['Pass'], hook(hook_Pass))
m.add_hook(apis['Fail'], hook(hook_Fail))
m.add_hook(apis['SoftFail'], hook(hook_SoftFail))
m.run()
def run_tests(args, state, apis):
"""Run all of the test cases."""
pool = multiprocessing.Pool(processes=max(1, args.num_workers))
results = []
tests = find_test_cases(state, apis['LastTestInfo'])
for test in tests:
print "Found test", test.name
res = pool.apply_async(run_test, (state, apis, test))
results.append(res)
pool.close()
pool.join()
exit(0)
def main():
parser = argparse.ArgumentParser(
description="Symbolically execute unit tests with Manticore")
parser.add_argument(
"--num_workers", default=1, type=int,
help="Number of workers to spawn for testing and test generation.")
parser.add_argument(
"binary", type=str, help="Path to the test binary to run.")
args = parser.parse_args()
m = manticore.Manticore(sys.argv[1], sys.argv[1:])
m.verbosity(1)
@ -110,24 +226,15 @@ def main():
m._binary_type = 'not elf'
m._binary_obj = m._initial_state.platform.elf
mctest = m._get_symbol_address('McTest_Run')
run_state = m._initial_state
setup_ea = m._get_symbol_address('McTest_Setup')
setup_state = m._initial_state
ea_of_api_table = m._get_symbol_address('McTest_API')
apis = read_api_table(run_state, ea_of_api_table)
apis = read_api_table(setup_state, ea_of_api_table)
# Introduce symbolic input that the tested code will use.
symbolic_input = make_symbolic_input(run_state,
apis['InputBegin'], apis['InputEnd'])
m.add_hook(setup_ea, lambda state: run_tests(args, state, apis))
m.run()
m.add_hook(apis['IsSymbolicUInt'], lambda state: state.invoke_model(IsSymbolicUInt))
m.add_hook(apis['Assume'], lambda state: state.invoke_model(Assume))
m.add_hook(apis['Pass'], lambda state: state.invoke_model(Pass))
m.add_hook(apis['Fail'], lambda state: state.invoke_model(Fail))
print "Finished establishing hooks"
# To Do: create test states
if "__main__" == __name__:
exit(main())

View File

@ -48,15 +48,15 @@ def read_c_string(state, ea):
def read_uintptr_t(state, ea):
"""Read a uint64_t value from memory."""
"""Read a uintptr_t value from memory."""
next_ea = ea + (state.arch.bits // 8)
val = state.solver.eval(state.mem[ea].uintptr_t.resolved, cast_to=int)
return val, next_ea
def read_uint32_t(state, ea):
"""Read a uint64_t value from memory."""
next_ea = ea + (state.arch.bits // 8)
"""Read a uint32_t value from memory."""
next_ea = ea + 4
val = state.solver.eval(state.mem[ea].uint32_t.resolved, cast_to=int)
return val, next_ea
@ -85,6 +85,18 @@ def read_test_info(state, ea):
return info, prev_test_ea
def find_test_cases(state, info_ea):
"""Find the test case descriptors."""
tests = []
info_ea, _ = read_uintptr_t(state, info_ea)
while info_ea:
test, info_ea = read_test_info(state, info_ea)
if test:
tests.append(test)
tests.sort(key=lambda t: (t.file_name, t.line_number))
return tests
def read_api_table(state, ea):
"""Reads in the API table."""
apis = {}
@ -98,17 +110,6 @@ def read_api_table(state, ea):
return apis
def find_test_cases(state, info_ea):
"""Find the test case descriptors."""
tests = []
while info_ea:
test, info_ea = read_test_info(state, info_ea)
if test:
tests.append(test)
tests.sort(key=lambda t: (t.file_name, t.line_number))
return tests
def make_symbolic_input(state, input_begin_ea, input_end_ea):
"""Fill in the input data array with symbolic data."""
input_size = input_end_ea - input_begin_ea
@ -133,7 +134,7 @@ class IsSymbolicUInt(angr.SimProcedure):
class Assume(angr.SimProcedure):
"""Implements _McTest_CanAssume, which tries to inject a constraint."""
"""Implements _McTest_Assume, which tries to inject a constraint."""
def run(self, arg):
constraint = arg != 0
self.state.solver.add(constraint)
@ -165,8 +166,8 @@ class SoftFail(angr.SimProcedure):
class Log(angr.SimProcedure):
"""Implements McTest_Log, which lets Angr intercept and handle the printing
of log messages from the simulated tests."""
"""Implements McTest_Log, which lets Angr intercept and handle the
printing of log messages from the simulated tests."""
LEVEL_TO_LOGGER = {
0: L.debug,
@ -297,10 +298,6 @@ def main():
ea_of_api_table = project.kb.labels.lookup('McTest_API')
apis = read_api_table(run_state, ea_of_api_table)
# Introduce symbolic input that the tested code will use.
symbolic_input = make_symbolic_input(
run_state, apis['InputBegin'], apis['InputEnd'])
# Hook various functions.
hook_function(project, apis['IsSymbolicUInt'], IsSymbolicUInt)
hook_function(project, apis['Assume'], Assume)
@ -318,6 +315,8 @@ def main():
# as failing.
run_state.globals['failed'] = 0
run_state.globals['log_messages'] = []
run_state.globals['input'] = make_symbolic_input(
run_state, apis['InputBegin'], apis['InputEnd'])
pool = multiprocessing.Pool(processes=max(1, args.num_workers))
results = []

View File

@ -205,15 +205,6 @@ const struct McTest_IndexEntry McTest_API[] = {
/* Set up McTest. */
void McTest_Setup(void) {
/* Manticore entrypoint. Manticore doesn't (yet?) support symbol lookups, so
* we instead interpose on this fake system call, and discover the API table
* via the first argument to the system call. */
#if defined(_MSC_VER)
# warning "TODO: Implement Windows interception support for Manticore."
#else
syscall(0x41414141, &McTest_API);
#endif
/* TODO(pag): Sort the test cases by file name and line number. */
}