diff --git a/VTIL-Architecture/VTIL-Architecture.vcxproj b/VTIL-Architecture/VTIL-Architecture.vcxproj index c7d1271..2174a1a 100644 --- a/VTIL-Architecture/VTIL-Architecture.vcxproj +++ b/VTIL-Architecture/VTIL-Architecture.vcxproj @@ -15,7 +15,6 @@ - @@ -24,6 +23,9 @@ + + + @@ -36,6 +38,9 @@ + + + diff --git a/VTIL-Architecture/VTIL-Architecture.vcxproj.filters b/VTIL-Architecture/VTIL-Architecture.vcxproj.filters index 68c7cdd..3359c50 100644 --- a/VTIL-Architecture/VTIL-Architecture.vcxproj.filters +++ b/VTIL-Architecture/VTIL-Architecture.vcxproj.filters @@ -19,6 +19,9 @@ {b070980d-a30d-421e-82c6-0298cfe01c0e} + + {6ba6b482-58aa-46cd-ad58-c0eef36c9333} + @@ -63,12 +66,18 @@ Virtual Machine - - Includes - Virtual Machine + + Value Tracing + + + Value Tracing + + + Value Tracing + @@ -98,6 +107,15 @@ Virtual Machine + + Value Tracing + + + Value Tracing + + + Value Tracing + diff --git a/VTIL-Architecture/includes/vtil/arch b/VTIL-Architecture/includes/vtil/arch index 2f147bc..1a932f5 100644 --- a/VTIL-Architecture/includes/vtil/arch +++ b/VTIL-Architecture/includes/vtil/arch @@ -7,4 +7,10 @@ #include "../../routine/routine.hpp" #include "../../routine/basic_block.hpp" #include "../../routine/instruction.hpp" -#include "../../routine/serialization.hpp" \ No newline at end of file +#include "../../routine/serialization.hpp" +#include "../../vm/interface.hpp" +#include "../../vm/symbolic.hpp" +#include "../../vm/lambda.hpp" +#include "../../trace/auxiliaries.hpp" +#include "../../trace/tracer.hpp" +#include "../../trace/cached_tracer.hpp" \ No newline at end of file diff --git a/VTIL-Architecture/includes/vtil/trace b/VTIL-Architecture/includes/vtil/trace new file mode 100644 index 0000000..87d19d3 --- /dev/null +++ b/VTIL-Architecture/includes/vtil/trace @@ -0,0 +1,3 @@ +#include "../../trace/auxiliaries.hpp" +#include "../../trace/tracer.hpp" +#include "../../trace/cached_tracer.hpp" \ No newline at end of file diff --git a/VTIL-Architecture/includes/vtil/vm b/VTIL-Architecture/includes/vtil/vm deleted file mode 100644 index e06595c..0000000 --- a/VTIL-Architecture/includes/vtil/vm +++ /dev/null @@ -1,3 +0,0 @@ -#include "../../vm/interface.hpp" -#include "../../vm/symbolic.hpp" -#include "../../vm/lambda.hpp" \ No newline at end of file diff --git a/VTIL-Architecture/trace/auxiliaries.cpp b/VTIL-Architecture/trace/auxiliaries.cpp new file mode 100644 index 0000000..78fe485 --- /dev/null +++ b/VTIL-Architecture/trace/auxiliaries.cpp @@ -0,0 +1,155 @@ +#include "auxiliaries.hpp" + +namespace vtil +{ + // Checks if the instruction given accesses the variable, optionally filtering to the + // access type specified, tracer passed will be used to generate pointers when needed. + // + access_details test_access( const il_const_iterator& it, const symbolic::variable::descriptor_t& var, tracer* tracer, access_type type ) + { + // If variable is of register type: + // + if ( auto reg = std::get_if( &var ) ) + { + // Iterate each operand: + // + for ( int i = 0; i < it->base->operand_count(); i++ ) + { + // Skip if not register. + // + if ( !it->operands[ i ].is_register() ) + continue; + + // Skip if access type does not match. + // + switch ( type ) + { + // ::read will filter to read or read/write. + // + case access_type::read: + if ( it->base->operand_types[ i ] == operand_type::write ) + continue; + break; + // ::write will filter to write or read/write. + // + case access_type::write: + if ( it->base->operand_types[ i ] < operand_type::write ) + continue; + break; + // ::readwrite will filter to only read/write. + // + case access_type::readwrite: + if ( it->base->operand_types[ i ] != operand_type::readwrite ) + continue; + break; + // ::none accepts any access. + // + case access_type::none: + break; + } + + // Skip if no overlap. + // + auto& ref_reg = it->operands[ i ].reg(); + if ( !ref_reg.overlaps( *reg ) ) + continue; + + // Return access details. + // + access_type type_found; + if ( it->base->operand_types[ i ] == operand_type::readwrite ) + type_found = access_type::readwrite; + else if ( it->base->operand_types[ i ] == operand_type::write ) + type_found = access_type::write; + else + type_found = access_type::read; + + return { + type_found, + ref_reg.bit_offset - reg->bit_offset, + ref_reg.bit_count + }; + } + } + // If variable is of memory type: + // + else if( auto mem = std::get_if( &var ) ) + { + // If instruction accesses memory: + // + if ( it->base->accesses_memory() ) + { + // Skip if access type does not match. + // + switch ( type ) + { + // ::read will filter to read. + // + case access_type::read: + if ( it->base->writes_memory() ) + return { access_type::none }; + break; + // ::write will filter to write. + // + case access_type::write: + if ( !it->base->writes_memory() ) + return { access_type::none }; + break; + // Read/write does not exist for memory operations. + // + case access_type::readwrite: + unreachable(); + // ::none accepts any access. + // + case access_type::none: + // Determine the type and set it. + // + type = it->base->writes_memory() ? access_type::write : access_type::read; + break; + } + + // Generate an expression for the pointer. + // + auto [base, offset] = it->get_mem_loc(); + symbolic::pointer ptr = { tracer->trace( { it, base } ) + offset }; + + // If the two pointers can overlap (not restrict qualified against each other): + // + if ( ptr.can_overlap( mem->base ) ) + { + // If it can be expressed as a constant: + // + if ( auto disp = ( ptr - mem->base ) ) + { + // Check if within boundaries: + // + int64_t low_offset = *disp; + int64_t high_offset = low_offset + it->access_size(); + if ( low_offset < ( mem->bit_count / 8 ) && high_offset > 0 ) + { + // Can safely multiply by 8 and shrink to bitcnt_t type from int64_t + // since variables are of maximum 64-bit size which means both offset + // and size will be small numbers. + // + return { + type, + bitcnt_t( low_offset * 8 ), + bitcnt_t( ( high_offset - low_offset ) * 8 ) + }; + } + } + // Otherwise, return unknown. + // + else + { + return { type, 0, -1 }; + } + } + } + } + + // No access case. + // + return { access_type::none }; + } +}; \ No newline at end of file diff --git a/VTIL-Optimizer/analysis/variable_aux.hpp b/VTIL-Architecture/trace/auxiliaries.hpp similarity index 73% rename from VTIL-Optimizer/analysis/variable_aux.hpp rename to VTIL-Architecture/trace/auxiliaries.hpp index d2c2a57..90a819c 100644 --- a/VTIL-Optimizer/analysis/variable_aux.hpp +++ b/VTIL-Architecture/trace/auxiliaries.hpp @@ -26,18 +26,14 @@ // POSSIBILITY OF SUCH DAMAGE. // #pragma once -#include #include #include -#include "trace.hpp" -#include +#include "tracer.hpp" +#include "../routine/basic_block.hpp" +#include "../symex/variable.hpp" -namespace vtil::optimizer +namespace vtil { - // Callback typedefs. - // - using partial_tracer_t = std::function; - // Enumeration used to describe the type of access to a variable. // enum class access_type @@ -74,24 +70,10 @@ namespace vtil::optimizer bool is_unknown() const { return bit_count == -1; } }; - // Makes a memory variable from the given instruction's src/dst, uses the tracer - // passed to resolve the absolute pointer. - // - symbolic::variable reference_memory( const il_const_iterator& it, - const trace_function_t& tracer = [ ] ( auto x ) { return trace( x ); } ); - // Checks if the instruction given accesses the variable, optionally filtering to the // access type specified, tracer passed will be used to generate pointers when needed. // access_details test_access( const il_const_iterator& it, const symbolic::variable::descriptor_t& var, - access_type type = access_type::none, - const trace_function_t& tracer = [ ] ( auto x ) { return trace( x ); } ); - - // Given a partial tracer, this routine will determine the full value of the variable - // at the given position where a partial write was found. - // - symbolic::expression resolve_partial( const access_details& access, - bitcnt_t bit_count, - const partial_tracer_t& ptracer ); + tracer* tracer, access_type type = access_type::none ); }; \ No newline at end of file diff --git a/VTIL-Architecture/trace/cached_tracer.cpp b/VTIL-Architecture/trace/cached_tracer.cpp new file mode 100644 index 0000000..e6bfa52 --- /dev/null +++ b/VTIL-Architecture/trace/cached_tracer.cpp @@ -0,0 +1,109 @@ +#include "cached_tracer.hpp" + +namespace vtil +{ + // Hooks default tracer and does a cache lookup before invokation. + // + symbolic::expression cached_tracer::trace( symbolic::variable lookup ) + { + using namespace logger; + + #if VTIL_OPT_TRACE_VERBOSE + // Log the beginning of the trace. + // + log( "CcTrace(%s)\n", lookup ); + scope_padding _p( 1 ); + #endif + // Handle base case. + // + if ( lookup.at.is_begin() ) + { + symbolic::expression result = lookup.to_expression(); + #if VTIL_OPT_TRACE_VERBOSE + // Log result. + // + log( "= %s [Base case]\n", result ); + #endif + return result; + } + + // Try lookup the exact variable in the map in a fast manner. + // + auto it = cache.find( lookup ); + if ( it != cache.end() ) + { + const symbolic::expression& result = *it->second; + #if VTIL_OPT_TRACE_VERBOSE + // Log result. + // + log( "= %s [Cached result]\n", result ); + #endif + return result; + } + // Declare a predicate for the search of the variable in the cache. + // + std::function predicate; + + // If memory variable: + // + if ( lookup.is_memory() ) + { + predicate = [ & ] ( const cache_entry& pair ) + { + // Key must be of memory type at the same position. + // + if ( !pair.first.is_memory() ) return false; + if ( pair.first.at != lookup.at ) return false; + + // Must be the same pointer and have a larger or equal size. + // + auto& self = lookup.mem(); + auto& other = pair.first.mem(); + return self.decay().equals( other.decay() ) && + self.bit_count >= other.bit_count; + }; + } + // If register variable: + // + else + { + fassert( lookup.is_register() ); + predicate = [ & ] ( const cache_entry& pair ) + { + // Key must be of memory type at the same position. + // + if ( !pair.first.is_register() ) return false; + if ( pair.first.at != lookup.at ) return false; + + // Must be the same register and have a larger or equal size. + // + auto& self = lookup.reg(); + auto& other = pair.first.reg(); + return self.flags == other.flags && + self.local_id == other.local_id && + self.bit_offset == other.bit_offset && + self.bit_count >= other.bit_count; + }; + } + + // Search the map, if we find a matching entry shrink and use as the result. + // + symbolic::expression result; + it = std::find_if( cache.begin(), cache.end(), predicate ); + if ( it != cache.end() ) + result = symbolic::expression{ *it->second }.resize( lookup.bit_count() ); + else + result = tracer::trace( lookup ); + + // Insert a cache entry for the exact variable we're looking up and return. + // + cache.emplace( lookup, result ); + + #if VTIL_OPT_TRACE_VERBOSE + // Log result. + // + log( "= %s\n", result ); + #endif + return result; + } +}; \ No newline at end of file diff --git a/VTIL-Optimizer/analysis/cached_tracer.hpp b/VTIL-Architecture/trace/cached_tracer.hpp similarity index 77% rename from VTIL-Optimizer/analysis/cached_tracer.hpp rename to VTIL-Architecture/trace/cached_tracer.hpp index 90f5e62..d573559 100644 --- a/VTIL-Optimizer/analysis/cached_tracer.hpp +++ b/VTIL-Architecture/trace/cached_tracer.hpp @@ -26,19 +26,18 @@ // POSSIBILITY OF SUCH DAMAGE. // #pragma once -#include -#include -#include -#include "trace.hpp" +#include +#include +#include "tracer.hpp" -namespace vtil::optimizer +namespace vtil { // Tracing is extremely costy and adding a simple cache reduces the cost // by ~100x fold, however we can't use a global cache since the optimizer // will change the instruction stream and all cache will be eventually // invalidated after each optimization pass so we use an instanced cache. // - struct cached_tracer + struct cached_tracer : tracer { // Define the type of the cache. // @@ -50,21 +49,12 @@ namespace vtil::optimizer // cache_type cache; - // Replicate trace_basic with the addition of a cache lookup. + // Hooks default tracer and does a cache lookup before invokation. // - symbolic::expression trace_basic_cached( const symbolic::variable& lookup, const trace_function_t& tracer = {} ); - - // Wrappers of trace and rtrace with cached basic tracer. - // - symbolic::expression trace( const symbolic::variable& lookup, bool pack = true ); - symbolic::expression rtrace( const symbolic::variable& lookup, bool pack = true ); + symbolic::expression trace( symbolic::variable lookup ) override; // Flushes the cache. // auto flush() { cache.clear(); return *this; } - - // Implicit casting to a trace function. - // - operator trace_function_t() { return [ this ] ( auto v ) { return trace_basic_cached( v ); }; } }; }; \ No newline at end of file diff --git a/VTIL-Architecture/trace/tracer.cpp b/VTIL-Architecture/trace/tracer.cpp new file mode 100644 index 0000000..85f64b6 --- /dev/null +++ b/VTIL-Architecture/trace/tracer.cpp @@ -0,0 +1,455 @@ +#include "tracer.hpp" +#include +#include "../vm/lambda.hpp" +#include "auxiliaries.hpp" + +namespace vtil +{ + // Internal type definitions. + // + using partial_tracer_t = std::function; + using path_history_t = std::map, uint32_t>; + + // Given a partial tracer, this routine will determine the full value of the variable + // at the given position where a partial write was found. + // + static symbolic::expression resolve_partial( const access_details& access, bitcnt_t bit_count, const partial_tracer_t& ptracer ) + { + using namespace logger; + + // Fetch the result of this operation. + // + symbolic::expression base = ptracer( access.bit_offset, access.bit_count ); + + // Trace a low part if we have to. + // + if ( access.bit_offset > 0 ) + { + bitcnt_t low_bcnt = access.bit_offset; + auto res = ptracer( 0, low_bcnt ); +#if VTIL_OPT_TRACE_VERBOSE + // Log the low and middle bits. + // + log( "dst[00..%02d] := %s\n", low_bcnt, res ); + log( "dst[%02d..%02d] := %s\n", access.bit_offset, access.bit_offset + access.bit_count, base ); +#endif + base = res | ( base.resize( bit_count ) << low_bcnt ); + } + // Shift the result if we have to. + // + else if ( access.bit_offset < 0 ) + { + base = ( base >> access.bit_offset ).resize( bit_count ); +#if VTIL_OPT_TRACE_VERBOSE + // Log the low bits after shifting. + // + log( "dst[00..%02d] := %s\n", access.bit_offset + access.bit_count, base ); +#endif + } + else + { +#if VTIL_OPT_TRACE_VERBOSE + // Log the low bits. + // + log( "dst[00..%02d] := %s\n", access.bit_offset + access.bit_count, base ); +#endif + } + + // Trace a high part if we have to. + // + if ( bit_count > ( access.bit_offset + access.bit_count ) ) + { + bitcnt_t high_bnct = bit_count - ( access.bit_offset + access.bit_count ); + auto res = ptracer( access.bit_offset + access.bit_count, high_bnct ); +#if VTIL_OPT_TRACE_VERBOSE + // Log the high bits. + // + log( "dst[%02d..%02d] := %s\n", access.bit_offset + access.bit_count, bit_count, res ); +#endif + base = base | ( res.resize( bit_count ) << ( access.bit_offset + access.bit_count ) ); + } + +#if VTIL_OPT_TRACE_VERBOSE + // Log the final result. + // + log( "dst := %s\n", base ); +#endif + // Resize and return. + // + return base.resize( bit_count ); + } + + // Propagates all variables in the reference expression onto the new iterator, if no + // history pointer given will do trace instead of rtrace. + // - Note: New iterator should be a connected block's end. + // + static symbolic::expression propagate( const symbolic::expression& ref, const il_const_iterator& it, tracer* tracer, path_history_t* history ) + { + using namespace logger; + scope_padding _p( 3 ); + + // Copy the reference expression. + // + symbolic::expression exp = ref; + + // For each unique variable: + // + std::set variables; + ref.count_unique_variables( &variables ); + for ( auto& uid : variables ) + { + // Move the variable to reference the previous block. + // + symbolic::variable var = uid.get(); + + // Skip if variable is position indepdendent or not at the beginning of the block. + // + if ( !var.at.is_valid() || !var.at.is_begin() ) + continue; + + // If register: + // + if ( var.is_register() ) + { + // Local temporary must not exist in an expression being propagated + // from the beginning of the block as that indicates use before assignment. + // Make sure this is not the case. + // + if ( var.reg().flags & register_local ) + error( "Local variable %s is used before value assignment.\n", var ); + + // If volatile iterator cannot be moved, skip. + // + if ( var.reg().flags & register_volatile ) + continue; + } + // If memory, propagate the pointer. + // + else if ( var.is_memory() ) + { + auto& pointer = var.mem().decay(); +#if VTIL_OPT_TRACE_VERBOSE + // Log original pointer. + // + log( "Propagating pointer: %s\n", pointer->to_string() ); +#endif + // Fail if propagation fails. + // + if ( !( pointer = propagate( pointer, it, tracer, nullptr ) ) ) + return {}; + +#if VTIL_OPT_TRACE_VERBOSE + // Log new pointer. + // + log( "Pointer' => %s\n", pointer->to_string() ); +#endif + } + + // Move the assigned iterator. + // + var.at = it; + + // Trace the variable in the destination block, fail if it fails. + // + symbolic::expression var_traced = history ? tracer->rtrace( var ) : tracer->trace( var ); + if ( !var_traced ) + return {}; + + // If we are tracing the value of RSP, add the stack pointer delta between blocks. + // + if ( var.is_register() && var.reg().is_stack_pointer() ) + var_traced = var_traced + it.container->sp_offset; + + // Rewrite the expression + // + exp.transform( [ & ] ( symbolic::expression& exp ) + { + if ( exp.is_variable() && exp.uid == uid ) + exp = var_traced; + } ); + } + + // Return the result. + // + return exp; + } + + // Internal implementation of ::trace with a path history. + // + static symbolic::expression rtrace_primitive( const symbolic::variable& lookup, tracer* tracer, const path_history_t& history ) + { + using namespace logger; + + // Trace through the current block first. + // + symbolic::expression result = tracer->trace( lookup ); + + // If result has any variables: + // + if ( result.count_unique_variables() != 0 ) + { + // Determine the paths we can take to iterate further. + // + std::vector it_list = lookup.at.is_valid() + ? lookup.at.recurse( false ) + : std::vector{}; + + // If there are paths take. + // + if ( !it_list.empty() ) + { +#if VTIL_OPT_TRACE_VERBOSE + // Log recursive tracing of the expression. + // + log( "Base case: %s\n", result ); +#endif + // Save current result as default result and clear it. + // + symbolic::expression default_result = {}; + std::swap( result, default_result ); + + // For each path: + // + for ( auto& it : it_list ) + { + // Create a local copy for the visited list for this path + // and increment the visit counter. + // + path_history_t history_local = { history }; + uint32_t& visit_counter = history_local[ { lookup.at.container, it.container } ]; + + // If we've taken this path more than twice, skip it. + // + if ( ++visit_counter > 2 ) + { +#if VTIL_OPT_TRACE_VERBOSE + // Log skipping of path. + // + log( "Path [%llx->%llx] is not taken as it's n-looping.\n", lookup.at.container->entry_vip, it.container->entry_vip ); +#endif + continue; + } + +#if VTIL_OPT_TRACE_VERBOSE + // Log tracing of path. + // + log( "Taking path [%llx->%llx]\n", lookup.at.container->entry_vip, it.container->entry_vip ); +#endif + // Propagate each variable onto to the destination block. + // + symbolic::expression exp = propagate( default_result, it, tracer, &history_local ); + +#if VTIL_OPT_TRACE_VERBOSE + // Log result. + // + log( "= %s\n", exp ); +#endif + // If no result is set yet, assign the current expression. + // + if ( !result.is_valid() ) + result = exp; + + // If expression is invalid or not equal to previous result, fail. + // + if ( !exp.is_valid() || !exp.equals( result ) ) + { +#if VTIL_OPT_TRACE_VERBOSE + // Log decision. + // + log( "Halting tracer as it was not deterministic.\n" ); +#endif + // If result was null, return lookup. + // + if ( !exp.is_valid() ) + { + result = lookup.to_expression(); + } + // If it was mismatchign, return default result as branch dependant. + // + else + { + result = default_result; + result.transform( [ ] ( symbolic::expression& exp ) + { + if ( exp.is_variable() ) + exp.uid.get().is_branch_dependant = true; + }, false ); + } + break; + } + } + } + } +#if VTIL_OPT_TRACE_VERBOSE + // Log result. + // + log( "= %s\n", result ); +#endif + return result; + } + + // Traces a variable across the basic block it belongs to and generates a symbolic expression + // that describes it's value at the bound point. The provided variable should not contain a + // pointer with out-of-block expressions. + // + symbolic::expression tracer::trace( symbolic::variable lookup ) + { + using namespace logger; + + // If invalid/.begin() iterator or register with "no-trace" flags, return as is. + // + if ( lookup.at.is_begin() || ( lookup.is_register() && ( lookup.reg().flags & ( register_volatile | register_readonly ) ) ) ) + return lookup.to_expression(); + + #ifdef _DEBUG + // If memory, make sure pointer is expressed as a variable within current block. + // + if ( lookup.is_memory() ) + { + using validator_t = std::function; + static const std::function make_validator = [ ] ( const basic_block* container ) + { + return[ container = std::move( container ) ]( auto& exp ) + { + if ( exp.is_variable() ) + { + // Make sure it either has no iterator or belongs to the current container. + // + auto& var = exp.uid.get(); + fassert( !var.at.is_valid() || var.at.container == container ); + + // If memory variable, validate pointer as well. + // + if ( var.is_memory() ) + var.mem().decay().enumerate( make_validator( container ) ); + } + }; + }; + lookup.mem().decay().enumerate( make_validator( lookup.at.container ) ); + } + #endif + + // Fast forward until iterator writes to the lookup, if none found return as is. + // + access_details details = {}; + while ( true ) + { + // If we reached the beginning, return as is. + // + if ( lookup.at.is_begin() ) + return lookup.to_expression(); + + // Decrement iterator. + // + --lookup.at; + + // If variable is being written to, break. + // + if ( details = test_access( lookup.at, lookup.descriptor, this, access_type::write ) ) + break; + } + + // If fails due to offset/size mismatch, invoke partial tracer. + // + bitcnt_t result_bcnt = lookup.bit_count(); + if ( !details.is_unknown() && + ( details.bit_offset != 0 || details.bit_count != result_bcnt ) ) + { + // Define partial tracer. + // + partial_tracer_t ptrace; + if ( lookup.is_register() ) + { + ptrace = [ &, ® = lookup.reg(), + it = std::next( lookup.at ) ]( bitcnt_t bit_offset, bitcnt_t bit_count ) + { + symbolic::variable::register_t tmp = { + reg.flags, + reg.local_id, + bit_count, + reg.bit_offset + bit_offset + }; + return trace( { it, tmp } ); + }; + } + else + { + ptrace = [ &, &mem = lookup.mem(), + it = std::next( lookup.at ) ]( bitcnt_t bit_offset, bitcnt_t bit_count ) + { + fassert( !( ( bit_offset | bit_count ) & 7 ) ); + symbolic::variable::memory_t tmp = { + mem.decay() + bit_offset / 8, + bit_count + }; + return trace( { it, tmp } ); + }; + } + + // Redirect to partial resolver. + // + return resolve_partial( details, result_bcnt, ptrace ); + } + + // Create a lambda virtual machine and allocate a temporary result. + // + lambda_vm lvm; + symbolic::expression result = {}; + + lvm.hooks.read_register = [ & ] ( const register_desc& desc ) + { + return trace( { lookup.at, desc } ); + }; + lvm.hooks.read_memory = [ & ] ( const symbolic::expression& pointer, size_t byte_count ) + { + auto exp = trace( symbolic::variable{ + lookup.at,{ pointer, bitcnt_t( byte_count * 8 ) } + } ); + return exp.is_valid() ? exp.resize( result_bcnt ) : exp; + }; + lvm.hooks.write_register = [ & ] ( const register_desc& desc, symbolic::expression value ) + { + if ( desc == lookup.reg() ) + result = std::move( value ); + }; + + lvm.hooks.write_memory = [ & ] ( const symbolic::expression& pointer, symbolic::expression value ) + { + if ( pointer.equals( lookup.mem().decay() ) ) + result = std::move( value ); + }; + + // If access details are known: + // + if ( !details.is_unknown() ) + { + // Step one instruction, if result was successfuly captured, return. + // + if ( lvm.execute( *lookup.at ), result ) + return result; + } + // If they are unknown, fallthrough to fail. + // + else + { + #if VTIL_OPT_TRACE_VERBOSE + // Log the state. + // + log( "[Unknown symbolic state.]\n" ); + #endif + } + + // If we could not describe the behaviour, increment iterator and return. + // + ++lookup.at; + return lookup.to_expression(); + } + + // Traces a variable across the entire routine and tries to generates a symbolic expression + // for it at the specified point of the block. + // + symbolic::expression tracer::rtrace( symbolic::variable lookup ) + { + return rtrace_primitive( lookup, this, {} ); + } +}; \ No newline at end of file diff --git a/VTIL-Architecture/trace/tracer.hpp b/VTIL-Architecture/trace/tracer.hpp new file mode 100644 index 0000000..b3af152 --- /dev/null +++ b/VTIL-Architecture/trace/tracer.hpp @@ -0,0 +1,30 @@ +#pragma once +#include +#include "../symex/variable.hpp" + +namespace vtil +{ + struct tracer + { + // Traces a variable across the basic block it belongs to and generates a symbolic expression + // that describes it's value at the bound point. The provided variable should not contain a + // pointer with out-of-block expressions. + // + virtual symbolic::expression trace( symbolic::variable lookup ); + + // Traces a variable across the entire routine and tries to generates a symbolic expression + // for it at the specified point of the block. + // + virtual symbolic::expression rtrace( symbolic::variable lookup ); + + // Wrappers around the functions above that return expressions with the registers packed. + // + symbolic::expression trace_p( symbolic::variable lookup ) { return symbolic::variable::pack_all( trace( std::move( lookup ) ) ); } + symbolic::expression rtrace_p( symbolic::variable lookup ) { return symbolic::variable::pack_all( rtrace( std::move( lookup ) ) ); } + + // Operator() wraps trace_p and [] wraps rtrace_p. + // + auto operator()( symbolic::variable lookup ) { return trace_p( std::move( lookup ) ); } + auto operator[]( symbolic::variable lookup ) { return rtrace_p( std::move( lookup ) ); } + }; +}; \ No newline at end of file diff --git a/VTIL-Optimizer/analysis/cached_tracer.cpp b/VTIL-Optimizer/analysis/cached_tracer.cpp deleted file mode 100644 index 30a8fba..0000000 --- a/VTIL-Optimizer/analysis/cached_tracer.cpp +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright (c) 2020 Can Boluk and contributors of the VTIL Project -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// 3. Neither the name of mosquitto nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. -// -#include "cached_tracer.hpp" - -namespace vtil::optimizer -{ - // Replicate trace_basic with the addition of a cache lookup. - // - symbolic::expression cached_tracer::trace_basic_cached( const symbolic::variable& lookup, const trace_function_t& tracer ) - { - using namespace logger; - -#if VTIL_OPT_TRACE_VERBOSE - // Log the beginning of the trace. - // - log( "CcTrace(%s)\n", lookup ); - scope_padding _p( 1 ); -#endif - // Handle base case. - // - if ( lookup.at.is_begin() ) - { - symbolic::expression result = lookup.to_expression(); -#if VTIL_OPT_TRACE_VERBOSE - // Log result. - // - log( "= %s [Base case]\n", result ); -#endif - return result; - } - - // Try lookup the exact variable in the map in a fast manner. - // - auto it = cache.find( lookup ); - if ( it != cache.end() ) - { - const symbolic::expression& result = *it->second; -#if VTIL_OPT_TRACE_VERBOSE - // Log result. - // - log( "= %s [Cached result]\n", result ); -#endif - return result; - } - // Declare a predicate for the search of the variable in the cache. - // - std::function predicate; - - // If memory variable: - // - if ( lookup.is_memory() ) - { - predicate = [ & ] ( const cache_entry& pair ) - { - // Key must be of memory type at the same position. - // - if ( !pair.first.is_memory() ) return false; - if ( pair.first.at != lookup.at ) return false; - - // Must be the same pointer and have a larger or equal size. - // - auto& self = lookup.mem(); - auto& other = pair.first.mem(); - return self.decay().equals( other.decay() ) && - self.bit_count >= other.bit_count; - }; - } - // If register variable: - // - else - { - fassert( lookup.is_register() ); - predicate = [ & ] ( const cache_entry& pair ) - { - // Key must be of memory type at the same position. - // - if ( !pair.first.is_register() ) return false; - if ( pair.first.at != lookup.at ) return false; - - // Must be the same register and have a larger or equal size. - // - auto& self = lookup.reg(); - auto& other = pair.first.reg(); - return self.flags == other.flags && - self.local_id == other.local_id && - self.bit_offset == other.bit_offset && - self.bit_count >= other.bit_count; - }; - } - - // Search the map, if we find a matching entry shrink and use as the result. - // - symbolic::expression result; - it = std::find_if( cache.begin(), cache.end(), predicate ); - if ( it != cache.end() ) - result = symbolic::expression{ *it->second }.resize( lookup.bit_count() ); - else - result = trace_primitive( lookup, tracer ? tracer : *this ); - - // Insert a cache entry for the exact variable we're looking up and return. - // - cache.emplace( lookup, result ); -#if VTIL_OPT_TRACE_VERBOSE - // Log result. - // - log( "= %s\n", result ); -#endif - return result; - } - - // Wrappers of trace and rtrace with cached basic tracer. - // - symbolic::expression cached_tracer::trace( const symbolic::variable& lookup, bool pack ) - { - symbolic::expression&& result = trace_basic_cached( lookup ); - return pack ? symbolic::variable::pack_all( result ) : result; - } - symbolic::expression cached_tracer::rtrace( const symbolic::variable& lookup, bool pack ) - { - symbolic::expression&& result = rtrace_primitive( lookup, *this ); - return pack ? symbolic::variable::pack_all( result ) : result; - } -} \ No newline at end of file diff --git a/VTIL-Optimizer/analysis/trace.cpp b/VTIL-Optimizer/analysis/trace.cpp deleted file mode 100644 index f8f80ce..0000000 --- a/VTIL-Optimizer/analysis/trace.cpp +++ /dev/null @@ -1,447 +0,0 @@ -// Copyright (c) 2020 Can Boluk and contributors of the VTIL Project -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// 3. Neither the name of mosquitto nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. -// -#include -#include "trace.hpp" -#include "variable_aux.hpp" - -namespace vtil::optimizer -{ - // Basic tracer with the trace_function_t signature implemented using primitive tracer. - // - static symbolic::expression trace_basic( const symbolic::variable& lookup ) - { - using namespace logger; - -#if VTIL_OPT_TRACE_VERBOSE - // Log the beginning of the trace. - // - log( "Trace(%s)\n", lookup ); - scope_padding _p( 1 ); -#endif - - // If base case reached, convert to an expression and return as is, - // otherwise invoke primitive tracer. - // - symbolic::expression result = lookup.at.is_begin() - ? lookup.to_expression() - : trace_primitive( lookup, trace_basic ); - -#if VTIL_OPT_TRACE_VERBOSE - // Log result. - // - log( "= %s\n", result ); -#endif - return result; - }; - - // Propagates all variables in the reference expression onto the new iterator given using - // the query helper given (except of pointer propagation where basic tracer will be used). - // - Note: New iterator should be a connected block's end. - // - static symbolic::expression propagate( const symbolic::expression& ref, - const il_const_iterator& it, - const trace_function_t& tracer ) - { - using namespace logger; - scope_padding _p( 3 ); - - // Copy the reference expression. - // - symbolic::expression exp = ref; - - // For each unique variable: - // - std::set variables; - ref.count_unique_variables( &variables ); - for ( auto& uid : variables ) - { - // Move the variable to reference the previous block. - // - symbolic::variable var = uid.get(); - - // Skip if variable is position indepdendent or not at the beginning of the block. - // - if ( !var.at.is_valid() || !var.at.is_begin() ) - continue; - - // If register: - // - if ( var.is_register() ) - { - // Local temporary must not exist in an expression being propagated - // from the beginning of the block as that indicates use before assignment. - // Make sure this is not the case. - // - if ( var.reg().flags & register_local ) - error( "Local variable %s is used before value assignment.\n", var ); - - // If volatile iterator cannot be moved, skip. - // - if ( var.reg().flags & register_volatile ) - continue; - } - // If memory, propagate the pointer. - // - else if ( var.is_memory() ) - { - auto& pointer = var.mem().decay(); -#if VTIL_OPT_TRACE_VERBOSE - // Log original pointer. - // - log( "Propagating pointer: %s\n", pointer->to_string() ); -#endif - // Fail if propagation fails. - // - if ( !( pointer = propagate( pointer, it, trace_basic ) ) ) - return {}; - -#if VTIL_OPT_TRACE_VERBOSE - // Log new pointer. - // - log( "Pointer' => %s\n", pointer->to_string() ); -#endif - } - - // Move the assigned iterator. - // - var.at = it; - - // Trace the variable in the destination block, fail if it fails. - // - symbolic::expression var_traced = tracer( var ); - if ( !var_traced ) - return {}; - - // If we are tracing the value of RSP, add the stack pointer delta between blocks. - // - if ( var.is_register() && var.reg().is_stack_pointer() ) - var_traced = var_traced + it.container->sp_offset; - - // Rewrite the expression - // - exp.transform( [ & ] ( symbolic::expression& exp ) - { - if ( exp.is_variable() && exp.uid == uid ) - exp = var_traced; - } ); - } - - // Return the result. - // - return exp; - } - - // Traces a variable across the basic block it belongs to and generates a symbolic expression - // that describes it's value at the bound point. Will invoke the passed tracer for any additional - // tracing it requires. - // - symbolic::expression trace_primitive( symbolic::variable lookup, const trace_function_t& tracer ) - { - using namespace logger; - - // If null iterator or register with "no-trace" flags, return as is. - // - if ( !lookup.at.is_valid() || - ( lookup.is_register() && ( lookup.reg().flags & ( register_volatile | register_readonly ) ) ) ) - return lookup.to_expression(); - -#ifdef _DEBUG - // If memory, make sure pointer is expressed as a variable within current block. - // - if ( lookup.is_memory() ) - { - using validator_t = std::function; - static const std::function make_validator = [ ] ( const basic_block* container ) - { - return[ container = std::move( container ) ]( auto& exp ) - { - if ( exp.is_variable() ) - { - // Make sure it either has no iterator or belongs to the current container. - // - auto& var = exp.uid.get(); - fassert( !var.at.is_valid() || var.at.container == container ); - - // If memory variable, validate pointer as well. - // - if ( var.is_memory() ) - var.mem().decay().enumerate( make_validator( container ) ); - } - }; - }; - lookup.mem().decay().enumerate( make_validator( lookup.at.container ) ); - } -#endif - // Fast forward until iterator writes to the lookup, if none found return as is. - // - access_details details = {}; - while ( true ) - { - // If we reached the beginning without any modifications, redirect to the helper passed. - // - if ( lookup.at.is_begin() ) - return tracer( lookup ); - - // Decrement iterator. - // - --lookup.at; - - // If variable is being written to, break. - // - if ( details = test_access( lookup.at, lookup.descriptor, access_type::write, tracer ) ) - break; - } - - // If fails due to offset/size mismatch, invoke partial tracer. - // - bitcnt_t result_bcnt = lookup.bit_count(); - if ( !details.is_unknown() && - ( details.bit_offset != 0 || details.bit_count != result_bcnt ) ) - { - // Define partial tracer. - // - partial_tracer_t ptrace; - if ( lookup.is_register() ) - { - ptrace = [ &, ® = lookup.reg(), - it = std::next( lookup.at ) ] ( bitcnt_t bit_offset, bitcnt_t bit_count ) - { - symbolic::variable::register_t tmp = { - reg.flags, - reg.local_id, - bit_count, - reg.bit_offset + bit_offset - }; - return tracer( { it, tmp } ); - }; - } - else - { - ptrace = [ &, &mem = lookup.mem(), - it = std::next( lookup.at ) ] ( bitcnt_t bit_offset, bitcnt_t bit_count ) - { - fassert( !( ( bit_offset | bit_count ) & 7 ) ); - symbolic::variable::memory_t tmp = { - mem.decay() + bit_offset / 8, - bit_count - }; - return tracer( { it, tmp } ); - }; - } - - // Redirect to partial resolver. - // - return resolve_partial( details, result_bcnt, ptrace ); - } - - // Create a lambda virtual machine and allocate a temporary result. - // - lambda_vm lvm; - symbolic::expression result = {}; - - lvm.hooks.read_register = [ & ] ( const register_desc& desc ) - { - return tracer( { lookup.at, desc } ); - }; - lvm.hooks.read_memory = [ & ] ( const symbolic::expression& pointer, size_t byte_count ) - { - auto exp = tracer( symbolic::variable{ - lookup.at, { pointer, bitcnt_t( byte_count * 8 ) } - } ); - return exp.is_valid() ? exp.resize( result_bcnt ) : exp; - }; - lvm.hooks.write_register = [ & ] ( const register_desc& desc, symbolic::expression value ) - { - if ( desc == lookup.reg() ) - result = std::move( value ); - }; - - lvm.hooks.write_memory = [ & ] ( const symbolic::expression& pointer, symbolic::expression value ) - { - if ( pointer.equals( lookup.mem().decay() ) ) - result = std::move( value ); - }; - - // If access details are known: - // - if ( !details.is_unknown() ) - { - // Step one instruction, if result was successfuly captured, return. - // - if ( lvm.execute( *lookup.at ), result ) - return result; - } - // If they are unknown, fallthrough to fail. - // - else - { -#if VTIL_OPT_TRACE_VERBOSE - // Log the state. - // - log( "[Unknown symbolic state.]\n" ); -#endif - } - - // If we could not describe the behaviour, increment iterator and return. - // - ++lookup.at; - return lookup.to_expression(); - } - - // Traces a variable across the entire routine and generates a symbolic expression that describes - // it's value at the bound point. Will invoke the passed tracer for any additional tracing it requires. - // Takes an optional path history used internally to recurse in a controlled fashion. - // - symbolic::expression rtrace_primitive( const symbolic::variable& lookup, const trace_function_t& tracer, const path_history_t& history ) - { - using namespace logger; - - // Trace through the current block first. - // - symbolic::expression result = tracer( lookup ); - - // If result has any variables: - // - if ( result.count_unique_variables() != 0 ) - { - // Determine the paths we can take to iterate further. - // - std::vector it_list = lookup.at.is_valid() - ? lookup.at.recurse( false ) - : std::vector{}; - - // If there are paths take. - // - if ( !it_list.empty() ) - { -#if VTIL_OPT_TRACE_VERBOSE - // Log recursive tracing of the expression. - // - log( "Base case: %s\n", result ); -#endif - // Save current result as default result and clear it. - // - symbolic::expression default_result = {}; - std::swap( result, default_result ); - - // For each path: - // - for ( auto& it : it_list ) - { - // Create a local copy for the visited list for this path - // and increment the visit counter. - // - path_history_t history_local = history; - uint32_t& visit_counter = history_local[ { lookup.at.container, it.container } ]; - - // If we've taken this path more than twice, skip it. - // - if ( ++visit_counter > 2 ) - { -#if VTIL_OPT_TRACE_VERBOSE - // Log skipping of path. - // - log( "Path [%llx->%llx] is not taken as it's n-looping.\n", lookup.at.container->entry_vip, it.container->entry_vip ); -#endif - continue; - } - -#if VTIL_OPT_TRACE_VERBOSE - // Log tracing of path. - // - log( "Taking path [%llx->%llx]\n", lookup.at.container->entry_vip, it.container->entry_vip ); -#endif - // Propagate each variable onto to the destination block. - // - symbolic::expression exp = propagate( default_result, it, [ & ] ( auto& var ) - { - return rtrace_primitive( var, tracer, history_local ); - } ); - -#if VTIL_OPT_TRACE_VERBOSE - // Log result. - // - log( "= %s\n", exp ); -#endif - // If no result is set yet, assign the current expression. - // - if ( !result.is_valid() ) - result = exp; - - // If expression is invalid or not equal to previous result, fail. - // - if ( !exp.is_valid() || !exp.equals( result ) ) - { -#if VTIL_OPT_TRACE_VERBOSE - // Log decision. - // - log( "Halting tracer as it was not deterministic.\n" ); -#endif - // If result was null, return lookup. - // - if ( !exp.is_valid() ) - { - result = lookup.to_expression(); - } - // If it was mismatchign, return default result as branch dependant. - // - else - { - result = default_result; - result.transform( [ ] ( symbolic::expression& exp ) - { - if ( exp.is_variable() ) - exp.uid.get().is_branch_dependant = true; - }, false ); - } - break; - } - } - } - } -#if VTIL_OPT_TRACE_VERBOSE - // Log result. - // - log( "= %s\n", result ); -#endif - return result; - } - - // Simple wrappers around primitive trace and rtrace to return in packed format. - // - symbolic::expression trace( const symbolic::variable& lookup, bool pack ) - { - symbolic::expression&& result = trace_basic( lookup ); - return pack ? symbolic::variable::pack_all( result ) : result; - } - symbolic::expression rtrace( const symbolic::variable& lookup, bool pack ) - { - symbolic::expression&& result = rtrace_primitive( lookup, trace_basic ); - return pack ? symbolic::variable::pack_all( result ) : result; - } -}; \ No newline at end of file diff --git a/VTIL-Optimizer/analysis/variable_aux.cpp b/VTIL-Optimizer/analysis/variable_aux.cpp deleted file mode 100644 index cd7114b..0000000 --- a/VTIL-Optimizer/analysis/variable_aux.cpp +++ /dev/null @@ -1,270 +0,0 @@ -// Copyright (c) 2020 Can Boluk and contributors of the VTIL Project -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// 3. Neither the name of mosquitto nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. -// -#include "variable_aux.hpp" - -namespace vtil::optimizer -{ - // Makes a memory variable from the given instruction's src/dst, uses the tracer - // passed to resolve the absolute pointer. - // - symbolic::variable reference_memory( const il_const_iterator& it, const trace_function_t& tracer ) - { - fassert( it->base->accesses_memory() ); - - // Generate an expression for the pointer. - // - auto [base, offset] = it->get_mem_loc(); - symbolic::expression ptr = tracer( { it, base } ) + offset; - - // Create the variable type. - // - return { - it, - { ptr, bitcnt_t( it->access_size() * 8 ) }, - }; - } - - // Checks if the instruction given accesses the variable, optionally filtering to the - // access type specified, tracer passed will be used to generate pointers when needed. - // - access_details test_access( const il_const_iterator& it, const symbolic::variable::descriptor_t& var, access_type type, const trace_function_t& tracer ) - { - // If variable is of register type: - // - if ( auto reg = std::get_if( &var ) ) - { - // Iterate each operand: - // - for ( int i = 0; i < it->base->operand_count(); i++ ) - { - // Skip if not register. - // - if ( !it->operands[ i ].is_register() ) - continue; - - // Skip if access type does not match. - // - switch ( type ) - { - // ::read will filter to read or read/write. - // - case access_type::read: - if ( it->base->operand_types[ i ] == operand_type::write ) - continue; - break; - // ::write will filter to write or read/write. - // - case access_type::write: - if ( it->base->operand_types[ i ] < operand_type::write ) - continue; - break; - // ::readwrite will filter to only read/write. - // - case access_type::readwrite: - if ( it->base->operand_types[ i ] != operand_type::readwrite ) - continue; - break; - // ::none accepts any access. - // - case access_type::none: - break; - } - - // Skip if no overlap. - // - auto& ref_reg = it->operands[ i ].reg(); - if ( !ref_reg.overlaps( *reg ) ) - continue; - - // Return access details. - // - access_type type_found; - if ( it->base->operand_types[ i ] == operand_type::readwrite ) - type_found = access_type::readwrite; - else if ( it->base->operand_types[ i ] == operand_type::write ) - type_found = access_type::write; - else - type_found = access_type::read; - - return { - type_found, - ref_reg.bit_offset - reg->bit_offset, - ref_reg.bit_count - }; - } - } - // If variable is of memory type: - // - else if( auto mem = std::get_if( &var ) ) - { - // If instruction accesses memory: - // - if ( it->base->accesses_memory() ) - { - // Skip if access type does not match. - // - switch ( type ) - { - // ::read will filter to read. - // - case access_type::read: - if ( it->base->writes_memory() ) - return { access_type::none }; - break; - // ::write will filter to write. - // - case access_type::write: - if ( !it->base->writes_memory() ) - return { access_type::none }; - break; - // Read/write does not exist for memory operations. - // - case access_type::readwrite: - unreachable(); - // ::none accepts any access. - // - case access_type::none: - // Determine the type and set it. - // - type = it->base->writes_memory() ? access_type::write : access_type::read; - break; - } - - // Generate a pointer. - // - auto ref_mem = reference_memory( it, tracer ).mem(); - - // If the two pointers can overlap (not restrict qualified against each other): - // - if ( ref_mem.base.can_overlap( mem->base ) ) - { - // If it can be expressed as a constant: - // - if ( auto disp = ( ref_mem.base - mem->base ) ) - { - // Check if within boundaries: - // - int64_t low_offset = *disp; - int64_t high_offset = low_offset + it->access_size(); - if ( low_offset < ( mem->bit_count / 8 ) && high_offset > 0 ) - { - // Can safely multiply by 8 and shrink to bitcnt_t type from int64_t - // since variables are of maximum 64-bit size which means both offset - // and size will be small numbers. - // - return { - type, - bitcnt_t( low_offset * 8 ), - bitcnt_t( ( high_offset - low_offset ) * 8 ) - }; - } - } - // Otherwise, return unknown. - // - else - { - return { type, 0, -1 }; - } - } - } - } - - // No access case. - // - return { access_type::none }; - } - - // Given a partial tracer, this routine will determine the full value of the variable - // at the given position where a partial write was found. - // - symbolic::expression resolve_partial( const access_details& access, bitcnt_t bit_count, const partial_tracer_t& ptracer ) - { - using namespace logger; - - // Fetch the result of this operation. - // - symbolic::expression base = ptracer( access.bit_offset, access.bit_count ); - - // Trace a low part if we have to. - // - if ( access.bit_offset > 0 ) - { - bitcnt_t low_bcnt = access.bit_offset; - auto res = ptracer( 0, low_bcnt ); -#if VTIL_OPT_TRACE_VERBOSE - // Log the low and middle bits. - // - log( "dst[00..%02d] := %s\n", low_bcnt, res ); - log( "dst[%02d..%02d] := %s\n", access.bit_offset, access.bit_offset + access.bit_count, base ); -#endif - base = res | ( base.resize( bit_count ) << low_bcnt ); - } - // Shift the result if we have to. - // - else if ( access.bit_offset < 0 ) - { - base = ( base >> access.bit_offset ).resize( bit_count ); -#if VTIL_OPT_TRACE_VERBOSE - // Log the low bits after shifting. - // - log( "dst[00..%02d] := %s\n", access.bit_offset + access.bit_count, base ); -#endif - } - else - { -#if VTIL_OPT_TRACE_VERBOSE - // Log the low bits. - // - log( "dst[00..%02d] := %s\n", access.bit_offset + access.bit_count, base ); -#endif - } - - // Trace a high part if we have to. - // - if ( bit_count > ( access.bit_offset + access.bit_count ) ) - { - bitcnt_t high_bnct = bit_count - ( access.bit_offset + access.bit_count ); - auto res = ptracer( access.bit_offset + access.bit_count, high_bnct ); -#if VTIL_OPT_TRACE_VERBOSE - // Log the high bits. - // - log( "dst[%02d..%02d] := %s\n", access.bit_offset + access.bit_count, bit_count, res ); -#endif - base = base | ( res.resize( bit_count ) << ( access.bit_offset + access.bit_count ) ); - } - -#if VTIL_OPT_TRACE_VERBOSE - // Log the final result. - // - log( "dst := %s\n", base ); -#endif - // Resize and return. - // - return base.resize( bit_count ); - } -}; \ No newline at end of file diff --git a/VTIL-Optimizer/passes/normalize_stack.cpp b/VTIL-Optimizer/passes/normalize_stack.cpp index 016654e..9b19969 100644 --- a/VTIL-Optimizer/passes/normalize_stack.cpp +++ b/VTIL-Optimizer/passes/normalize_stack.cpp @@ -29,8 +29,6 @@ #include #include #include -#include "../analysis/cached_tracer.hpp" -#include "../analysis/variable_aux.hpp" namespace vtil::optimizer { @@ -60,8 +58,8 @@ namespace vtil::optimizer // Calculate the difference between current virtual stack pointer // and the next stack pointer instance. // - auto sp_curr = ctrace.trace( { it, REG_SP } ) + it->sp_offset; - auto sp_next = ctrace.trace( { std::next( it ), REG_SP } ); + auto sp_curr = ctrace( { it, REG_SP } ) + it->sp_offset; + auto sp_next = ctrace( { std::next( it ), REG_SP } ); // If it simplifies to a constant, replace with a stack shift. // @@ -102,8 +100,8 @@ namespace vtil::optimizer { // Try to simplify pointer to SP + C. // - auto delta = ctrace.trace( { it, it->get_mem_loc().first } ) - - ctrace.trace( { it, REG_SP } ); + auto delta = ctrace( { it, it->get_mem_loc().first } ) - + ctrace( { it, REG_SP } ); // If successful, replace the operands. // @@ -125,34 +123,36 @@ namespace vtil::optimizer // Wrap cached tracer with a filter that returns a constant pseudo-variable for each // register query representing $sp and rejects queries of registers. // - cached_tracer ctrace = {}; - trace_function_t lazy_tracer = [ & ] ( const symbolic::variable& lookup ) + struct lazy_tracer : cached_tracer { - // If register: - // - if ( lookup.is_register() ) + symbolic::expression trace( symbolic::variable lookup ) override { - // If stack pointer, return unique pseudo-register per stack instance. + // If register: // - if ( lookup.reg().is_stack_pointer() ) + if ( lookup.is_register() ) { - register_desc desc = { - register_local, - lookup.at->sp_index, - lookup.reg().bit_count - }; - return symbolic::variable{ lookup.at.container->begin(), desc }.to_expression(); + // If stack pointer, return unique pseudo-register per stack instance. + // + if ( lookup.reg().is_stack_pointer() ) + { + register_desc desc = { + register_local, + lookup.at->sp_index, + lookup.reg().bit_count + }; + return symbolic::variable{ lookup.at.container->begin(), desc }.to_expression(); + } + + // Otherwise, return without tracing. + // + return lookup.to_expression(); } - // Otherwise, return without tracing. + // Fallback to default tracer. // - return lookup.to_expression(); + return cached_tracer::trace( lookup ); } - - // Fallback to default tracer. - // - return ctrace.trace_basic_cached( lookup, lazy_tracer ); - }; + } tracer = {}; // => Begin a foward iterating query. // @@ -174,7 +174,8 @@ namespace vtil::optimizer // Lazy-trace the value. // - symbolic::expression exp = trace_primitive( reference_memory( it, lazy_tracer ), lazy_tracer ); + symbolic::pointer ptr = { tracer( { it, REG_SP } ) + it->get_mem_loc().second }; + symbolic::expression exp = tracer( { it, { ptr, bitcnt_t( it->access_size() * 8 ) } } ); // Resize and pack variables. // @@ -232,7 +233,7 @@ namespace vtil::optimizer // bool is_alive = !reg.is_volatile(); for ( auto it2 = access_point; !it2.is_end() && is_alive && it2 != it; it2++ ) - is_alive &= !test_access( it2, var.descriptor, access_type::write ); + is_alive &= !test_access( it2, var.descriptor, &tracer, access_type::write ); // If not, try hijacking the value declaration. // diff --git a/VTIL/includes/vtil/vtil b/VTIL/includes/vtil/vtil index 9ae0247..22f6238 100644 --- a/VTIL/includes/vtil/vtil +++ b/VTIL/includes/vtil/vtil @@ -2,5 +2,4 @@ #include #include #include -#include -#include \ No newline at end of file +#include \ No newline at end of file