mirror of
https://github.com/vtil-project/VTIL-Core
synced 2026-08-17 08:23:03 -04:00
353 lines
11 KiB
C++
353 lines
11 KiB
C++
// 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 VTIL Project 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 "pass_validation.hpp"
|
|
|
|
namespace vtil::optimizer::validation
|
|
{
|
|
// Helper routine used to compare routine behaviour against expected behaviour.
|
|
//
|
|
bool verify_symbolic( const routine* rtn, const std::vector<uint64_t>& parameters, const std::vector<observable_action>& action_log )
|
|
{
|
|
auto action_it = action_log.begin();
|
|
auto action_end = action_log.end();
|
|
|
|
// Create the symbolic virtual machine.
|
|
//
|
|
lambda_vm<symbolic_vm> vm = {};
|
|
|
|
// Write default register state.
|
|
//
|
|
for ( auto& [k, v] : default_register_state )
|
|
vm.write_register( k, v );
|
|
|
|
// Write the parameters.
|
|
//
|
|
const call_convention& call_conv = rtn->routine_convention;
|
|
auto rit = call_conv.param_registers.begin();
|
|
for ( auto [value, id] : zip( parameters, iindices() ) )
|
|
{
|
|
// If we did not reach the end of registers yet:
|
|
//
|
|
if ( rit != call_conv.param_registers.end() )
|
|
{
|
|
vm.write_register( *rit++, value );
|
|
}
|
|
// Otherwise, write into the stack.
|
|
//
|
|
else
|
|
{
|
|
// Calculate the surplus index.
|
|
//
|
|
size_t idx = id - call_conv.param_registers.size();
|
|
|
|
// Calculate the address on stack and write into it.
|
|
//
|
|
vm.write_memory(
|
|
vm.read_register( REG_SP ) + ( idx * 8 ) + call_conv.shadow_space + 8,
|
|
value
|
|
);
|
|
}
|
|
}
|
|
|
|
// Write the return address, any random will work so let's
|
|
// use a pointer off of our own stack.
|
|
//
|
|
const uint64_t return_address = ( uint64_t ) &vm;
|
|
vm.write_memory( vm.read_register( REG_SP ), return_address );
|
|
|
|
// Instrument the virtual execution to verify actions:
|
|
//
|
|
bool success = true;
|
|
vm.hooks.execute = [ & ] ( const instruction& ins )
|
|
{
|
|
// If failed already, exit.
|
|
//
|
|
if ( !success ) return false;
|
|
|
|
// If hint is hit, skip.
|
|
//
|
|
if ( *ins.base == ins::vpinr ) return true;
|
|
if ( *ins.base == ins::vpinw ) return true;
|
|
|
|
// If a virtual branch is hit, exit the virtual machine so we can handle it.
|
|
//
|
|
if ( ins.base->is_branching_virt() )
|
|
return false;
|
|
|
|
// If branching to real location:
|
|
//
|
|
if ( ins.base->is_branching_real() )
|
|
{
|
|
// If external call:
|
|
//
|
|
if ( ins.base == &ins::vxcall )
|
|
{
|
|
// If was not expected, fail and exit the virtual machine.
|
|
//
|
|
if ( action_it == action_end || !std::get_if<external_call>( &*action_it ) )
|
|
{
|
|
logger::warning( "Unexpected call." );
|
|
success = false;
|
|
return false;
|
|
}
|
|
|
|
// Pop it off the stack.
|
|
//
|
|
const external_call& call = std::get<external_call>( *action_it );
|
|
++action_it;
|
|
|
|
// Validate target.
|
|
//
|
|
auto target_call = ins.operands[ 0 ].is_immediate()
|
|
? symbolic::expression::reference{ ins.operands[ 0 ].imm().u64 }
|
|
: vm.read_register( ins.operands[ 0 ].reg() );
|
|
if ( target_call->value.get() != call.address )
|
|
{
|
|
logger::warning( "Unexpected callee, expected 0x%llx, got [%s].", call.address, *target_call );
|
|
success = false;
|
|
return false;
|
|
}
|
|
|
|
// Validate parameters.
|
|
//
|
|
const call_convention& call_conv = rtn->get_cconv( ins.vip );
|
|
auto it = call_conv.param_registers.begin();
|
|
for ( auto [value, id] : zip( call.parameters, iindices() ) )
|
|
{
|
|
symbolic::expression::reference exp;
|
|
|
|
// If we did not reach the end of registers yet:
|
|
//
|
|
if ( it != call_conv.param_registers.end() )
|
|
{
|
|
// Read from the register and increment iterator.
|
|
//
|
|
exp = vm.read_register( *it );
|
|
it++;
|
|
}
|
|
else
|
|
{
|
|
// Calculate the surplus index.
|
|
//
|
|
size_t idx = id - call_conv.param_registers.size();
|
|
|
|
// Calculate the address on stack and read from it.
|
|
//
|
|
exp = vm.read_memory( vm.read_register( REG_SP ) + ( idx * 8 ) + call_conv.shadow_space + 8, 8 );
|
|
}
|
|
|
|
// Fail if value does not match.
|
|
//
|
|
if ( exp->value.get() != value )
|
|
{
|
|
logger::warning( "Parameter %d does not match, expected 0x%llx, got [%s].", id, value, exp );
|
|
success = false;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Write the simulated return value.
|
|
//
|
|
for ( auto [value, target] : zip( call.fake_result, call_conv.retval_registers ) )
|
|
vm.write_register( target, value );
|
|
}
|
|
// If we're exiting the virtual machine:
|
|
//
|
|
else if ( ins.base == &ins::vexit )
|
|
{
|
|
// If was not expected, fail and exit the virtual machine.
|
|
//
|
|
if ( action_it == action_end || !std::get_if<vm_exit>( &*action_it ) )
|
|
{
|
|
logger::warning( "Unexpected exit." );
|
|
success = false;
|
|
return false;
|
|
}
|
|
|
|
// Pop it off the stack.
|
|
//
|
|
vm_exit exit = std::get<vm_exit>( *action_it );
|
|
++action_it;
|
|
|
|
// Validate return address.
|
|
//
|
|
auto sreturn_address = ins.operands[ 0 ].is_immediate()
|
|
? symbolic::expression::reference{ ins.operands[ 0 ].imm().u64 }
|
|
: vm.read_register( ins.operands[ 0 ].reg() );
|
|
if ( sreturn_address->value.get() != return_address )
|
|
{
|
|
logger::warning( "Unexpected return address, expected 0x%llx, got [%s].", return_address, *sreturn_address );
|
|
success = false;
|
|
return false;
|
|
}
|
|
|
|
// Validate the register state / return value.
|
|
//
|
|
for ( auto& [reg, value] : exit.register_state )
|
|
{
|
|
auto exp = vm.read_register( reg );
|
|
if ( exp->value.get() != value )
|
|
{
|
|
logger::warning( "Return state %s does not match, expected 0x%llx, got [%s].", reg, value, exp );
|
|
success = false;
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// If none matches, redirect to original handler.
|
|
//
|
|
return vm.symbolic_vm::execute( ins );
|
|
};
|
|
|
|
vm.hooks.read_memory = [ & ] ( const symbolic::expression::reference& pointer, size_t sz )
|
|
{
|
|
// If action log has a matching read memory on top of the stack:
|
|
//
|
|
if ( action_it != action_end && std::get_if<memory_read>( &*action_it ) )
|
|
{
|
|
auto& mem = std::get<memory_read>( *action_it );
|
|
if ( pointer->value.get() == mem.address )
|
|
{
|
|
// Write fake value to the state and pop the stack.
|
|
//
|
|
symbolic::expression value = { mem.fake_value, mem.size };
|
|
vm.symbolic_vm::write_memory( pointer, value );
|
|
++action_it;
|
|
}
|
|
}
|
|
|
|
return vm.symbolic_vm::read_memory( pointer, sz );
|
|
};
|
|
|
|
vm.hooks.write_memory = [ & ] ( const symbolic::expression::reference& pointer, symbolic::expression::reference exp )
|
|
{
|
|
// If action log has a matching write memory on top of the stack:
|
|
//
|
|
if ( action_it != action_end && std::get_if<memory_write>( &*action_it ) )
|
|
{
|
|
auto& mem = std::get<memory_write>( *action_it );
|
|
if ( pointer->value.get() == mem.address )
|
|
{
|
|
// Pop the stack and validate the value.
|
|
//
|
|
if ( exp->value.get() != mem.value )
|
|
{
|
|
logger::warning( "Unexpected memory write into 0x%llx, expected 0x%llx, got [%s].", mem.address, mem.value, *exp );
|
|
success = false;
|
|
}
|
|
++action_it;
|
|
}
|
|
}
|
|
return vm.symbolic_vm::write_memory( pointer, exp );
|
|
};
|
|
|
|
|
|
// Begin from the entry point:
|
|
//
|
|
il_const_iterator it = rtn->entry_point->begin();
|
|
while ( true )
|
|
{
|
|
// Run until it VM exits.
|
|
//
|
|
auto lim = vm.run( it, true );
|
|
|
|
// If failed, return.
|
|
//
|
|
if ( !success ) return false;
|
|
|
|
// If we've reached the end of the virtual machine:
|
|
//
|
|
if ( lim.is_end() )
|
|
{
|
|
// If we have a single continue destination (VXCALL), fix iterator and the stack, continue.
|
|
//
|
|
size_t num_continue_dst = lim.container->next.size();
|
|
if ( num_continue_dst == 1 )
|
|
{
|
|
it = lim.container->next[ 0 ]->begin();
|
|
vm.write_register( REG_SP, vm.read_register( REG_SP ) + lim.container->sp_offset );
|
|
continue;
|
|
}
|
|
// If we've reached the end of the routine (VEXIT), signal success if all actions are complete, or fail.
|
|
//
|
|
else if ( num_continue_dst == 0 )
|
|
{
|
|
return action_it == action_end;
|
|
}
|
|
unreachable();
|
|
}
|
|
|
|
// If unhandled instruction is branching into virtual location:
|
|
//
|
|
if ( lim->base->is_branching_virt() )
|
|
{
|
|
// Determine the destination.
|
|
//
|
|
operand dst = {};
|
|
if ( lim->base == &ins::js )
|
|
dst = *vm.read_register( lim->operands[ 0 ].reg() )->get<bool>() ? lim->operands[ 1 ] : lim->operands[ 2 ];
|
|
else if ( lim->base == &ins::jmp )
|
|
dst = lim->operands[ 0 ];
|
|
|
|
// If operand is an immediate, use as is:
|
|
//
|
|
auto eit = rtn->explored_blocks.end();
|
|
if ( dst.is_immediate() )
|
|
eit = rtn->explored_blocks.find( dst.imm().u64 );
|
|
// Otherwise read VM context.
|
|
//
|
|
else if ( auto jmp_dst = vm.read_register( dst.reg() )->get() )
|
|
eit = rtn->explored_blocks.find( *jmp_dst );
|
|
|
|
// If no valid destination, fail.
|
|
//
|
|
if ( eit == rtn->explored_blocks.end() )
|
|
{
|
|
logger::warning( "Invalid virtual jump." );
|
|
return false;
|
|
}
|
|
|
|
// Fix iterator and the stack, continue.
|
|
//
|
|
it = eit->second->begin();
|
|
vm.write_register( REG_SP, vm.read_register( REG_SP ) + lim.container->sp_offset );
|
|
continue;
|
|
}
|
|
|
|
logger::warning( "Failing execution at: %s\n", lim->to_string() );
|
|
return false;
|
|
}
|
|
|
|
// Purge simplifier cache.
|
|
//
|
|
symbolic::purge_simplifier_cache();
|
|
}
|
|
};
|