Shifted all tracing logic to VTIL-Arch and merged headers.

This commit is contained in:
Can Bölük 2020-05-26 18:17:58 +02:00
parent 781dbbad36
commit 075679ba71
16 changed files with 828 additions and 943 deletions

View file

@ -15,7 +15,6 @@
<ClInclude Include="arch\instruction_set.hpp" />
<ClInclude Include="arch\operands.hpp" />
<ClInclude Include="arch\register_desc.hpp" />
<ClInclude Include="includes\vtil\vm" />
<ClInclude Include="misc\debug.hpp" />
<ClInclude Include="routine\basic_block.hpp" />
<ClInclude Include="routine\instruction.hpp" />
@ -24,6 +23,9 @@
<ClInclude Include="symex\memory.hpp" />
<ClInclude Include="symex\pointer.hpp" />
<ClInclude Include="symex\variable.hpp" />
<ClInclude Include="trace\auxiliaries.hpp" />
<ClInclude Include="trace\cached_tracer.hpp" />
<ClInclude Include="trace\tracer.hpp" />
<ClInclude Include="vm\lambda.hpp" />
<ClInclude Include="vm\symbolic.hpp" />
<ClInclude Include="vm\interface.hpp" />
@ -36,6 +38,9 @@
<ClCompile Include="routine\serialization.cpp" />
<ClCompile Include="symex\pointer.cpp" />
<ClCompile Include="symex\variable.cpp" />
<ClCompile Include="trace\auxiliaries.cpp" />
<ClCompile Include="trace\cached_tracer.cpp" />
<ClCompile Include="trace\tracer.cpp" />
<ClCompile Include="vm\symbolic.cpp" />
<ClCompile Include="vm\interface.cpp" />
</ItemGroup>

View file

@ -19,6 +19,9 @@
<Filter Include="SymEx Integration">
<UniqueIdentifier>{b070980d-a30d-421e-82c6-0298cfe01c0e}</UniqueIdentifier>
</Filter>
<Filter Include="Value Tracing">
<UniqueIdentifier>{6ba6b482-58aa-46cd-ad58-c0eef36c9333}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="arch\operands.hpp">
@ -63,12 +66,18 @@
<ClInclude Include="vm\symbolic.hpp">
<Filter>Virtual Machine</Filter>
</ClInclude>
<ClInclude Include="includes\vtil\vm">
<Filter>Includes</Filter>
</ClInclude>
<ClInclude Include="vm\lambda.hpp">
<Filter>Virtual Machine</Filter>
</ClInclude>
<ClInclude Include="trace\tracer.hpp">
<Filter>Value Tracing</Filter>
</ClInclude>
<ClInclude Include="trace\auxiliaries.hpp">
<Filter>Value Tracing</Filter>
</ClInclude>
<ClInclude Include="trace\cached_tracer.hpp">
<Filter>Value Tracing</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="arch\instruction_desc.cpp">
@ -98,6 +107,15 @@
<ClCompile Include="vm\symbolic.cpp">
<Filter>Virtual Machine</Filter>
</ClCompile>
<ClCompile Include="trace\tracer.cpp">
<Filter>Value Tracing</Filter>
</ClCompile>
<ClCompile Include="trace\auxiliaries.cpp">
<Filter>Value Tracing</Filter>
</ClCompile>
<ClCompile Include="trace\cached_tracer.cpp">
<Filter>Value Tracing</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="VTIL-Architecture.licenseheader" />

View file

@ -7,4 +7,10 @@
#include "../../routine/routine.hpp"
#include "../../routine/basic_block.hpp"
#include "../../routine/instruction.hpp"
#include "../../routine/serialization.hpp"
#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"

View file

@ -0,0 +1,3 @@
#include "../../trace/auxiliaries.hpp"
#include "../../trace/tracer.hpp"
#include "../../trace/cached_tracer.hpp"

View file

@ -1,3 +0,0 @@
#include "../../vm/interface.hpp"
#include "../../vm/symbolic.hpp"
#include "../../vm/lambda.hpp"

View file

@ -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<symbolic::variable::register_t>( &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<symbolic::variable::memory_t>( &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 };
}
};

View file

@ -26,18 +26,14 @@
// POSSIBILITY OF SUCH DAMAGE.
//
#pragma once
#include <vtil/arch>
#include <vtil/symex>
#include <vtil/io>
#include "trace.hpp"
#include <vtil/vm>
#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<symbolic::expression( bitcnt_t offset, bitcnt_t size )>;
// 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 );
};

View file

@ -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<CON_BRG>( "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<CON_BRG>( "= %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<CON_BLU>( "= %s [Cached result]\n", result );
#endif
return result;
}
// Declare a predicate for the search of the variable in the cache.
//
std::function<bool( const cache_entry& )> 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<CON_BRG>( "= %s\n", result );
#endif
return result;
}
};

View file

@ -26,19 +26,18 @@
// POSSIBILITY OF SUCH DAMAGE.
//
#pragma once
#include <functional>
#include <map>
#include <vtil/vm>
#include "trace.hpp"
#include <unordered_map>
#include <vtil/utility>
#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 ); }; }
};
};

View file

@ -0,0 +1,455 @@
#include "tracer.hpp"
#include <vtil/io>
#include "../vm/lambda.hpp"
#include "auxiliaries.hpp"
namespace vtil
{
// Internal type definitions.
//
using partial_tracer_t = std::function<symbolic::expression( bitcnt_t offset, bitcnt_t size )>;
using path_history_t = std::map<std::pair<const basic_block*, const basic_block*>, 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<CON_RED>( "dst[00..%02d] := %s\n", low_bcnt, res );
log<CON_YLW>( "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<CON_YLW>( "dst[00..%02d] := %s\n", access.bit_offset + access.bit_count, base );
#endif
}
else
{
#if VTIL_OPT_TRACE_VERBOSE
// Log the low bits.
//
log<CON_YLW>( "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<CON_PRP>( "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<CON_GRN>( "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<symbolic::unique_identifier> variables;
ref.count_unique_variables( &variables );
for ( auto& uid : variables )
{
// Move the variable to reference the previous block.
//
symbolic::variable var = uid.get<symbolic::variable>();
// 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<CON_PRP>( "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<CON_PRP>( "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<il_const_iterator>{};
// If there are paths take.
//
if ( !it_list.empty() )
{
#if VTIL_OPT_TRACE_VERBOSE
// Log recursive tracing of the expression.
//
log<CON_GRN>( "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<CON_CYN>( "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<CON_YLW>( "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<CON_BLU>( "= %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<CON_RED>( "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<symbolic::variable>().is_branch_dependant = true;
}, false );
}
break;
}
}
}
}
#if VTIL_OPT_TRACE_VERBOSE
// Log result.
//
log<CON_BRG>( "= %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<void( const symbolic::expression& )>;
static const std::function<validator_t( const basic_block* )> 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<symbolic::variable>();
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 = [ &, &reg = 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<CON_RED>( "[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, {} );
}
};

View file

@ -0,0 +1,30 @@
#pragma once
#include <vtil/symex>
#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 ) ); }
};
};

View file

@ -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<CON_BRG>( "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<CON_BRG>( "= %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<CON_BLU>( "= %s [Cached result]\n", result );
#endif
return result;
}
// Declare a predicate for the search of the variable in the cache.
//
std::function<bool( const cache_entry& )> 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<CON_BRG>( "= %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;
}
}

View file

@ -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 <vtil/vm>
#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<CON_BRG>( "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<CON_BRG>( "= %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<symbolic::unique_identifier> variables;
ref.count_unique_variables( &variables );
for ( auto& uid : variables )
{
// Move the variable to reference the previous block.
//
symbolic::variable var = uid.get<symbolic::variable>();
// 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<CON_PRP>( "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<CON_PRP>( "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<void( const symbolic::expression& )>;
static const std::function<validator_t( const basic_block* )> 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<symbolic::variable>();
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 = [ &, &reg = 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<CON_RED>( "[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<il_const_iterator>{};
// If there are paths take.
//
if ( !it_list.empty() )
{
#if VTIL_OPT_TRACE_VERBOSE
// Log recursive tracing of the expression.
//
log<CON_GRN>( "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<CON_CYN>( "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<CON_YLW>( "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<CON_BLU>( "= %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<CON_RED>( "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<symbolic::variable>().is_branch_dependant = true;
}, false );
}
break;
}
}
}
}
#if VTIL_OPT_TRACE_VERBOSE
// Log result.
//
log<CON_BRG>( "= %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;
}
};

View file

@ -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<symbolic::variable::register_t>( &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<symbolic::variable::memory_t>( &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<CON_RED>( "dst[00..%02d] := %s\n", low_bcnt, res );
log<CON_YLW>( "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<CON_YLW>( "dst[00..%02d] := %s\n", access.bit_offset + access.bit_count, base );
#endif
}
else
{
#if VTIL_OPT_TRACE_VERBOSE
// Log the low bits.
//
log<CON_YLW>( "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<CON_PRP>( "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<CON_GRN>( "dst := %s\n", base );
#endif
// Resize and return.
//
return base.resize( bit_count );
}
};

View file

@ -29,8 +29,6 @@
#include <vector>
#include <vtil/query>
#include <vtil/symex>
#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.
//

View file

@ -2,5 +2,4 @@
#include <vtil/common>
#include <vtil/symex>
#include <vtil/arch>
#include <vtil/optimizer>
#include <vtil/vm>
#include <vtil/optimizer>