Improvements. Now hooking fewer functions, and instead making on big array symbolic. This will make it easier to run the inputs concretely (by eventually filling in the array with the concrete bytes), and will also make possible fuzzer integration (where the fuzzer mutates the bytes of the array) possible.

This commit is contained in:
Peter Goodman
2017-10-28 00:09:33 -04:00
parent 6249ec6208
commit 76585f095b
6 changed files with 246 additions and 155 deletions

View File

@@ -18,6 +18,16 @@ cmake_minimum_required(VERSION 2.8)
enable_language(C)
enable_language(CXX)
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release)
endif()
set(CMAKE_C_FLAGS_DEBUG "-O3")
set(CMAKE_C_FLAGS_RELEASE "-O3")
set(CMAKE_CXX_FLAGS_DEBUG "-O3")
set(CMAKE_CXX_FLAGS_RELEASE "-O3")
find_program(PYTHON "python")
# Enable the GNU extensions

View File

@@ -14,8 +14,18 @@
# limitations under the License.
import angr
import logging
import sys
L = logging.getLogger("mctest")
L.setLevel(logging.INFO)
def hook_function(project, name, cls):
"""Hook the function `name` with the SimProcedure `cls`."""
project.hook(project.kb.labels.lookup(name),
cls(project=project))
def read_c_string(state, ea):
"""Read a concrete NUL-terminated string from `ea`."""
assert isinstance(ea, (int, long))
@@ -31,67 +41,87 @@ def read_c_string(state, ea):
return "".join(chars)
def read_uint64_t(state, ea):
def read_uintptr_t(state, ea):
"""Read a uint64_t value from memory."""
return state.solver.eval(state.mem[ea].uint64_t.resolved,
cast_to=int)
return state.solver.eval(state.mem[ea].uintptr_t.resolved, cast_to=int)
def read_uint32_t(state, ea):
"""Read a uint64_t value from memory."""
return state.solver.eval(state.mem[ea].uint32_t.resolved, cast_to=int)
def find_test_cases(project, state):
"""Find the test case descriptors."""
obj = project.loader.main_object
tests = []
addr_size_bytes = state.arch.bits // 8
for sec in obj.sections:
if sec.name != ".mctest_entrypoints":
continue
for ea in xrange(sec.vaddr, sec.vaddr + sec.memsize, 32):
test_func_ea = read_uint64_t(state, ea + 0)
test_name_ea = read_uint64_t(state, ea + 8)
file_name_ea = read_uint64_t(state, ea + 16)
file_line_num = read_uint64_t(state, ea + 24)
test_func_ea = read_uintptr_t(state, ea + 0 * addr_size_bytes)
test_name_ea = read_uintptr_t(state, ea + 1 * addr_size_bytes)
file_name_ea = read_uintptr_t(state, ea + 2 * addr_size_bytes)
file_line_num = read_uint32_t(state, ea + 3 * addr_size_bytes)
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` ;-)
continue
test_name = read_c_string(state, test_name_ea)
file_name = read_c_string(state, file_name_ea)
L.info("Test case {} at {:x} is at {}:{}".format(
test_name, test_func_ea, file_name, file_line_num))
tests.append((test_func_ea, test_name, file_name, file_line_num))
return tests
def hook_symbolic_int_func(project, state, name, num_bits):
"""Hook a McTest function and make it return a symbolic integer."""
class Function(angr.SimProcedure):
def run(self):
return self.state.solver.BVS("", num_bits)
func_name = "McTest_{}".format(name)
ea = project.kb.labels.lookup(func_name)
project.hook(ea, Function(project=project))
def make_symbolic_input(project, state):
"""Fill in the input data array with symbolic data."""
obj = project.loader.main_object
for sec in obj.sections:
if sec.name == ".mctest_input_data":
data = state.se.Unconstrained('MCTEST_INPUT', sec.memsize * 8)
state.memory.store(sec.vaddr, data)
return data
def hook_predicate_int_func(project, state, name, num_bits):
"""Hook a McTest function that checks whether or not its integer argument
is symbolic."""
class Function(angr.SimProcedure):
class Hook(angr.SimProcedure):
def run(self, arg):
return int(self.state.se.symbolic(arg))
func_name = "McTest_IsSymbolic{}".format(name)
ea = project.kb.labels.lookup(func_name)
project.hook(ea, Function(project=project))
hook_function(project, "McTest_IsSymbolic{}".format(name), Hook)
class Assume(angr.SimProcedure):
"""Implements McTest_Assume, which injects a constraint."""
"""Implements _McTest_CanAssume, which tries to inject a constraint."""
def run(self, arg):
if self.state.se.symbolic(arg):
constraint = arg != 0
eval_res = self.state.se.eval(constraint)
if eval_res:
self.state.add_constraints(constraint)
ret = eval_res
else:
ret = self.state.se.eval(arg) != 0
return int(ret)
constraint = arg != 0
self.state.solver.add(constraint)
if not self.state.solver.satisfiable():
L.error("Failed to assert assumption {}".format(constraint))
self.exit(2)
class Pass(angr.SimProcedure):
"""Implements McTest_Pass, which notifies us of a passing test."""
def run(self):
L.info("Passed test case")
self.exit(0)
class Fail(angr.SimProcedure):
"""Implements McTest_Fail, which notifies us of a passing test."""
def run(self):
L.error("Failed test case")
self.exit(1)
def main():
@@ -103,7 +133,8 @@ def main():
sys.argv[1],
use_sim_procedures=True,
translation_cache=True,
support_selfmodifying_code=False)
support_selfmodifying_code=False,
auto_load_libs=False)
entry_state = project.factory.entry_state()
addr_size_bits = entry_state.arch.bits
@@ -119,23 +150,16 @@ def main():
concrete_manager.explore(find=ea_of_main)
main_state = concrete_manager.found[0]
#concrete_manager.move(from_stash='found', to_stash='deadended')
# Hook functions that should now return symbolic values.
hook_symbolic_int_func(project, main_state, 'Bool', 1)
hook_symbolic_int_func(project, main_state, 'Size', addr_size_bits)
hook_symbolic_int_func(project, main_state, 'UInt64', 64)
hook_symbolic_int_func(project, main_state, 'UInt', 32)
# Introduce symbolic input that the tested code will use.
symbolic_input = make_symbolic_input(project, main_state)
# Hook predicate functions that should return 1 or 0 depending on whether
# or not their argument is symbolic.
hook_predicate_int_func(project, main_state, 'UInt', 32)
# Hook the assume function.
project.hook(project.kb.labels.lookup('McTest_Assume'),
Assume(project=project))
ea_of_done = project.kb.labels.lookup('McTest_DoneTestCase')
hook_function(project, '_McTest_Assume', Assume)
hook_function(project, 'McTest_Pass', Pass)
hook_function(project, 'McTest_Fail', Fail)
# For each test, create a simulation manager whose initial state calls into
# the test case function, and returns to `McTest_DoneTestCase`.
@@ -143,8 +167,7 @@ def main():
for entry_ea, test_name, file_name, line_num in tests:
test_state = project.factory.call_state(
entry_ea,
base_state=main_state,
ret_addr=ea_of_done)
base_state=main_state)
# NOTE(pag): Enabling Veritesting seems to miss some cases where the
# tests fail.
@@ -152,13 +175,13 @@ def main():
project=project,
active_states=[test_state])
L.info("Running test case {}".format(test_name))
test_manager.run()
for state in test_manager.deadended:
last_event = state.history.events[-1]
if 'terminate' == last_event.type:
code = last_event.objects['exit_code']._model_concrete.value
print "{} in {}:{} terminated with {}".format(test_name, file_name, line_num, code)
return 0

View File

@@ -22,7 +22,7 @@ McTest_EntryPoint(YIsAlwaysPositive) {
McTest_Assert(y >= 0);
}
McTest_EntryPoint(YIsAlwaysPositive_WillFail) {
McTest_EntryPoint(YIsAlwaysPositive_CanFail) {
int x = McTest_IntInRange(-10, 10);
int y = x * x * x;
McTest_Assert(y >= 0); /* This will fail */

View File

@@ -25,6 +25,18 @@
extern "C" {
#endif /* __cplusplus */
/* Return a symbolic value of a given type. */
extern int McTest_Bool(void);
extern size_t McTest_Size(void);
extern uint64_t McTest_UInt64(void);
extern int64_t McTest_Int64(void);
extern uint32_t McTest_UInt(void);
extern int32_t McTest_Int(void);
extern uint16_t McTest_UShort(void);
extern int16_t McTest_Short(void);
extern uint8_t McTest_UChar(void);
extern int8_t McTest_Char(void);
/* Symbolize the data in the range `[begin, end)`. */
extern void McTest_SymbolizeData(void *begin, void *end);
@@ -65,45 +77,23 @@ inline static char *McTest_CStr(size_t len) {
/* Creates an assumption about a symbolic value. Returns `1` if the assumption
* can hold and was asserted. */
extern int McTest_Assume(int expr);
extern void _McTest_Assume(int expr);
#define McTest_Assume(x) _McTest_Assume(!!(x))
__attribute__((noreturn))
extern void McTest_Fail(void);
__attribute__((noreturn))
extern void McTest_Pass(void);
/* Asserts that `expr` must hold. */
inline static void McTest_Assert(int expr) {
if (McTest_Assume(!expr)) {
abort();
if (!expr) {
McTest_Fail();
}
}
/* Return a symbolic value of a given type. */
extern int McTest_Bool(void);
extern size_t McTest_Size(void);
extern uint64_t McTest_UInt64(void);
extern uint32_t McTest_UInt(void);
inline static int64_t McTest_Int64(void) {
return (int64_t) McTest_UInt64();
}
inline static int32_t McTest_Int(void) {
return (int32_t) McTest_UInt();
}
inline static uint16_t McTest_UShort(void) {
return (uint16_t) McTest_UInt();
}
inline static int16_t McTest_Short(void) {
return (int16_t) McTest_UInt();
}
inline static unsigned char McTest_UChar(void) {
return (unsigned char) McTest_UInt();
}
inline static char McTest_Char(void) {
return (char) McTest_UInt();
}
/* Return a symbolic value in a the range `[low_inc, high_inc]`. */
#define MCTEST_MAKE_SYMBOLIC_RANGE(Tname, tname) \
inline static tname McTest_ ## Tname ## InRange( \
@@ -124,19 +114,6 @@ MCTEST_MAKE_SYMBOLIC_RANGE(UChar, unsigned char)
#undef MCTEST_MAKE_SYMBOLIC_RANGE
/* Return a symbolic value of a given type. */
extern int McTest_Bool(void);
extern size_t McTest_Size(void);
extern uint64_t McTest_UInt64(void);
extern int64_t McTest_Int64(void);
extern uint32_t McTest_UInt(void);
extern int32_t McTest_Int(void);
extern uint16_t McTest_UShort(void);
extern int16_t McTest_Short(void);
extern unsigned char McTest_UChar(void);
extern char McTest_Char(void);
/* Predicates to check whether or not a particular value is symbolic */
extern int McTest_IsSymbolicUInt(uint32_t x);
@@ -210,29 +187,28 @@ inline static int McTest_IsSymbolicDouble(double x) {
#define McTest_EntryPoint(test_name) \
_McTest_EntryPoint(test_name, __FILE__, __LINE__)
struct __attribute__((packed)) McTest_TestInfo {
void (*test_func)(void);
const char *test_name;
const char *file_name;
unsigned line_number;
uint8_t padding[28 - (3 * sizeof(void *))];
};
#define _McTest_EntryPoint(test_name, file, line) \
static void McTest_Run_ ## test_name (void); \
__attribute__((noinline, used)) \
MCTEST_EXTERN_C void McTest_Register_ ## test_name (void) { \
__asm__ __volatile__ ( \
".pushsection .mctest_strtab,\"a\" \n" \
"1: \n" \
".asciz \"" _MCTEST_TO_STR(test_name) "\" \n" \
"2: \n" \
".asciz \"" file "\" \n" \
".popsection \n" \
".pushsection .mctest_entrypoints,\"a\" \n" \
".balign 16 \n" \
".quad %p0 \n" \
".quad 1b \n" \
".quad 2b \n" \
".quad " _MCTEST_TO_STR(line) " \n" \
".popsection \n" \
: \
: "i"(McTest_Run_ ## test_name) \
); \
static void McTest_Test_ ## test_name (void); \
static void McTest_Run_ ## test_name (void) { \
McTest_Test_ ## test_name(); \
McTest_Pass(); \
} \
void McTest_Run_ ## test_name(void)
MCTEST_EXTERN_C struct McTest_TestInfo McTest_Register_ ## test_name \
__attribute__((section(".mctest_entrypoints"))) = { \
McTest_Run_ ## test_name, \
_MCTEST_TO_STR(test_name), \
file, \
line \
}; \
void McTest_Test_ ## test_name(void)
#ifdef __cplusplus

View File

@@ -117,32 +117,77 @@ inline static int IsSymbolic(void *x) {
return McTest_IsSymbolicPtr(x);
}
#if 199711L < __cplusplus
template <typename T>
class Symbolic {
public:
template <typename... Args>
inline Symbolic(Args&& ...args)
: value(std::forward<Args...>(args)...) {}
/* Returns a symbolic container (where it makes sense). Container entries
* do not necessarily need to be contiguous. */
template <typename ContainerT, typename... Args>
inline static ContainerT Symbolic(Args&& ...args) {
ContainerT cont(std::forward<Args...>(args)...);
if (!cont.empty()) {
for (auto &entry : cont) {
auto entry_ptr = &entry;
McTest_SymbolizeData(entry_ptr, &(entry_ptr[1]));
inline Symbolic(void) {
T *val_ptr = &value;
McTest_SymbolizeData(val_ptr, &(val_ptr[1]));
}
inline operator T (void) const {
return value;
}
T value;
};
template <T>
class SymbolicLinearContainer {
public:
inline explicit Symbolic(size_t len)
: value(len) {
if (len) {
McTest_SymbolizeData(&(str.begin()), &(str.end()));
}
}
return cont;
}
#endif // 199711L < __cplusplus
// Returns a symbolic std::string of length `len`.
inline static std::string Str(size_t len) {
std::string str(len);
if (len) {
McTest_SymbolizeData(&(str.begin()), &(str.end()));
inline operator T (void) const {
return value;
}
return str;
}
T value;
private:
Symblic(void) = delete;
};
template <>
class Symbolic<std::string> : public SymbolicLinearContainer<std::string> {};
template <>
class Symbolic<std::wstring> : public SymbolicLinearContainer<std::wstring> {};
template <typename T>
class Symbolic<std::vector<T>> :
public SymbolicLinearContainer<std::vector<T>> {};
#define MAKE_SYMBOL_SPECIALIZATION(Tname, tname) \
template <> \
class Symbolic<tname> { \
public: \
inline Symbolic(void)
: value(McTest_ ## Tname) {} \
inline operator tname (void) const { \
return value; \
} \
tname value; \
};
MAKE_SYMBOL_SPECIALIZATION(UInt64, uint64_t)
MAKE_SYMBOL_SPECIALIZATION(Int64, int64_t)
MAKE_SYMBOL_SPECIALIZATION(UInt, uint32_t)
MAKE_SYMBOL_SPECIALIZATION(Int, int32_t)
MAKE_SYMBOL_SPECIALIZATION(UShort, uint16_t)
MAKE_SYMBOL_SPECIALIZATION(Short, int16_t)
MAKE_SYMBOL_SPECIALIZATION(UChar, uint8_t)
MAKE_SYMBOL_SPECIALIZATION(Char, int8_t)
#undef MAKE_SYMBOL_SPECIALIZATION
} // namespace mctest

View File

@@ -22,34 +22,76 @@
extern "C" {
#endif /* __cplusplus */
static uint32_t gPlaybookIndex = 0;
static uint32_t gPlaybook[8192] = {};
volatile uint8_t McTest_Input[8192]
__attribute__((section(".mctest_input_data")));
uint32_t McTest_InputIndex = 0;
__attribute__((noreturn))
extern void McTest_Fail(void) {
exit(EXIT_FAILURE);
}
__attribute__((noreturn))
extern void McTest_Pass(void) {
exit(EXIT_SUCCESS);
}
void McTest_SymbolizeData(void *begin, void *end) {
(void) begin;
(void) end;
uintptr_t begin_addr = (uintptr_t) begin;
uintptr_t end_addr = (uintptr_t) end;
if (begin_addr > end_addr) {
abort();
} else if (begin_addr == end_addr) {
return;
} else {
uint8_t *bytes = (uint8_t *) begin;
for (uintptr_t i = 0, max_i = (end_addr - begin_addr); i < max_i; ++i) {
bytes[i] = McTest_Input[McTest_InputIndex++];
}
}
}
/* Return a symbolic value of a given type. */
int McTest_Bool(void) {
return 0;
return McTest_Input[McTest_InputIndex++] & 1;
}
size_t McTest_Size(void) {
return 0;
#define MAKE_SYMBOL_FUNC(Type, type) \
type McTest_ ## Type(void) { \
type val = 0; \
_Pragma("unroll") \
for (size_t i = 0; i < sizeof(type); ++i) { \
val = (val << 8) | ((type) McTest_Input[McTest_InputIndex++]); \
} \
return val; \
}
MAKE_SYMBOL_FUNC(UInt64, uint64_t)
int64_t McTest_Int64(void) {
return (int64_t) McTest_UInt64();
}
uint64_t McTest_UInt64(void) {
return 0;
MAKE_SYMBOL_FUNC(UInt, uint32_t)
int32_t McTest_Int(void) {
return (int32_t) McTest_UInt();
}
uint32_t McTest_UInt(void) {
return 0;
MAKE_SYMBOL_FUNC(UShort, uint16_t)
int16_t McTest_Short(void) {
return (int16_t) McTest_UShort();
}
int McTest_Assume(int expr) {
MAKE_SYMBOL_FUNC(UChar, uint8_t)
int8_t McTest_Char(void) {
return (int8_t) McTest_UChar();
}
#undef MAKE_SYMBOL_FUNC
void _McTest_Assume(int expr) {
assert(expr);
return 1;
}
int McTest_IsSymbolicUInt(uint32_t x) {
@@ -63,11 +105,6 @@ void McTest_DoneTestCase(void) {
/* McTest implements the `main` function so that test code can focus on tests */
int main(void) {
#if defined(__x86_64__) || defined(__i386__) || defined(_M_X86)
asm("ud2; .asciz \"I'm McTest'in it\";");
#else
# error "Unsupported platform (for now)."
#endif
return EXIT_SUCCESS;
}