Merged with VTIL-Project Common quickly, still WIP.

This commit is contained in:
Can Bölük 2020-04-17 17:03:53 +02:00
parent 8b0cbfb0c5
commit b4921c7784
33 changed files with 1328 additions and 4368 deletions

18
VTIL-Architecture/.gitignore vendored Normal file
View file

@ -0,0 +1,18 @@
*.db
*.ipch
*.opendb
*.user
*.log
*.exe
*.tlog
*.obj
*.exp
*.pdb
*.lib
*.suo
*.ilk
*.db-shm
*.db-wal
.vs/
x64/Debug/
x64/Release/

View file

@ -0,0 +1,28 @@
extensions: .hpp .cpp .h .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 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.
//

View file

@ -0,0 +1,66 @@
// 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 "control_registers.hpp"
namespace vtil::arch
{
// Global list of control registers and the mutex protecting it.
//
static std::mutex control_register_list_mutex = {};
static std::vector<control_register_desc> control_register_list = {};
// Looks up the descriptor for the given control register.
//
std::optional<control_register_desc> lookup_control_register( x86_reg reg )
{
std::lock_guard g( control_register_list_mutex );
// Calculate the index and lookup the global list
//
size_t index = reg - X86_REG_VCR0;
if ( control_register_list.size() <= index )
return std::nullopt;
return control_register_list[ index ];
}
// Creates a new control register based on the descriptor and returns the
// x86_reg value that it is mapped to.
//
x86_reg create_control_register( const control_register_desc& descriptor )
{
std::lock_guard g( control_register_list_mutex );
// Calculate the index we will place this register at
// push it up the list and then return the equivalent
// x86_reg value
//
size_t index = control_register_list.size();
control_register_list.push_back( descriptor );
return X86_REG_VCR( index );
}
};

View file

@ -1,7 +1,34 @@
// 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.
//
#pragma once
#include <mutex>
#include <optional>
#include <capstone.hpp>
#include <vtil/amd64>
// The x86_reg value that equates to the first user-defined control register
// and a handy macro to get the value for Nth instance.
@ -24,38 +51,12 @@ namespace vtil::arch
bool read_only = false;
};
// Global list of control registers and the mutex protecting it.
//
static std::mutex control_register_list_mutex;
static std::vector<control_register_desc> control_register_list;
// Looks up the descriptor for the given control register.
//
static std::optional<control_register_desc> lookup_control_register( x86_reg reg )
{
std::lock_guard g( control_register_list_mutex );
// Calculate the index and lookup the global list
//
size_t index = reg - X86_REG_VCR0;
if ( control_register_list.size() <= index )
return std::nullopt;
return control_register_list[ index ];
}
std::optional<control_register_desc> lookup_control_register( x86_reg reg );
// Creates a new control register based on the descriptor and returns the
// x86_reg value that it is mapped to.
//
static x86_reg create_control_register( const control_register_desc& descriptor )
{
std::lock_guard g( control_register_list_mutex );
// Calculate the index we will place this register at
// push it up the list and then return the equivalent
// x86_reg value
//
size_t index = control_register_list.size();
control_register_list.push_back( descriptor );
return X86_REG_VCR( index );
}
x86_reg create_control_register( const control_register_desc& descriptor );
};

View file

@ -0,0 +1,64 @@
// 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 "instruction_desc.hpp"
namespace vtil::arch
{
// Generic data-assignment constructor with certain validity checks.
//
instruction_desc::instruction_desc( const std::string& name,
const std::vector<operand_access>& access_types,
int access_size_index,
bool is_volatile,
const std::string& symbolic_operator,
std::vector<int> branch_operands,
const std::pair<int, bool>& memory_operands ) :
name( name ), access_types( access_types ), access_size_index( access_size_index - 1 ),
is_volatile( is_volatile ), symbolic_operator( symbolic_operator ),
memory_operand_index( memory_operands.first - 1 ), memory_write( memory_operands.second )
{
fassert( operand_count() <= max_operand_count );
// Validate all operand indices.
//
fassert( access_size_index == 0 || abs( access_size_index ) <= operand_count() );
fassert( memory_operands.first == 0 || abs( memory_operands.first ) <= operand_count() );
for ( int op : branch_operands )
fassert( op != 0 && abs( op ) <= operand_count() );
// Process branch operands.
//
for ( int op : branch_operands )
{
if ( op > 0 )
branch_operands_vip.push_back( op - 1 );
else
branch_operands_rip.push_back( -op - 1 );
}
}
};

View file

@ -0,0 +1,144 @@
// 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.
//
#pragma once
#include <string>
#include <vector>
#include "operands.hpp"
namespace vtil::arch
{
// Maximum operand count.
//
static constexpr size_t max_operand_count = 4;
// Describes the way an instruction acceses it's operands and the
// constraints built around that, such as "immediate only" implied
// by the "_imm" suffix.
//
enum operand_access : uint8_t
{
// Note:
// It still is valid to do != write for read and >= write for writes.
// this operand access type is illegal to use outside of function arguments.
//
invalid = 0,
// Read group:
//
read_imm,
read_reg,
read_any,
read = read_any,
// Write group:
// - Implicit "_reg" as we cannot write into an immediate
//
write,
readwrite
};
// Instruction descriptors are used to describe each unique instruction
// in the VTIL instruction set. This type should be only constructed
// as a global constant. For the sake of consistency all operand indices
// passed to the constructor start from 1. [Ref: branch_operands desc.]
//
struct instruction_desc
{
// Name of the instruction.
//
std::string name;
// List of the access types for each operand.
//
std::vector<operand_access> access_types;
// Index of the operand that determines the instruction's
// access size property.
//
int access_size_index = 0;
// Whether the instruction is volatile or not meaning it
// should not be discarded even if it is no-op or dead.
//
bool is_volatile = false;
// A pointer to the expression operator that describes the
// operation of this instruction if applicable.
//
std::string symbolic_operator = "";
// List of operands that are thread as branching destinations.
// - In the constructor version negative indices are used to
// indicate "real" destinations and thus for the sake of
// simplicity indices start from 1.
//
std::vector<int> branch_operands_rip = {};
std::vector<int> branch_operands_vip = {};
// Operand that marks the beginning of a memory reference and whether
// it writes to the pointer or not. [Idx] must be a register and [Idx+1]
// must be an immediate.
//
int memory_operand_index = -1;
bool memory_write = false;
// Generic data-assignment constructor with certain validity checks.
//
instruction_desc( const std::string& name,
const std::vector<operand_access>& access_types,
int access_size_index,
bool is_volatile,
const std::string& symbolic_operator,
std::vector<int> branch_operands,
const std::pair<int, bool>& memory_operands );
// Number of operands this instruction has.
//
int operand_count() const { return access_types.size(); }
// Whether the instruction branches for not.
//
bool is_branching_virt() const { return !branch_operands_vip.empty(); }
bool is_branching_real() const { return !branch_operands_rip.empty(); }
bool is_branching() const { return is_branching_virt() || is_branching_real(); }
// Whether the instruction acceses/reads/writes memory or not.
//
bool reads_memory() const { return accesses_memory() && !memory_write; }
bool writes_memory() const { return accesses_memory() && memory_write; }
bool accesses_memory() const { return memory_operand_index != -1; }
// Conversion to human-readable format.
//
std::string to_string( uint8_t access_size ) const
{
if ( !access_size ) return name;
return name + ( char ) format::suffix_map[ access_size ];
}
};
};

View file

@ -1,154 +1,36 @@
// 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.
//
#pragma once
#include <string>
#include <vector>
#include <platform.hpp>
#include "operands.hpp"
#include "..\misc\format.hpp"
#include "..\symbolic\operators.hpp"
#include "instruction_desc.hpp"
namespace vtil::arch
{
// Maximum operand count.
//
static constexpr size_t max_operand_count = 4;
// Describes the way an instruction acceses it's operands and the
// constraints built around that, such as "immediate only" implied
// by the "_imm" suffix.
//
enum operand_access : uint8_t
{
// Note:
// It still is valid to do != write for read and >= write for writes.
// this operand access type is illegal to use outside of function arguments.
//
invalid = 0,
// Read group:
//
read_imm,
read_reg,
read_any,
read = read_any,
// Write group:
// - Implicit "_reg" as we cannot write into an immediate
//
write,
readwrite
};
// Instruction descriptors are used to describe each unique instruction
// in the VTIL instruction set. This type should be only constructed
// as a global constant. For the sake of consistency all operand indices
// passed to the constructor start from 1. [Ref: branch_operands desc.]
//
struct instruction_desc
{
// List of all instances, to be filled by the constructor.
//
static std::vector<const instruction_desc*> list;
// Name of the instruction.
//
std::string name;
// List of the access types for each operand.
//
std::vector<operand_access> access_types;
// Index of the operand that determines the instruction's
// access size property.
//
int access_size_index = 0;
// Whether the instruction is volatile or not meaning it
// should not be discarded even if it is no-op or dead.
//
bool is_volatile = false;
// A pointer to the expression operator that describes the
// operation of this instruction if applicable.
//
std::string symbolic_operator = "";
// List of operands that are thread as branching destinations.
// - In the constructor version negative indices are used to
// indicate "real" destinations and thus for the sake of
// simplicity indices start from 1.
//
std::vector<int> branch_operands_rip = {};
std::vector<int> branch_operands_vip = {};
// Operand that marks the beginning of a memory reference and whether
// it writes to the pointer or not. [Idx] must be a register and [Idx+1]
// must be an immediate.
//
int memory_operand_index = -1;
bool memory_write = false;
// Constructor of this structure will push a reference to
// itself up the global ::list, which implies any construction
// should take place in a global context.
//
instruction_desc( const std::string& name,
const std::vector<operand_access>& access_types,
int access_size_index,
bool is_volatile,
const std::string& symbolic_operator,
std::vector<int> branch_operands,
const std::pair<int, bool>& memory_operands ) :
name( name ), access_types( access_types ), access_size_index( access_size_index - 1 ),
is_volatile( is_volatile ), symbolic_operator( symbolic_operator ),
memory_operand_index( memory_operands.first - 1 ), memory_write( memory_operands.second )
{
list.push_back( this );
fassert( operand_count() <= max_operand_count );
// Validate all operand indices.
//
fassert( access_size_index == 0 || abs( access_size_index ) <= operand_count() );
fassert( memory_operands.first == 0 || abs( memory_operands.first ) <= operand_count() );
for ( int op : branch_operands )
fassert( op != 0 && abs( op ) <= operand_count() );
// Process branch operands.
//
for ( int op : branch_operands )
{
if ( op > 0 )
branch_operands_vip.push_back( op - 1 );
else
branch_operands_rip.push_back( -op - 1 );
}
}
// Number of operands this instruction has.
//
int operand_count() const { return access_types.size(); }
// Whether the instruction branches for not.
//
bool is_branching_virt() const { return !branch_operands_vip.empty(); }
bool is_branching_real() const { return !branch_operands_rip.empty(); }
bool is_branching() const { return is_branching_virt() || is_branching_real(); }
// Whether the instruction acceses/reads/writes memory or not.
//
bool reads_memory() const { return accesses_memory() && !memory_write; }
bool writes_memory() const { return accesses_memory() && memory_write; }
bool accesses_memory() const { return memory_operand_index != -1; }
// Conversion to human-readable format.
//
std::string to_string( uint8_t access_size ) const
{
if ( !access_size ) return name;
return name + ( char ) format::suffix_map[ access_size ];
}
};
std::vector<const instruction_desc*> instruction_desc::list = {};
namespace ins
{
// -- Data/Memory instructions
@ -217,6 +99,24 @@ namespace vtil::arch
static const instruction_desc bror = { "ror", { readwrite, read_any }, 1, false, "ror", {}, {} };
static const instruction_desc brol = { "rol", { readwrite, read_any }, 1, false, "rol", {}, {} };
/*-----------------------------------------------------------------------------------------------------------------------------------------------------------------------*/
// -- Flag-creation instructions
//
// SETS Reg | OP1 = EFLAGS(OP2)[SF]
// SETZ Reg | OP1 = EFLAGS(OP2)[ZF]
// SETP Reg | OP1 = EFLAGS(OP2)[PF]
// SETC Reg | OP1 = EFLAGS(OP2)[CF]
// SETO Reg | OP1 = EFLAGS(OP2)[OF]
//
/*-----------------------------------------------------------------------------------------------------------------------------------------------------------------------*/
/* [Name] [Operands...] [ASizeOp] [Volatile] [Operator] [BranchOps] [MemOps] */
static const instruction_desc sets = { "sets", { write, read_any }, 1, false, "sets", {}, {} };
static const instruction_desc setz = { "setz", { write, read_any }, 1, false, "setz", {}, {} };
static const instruction_desc setp = { "setp", { write, read_any }, 1, false, "setp", {}, {} };
static const instruction_desc setc = { "setc", { write, read_any }, 1, false, "setc", {}, {} };
static const instruction_desc seto = { "seto", { write, read_any }, 1, false, "seto", {}, {} };
/*-----------------------------------------------------------------------------------------------------------------------------------------------------------------------*/
// -- Control flow instructions
//
@ -227,10 +127,10 @@ namespace vtil::arch
//
/*-----------------------------------------------------------------------------------------------------------------------------------------------------------------------*/
/* [Name] [Operands...] [ASizeOp] [Volatile] [Operator] [BranchOps] [MemOps] */
static const instruction_desc js = { "js", { read_reg, read_any, read_any }, 2, true, {}, { 1, 2 }, {} };
static const instruction_desc jmp = { "jmp", { read_any }, 1, true, {}, { 1 }, {} };
static const instruction_desc vexit = { "vexit", { read_any }, 1, true, {}, { -1 }, {} };
static const instruction_desc vxcall = { "vxcall", { read_any }, 1, true, {}, {}, {} };
static const instruction_desc js = { "js", { read_reg, read_any, read_any }, 2, true, {}, { 1, 2 }, {} };
static const instruction_desc jmp = { "jmp", { read_any }, 1, true, {}, { 1 }, {} };
static const instruction_desc vexit = { "vexit", { read_any }, 1, true, {}, { -1 }, {} };
static const instruction_desc vxcall = { "vxcall", { read_any }, 1, true, {}, {}, {} };
/*-----------------------------------------------------------------------------------------------------------------------------------------------------------------------*/
// -- Special instructions
@ -256,4 +156,15 @@ namespace vtil::arch
static const instruction_desc vpinwm = { "vpinwm", { read_reg, read_imm }, 1, true, {}, {}, { 1, true } };
/*-----------------------------------------------------------------------------------------------------------------------------------------------------------------------*/
};
// List of all instructions.
//
static const instruction_desc* instruction_list[] =
{
&ins::mov, &ins::movr, &ins::str, &ins::ldd, &ins::neg, &ins::add, &ins::sub, &ins::mul,
&ins::imul, &ins::mulhi, &ins::imulhi, &ins::div, &ins::idiv, &ins::rem, &ins::irem, &ins::bnot,
&ins::bshr, &ins::bshl, &ins::bxor, &ins::bor, &ins::band, &ins::bror, &ins::brol, &ins::sets,
&ins::setz, &ins::setp, &ins::setc, &ins::seto, &ins::js, &ins::jmp, &ins::vexit, &ins::vxcall,
&ins::nop, &ins::vcmp0, &ins::vsetcc, &ins::vemit, &ins::vpinr, &ins::vpinw, &ins::vpinrm, &ins::vpinwm
};
};

View file

@ -1,8 +1,34 @@
// 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.
//
#pragma once
#include <string>
#include <capstone.hpp>
#include "registers.hpp"
#include "..\misc\format.hpp"
#include <vtil/amd64>
#include "register_view.hpp"
// Any operand used in a VTIL instruction will be essentialy either a register or an
// immediate value, where registers can also be either temporaries, physical registers or
@ -36,7 +62,7 @@ namespace vtil::arch
// Operand type is constructed either by a register view or an immediate
// followed by an explicit size.
//
operand() {}
operand() = default;
operand( const register_view& rw ) : reg( rw ), imm_size( 0 ) {}
operand( uint64_t v, uint8_t size ) : u64( v ), imm_size( size ) {}

View file

@ -0,0 +1,77 @@
// 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.
//
#pragma once
#include <string>
#include <vtil/amd64>
#include "control_registers.hpp"
namespace vtil::arch
{
// Register descriptors are used to describe each unique "full" register such as RAX.
// Any physical register such as EAX will be extended to its full form (RAX in this case).
// - Note: Size of a register is always assumed to be 64-bits.
//
struct register_desc
{
// Descriptor's identifier will be used for comparison if the register
// instance is not mapped to any physical register.
//
std::string identifier = "";
// If this field is not X86_REG_INVALID, it's an indicator that this
// register and the alias essentially maps to a physical register.
//
x86_reg maps_to = X86_REG_INVALID;
// Either a x86 register identifier or an arbitrary string must be passed
// to construct a register descriptor.
//
register_desc() = default;
register_desc( x86_reg reg )
{
maps_to = reg >= X86_REG_VCR0 ? reg : amd64::extend( reg );
identifier = reg >= X86_REG_VCR0 ? lookup_control_register( reg )->identifier : amd64::name( maps_to );
}
register_desc( const std::string& id ) : identifier( id ) {}
// Conversion to human-readable format.
//
std::string to_string() const { return identifier; }
// Simple helpers to determine the type of register.
//
bool is_physical() const { return maps_to != X86_REG_INVALID; }
bool is_valid() const { return !identifier.empty(); }
// Basic comparison operators.
//
bool operator!=( const register_desc& o ) const { return !operator==( o ); }
bool operator==( const register_desc& o ) const { return identifier == o.identifier; }
bool operator<( const register_desc& o ) const { return identifier < o.identifier; }
};
};

View file

@ -1,182 +0,0 @@
#pragma once
#include <map>
#include <tuple>
#include <capstone.hpp>
#include "control_registers.hpp"
namespace vtil::arch
{
// List of all physical registers and the base registers they map to <0> at offset <1> of size <2>.
//
static const std::map<x86_reg, std::tuple<x86_reg, uint8_t, uint8_t>> register_mappings
{
/* [Instance] [Base] [Offset] [Size] */
{ X86_REG_RAX, { X86_REG_RAX, 0, 8 } },
{ X86_REG_EAX, { X86_REG_RAX, 0, 4 } },
{ X86_REG_AX, { X86_REG_RAX, 0, 2 } },
{ X86_REG_AH, { X86_REG_RAX, 1, 1 } },
{ X86_REG_AL, { X86_REG_RAX, 0, 1 } },
{ X86_REG_RBX, { X86_REG_RBX, 0, 8 } },
{ X86_REG_EBX, { X86_REG_RBX, 0, 4 } },
{ X86_REG_BX, { X86_REG_RBX, 0, 2 } },
{ X86_REG_BH, { X86_REG_RBX, 1, 1 } },
{ X86_REG_BL, { X86_REG_RBX, 0, 1 } },
{ X86_REG_RCX, { X86_REG_RCX, 0, 8 } },
{ X86_REG_ECX, { X86_REG_RCX, 0, 4 } },
{ X86_REG_CX, { X86_REG_RCX, 0, 2 } },
{ X86_REG_CH, { X86_REG_RCX, 1, 1 } },
{ X86_REG_CL, { X86_REG_RCX, 0, 1 } },
{ X86_REG_RDX, { X86_REG_RDX, 0, 8 } },
{ X86_REG_EDX, { X86_REG_RDX, 0, 4 } },
{ X86_REG_DX, { X86_REG_RDX, 0, 2 } },
{ X86_REG_DH, { X86_REG_RDX, 1, 1 } },
{ X86_REG_DL, { X86_REG_RDX, 0, 1 } },
{ X86_REG_RDI, { X86_REG_RDI, 0, 8 } },
{ X86_REG_EDI, { X86_REG_RDI, 0, 4 } },
{ X86_REG_DI, { X86_REG_RDI, 0, 2 } },
{ X86_REG_DIL, { X86_REG_RDI, 0, 1 } },
{ X86_REG_RSI, { X86_REG_RSI, 0, 8 } },
{ X86_REG_ESI, { X86_REG_RSI, 0, 4 } },
{ X86_REG_SI, { X86_REG_RSI, 0, 2 } },
{ X86_REG_SIL, { X86_REG_RSI, 0, 1 } },
{ X86_REG_RBP, { X86_REG_RBP, 0, 8 } },
{ X86_REG_EBP, { X86_REG_RBP, 0, 4 } },
{ X86_REG_BP, { X86_REG_RBP, 0, 2 } },
{ X86_REG_BPL, { X86_REG_RBP, 0, 1 } },
{ X86_REG_RSP, { X86_REG_RSP, 0, 8 } },
{ X86_REG_ESP, { X86_REG_RSP, 0, 4 } },
{ X86_REG_SP, { X86_REG_RSP, 0, 2 } },
{ X86_REG_SPL, { X86_REG_RSP, 0, 1 } },
{ X86_REG_R8, { X86_REG_R8, 0, 8 } },
{ X86_REG_R8D, { X86_REG_R8, 0, 4 } },
{ X86_REG_R8W, { X86_REG_R8, 0, 2 } },
{ X86_REG_R8B, { X86_REG_R8, 0, 1 } },
{ X86_REG_R9, { X86_REG_R9, 0, 8 } },
{ X86_REG_R9D, { X86_REG_R9, 0, 4 } },
{ X86_REG_R9W, { X86_REG_R9, 0, 2 } },
{ X86_REG_R9B, { X86_REG_R9, 0, 1 } },
{ X86_REG_R10, { X86_REG_R10, 0, 8 } },
{ X86_REG_R10D, { X86_REG_R10, 0, 4 } },
{ X86_REG_R10W, { X86_REG_R10, 0, 2 } },
{ X86_REG_R10B, { X86_REG_R10, 0, 1 } },
{ X86_REG_R11, { X86_REG_R11, 0, 8 } },
{ X86_REG_R11D, { X86_REG_R11, 0, 4 } },
{ X86_REG_R11W, { X86_REG_R11, 0, 2 } },
{ X86_REG_R11B, { X86_REG_R11, 0, 1 } },
{ X86_REG_R12, { X86_REG_R12, 0, 8 } },
{ X86_REG_R12D, { X86_REG_R12, 0, 4 } },
{ X86_REG_R12W, { X86_REG_R12, 0, 2 } },
{ X86_REG_R12B, { X86_REG_R12, 0, 1 } },
{ X86_REG_R13, { X86_REG_R13, 0, 8 } },
{ X86_REG_R13D, { X86_REG_R13, 0, 4 } },
{ X86_REG_R13W, { X86_REG_R13, 0, 2 } },
{ X86_REG_R13B, { X86_REG_R13, 0, 1 } },
{ X86_REG_R14, { X86_REG_R14, 0, 8 } },
{ X86_REG_R14D, { X86_REG_R14, 0, 4 } },
{ X86_REG_R14W, { X86_REG_R14, 0, 2 } },
{ X86_REG_R14B, { X86_REG_R14, 0, 1 } },
{ X86_REG_R15, { X86_REG_R15, 0, 8 } },
{ X86_REG_R15D, { X86_REG_R15, 0, 4 } },
{ X86_REG_R15W, { X86_REG_R15, 0, 2 } },
{ X86_REG_R15B, { X86_REG_R15, 0, 1 } },
{ X86_REG_EFLAGS, { X86_REG_EFLAGS, 0, 8 } },
};
// Gets the offset<0> and size<1> of the mapping for the given register.
//
template<typename T>
static std::pair<uint8_t, uint8_t> get_register_mapping( T _reg )
{
// Return default mapping if it's a control register.
//
if ( _reg > X86_REG_VCR0 )
return { 0, 8 };
// Try to find the register mapping, if succesful return if
//
auto it = register_mappings.find( ( x86_reg ) _reg );
if( it != register_mappings.end() )
return { std::get<1>( it->second ), std::get<2>( it->second ) };
// Otherwise return default mapping after making sure it's valid.
//
fassert( _reg != X86_REG_INVALID );
return { 0, 8 };
}
// Gets the base register for the given register.
//
template<typename T>
static x86_reg extend_register( T _reg )
{
// Try to find the register mapping,
// return as is if we fail to do so.
//
auto it = register_mappings.find( ( x86_reg ) _reg );
if ( it == register_mappings.end() )
return ( x86_reg ) _reg;
// Otherwise return the base register.
//
return std::get<0>( it->second );
}
// Converts the enum into human-readable format.
//
template<typename T>
static std::string name_register( T _reg )
{
// Return control register name if it's one.
//
if ( _reg >= X86_REG_VCR0 )
return lookup_control_register( ( x86_reg ) _reg )->identifier;
// Else lookup the name from capstone.
//
return cs_reg_name( disasm, ( x86_reg ) _reg );
}
// Remaps the given register at given specifications.
//
template<typename T>
static x86_reg remap_register( T _reg, uint8_t offset, uint8_t size )
{
// Extend passed register
//
x86_reg base_register = extend_register( _reg );
// For each mapping described:
//
for ( auto& pair : register_mappings )
{
// If matches the specifications, return.
//
if ( std::get<0>( pair.second ) == base_register &&
std::get<1>( pair.second ) == offset &&
std::get<2>( pair.second ) == size )
return pair.first;
}
// If we fail to find, and we're strictly
// remapping to a full register, return as is.
//
fassert( offset == 0 && size == 8);
return base_register;
}
};

View file

@ -0,0 +1,82 @@
// 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 "register_view.hpp"
#include <vtil/math>
#include <vtil/amd64>
namespace vtil::arch
{
// Basically an extended version of the register descriptor constructor
// with the addition of an offset and a size value.
//
register_view::register_view( x86_reg base, uint8_t offset, uint8_t size ) : base( base ), size( size ), offset( offset ) { fassert( is_valid() ); }
register_view::register_view( const std::string& base, uint8_t offset, uint8_t size ) : base( base ), size( size ), offset( offset ) { fassert( is_valid() ); }
register_view::register_view( const register_desc& base, uint8_t offset, uint8_t size ) : base( base ), size( size ), offset( offset ) { fassert( is_valid() ); }
// Basic comparison operators.
//
bool register_view::operator<( const register_view& o ) const
{
// Try to sort based on base identifier first.
//
if ( base.identifier != o.base.identifier )
return base.identifier < o.base.identifier;
// If matching, check offset and size.
//
uint64_t mask_0 = math::mask( size, offset );
uint64_t mask_1 = math::mask( o.size, o.offset );
return mask_0 < mask_1;
}
bool register_view::operator==( const register_view& o ) const
{
return base.identifier == o.base.identifier && offset == o.offset && size == o.size;
}
bool register_view::operator!=( const register_view& o ) const { return !operator==( o ); };
// Conversion to human-readable format.
//
std::string register_view::to_string( bool explicit_size ) const
{
if ( base.is_physical() )
{
if ( base.maps_to >= X86_REG_VCR0 )
return lookup_control_register( base.maps_to )->identifier;
x86_reg reg = amd64::remap( base.maps_to, offset, size );
return amd64::name( reg );
}
std::string out = base.identifier;
if ( explicit_size && size != 8 )
out += format::suffix_map[ size ];
if ( offset != 0 )
out += "@" + std::to_string( offset );
return out;
}
};

View file

@ -0,0 +1,93 @@
// 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.
//
#pragma once
#include <string>
#include "control_registers.hpp"
#include "register_descriptor.hpp"
namespace vtil::arch
{
// Register views are used to describe well-defined segments of registers.
// - AX views RAX @ {0, 2}, BH views RBX @ {1, 1} so on.
//
struct register_view
{
// The base register descriptor.
//
register_desc base = {};
// Offset into that register and the segment referenced.
//
uint8_t offset = 0;
uint8_t size = 8;
// Basically an extended version of the register descriptor constructor
// with the addition of an offset and a size value.
//
register_view() = default;
register_view( x86_reg base, uint8_t offset = 0, uint8_t size = 8 );
register_view( const std::string& base, uint8_t offset = 0, uint8_t size = 8 );
register_view( const register_desc& base, uint8_t offset = 0, uint8_t size = 8 );
// Mask that describes how we map to the base register and a
// basic "overlapping" check using this mask.
//
uint64_t get_mask() const { return ( ~0ull >> ( 64 - size * 8 ) ) << ( offset * 8 ); }
bool overlaps( const register_view& o ) const { return base == o.base && ( get_mask() & o.get_mask() ); }
// Conversion to human-readable format.
//
std::string to_string( bool explicit_size = false ) const;
// Validity check.
//
bool is_valid() const { return base.is_valid() && ( offset + size ) <= 8; }
// Basic comparison operators.
//
bool operator!=( const register_view& o ) const;
bool operator==( const register_view& o ) const;
bool operator<( const register_view& o ) const;
};
};
// Testing new flags system, will remove. [TODO]
//
namespace vtil
{
static const arch::register_view REG_UNKB = { arch::create_control_register( { "unkb", true } ), 0, 1 };
static const arch::register_view REG_UNKW = { arch::create_control_register( { "unkw", true } ), 0, 2 };
static const arch::register_view REG_UNKD = { arch::create_control_register( { "unkd", true } ), 0, 4 };
static const arch::register_view REG_UNKQ = { arch::create_control_register( { "unkq", true } ), 0, 8 };
static const arch::register_view REG_ZF = { arch::create_control_register( { "eflags.zf", false } ), 0, 1 };
static const arch::register_view REG_SF = { arch::create_control_register( { "eflags.sf", false } ), 0, 1 };
static const arch::register_view REG_PF = { arch::create_control_register( { "eflags.pf", false } ), 0, 1 };
static const arch::register_view REG_AF = { arch::create_control_register( { "eflags.af", false } ), 0, 1 };
static const arch::register_view REG_OF = { arch::create_control_register( { "eflags.of", false } ), 0, 1 };
static const arch::register_view REG_CF = { arch::create_control_register( { "eflags.cf", false } ), 0, 1 };
};

View file

@ -1,100 +0,0 @@
#pragma once
#include <string>
#include <capstone.hpp>
#include "..\misc\format.hpp"
#include "register_details.hpp"
namespace vtil::arch
{
// Register descriptors are used to describe each unique "full" register such as RAX.
// Any physical register such as EAX will be extended to its full form (RAX in this case).
// - Note: Size of a register is always assumed to be 64-bits.
//
struct register_desc
{
// Descriptor's identifier will be used for comparison if the register
// instance is not mapped to any physical register.
//
std::string identifier = "";
// If this field is not X86_REG_INVALID, it's an indicator that this
// register and the alias essentially maps to a physical register.
//
x86_reg maps_to = X86_REG_INVALID;
// Either a x86 register identifier or an arbitrary string must be passed
// to construct a register descriptor.
//
register_desc() {}
register_desc( x86_reg reg ) { maps_to = extend_register( reg ); identifier = name_register( maps_to ); }
register_desc( const std::string& id ) : identifier( id ) {}
// Conversion to human-readable format.
//
std::string to_string() const { return identifier; }
// Simple helpers to determine the type of register.
//
bool is_physical() const { return maps_to != X86_REG_INVALID; }
bool is_valid() const { return !identifier.empty(); }
// Basic comparison operators.
//
bool operator!=( const register_desc& o ) const { return !operator==( o ); }
bool operator==( const register_desc& o ) const { return identifier == o.identifier; }
bool operator<( const register_desc& o ) const { return identifier < o.identifier; }
};
// Register views are used to describe well-defined segments of registers.
// - AX views RAX @ {0, 2}, BH views RBX @ {1, 1} so on.
//
struct register_view
{
// The base register descriptor.
//
register_desc base = {};
// Offset into that register and the segment referenced.
//
uint8_t offset = 0;
uint8_t size = 8;
// Basically an extended version of the register descriptor constructor
// with the addition of an offset and a size value.
//
register_view() {}
register_view( x86_reg base, uint8_t offset = 0, uint8_t size = 8 ) : base( base ), size( size ), offset( offset ) { fassert( is_valid() ); }
register_view( const std::string& base, uint8_t offset = 0, uint8_t size = 8 ) : base( base ), size( size ), offset( offset ) { fassert( is_valid() ); }
register_view( const register_desc& base, uint8_t offset = 0, uint8_t size = 8 ) : base( base ), size( size ), offset( offset ) { fassert( is_valid() ); }
// Mask that describes how we map to the base register and a
// basic "overlapping" check using this mask.
//
uint64_t get_mask() const { return ( ~0ull >> ( 64 - size * 8 ) ) << ( offset * 8 ); }
bool overlaps( const register_view& o ) const { return base == o.base && ( get_mask() & o.get_mask() ); }
// Conversion to human-readable format.
//
std::string to_string( bool explicit_size = false ) const
{
if ( base.is_physical() )
return name_register( remap_register( base.maps_to, offset, size ) );
std::string out = base.identifier;
if ( explicit_size && size != 8 )
out += format::suffix_map[ size ];
if ( offset != 0 )
out += "@" + std::to_string( offset );
return out;
}
// Validity check.
//
bool is_valid() const { return base.is_valid() && ( offset + size ) <= 8; }
// Basic comparison operators.
//
bool operator!=( const register_view& o ) const { return !operator==( o ); };
bool operator==( const register_view& o ) const { return base.identifier == o.base.identifier && offset == o.offset && size == o.size; }
bool operator<( const register_view& o ) const { return base.identifier == o.base.identifier ? ( offset == o.offset ? size < o.size : offset < o.offset ) : base.identifier < o.base.identifier; }
};
};

View file

@ -1,104 +0,0 @@
#pragma once
#include <vector>
#include <map>
#include <string>
#include <cstring>
#include <capstone/capstone.h>
#include "platform.hpp"
#pragma comment(lib, "capstone_i.lib")
namespace capstone
{
struct instruction
{
// Base object
uint32_t id = 0;
uint64_t address = 0;
std::vector<uint8_t> bytes;
std::string mnemonic;
std::string operand_string;
// From ->detail
std::vector<uint16_t> regs_read;
std::vector<uint16_t> regs_write;
std::vector<uint8_t> groups;
cs_x86 details;
instruction() {};
instruction( const cs_insn& ins ) :
id( ins.id ), address( ins.address ),
mnemonic( ins.mnemonic ), operand_string( ins.op_str ),
bytes( ins.bytes, ins.bytes + ins.size ),
regs_read( ins.detail->regs_read, ins.detail->regs_read + ins.detail->regs_read_count ),
regs_write( ins.detail->regs_write, ins.detail->regs_write + ins.detail->regs_write_count ),
groups( ins.detail->groups, ins.detail->groups + ins.detail->groups_count ),
details( ins.detail->x86 )
{
}
std::string dump() const
{
char bfr[ 64 ];
sprintf_s( bfr, "%p: %s\t%s", address, mnemonic.data(), operand_string.data() );
return bfr;
}
bool is( uint32_t idx, const std::vector<x86_op_type>& operands ) const
{
if ( id != idx ) return false;
if ( details.op_count != operands.size() ) return false;
for ( int i = 0; i < details.op_count; i++ )
if ( details.operands[ i ].type != operands[ i ] )
return false;
return true;
}
bool in_group( uint8_t g ) const
{
for ( auto o : groups )
if ( o == g ) return true;
return false;
}
};
struct context
{
csh handle = 0;
void destroy() { cs_close( &handle ); }
operator csh() { return handle; }
std::vector<instruction> operator()( const void* bytes, uint64_t address, size_t size = 0, size_t count = 1 )
{
std::vector<instruction> out;
cs_insn* ins;
count = cs_disasm
(
handle,
( uint8_t* ) bytes,
size ? size : -1,
address,
size ? 0 : count,
&ins
);
for ( int i = 0; i < count; i++ )
out.push_back( ins[ i ] );
cs_free( ins, count );
return out;
}
};
static context create( cs_arch arch, cs_mode mode )
{
context ctx;
if ( !cs_open( arch, mode, &ctx.handle ) )
cs_option( ctx.handle, CS_OPT_DETAIL, CS_OPT_ON );
else
throw "Failed to create the disassembler!";
return ctx;
}
};
static auto disasm = capstone::create( CS_ARCH_X86, CS_MODE_64 );

View file

@ -1,48 +0,0 @@
#pragma once
#include <string>
#include <vector>
#include <keystone/keystone.h>
#pragma comment(lib, "keystone.lib")
namespace keystone
{
struct context
{
ks_engine* handle = 0;
void destroy() { ks_close( handle ); }
operator ks_engine*() { return handle; }
std::vector<uint8_t> operator()( const std::string& src, uint64_t va = 0 )
{
std::vector<uint8_t> out;
size_t count;
unsigned char* encode;
size_t size;
if ( ks_asm( handle, ( ".code64;" + src ).data(), va, &encode, &size, &count ) )
{
ks_free( encode );
return {};
}
if ( size )
{
out = { encode, encode + size };
ks_free( encode );
}
return out;
}
};
static context create( ks_arch arch, ks_mode mode )
{
context ctx;
if ( ks_open( arch, mode, &ctx.handle ) )
throw "Failed to create the assembler!";
return ctx;
}
};
static auto assemble = keystone::create( KS_ARCH_X86, KS_MODE_64 );

View file

@ -1,137 +0,0 @@
#pragma once
#if _WIN64
#include <Windows.h>
#else
#include <sys/mman.h>
#endif
#include <iostream>
#include <stdint.h>
#include <string>
#include <fstream>
#include <vector>
#include <mutex>
#undef min
#undef max
enum console_color
{
CON_BRG = 15,
CON_YLW = 14,
CON_PRP = 13,
CON_RED = 12,
CON_CYN = 11,
CON_GRN = 10,
CON_BLU = 9,
CON_DEF = 7,
};
namespace mem
{
static void* allocate_rwx( size_t size )
{
#if _WIN64
return VirtualAlloc( 0, size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE );
#else
return mmap( 0, size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0 );
#endif
}
static void free_rwx( void* pointer, size_t size )
{
#if _WIN64
VirtualFree( pointer, 0, MEM_FREE | MEM_RELEASE );
#else
mmunmap( pointer, size );
#endif
}
};
namespace io
{
template<bool critical = false>
static void bp()
{
#ifdef _DEBUG
__asm { int 3 };
#else
if constexpr ( critical )
exit( -1 );
#endif
}
static int log_padding = 0;
static std::mutex print_mutex;
static bool log_init = false;
template<typename T>
__forceinline static auto fix_format_paramter( const T& x )
{
if constexpr ( std::is_same_v<T, std::string> ||
std::is_same_v<T, std::wstring> )
return x.data();
else
return x;
}
template<console_color color = CON_DEF, typename... params>
static int log( const char* fmt, params&&... ps )
{
std::lock_guard g( print_mutex );
#if _WIN64
SetConsoleTextAttribute( GetStdHandle( STD_OUTPUT_HANDLE ), color );
if ( !log_init )
{
SetConsoleOutputCP( CP_UTF8 );
log_init = true;
}
#endif
int v = printf( fmt, fix_format_paramter( ps )... );
if ( fmt[ strlen( fmt ) - 1 ] == '\n' && log_padding > 0 )
v += printf( "%*c", log_padding * 8, ' ' );
return v;
}
template<bool critical = true, typename... params>
static void error( const char* fmt, params&&... ps )
{
log<CON_RED>( fmt, std::forward<params>( ps )... );
bp<critical>();
}
static void assert_helper( bool condition, const char* file_name, const char* condition_str, uint32_t line_number )
{
if ( condition ) return;
error
(
"Assertion failure at %s:%d (%s)",
file_name,
line_number,
condition_str
);
}
static std::vector<uint8_t> read_raw( const std::wstring& file_path )
{
// Try to open file as binary
std::ifstream file( file_path, std::ios::binary );
if ( !file.good() ) throw "Input file cannot be opened.";
// Read the whole file
std::vector<uint8_t> bytes = std::vector<uint8_t>( std::istreambuf_iterator<char>( file ), {} );
if ( bytes.size() == 0 ) throw "Input file is empty.";
return bytes;
}
static void write_raw( void* data, size_t size, const std::wstring& file_path )
{
std::ofstream file( file_path, std::ios::binary );
if ( !file.good() ) throw "Output file cannot be opened.";
file.write( ( char* ) data, size );
}
};
#define fassert__stringify(x) #x
#define fassert(...) io::assert_helper( (__VA_ARGS__), __FILE__, fassert__stringify(__VA_ARGS__), __LINE__ )
#define unreachable() fassert( false )

View file

@ -1,33 +1,61 @@
// 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.
//
#pragma once
#include <string>
#include <set>
#include <platform.hpp>
#include <vtil/io>
#include "..\arch\instruction_set.hpp"
#include "..\routine\basic_block.hpp"
#include "..\routine\instruction.hpp"
#include "format.hpp"
namespace vtil::debug
{
void dump( const instruction& ins, const instruction* prev = nullptr )
static void dump( const instruction& ins, const instruction* prev = nullptr )
{
using namespace vtil::logger;
// Print stack pointer offset
//
if ( ins.sp_reset )
io::log<CON_PRP>( ">%c0x%-4x ", ins.sp_offset >= 0 ? '+' : '-', abs( ins.sp_offset ) );
log<CON_PRP>( ">%c0x%-4x ", ins.sp_offset >= 0 ? '+' : '-', abs( ins.sp_offset ) );
else if ( ( prev ? prev->sp_offset : 0 ) == ins.sp_offset )
io::log<CON_DEF>( "%c0x%-4x ", ins.sp_offset >= 0 ? '+' : '-', abs( ins.sp_offset ) );
log<CON_DEF>( "%c0x%-4x ", ins.sp_offset >= 0 ? '+' : '-', abs( ins.sp_offset ) );
else if ( ( prev ? prev->sp_offset : 0 ) > ins.sp_offset )
io::log<CON_RED>( "%c0x%-4x ", ins.sp_offset >= 0 ? '+' : '-', abs( ins.sp_offset ) );
log<CON_RED>( "%c0x%-4x ", ins.sp_offset >= 0 ? '+' : '-', abs( ins.sp_offset ) );
else
io::log<CON_BLU>( "%c0x%-4x ", ins.sp_offset >= 0 ? '+' : '-', abs( ins.sp_offset ) );
log<CON_BLU>( "%c0x%-4x ", ins.sp_offset >= 0 ? '+' : '-', abs( ins.sp_offset ) );
// Print name
//
if ( ins.is_volatile() )
io::log<CON_RED>( FMT_INS_MNM " ", ins.base->to_string( ins.access_size() ) ); // Volatile instruction
log<CON_RED>( FMT_INS_MNM " ", ins.base->to_string( ins.access_size() ) ); // Volatile instruction
else
io::log<CON_BRG>( FMT_INS_MNM " ", ins.base->to_string( ins.access_size() ) ); // Non-volatile instruction
log<CON_BRG>( FMT_INS_MNM " ", ins.base->to_string( ins.access_size() ) ); // Non-volatile instruction
// Print each operand
//
@ -36,11 +64,11 @@ namespace vtil::debug
if ( op.is_register() )
{
if ( op.reg.base.maps_to == X86_REG_RSP )
io::log<CON_PRP>( FMT_INS_OPR " ", op.reg.to_string() ); // Stack pointer
log<CON_PRP>( FMT_INS_OPR " ", op.reg.to_string() ); // Stack pointer
else if ( op.reg.base.maps_to != X86_REG_INVALID )
io::log<CON_BLU>( FMT_INS_OPR " ", op.reg.to_string() ); // Any hardware/special register
log<CON_BLU>( FMT_INS_OPR " ", op.reg.to_string() ); // Any hardware/special register
else
io::log<CON_GRN>( FMT_INS_OPR " ", op.reg.to_string() ); // Virtual register
log<CON_GRN>( FMT_INS_OPR " ", op.reg.to_string() ); // Virtual register
}
else
{
@ -51,13 +79,13 @@ namespace vtil::debug
ins.operands[ ins.base->memory_operand_index ].reg == X86_REG_RSP )
{
if ( op.i64 >= 0 )
io::log<CON_YLW>( FMT_INS_OPR " ", format::hex( op.i64 ) ); // External stack
log<CON_YLW>( FMT_INS_OPR " ", format::hex( op.i64 ) ); // External stack
else
io::log<CON_BRG>( FMT_INS_OPR " ", format::hex( op.i64 ) ); // VM stack
log<CON_BRG>( FMT_INS_OPR " ", format::hex( op.i64 ) ); // VM stack
}
else
{
io::log<CON_CYN>( FMT_INS_OPR " ", format::hex( op.i64 ) ); // Any immediate
log<CON_CYN>( FMT_INS_OPR " ", format::hex( op.i64 ) ); // Any immediate
}
}
}
@ -66,30 +94,32 @@ namespace vtil::debug
//
fassert( ins.operands.size() <= arch::max_operand_count );
for ( int i = ins.operands.size(); i < arch::max_operand_count; i++ )
io::log( FMT_INS_OPR " ", "" );
io::log( "\n" );
log( FMT_INS_OPR " ", "" );
log( "\n" );
}
void dump( const basic_block* blk, std::set<const basic_block*>* visited = nullptr )
static void dump( const basic_block* blk, std::set<const basic_block*>* visited = nullptr )
{
using namespace vtil::logger;
bool blk_visited = visited ? visited->find( blk ) != visited->end() : false;
auto end_with_bool = [ ] ( bool b )
{
if ( b ) io::log<CON_GRN>( "Y\n" );
else io::log<CON_RED>( "N\n" );
if ( b ) log<CON_GRN>( "Y\n" );
else log<CON_RED>( "N\n" );
};
io::log<CON_DEF>( "Entry point VIP: " );
io::log<CON_CYN>( "0x%llx\n", blk->entry_vip );
io::log<CON_DEF>( "Stack pointer: " );
log<CON_DEF>( "Entry point VIP: " );
log<CON_CYN>( "0x%llx\n", blk->entry_vip );
log<CON_DEF>( "Stack pointer: " );
if ( blk->sp_offset < 0 )
io::log<CON_RED>( "%s\n", format::hex( blk->sp_offset ) );
log<CON_RED>( "%s\n", format::hex( blk->sp_offset ) );
else
io::log<CON_GRN>( "%s\n", format::hex( blk->sp_offset ) );
io::log<CON_DEF>( "Already visited?: " );
log<CON_GRN>( "%s\n", format::hex( blk->sp_offset ) );
log<CON_DEF>( "Already visited?: " );
end_with_bool( blk_visited );
io::log<CON_DEF>( "------------------------\n" );
log<CON_DEF>( "------------------------\n" );
if ( blk_visited )
return;
@ -99,11 +129,11 @@ namespace vtil::debug
int ins_idx = 0;
for ( auto it = blk->begin(); it != blk->end(); it++, ins_idx++ )
{
io::log<CON_BLU>( "%04d: ", ins_idx );
log<CON_BLU>( "%04d: ", ins_idx );
if ( it->vip == invalid_vip )
io::log<CON_DEF>( "[PSEUDO] " );
log<CON_DEF>( "[PSEUDO] " );
else
io::log<CON_DEF>( "[%06x] ", it->vip );
log<CON_DEF>( "[%06x] ", it->vip );
dump( *it, it.is_begin() ? nullptr : &*std::prev( it ) );
}
@ -112,16 +142,16 @@ namespace vtil::debug
if ( visited )
{
visited->insert( blk );
io::log_padding++;
io::log( "\n" );
log_padding++;
log( "\n" );
for ( auto& child : blk->next )
dump( child, visited );
io::log_padding--;
io::log( "\n" );
log_padding--;
log( "\n" );
}
}
void dump( const routine* routine )
static void dump( const routine* routine )
{
std::set<const basic_block*> vs;
dump( routine->entry_point, &vs );

View file

@ -1,40 +0,0 @@
#pragma once
#include <string>
#include <type_traits>
#include <platform.h>
#define FMT_TEMP_REG "t%d"
#define FMT_INS_MNM "%-8s"
#define FMT_INS_OPR "%-12s"
#define FMT_INS FMT_INS_MNM " " FMT_INS_OPR " " FMT_INS_OPR " " FMT_INS_OPR " " FMT_INS_OPR
namespace vtil::format
{
static constexpr char suffix_map[] = { ' ', 'b', 'w', ' ', 'd', ' ', ' ', ' ', 'q' };
template<typename... params>
static std::string str( const char* fmt, params... ps )
{
char buffer[ 512 ];
sprintf_s( buffer, fmt, io::fix_format_paramter( ps )... );
return buffer;
}
template<typename T>
static std::string hex( T value )
{
if ( !std::is_signed_v<T> || value >= 0 )
return str( "0x%llx", value );
else
return str( "-0x%llx", -value );
}
static std::string offset( int64_t value )
{
if ( value >= 0 )
return str( "+ 0x%llx", value );
else
return str( "- 0x%llx", -value );
}
};

View file

@ -1,183 +0,0 @@
#pragma once
#include <set>
#include <vector>
#include <iterator>
#include <functional>
#include "ranges.hpp"
namespace vtil::query
{
// Query descriptor is a non-projected-type dependent structure
// that describes the base state of any query object.
//
template<typename _iterator_type>
struct query_desc
{
using iterator_type = _iterator_type;
using reference_type = decltype( *std::declval<iterator_type>() );
// Range iterator itself and the saved instance for at()
//
iterator_type iterator = {};
// Direction of iteration:
// => +1 for forward
// => -1 for backwar
//
int8_t direction = 0;
// Iteration function let's us define a generic iteration logic.
//
// Returns:
// - 1 if there's a valid result
// - 0 if reached end of the stream
// - -1 if terminated due to until(...)
//
using fn_controller = std::function<int( query_desc&, iterator_type )>;
fn_controller controller = [ ] ( auto&, auto ) { return 1; };
// Queries can be simply constructed from an iterator and an
// optional direction value, where it defaults to forward iteration
// if not .end(), backwards otherwise.
//
query_desc() {};
query_desc( _iterator_type it, int8_t dir = 0 ) : iterator( it )
{
if ( it.is_end() && !it.is_begin() )
direction = dir != 0 ? dir : -1;
else
direction = dir != 0 ? dir : +1;
}
// Wraps ::recurse(...) of range iterators, returning query descriptors.
//
std::vector<query_desc> recurse() const
{
// Return an empty list if direction is invalid.
//
if ( direction == 0 ) return {};
// Get the list of possible iterators we could continue from.
//
std::vector iterators = iterator.recurse( direction == +1 );
// Convert into query descriptors.
//
std::vector<query_desc> query_descriptors;
for ( iterator_type& it : iterators )
{
// Create a default descriptor with the iterator and the direction,
// afterwards propagate the iteration logic.
//
query_desc qd = { it, direction };
qd.controller = controller;
query_descriptors.push_back( qd );
}
return query_descriptors;
}
// Invalidates current query.
//
void stop()
{
iterator = prev();
direction = 0;
}
// Value that next() processed previously.
//
auto prev() const { return direction != +1 ? iterator : ( iterator.is_begin() ? iterator_type{} : std::prev( iterator ) ); }
// Value that next() will process next.
//
auto next() const { return direction != -1 ? iterator : ( iterator.is_begin() ? iterator_type{} : std::prev( iterator ) ); }
// Reverses the current query direction
//
void reverse()
{
// We have to fix the iterators since
// .end() is valid for [-1] but not [+1]
// and .begin() is valid for [+1] but not [-1].
//
if ( direction == -1 )
{
if ( !iterator.is_begin() ) iterator--;
direction = +1;
}
else if ( direction == +1 )
{
if ( !iterator.is_end() ) iterator++;
direction = -1;
}
}
// Forwards the iterator in the specified direction [n] times.
//
int forward( int n = 1 )
{
// Until we exhaust the item counter:
//
while ( n > 0 )
{
// If direction is backwards:
//
if ( direction == -1 )
{
// If we've reached .begin(), break.
//
if ( iterator.is_begin() )
break;
// Point the iterator at the current item.
//
iterator--;
// If invalid, break.
//
if ( !iterator.is_valid() )
break;
}
// If direction is forwards:
//
else if ( direction == +1 )
{
// If we've reached .end(), break.
//
if ( iterator.is_end() )
break;
}
// If no direction specified, break.
//
else
{
break;
}
// Invoke the iteration logic.
//
int res = controller( *this, iterator );
// If direction was forward, increment the iterator now.
//
if ( direction == +1 )
iterator++;
// If a breaking condition was satisfied, report so.
//
if ( res == -1 )
return -1;
// If filters were passed and we've exhausted the
// item counter, report success.
//
if ( res == 1 && --n <= 0 )
return 1;
}
// Report end-of-stream.
//
return 0;
}
};
};

View file

@ -1,63 +0,0 @@
#pragma once
#include <vector>
namespace vtil::query
{
// Basic range iterators provide a simple range iterator
// implementation for default STL objects and pretty much
// any other class adhereing to their standarts, to be used
// with VTIL queries.
//
template<typename container_type, typename iterator_type>
struct range_iterator : iterator_type
{
using container_type = container_type;
using iterator_type = iterator_type;
// Reference to the container.
//
container_type* container = nullptr;
// Default constructor and the container-bound constructor.
//
range_iterator() {}
range_iterator( container_type* container, iterator_type i ) : iterator_type( i ), container( container ) {}
template<typename X, typename Y> range_iterator( const range_iterator<X, Y>& o ) : container( o.container ), iterator_type( Y( o ) ) {}
// Override equality operators to check container first.
//
bool operator!=( const range_iterator& o ) const { return container != o.container || iterator_type::operator!=( o ); }
bool operator==( const range_iterator& o ) const { return container == o.container && iterator_type::operator==( o ); }
// Simple position/validity checks.
//
bool is_end() const { return !container || iterator_type::operator==( ( iterator_type ) container->end() ); }
bool is_begin() const { return !container || iterator_type::operator==( ( iterator_type ) container->begin() ); }
bool is_valid() const { return !is_begin() || !is_end(); }
// No default implementation for recursion since STL has no default tree-based container.
//
std::vector<range_iterator> recurse( bool forward ) const { return {}; }
};
// Makes range iterator from any container and iterator combination based on basic_range_iterator.
//
template<typename container_type>
static auto bind( container_type& container, typename container_type::iterator iterator )
{
return range_iterator<container_type, typename container_type::iterator>
{
&container,
iterator
};
}
template<typename container_type>
static auto bind( const container_type& container, typename container_type::const_iterator iterator )
{
return range_iterator<const container_type, typename container_type::const_iterator>
{
&container,
iterator
};
}
};

View file

@ -1,356 +0,0 @@
#pragma once
#include <type_traits>
#include <vector>
#include "view.hpp"
#include "query_desc.hpp"
namespace vtil::query
{
// Recursive results are used to collect results
// in a way that clearly indicates the path taken
// to get the result, and the source container.
//
template<typename result_type, typename container_type>
struct recursive_result
{
// Whether we've visited this container before or not.
//
bool is_looping = false;
// The container that the result belong to.
//
const container_type* source = nullptr;
// Result of the collection.
//
result_type result;
// Results of deeper recursions.
//
std::vector<recursive_result> paths;
// Merges the results of all or extended basic-blocks.
//
template<typename>
struct is_std_vector : std::false_type {};
template<typename T, typename A>
struct is_std_vector<std::vector<T, A>> : std::true_type {};
recursive_result& flatten( bool force = false )
{
// Apply to each path recursively.
//
for ( auto& path : paths )
path = path.flatten();
// If single possible path or force mode:
//
if ( paths.size() == 1 || force )
{
std::vector paths_p = paths;
paths.clear();
for ( auto& r : paths_p )
{
// Merge basic result.
//
is_looping |= r.is_looping;
if( !r.paths.empty() )
paths.insert( paths.end(), r.paths.begin(), r.paths.end() );
// Either combine vectors or use the addition operator.
//
if constexpr ( is_std_vector<result_type>::value )
result.insert( result.end(), r.result.begin(), r.result.end() );
else
result += r.result;
}
}
// Return as is.
//
return *this;
}
};
template<typename view_type>
struct recursive_view
{
// Base view and its typedefs.
//
using iterator_type = typename view_type::iterator_type;
using projected_type = typename view_type::projected_type;
using container_type = typename iterator_type::container_type;
view_type view;
// Container filters determine whether we should
// recurse into the passed container or not.
//
using fn_container_filter = std::function<bool( const container_type* src, const container_type * dst, bool first_time )>;
fn_container_filter filter = {};
// Special iterator saved by the root to mark the
// beginning of it's iteration so loops can properly
// lead to it.
//
iterator_type it0 = {};
bool it0_oob = false;
// List of containers that we've recursively visited.
// The second argument of the container filter, first_time,
// is determined by whether the container we're trying to
// visit is in this list or not.
//
std::set<const void*> visited = {};
// Constructs a recursive view from the view structure passed.
//
// - If partial visits are allowed, in case of an infinite loop,
// still iterates up to the starting point of view, first_time will
// be set to true in this case when we reach the root container.
//
// - Filter is a function that takes the pointer to the next container
// and whether it's being visit for the first time or not and returns
// whether we should visit it or not.
//
//
recursive_view() {};
recursive_view( const view_type& view, bool partial_visits, fn_container_filter filter ) : view( view ), filter( filter )
{
// If partial visits are allowed:
//
if ( partial_visits )
{
// Set it0 as the next iterator, if invalid (meaning we'll skip to next
// container right away) set the container property.
//
it0 = view.query.next();
if ( !it0.is_valid() )
{
it0.container = view.query.iterator.container;
it0_oob = true;
}
}
else
{
// Assing an invalid iterator to it0 and mark current
// iterator's container visited.
//
it0 = {};
visited.insert( view.query.iterator.container );
}
}
// Simply clones the current state.
//
recursive_view clone()
{
return *this;
}
// Simple wrappers around the real view.
// - If body only contains unreachable(), call is not valid for recursive view.
// - Collection must not have started yet, otherwise calls are invalid.
//
void prev() { unreachable(); }
void next() { unreachable(); }
void skip( int n = 1 ) { unreachable(); }
void last() { unreachable(); }
auto& reverse() { view.reverse(); return *this; }
template<typename T> auto& run( T next ) { view.run( next ); return *this; }
template<typename T> auto& with( T next ) { view.with( next ); return *this; }
template<typename T> auto& where( T next ) { view.where( next ); return *this; }
template<typename T> auto& until( T next ) { view.until( next ); return *this; }
template<typename T> auto& whilst( T next ) { view.whilst( next ); return *this; }
template<typename projector_type>
auto project( projector_type next ) { return recursive_view<decltype( view.project( next ) )>{ view.project( next ), it0.container != nullptr, filter }; }
template<typename projector_type>
auto reproject( projector_type next ) { return recursive_view<decltype( view.reproject( next ) )>{ view.reproject( next ), it0.container != nullptr, filter }; }
auto unproject() { return recursive_view<decltype( view.unproject() )>{ view.unproject(), it0.container != nullptr, filter }; }
// [Collection method]
// Invokes the enumerator for each entry, if enumerator returns void/bool
// saves the number of (?=true) entries, otherwise collects the return value
// in std::vector<> and saves that in the recursive_result structure.
// Continues appending paths and results in that structure until
// stream is finished.
//
template<typename enumerator_type,
typename return_type = decltype( std::declval<enumerator_type>()( std::declval<projected_type>() ) ),
typename result_type = std::conditional_t<std::is_same_v<return_type, void>, size_t, std::vector<return_type>>
>
recursive_result<result_type, container_type> for_each( const enumerator_type& enumerator )
{
// Begin the iteration loop.
//
recursive_result<result_type, container_type> output = { false, view.query.iterator.container, {}, {} };
while ( true )
{
int r = view.query.forward();
// If a breaking condition was satisfied, end the loop.
//
if ( r == -1 )
{
break;
}
// If entry passed the filters, append the result and continue.
//
else if ( r == 1 )
{
if constexpr ( std::is_same_v<return_type, void> )
output.result++, enumerator( view.prev() );
else if constexpr ( std::is_same_v<return_type, bool> )
output.result += enumerator( view.prev() );
else
output.result.push_back( enumerator( view.prev() ) );
}
// If we've reached the end of the stream, try recursing.
//
else
{
// For each plausible path:
//
std::vector desc_list = view.query.recurse();
for ( auto& desc : desc_list )
{
auto visited_copy = visited;
// If we did not already visit it:
//
bool first_visit = visited_copy.find( desc.iterator.container ) == visited_copy.end();
if ( filter( view.query.iterator.container, desc.iterator.container, first_visit ) )
{
// Mark the container visited.
//
visited_copy.insert( desc.iterator.container );
// Create another recursive view with the new query.
//
recursive_view view_new = clone();
view_new.view.query = desc;
view_new.visited = visited_copy;
// If iterator belongs to the same container as the root:
//
bool partial_loop = desc.iterator.container == it0.container;
if ( partial_loop )
{
// Append an additional rule to the iteration.
//
if( !it0_oob )
view_new.view = view_new.view.until( it0 );
}
// Invoke the same enumerator and append as a path.
//
recursive_result<result_type, container_type> result = view_new.for_each<enumerator_type, return_type, result_type>( enumerator );
result.is_looping = partial_loop;
output.paths.push_back( result );
}
// Otherwise:
//
else
{
// Append an empty path marked as a loop.
//
output.paths.push_back( { true, view.query.iterator.container, {}, {} } );
}
}
// Break out.
//
break;
}
}
// Return the final result.
//
return output;
}
// [Collection method]
// Collects each entry in std::vector<> and saves that in the
// recursive_result structure. Continues appending paths and
// results in that structure until stream is finished.
//
auto collect()
{
return for_each( [ ] ( projected_type r ) { return r; } );
}
// [Collection method]
// Evaluates the iteration logic and returns the number of hits
// in terms of recursive_result.
//
auto evaluate()
{
return for_each( [ ] ( projected_type r ) {} );
}
// [Collection method]
// Collects first entry in std::vector<>, saves that in the
// recursive_result structure and stops if applicable.
// Otherwise continues appending paths in that structure
// until a valid entry is hit.
//
auto first()
{
auto prev = view.query.controller;
view.query.controller = [ prev ] ( auto& self, iterator_type i ) -> int
{
// If current iterator reports end or filtered-out,
// return as is.
//
int res = prev( self, i );
if ( res <= 0 )
return res;
// Else, stop the query and return it to be processed.
//
self.stop();
return 1;
};
return collect();
}
};
// Converts the view to a recursive view. Since recursive view only contains collection
// methods, all filtering/projection/iteration-logic should be processed at the base view.
//
template<typename view_type>
static auto recurse( view_type view, typename recursive_view<view_type>::fn_container_filter filter = {}, bool partial_visits = true, bool safe = true )
{
return recursive_view<view_type>( view, partial_visits, [ filter, safe ] ( auto* src, auto* dst, bool first_time )
{
return ( !safe || first_time ) && ( !filter || filter( src, dst, true ) );
} );
}
// Creates a reference-view query for the given query base.
//
template<typename iterator_type,
typename view_type = view<typename query_desc<iterator_type>::reference_type, query_desc<iterator_type>>
>
static auto create_recursive( query_desc<iterator_type> q, typename recursive_view<view_type>::fn_container_filter filter = {}, bool partial_visits = true, bool safe = true )
{
return recurse<view_type>
(
view_type( q ),
filter,
partial_visits,
safe
);
}
// Creates a reference-view query for the given range iterator.
//
template<typename iterator_type,
typename view_type = view<typename query_desc<iterator_type>::reference_type, query_desc<iterator_type>>
>
static auto create_recursive( iterator_type r, int8_t dir = 0, typename recursive_view<view_type>::fn_container_filter filter = {}, bool partial_visits = true, bool safe = true )
{
return create_recursive( query_desc{ r, dir }, filter, partial_visits, safe );
}
};

View file

@ -1,444 +0,0 @@
#pragma once
#include <vector>
#include <optional>
#include <functional>
#include <type_traits>
#include "query_desc.hpp"
namespace vtil::query
{
// Query views provide the user with an interface interact
// with any query in a simple fashion using a projected type
// of their own choice.
//
template<typename _projected_type, typename query_desc>
struct view
{
using projected_type = _projected_type;
// Base query and its typedefs.
//
query_desc query;
using fn_controller = typename query_desc::fn_controller;
using iterator_type = typename query_desc::iterator_type;
using reference_type = typename query_desc::reference_type;
// Projectors convert the iterator into a user-defined format
// the invoker will be using.
//
using fn_projector = std::function<projected_type( query_desc&, iterator_type )>;
fn_projector project_value;
// Generic callback wrapper used so that expressions accepting the
// projected type as an argument and expressions accepting the base
// iterator type as an argument can be passed via the same function.
//
template<typename callback_type>
struct callback_wrapper
{
// Storage of the callback in it's original type.
//
callback_type callback_stored;
callback_wrapper( callback_type cb ) : callback_stored( cb ) {}
// This definition will be used for callbacks that can accept the
// projected value over the iterator type where possible as it's the
// prefered type.
//
template<typename fn_projector_i, typename = decltype( std::declval<callback_type>()( std::declval<projected_type>() ) )>
auto invoke( iterator_type it, query_desc& desc, fn_projector_i project, bool ) const
{
return callback_stored( project( desc, it ) );
}
// This definition will be used for callbacks that can only work with the
// iterator type and not the projected type. This is not the prefered method
// and that's an important distinction to make as in the situation of projected type
// being std::next(it) for instance, this method being prefered would make it so that
// the projector is ignored. That is not the case thanks to this method being the second
// option.
//
template<typename fn_projector_i>
auto invoke( iterator_type it, query_desc& desc, fn_projector_i project, ... ) const
{
return callback_stored( it );
}
// Calls into invoke with a boolean and picks whichever possible.
//
template<typename fn_projector_i>
auto operator()( iterator_type it, query_desc& desc, fn_projector_i project ) const
{
return invoke( it, desc, project, true );
}
};
// Constructor takes the query descriptor and a projector.
//
view( query_desc desc, fn_projector projector = {} ) : project_value( projector ), query( desc )
{
if ( !project_value )
{
if constexpr ( std::is_same_v<projected_type, reference_type> )
project_value = [ ] ( auto&, iterator_type i ) -> projected_type { return *i; };
else if constexpr ( std::is_same_v<iterator_type, projected_type> )
project_value = [ ] ( auto&, iterator_type i ) -> projected_type { return i; };
else
unreachable();
}
}
// Simply clones the current state of the view.
//
view clone()
{
return *this;
}
// These provide a simple wrapper around the query descrtiptor's
// ::prev() and ::next() returning the projected value instead.
//
projected_type prev() { return project_value( query, query.prev() ); }
projected_type next() { return project_value( query, query.next() ); }
// Reverses the query descriptor's direction.
//
auto& reverse()
{
query.reverse();
return *this;
}
// Skips the [n] valid entries.
//
auto& skip( int n = 1 )
{
query.forward( n );
return *this;
}
// Returns the current controller.
// This function can be used to implement query extensions
// where one just passes the summarized controller of a dummy view
// as an argument to a routine that creates the view after which the
// routine invokes ::control( arg ) on the real view.
//
fn_controller to_controller()
{
return query.controller;
}
// [Projection method]
// Projects the current result type as specified by the projector and
// returns a new query view of that type.
//
template<typename projector_type>
auto project( projector_type next )
{
// Find out the new projected type
//
using projected_type_n = decltype( next( std::declval<projected_type>() ) );
// Save previous projector and create the new view
//
fn_projector prev = project_value;
return view<projected_type_n, query_desc>
{
query,
[ prev, next ] ( query_desc& self, iterator_type i ) -> projected_type_n
{
return next( prev( self, i ) );
}
};
}
// [Projection method]
// Reverts the projected type to the iterator of the entry.
//
auto unproject()
{
return view<iterator_type, query_desc>{ query };
}
// [Projection method]
// Projects the iterator type as specified by the projector and
// returns a new query view of that type. (Equivalent to the
// combination of unproject + project)
//
template<typename projector_type>
auto reproject( projector_type next )
{
// Find out the new projected type
//
using projected_type_n = decltype( next( std::declval<iterator_type>() ) );
// Save previous projector and create the new view
//
return view<projected_type_n, query_desc>
{
query,
[ next ] ( query_desc& self, iterator_type i ) -> projected_type_n
{
return next( i );
}
};
}
// [Combination method]
// For each entry, invokes the controller and returns the integer
// it returns as is. For more details on what this value
// represents, read the note for query_base::fn_controller and
// ::to_controller()
//
auto& with( fn_controller controller )
{
// Override iteration logic.
//
fn_controller prev = query.controller;
query.controller = [ prev, controller ] ( query_desc& self, iterator_type i ) -> int
{
// If current iterator reports end or filtered-out,
// return as is.
//
int res = prev( self, i );
if ( res <= 0 )
return res;
// Else, run our additional layer of logic.
//
return controller( self, i );
};
return *this;
}
// [Filtering method]
// For each entry, invokes the filter and if it returns false,
// skips it and continues from the next one.
//
template<typename callback_type>
auto& where( callback_type cb )
{
// Allow callbacks taking non-projected type if they take
// the base iterator type.
//
fn_projector project = project_value;
callback_wrapper<callback_type> callback = { cb };
// Override iteration logic.
//
fn_controller prev = query.controller;
query.controller = [ prev, project, callback ] ( query_desc& self, iterator_type i ) -> int
{
// If current iterator reports end or filtered-out,
// return as is.
//
int res = prev( self, i );
if ( res <= 0 )
return res;
// Else, run logic for our additional layer of filtering.
//
return callback( i, self, project ) ? 1 : 0;
};
return *this;
}
// [Filtering method]
// For each entry, invokes the filter and if it returns true,
/// breaks out of the loop. If user provides an iterator instead
// of a callback function, it breaks out of iteration when it
// reaches that iterator instead.
//
template<typename argument_type>
auto& until( argument_type arg )
{
if constexpr ( !std::is_same_v<std::remove_cvref_t<argument_type>, iterator_type> )
{
// Allow callbacks taking non-projected type if they take
// the base iterator type.
//
fn_projector project = project_value;
callback_wrapper<argument_type> callback = { arg };
// Override iteration logic.
//
fn_controller prev = query.controller;
query.controller = [ prev, project, callback ] ( query_desc& self, iterator_type i ) -> int
{
// If current iterator reports end or filtered-out,
// return as is.
//
int res = prev( self, i );
if ( res <= 0 )
return res;
// Else, run logic for our additional layer of filtering.
//
return callback( i, self, project ) ? -1 : 1;
};
return *this;
}
else
{
// Override iteration logic.
//
fn_controller prev = query.controller;
iterator_type stop_at = arg;
query.controller = [ prev, stop_at ] ( query_desc& self, iterator_type i ) -> int
{
// Break if we reached the target iterator.
//
return i == stop_at ? -1 : prev( self, i );
};
return *this;
}
}
// [Filtering method]
// For each entry, invokes the filter and if it returns false,
/// breaks out of the loop.
//
template<typename callback_type>
auto& whilst( callback_type cb )
{
// Allow callbacks taking non-projected type if they take
// the base iterator type.
//
fn_projector project = project_value;
callback_wrapper<callback_type> callback = { cb };
// Override iteration logic.
//
fn_controller prev = query.controller;
query.controller = [ prev, project, callback ] ( query_desc& self, iterator_type i ) -> int
{
// If current iterator reports end or filtered-out,
// return as is.
//
int res = prev( self, i );
if ( res <= 0 )
return res;
// Else, run logic for our additional layer of filtering.
//
return callback( i, self, project ) ? 1 : -1;
};
return *this;
}
// [Filtering method]
// For each entry valid in the current(!) conditions, invokes
// the given enumerator function.
//
template<typename callback_type>
auto& run( callback_type cb )
{
if ( !next ) return *this;
// Allow callbacks taking non-projected type if they take
// the base iterator type.
//
fn_projector project = project_value;
callback_wrapper<callback_type> callback = { cb };
// Override iteration logic.
//
fn_controller prev = query.controller;
query.controller = [ prev, project, callback ] ( query_desc& self, iterator_type i ) -> int
{
// If current iterator reports end or filtered-out,
// return as is.
//
int res = prev( self, i );
if ( res <= 0 )
return res;
// Else, invoke the enumerator and continue.
//
callback( i, self, project );
return 1;
};
return *this;
}
// [Collection method]
// Invokes the enumerator for each entry, if enumerator returns void/bool
// returns the number of (?=true) entries, otherwise collects the return value
// in std::vector<> and returns that.
//
template<typename enumerator_type>
auto for_each( const enumerator_type& enumerator )
{
using T = decltype( enumerator( std::declval<projected_type>() ) );
if constexpr ( std::is_same_v<T, void> )
{
size_t count = 0;
while ( query.forward() == 1 )
count++, enumerator( prev() );
return count;
}
else if constexpr ( std::is_same_v<T, bool> )
{
size_t count = 0;
while ( query.forward() == 1 )
count += enumerator( prev() );
return count;
}
else
{
std::vector<T> result;
while ( query.forward() == 1 )
result.push_back( enumerator( prev() ) );
return result;
}
}
// [Collection method]
// Collects all entries in a vector and returns it as is.
//
std::vector<projected_type> collect()
{
return for_each( [ ] ( projected_type r ) { return r; } );
}
// [Collection method]
// Evaluates the iteration logic and returns the number of hits.
//
auto evaluate()
{
return for_each( [ ] ( projected_type r ) {} );
}
// [Collection method]
// Returns the first valid entry in the stream, nullopt if there were none.
//
std::optional<projected_type> first()
{
if ( query.forward() == 1 )
return prev();
return std::nullopt;
}
// [Collection method]
// Returns the last valid entry in the stream, nullopt if there were none.
//
std::optional<projected_type> last()
{
std::optional<projected_type> res;
while ( query.forward() == 1 )
res = prev();
return res;
}
};
// Creates a reference-view query for the given query base.
//
template<typename iterator_type>
static auto create( query_desc<iterator_type> q ) { return view<typename query_desc<iterator_type>::reference_type, query_desc<iterator_type>>( q ); }
// Creates a reference-view query for the given range iterator.
//
template<typename iterator_type>
static auto create( iterator_type r, int8_t dir = 0 ) { return create( query_desc{ r, dir } ); }
// Creates a dummy view that can be used to extract the summarized control logic.
//
template<typename iterator_type>
static auto dummy() { return create( query_desc<iterator_type>{} ); }
};

View file

@ -0,0 +1,281 @@
// 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 "basic_block.hpp"
namespace vtil
{
// Constructor does not exist. Should be created either using
// ::begin(...) or ->fork(...).
//
basic_block* basic_block::begin( vip_t entry_vip )
{
// Caller must provide a valid virtual instruction pointer.
//
fassert( entry_vip != invalid_vip );
// Create the basic block with depth = 0, identifier = "0"
//
basic_block* blk = new basic_block;
blk->entry_vip = entry_vip;
// Create the routine and assign this block as the entry-point
//
blk->owner = new routine;
blk->owner->entry_point = blk;
blk->owner->explored_blocks[ entry_vip ] = blk;
// Return the block
//
return blk;
}
basic_block* basic_block::fork( vip_t entry_vip )
{
// Block cannot be forked before a branching instruction is hit.
//
fassert( is_complete() );
// Caller must provide a valid virtual instruction pointer.
//
fassert( entry_vip != invalid_vip );
// Check if the routine has already explored this block.
//
std::lock_guard g( owner->mutex );
basic_block* result = nullptr;
basic_block*& entry = owner->explored_blocks[ entry_vip ];
if ( !entry )
{
// If it did not, create a block and assign it.
//
result = new basic_block;
result->owner = owner;
result->entry_vip = entry_vip;
result->sp_offset = 0;
entry = result;
}
// Fix the links and quit the scope holding the lock.
//
next.push_back( entry );
entry->prev.push_back( this );
return result;
}
// Helpers for the allocation of unique temporary registers
//
arch::register_view basic_block::tmp( uint8_t size )
{
return arch::register_view
{
"t" + std::to_string( ++owner->temporary_index_counter ),
0,
size
};
}
// Instruction pre-processor
//
void basic_block::append_instruction( instruction ins )
{
// Cannot access EFLAGS directly.
//
static const auto EFLAGS_MAPPINGS = { std::pair{ REG_OF, X86_EFLAGS_OF_BIT },
std::pair{ REG_SF, X86_EFLAGS_SF_BIT },
std::pair{ REG_ZF, X86_EFLAGS_ZF_BIT },
std::pair{ REG_AF, X86_EFLAGS_AF_BIT },
std::pair{ REG_PF, X86_EFLAGS_PF_BIT } };
if ( int w_op = ins.writes_to( X86_REG_EFLAGS ) )
{
auto t1 = tmp( 8 );
mov( t1, 0ull );
ins.operands[ w_op - 1 ].reg.base = t1.base;
append_instruction( ins );
for ( auto& pair : EFLAGS_MAPPINGS )
{
auto t0 = tmp( 8 );
mov( t0, t1 );
bshr( t0, pair.second );
band( t0, 1 );
mov( pair.first, t0 )->stream.back().make_volatile();
}
return;
}
if ( int r_op = ins.reads_from( X86_REG_EFLAGS ) )
{
auto t1 = tmp( 8 );
mov( t1, 0ull );
ins.operands[ r_op - 1 ].reg.base = t1.base;
for ( auto& pair : EFLAGS_MAPPINGS )
{
auto t0 = tmp( 8 );
mov( t0, pair.first );
bshl( t0, pair.second );
band( t0, 1ull << pair.second );
bor( t1, t0 );
}
}
fassert( !ins.writes_to( X86_REG_EFLAGS ) && !ins.reads_from( X86_REG_EFLAGS ) );
// Instructions cannot be appended after a branching instruction was hit.
//
fassert( !is_complete() );
// Write the stack pointer details.
//
ins.sp_offset = sp_offset;
ins.sp_index = sp_index;
// If instruction writes to RSP, reset the queued stack pointer.
//
if ( ins.writes_to( X86_REG_RSP ) )
{
sp_offset = 0;
sp_index++;
ins.sp_reset = true;
}
// Append the instruction to the stream.
//
stream.push_back( ins );
}
// Updates EFLAGS register based on the previous instruction executed and writes it
// into the operand given.
//
basic_block* basic_block::uflags_result( operand op, bool carry )
{
// SF, ZF, PF will be set according to the result.
//
//
// AF will be set to undefined, as they are not implemented yet.
// OF, CF will be cleared.
this->setz( REG_ZF, op )
->sets( REG_SF, op )
->setp( REG_PF, op )
->mov( REG_AF, REG_UNKB );
if ( carry )
seto( REG_OF, op )->setc( REG_CF, op );
else
mov( REG_OF, 0ull )->mov( REG_CF, 0ull );
return this;
}
// Queues a stack shift.
//
basic_block* basic_block::shift_sp( int64_t offset, bool merge_instance, iterator it )
{
// If requested, shift the stack index first.
//
if ( merge_instance )
{
// Assert instruction at iterator indeed resets stack pointer.
//
fassert( !it.is_end() && it->sp_reset );
// Decrement stack index for each instruction afterwards.
//
for ( auto i = std::next( it ); !i.is_end(); i++ )
i->sp_index--;
sp_index--;
// Remove the reset flag and merge the offsets.
//
it->sp_reset = false;
offset += it->sp_offset;
it->sp_offset = 0;
}
// If an iterator is provided, shift the stack pointer
// for every instruction that precedes it as well.
//
std::optional<uint32_t> sp_index_prev;
while ( !it.is_end() )
{
// Shift the stack offset accordingly.
//
it->sp_offset += offset;
// If instruction reads from RSP:
//
if ( it->reads_from( X86_REG_RSP ) )
{
// If LDR|STR with memory operand RSP:
//
if ( it->base->accesses_memory() && it->operands[ it->base->memory_operand_index ].reg == X86_REG_RSP )
{
// Assert the offset operand is an immediate and
// shift the offset as well.
//
fassert( it->operands[ it->base->memory_operand_index + 1 ].is_immediate() );
it->operands[ it->base->memory_operand_index + 1 ].i64 += offset;
}
}
// If stack changed changed, return, else forward the iterator.
//
if ( sp_index_prev.value_or( it->sp_index ) != it->sp_index )
return this;
sp_index_prev = it->sp_index;
++it;
}
// Shift the stack pointer and continue as usual
// without emitting any sub or add instructions.
// Queued stack pointer changes will be processed
// in bulk at the end of the routine.
//
sp_offset += offset;
return this;
}
// Pushes current flags value up the stack queueing the
// shift in stack pointer.
//
basic_block* basic_block::pushf()
{
return push( X86_REG_EFLAGS );
}
// Emits an entire instruction using series of VEMITs.
//
basic_block* basic_block::vemits( const std::string& assembly )
{
auto res = keystone::assemble( assembly );
fassert( !res.empty() );
for ( uint8_t byte : res )
vemit( byte );
return this;
}
};

View file

@ -1,10 +1,37 @@
// 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.
//
#pragma once
#include <set>
#include <list>
#include <vector>
#include <algorithm>
#include <iterator>
#include <keystone.hpp>
#include <vtil/amd64>
#include "routine.hpp"
#include "instruction.hpp"
@ -20,9 +47,8 @@ namespace vtil
// expression simplification in order to resolve branch destinations
// or stack pointer value when required.
//
// - No block should under any
// circumstance modify any of the properties of any other block,
// with the only exception being .prev.
// - No block should under any circumstance modify any of the properties
// of any other block, with the only exception being .prev.
//
struct basic_block
{
@ -45,7 +71,7 @@ namespace vtil
// Default constructor and the block-bound constructor.
//
riterator_base() {}
riterator_base() = default;
riterator_base( container_type* ref, const iterator_type& i ): container( ref ), iterator_type( i ) {}
template<typename X, typename Y> riterator_base( const riterator_base<X, Y>& o ) : container( o.container ), iterator_type( Y( o ) ) {}
@ -190,90 +216,26 @@ namespace vtil
// Wrap the std::list fundamentals.
//
auto size() const { return stream.size(); }
iterator end() { return { this, stream.end() }; }
iterator begin() { return { this, stream.begin() }; }
const_iterator end() const { return { this, stream.end() }; }
const_iterator begin() const { return { this, stream.begin() }; }
inline auto size() const { return stream.size(); }
inline iterator end() { return { this, stream.end() }; }
inline iterator begin() { return { this, stream.begin() }; }
inline const_iterator end() const { return { this, stream.end() }; }
inline const_iterator begin() const { return { this, stream.begin() }; }
// Returns whether or not stream is complete.
// Returns whether or not block is complete, a complete
// block ends with a branching instruction.
//
bool is_complete() const
{
// Instructions cannot be appended after a branching instruction was hit.
//
return !stream.empty() && stream.back().base->is_branching();
}
inline bool is_complete() const { return !stream.empty() && stream.back().base->is_branching(); }
// Constructor does not exist. Should be created either using
// ::begin(...) or ->fork(...).
//
static basic_block* begin( vip_t entry_vip )
{
// Caller must provide a valid virtual instruction pointer.
//
fassert( entry_vip != invalid_vip );
// Create the basic block with depth = 0, identifier = "0"
//
basic_block* blk = new basic_block;
blk->entry_vip = entry_vip;
// Create the routine and assign this block as the entry-point
//
blk->owner = new routine;
blk->owner->entry_point = blk;
blk->owner->explored_blocks[ entry_vip ] = blk;
// Return the block
//
return blk;
}
basic_block* fork( vip_t entry_vip )
{
// Block cannot be forked before a branching instruction is hit.
//
fassert( is_complete() );
// Caller must provide a valid virtual instruction pointer.
//
fassert( entry_vip != invalid_vip );
// Check if the routine has already explored this block.
//
std::lock_guard g( owner->mutex );
basic_block* result = nullptr;
basic_block*& entry = owner->explored_blocks[ entry_vip ];
if ( !entry )
{
// If it did not, create a block and assign it.
//
result = new basic_block;
result->owner = owner;
result->entry_vip = entry_vip;
result->sp_offset = 0;
entry = result;
}
// Fix the links and quit the scope holding the lock.
//
next.push_back( entry );
entry->prev.push_back( this );
return result;
}
static basic_block* begin( vip_t entry_vip );
basic_block* fork( vip_t entry_vip );
// Helpers for the allocation of unique temporary registers
//
auto tmp( uint8_t size )
{
return arch::register_view
{
"t" + std::to_string( ++owner->temporary_index_counter ),
0,
size
};
}
arch::register_view tmp( uint8_t size );
template<typename... params>
auto tmp( uint8_t size_0, params... size_n )
{
@ -282,30 +244,7 @@ namespace vtil
// Instruction pre-processor
//
void append_instruction( instruction ins )
{
// Instructions cannot be appended after a branching instruction was hit.
//
fassert( !is_complete() );
// Write the stack pointer details.
//
ins.sp_offset = sp_offset;
ins.sp_index = sp_index;
// If instruction writes to RSP, reset the queued stack pointer.
//
if ( ins.writes_to( X86_REG_RSP ) )
{
sp_offset = 0;
sp_index++;
ins.sp_reset = true;
}
// Append the instruction to the stream.
//
stream.push_back( ins );
}
void append_instruction( instruction ins );
// Lazy wrappers for every instruction
//
@ -317,18 +256,21 @@ namespace vtil
if constexpr ( std::is_same_v<T, register_view> ||
std::is_same_v<T, operand> )
return operand( value );
// If x86_reg, map to register_view
//
else if constexpr ( std::is_same_v<T, x86_reg> )
{
auto [offset, size] = arch::get_register_mapping( value );
auto [base, offset, size] = amd64::resolve_mapping( value );
return operand( register_view( value, offset, size ) );
}
// If std::string/register_desc, cast to register_view
//
else if constexpr ( std::is_same_v<T, std::string> ||
std::is_same_v<T, arch::register_desc> )
return operand( register_view( value ) );
// Else, treat as immediate
//
else if constexpr ( std::is_integral_v<T> )
@ -369,6 +311,11 @@ namespace vtil
WRAP_LAZY( band );
WRAP_LAZY( bror );
WRAP_LAZY( brol );
WRAP_LAZY( sets );
WRAP_LAZY( setz );
WRAP_LAZY( setp );
WRAP_LAZY( setc );
WRAP_LAZY( seto );
WRAP_LAZY( js );
WRAP_LAZY( jmp );
WRAP_LAZY( vexit );
@ -382,73 +329,23 @@ namespace vtil
WRAP_LAZY( vpinwm );
#undef WRAP_LAZY
// Updates EFLAGS register based on the previous instruction executed and writes it
// into the operand given.
//
basic_block* uflags_result( operand op, bool carry );
// Queues a stack shift.
//
basic_block* shift_sp( int64_t offset, bool merge_instance = false, iterator it = {} )
{
// If requested, shift the stack index first.
//
if ( merge_instance )
{
// Assert instruction at iterator indeed resets stack pointer.
//
fassert( !it.is_end() && it->sp_reset );
// Decrement stack index for each instruction afterwards.
//
for ( auto i = std::next( it ); !i.is_end(); i++ )
i->sp_index--;
sp_index--;
// Remove the reset flag and merge the offsets.
//
it->sp_reset = false;
offset += it->sp_offset;
it->sp_offset = 0;
}
basic_block* shift_sp( int64_t offset, bool merge_instance = false, iterator it = {} );
// If an iterator is provided, shift the stack pointer
// for every instruction that precedes it as well.
//
std::optional<uint32_t> sp_index_prev;
while ( !it.is_end() )
{
// Shift the stack offset accordingly.
//
it->sp_offset += offset;
// Pushes current flags value up the stack queueing the
// shift in stack pointer.
//
basic_block* pushf();
// If instruction reads from RSP:
//
if ( it->reads_from( X86_REG_RSP ) )
{
// If LDR|STR with memory operand RSP:
//
if ( it->base->accesses_memory() && it->operands[ it->base->memory_operand_index ].reg == X86_REG_RSP )
{
// Assert the offset operand is an immediate and
// shift the offset as well.
//
fassert( it->operands[ it->base->memory_operand_index + 1 ].is_immediate() );
it->operands[ it->base->memory_operand_index + 1 ].i64 += offset;
}
}
// If stack changed changed, return, else forward the iterator.
//
if ( sp_index_prev.value_or( it->sp_index ) != it->sp_index )
return this;
sp_index_prev = it->sp_index;
++it;
}
// Shift the stack pointer and continue as usual
// without emitting any sub or add instructions.
// Queued stack pointer changes will be processed
// in bulk at the end of the routine.
//
sp_offset += offset;
return this;
}
// Emits an entire instruction using series of VEMITs.
//
basic_block* vemits( const std::string& assembly );
// Pushes an operand up the stack queueing the
// shift in stack pointer.
@ -466,7 +363,7 @@ namespace vtil
auto t0 = tmp( 8 );
return mov( t0, op )->push( t0 );
}
shift_sp( op.size() < stack_alignment ? -stack_alignment : -op.size() );
str( X86_REG_RSP, sp_offset, op );
return this;
@ -484,25 +381,6 @@ namespace vtil
ldd( op, X86_REG_RSP, offset );
return this;
}
// Pushes current flags value up the stack queueing the
// shift in stack pointer.
//
basic_block* pushf()
{
return push( X86_REG_EFLAGS );
}
// Emits an entire instruction using series of VEMITs.
//
basic_block* vemits( const std::string& assembly )
{
auto res = assemble( assembly );
fassert( !res.empty() );
for ( uint8_t byte : res )
vemit( byte );
return this;
}
};
// Export iterator type for the sake of convinience.

View file

@ -0,0 +1,159 @@
// 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 "instruction.hpp"
namespace vtil
{
// Returns whether the instruction is valid or not.
//
bool vtil::instruction::is_valid() const
{
// Instruction must have a base descriptor assigned.
//
if ( !base )
return false;
// Validate operand count.
//
if ( operands.size() != base->operand_count() )
return false;
// Validate operand types against the base access type.
//
for ( int i = 0; i < base->access_types.size(); i++ )
{
if ( !operands[ i ].is_valid() )
return false;
if ( base->access_types[ i ] == arch::read_imm && !operands[ i ].is_immediate() )
return false;
if ( base->access_types[ i ] == arch::read_reg && !operands[ i ].is_register() )
return false;
}
// Validate memory operands.
//
if ( base->accesses_memory() )
{
const operand& mem_base = operands[ base->memory_operand_index ];
const operand& mem_offset = operands[ base->memory_operand_index + 1 ];
if ( !mem_base.is_register() || mem_base.size() != 8 )
return false;
if ( !mem_offset.is_immediate() )
return false;
}
// Validate branching operands.
//
for ( auto& list : { base->branch_operands_rip, base->branch_operands_vip } )
{
for ( int idx : list )
{
if ( operands[ idx ].size() != 8 )
return false;
}
}
return true;
}
// Returns all memory accesses matching the criteria.
//
std::pair<arch::register_view, int64_t> instruction::get_mem_loc( arch::operand_access access ) const
{
// Validate arguments.
//
fassert( access == arch::invalid || access == arch::read || access == arch::write );
// If instruction does access memory:
//
if ( base->accesses_memory() )
{
// Fetch and validate memory operands pair.
//
const register_view& mem_base = operands[ base->memory_operand_index ].reg;
const operand& mem_offset = operands[ base->memory_operand_index + 1 ];
if ( !base->memory_write && ( access == arch::read || access == arch::invalid ) )
return { mem_base, mem_offset.i64 };
else if ( base->memory_write && ( access == arch::write || access == arch::invalid ) )
return { mem_base, mem_offset.i64 };
}
return {};
}
// Checks whether the instruction reads from the given register or not.
//
int instruction::reads_from( const register_view& rw ) const
{
for ( int i = 0; i < base->access_types.size(); i++ )
if ( base->access_types[ i ] != arch::write && operands[ i ].reg.overlaps( rw ) )
return i + 1;
return 0;
}
// Checks whether the instruction writes to the given register or not.
//
int instruction::writes_to( const register_view& rw ) const
{
for ( int i = 0; i < base->access_types.size(); i++ )
if ( base->access_types[ i ] >= arch::write && operands[ i ].reg.overlaps( rw ) )
return i + 1;
return 0;
}
// Checks whether the instruction overwrites the given register or not.
//
int instruction::overwrites( const register_view& rw ) const
{
for ( int i = 0; i < base->access_types.size(); i++ )
if ( base->access_types[ i ] == arch::write && operands[ i ].reg.overlaps( rw ) )
return i + 1;
return 0;
}
// Conversion to human-readable format.
//
std::string instruction::to_string() const
{
std::vector<std::string> operand_str;
for ( auto& op : operands )
operand_str.push_back( op.to_string() );
fassert( operand_str.size() <= arch::max_operand_count &&
arch::max_operand_count == 4 );
operand_str.resize( arch::max_operand_count );
return format::str
(
FMT_INS,
base->to_string( access_size() ),
operand_str[ 0 ],
operand_str[ 1 ],
operand_str[ 2 ],
operand_str[ 3 ]
);
}
};

View file

@ -1,8 +1,34 @@
// 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.
//
#pragma once
#include <vector>
#include <string>
#include "..\arch\instruction_set.hpp"
#include "..\misc\format.hpp"
namespace vtil
{
@ -15,7 +41,8 @@ namespace vtil
// Simple helper to create an immediate operand since vtil::operand( v, size ) gets redundant.
//
template<typename T> static operand make_imm( T value ) { return operand( value, sizeof( T ) ); }
template<typename T>
static operand make_imm( T value ) { return operand( value, sizeof( T ) ); }
// Type we use to describe virtual instruction pointer in.
//
@ -29,7 +56,7 @@ namespace vtil
{
// Base instruction type.
//
const arch::instruction_desc* base;
const arch::instruction_desc* base = nullptr;
// List of operands.
//
@ -55,7 +82,7 @@ namespace vtil
// Basic constructor, non-default constructor asserts the constructed
// instruction is valid according to the instruction descriptor.
//
instruction() {}
instruction() = default;
instruction( const arch::instruction_desc* base,
const std::vector<operand>& operands = {},
vip_t vip = invalid_vip,
@ -68,55 +95,7 @@ namespace vtil
// Returns whether the instruction is valid or not.
//
bool is_valid() const
{
// Instruction must have a base descriptor assigned.
//
if ( !base )
return false;
// Validate operand count.
//
if ( operands.size() != base->operand_count() )
return false;
// Validate operand types against the base access type.
//
for ( int i = 0; i < base->access_types.size(); i++ )
{
if ( !operands[ i ].is_valid() )
return false;
if ( base->access_types[ i ] == arch::read_imm && !operands[ i ].is_immediate() )
return false;
if ( base->access_types[ i ] == arch::read_reg && !operands[ i ].is_register() )
return false;
}
// Validate memory operands.
//
if ( base->accesses_memory() )
{
const operand& mem_base = operands[ base->memory_operand_index ];
const operand& mem_offset = operands[ base->memory_operand_index + 1 ];
if ( !mem_base.is_register() || mem_base.size() != 8 )
return false;
if ( !mem_offset.is_immediate() )
return false;
}
// Validate branching operands.
//
for ( auto& list : { base->branch_operands_rip, base->branch_operands_vip } )
{
for ( int idx : list )
{
if ( operands[ idx ].size() != 8 )
return false;
}
}
return true;
}
bool is_valid() const;
// Makes the instruction explicitly volatile.
//
@ -137,58 +116,19 @@ namespace vtil
// Returns all memory accesses matching the criteria.
//
std::pair<arch::register_view, int64_t> get_mem_loc( arch::operand_access access = arch::invalid ) const
{
// Validate arguments.
//
fassert( access == arch::invalid || access == arch::read || access == arch::write );
// If instruction does access memory:
//
if ( base->accesses_memory() )
{
// Fetch and validate memory operands pair.
//
const register_view& mem_base = operands[ base->memory_operand_index ].reg;
const operand& mem_offset = operands[ base->memory_operand_index + 1 ];
if ( !base->memory_write && ( access == arch::read || access == arch::invalid ) )
return { mem_base, mem_offset.i64 };
else if ( base->memory_write && ( access == arch::write || access == arch::invalid ) )
return { mem_base, mem_offset.i64 };
}
return {};
}
std::pair<arch::register_view, int64_t> get_mem_loc( arch::operand_access access = arch::invalid ) const;
// Checks whether the instruction reads from the given register or not.
//
bool reads_from( const register_view& rw ) const
{
for ( int i = 0; i < base->access_types.size(); i++ )
if ( base->access_types[ i ] != arch::write && operands[ i ].reg.overlaps( rw ) )
return true;
return false;
}
int reads_from( const register_view& rw ) const;
// Checks whether the instruction writes to the given register or not.
//
bool writes_to( const register_view& rw ) const
{
for ( int i = 0; i < base->access_types.size(); i++ )
if ( base->access_types[ i ] >= arch::write && operands[ i ].reg.overlaps( rw ) )
return true;
return false;
}
int writes_to( const register_view& rw ) const;
// Checks whether the instruction overwrites the given register or not.
//
bool overwrites( const register_view& rw ) const
{
for ( int i = 0; i < base->access_types.size(); i++ )
if ( base->access_types[ i ] == arch::write && operands[ i ].reg.overlaps( rw ) )
return true;
return false;
}
int overwrites( const register_view& rw ) const;
// Basic comparison operators.
//
@ -198,24 +138,6 @@ namespace vtil
// Conversion to human-readable format.
//
std::string to_string() const
{
std::vector<std::string> operand_str;
for ( auto& op : operands )
operand_str.push_back( op.to_string() );
fassert( operand_str.size() <= arch::max_operand_count &&
arch::max_operand_count == 4 );
operand_str.resize( arch::max_operand_count );
return format::str
(
FMT_INS,
base->to_string( access_size() ),
operand_str[ 0 ],
operand_str[ 1 ],
operand_str[ 2 ],
operand_str[ 3 ]
);
}
std::string to_string() const;
};
};

View file

@ -1,3 +1,30 @@
// 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.
//
#pragma once
#include <map>
#include <atomic>

View file

@ -1,461 +0,0 @@
#pragma once
#include <vector>
#include <optional>
#include <functional>
#include <type_traits>
#include "variable.hpp"
#include "operators.hpp"
namespace vtil::symbolic
{
struct expression;
using symbol_set = std::set<unique_identifier>;
using symbol_map = std::map<variable, expression>;
using fn_exp_callback = std::function<void( expression& )>;
using fn_const_exp_callback = std::function<void( const expression& )>;
// Describes an expression tree.
//
struct expression
{
// If variable, the value of the expression.
//
std::optional<variable> value;
// If result, list of operands and the operator description.
//
const operator_desc* fn;
std::vector<expression> operands;
// Set to true if the expression was already simplified by the simplifier.
// This allows us to indicate that this expression cannot be simplified
// any further and thus makes recursive simplification much more time
// efficient when it's called from multiple points of the application.
//
bool is_simplest_form = false;
auto& declare_simple() { is_simplest_form = true; return *this; }
auto& declare_changed() { is_simplest_form = false; return *this; }
// Default constructors.
//
expression() : fn( nullptr ) {}
expression( const variable& a ) : value( a ), fn( nullptr ), is_simplest_form( true ) {}
expression( const operator_desc* fn, const expression& a ) : operands( { a } ), fn( fn ) {}
expression( const expression& a, const operator_desc* fn, const expression& b ) : operands( { a, b } ), fn( fn ) {}
// Helpers to determine the type of the expression.
//
bool is_expression() const { return operands.size() && !value; }
bool is_variable() const { return operands.empty() && value; }
bool is_constant() const { return is_variable() && value->is_constant(); }
bool is_valid() const { return is_variable() ? operands.empty() : fn && ( fn->is_unary ? 1 : 2 ) == operands.size(); }
// Calls the callback provided for every expression that
// is actually a boxed symbolic variable.
//
void enum_symbols( const fn_exp_callback& cb )
{
if ( is_variable() )
{
if ( value->is_symbolic() )
cb( *this );
return;
}
for ( auto& op : operands )
op.enum_symbols( cb );
}
// Calls the callback provided for every expression that
// is actually a boxed symbolic variable. [Const]
//
void enum_symbols( const fn_const_exp_callback& cb ) const
{
if ( is_variable() )
{
if ( value->is_symbolic() )
cb( *this );
return;
}
for ( auto& op : operands )
op.enum_symbols( cb );
}
// Creates a set containing every unique symbol used in
// this expression tree.
//
symbol_set enum_symbols() const
{
symbol_set tmp;
enum_symbols( [ & ] ( auto& x ) { tmp.insert( x.value->uid ); } );
return tmp;
}
// Returns whether the expression contains the given
// symbolic variable in any size or not.
//
bool contains_symbol( const unique_identifier& uid ) const
{
if ( is_variable() )
return value->uid == uid;
for ( auto& operand : operands )
if ( operand.contains_symbol( uid ) )
return true;
return false;
}
// Counts the number of unique symbols used in this
// expression tree.
//
size_t count_symbols() const
{
return enum_symbols().size();
}
// Remaps every occurance of the symbol<uid> with the
// expression provided.
//
void remap_symbols( const symbol_map& sym_map )
{
enum_symbols( [ & ]( expression& v )
{
for ( auto& sym : sym_map )
{
if ( sym.first.uid == v.value->uid )
v = sym.second;
}
} );
}
void remap_symbol( const unique_identifier& uid, const expression& as )
{
enum_symbols( [ & ] ( expression& v )
{
if ( v.value->uid == uid )
v = as;
} );
}
// Checks if the expression is normalized.
//
bool is_normalized() const
{
if ( is_variable() || fn->is_unary || fn->result_size == 0 )
return true;
uint8_t s0 = operands[ 0 ].size();
uint8_t s1 = operands[ 1 ].size();
return s0 == 0 || s1 == 0 || s0 == s1;
}
// Returns the size of the output value.
//
uint8_t size( bool real_size = false ) const
{
// If variable, return size as is.
//
if ( is_variable() )
return value->calc_size( false );
// Exceptional operators:
//
if ( fn->function == "__zx" ||
fn->function == "__sx" )
return real_size ? operands[ 0 ].value->get() : operands[ 1 ].value->get();
if ( fn->function == "__bcnt" ||
fn->function == "__bcntN" )
return 1;
// If unary operator or result size is first operand,
// redirect to the first operand.
//
if ( fn->is_unary || fn->result_size == 0 )
return operands[ 0 ].size( real_size );
// Process according to the operator definition.
//
uint8_t s0 = operands[ 0 ].size( real_size );
uint8_t s1 = operands[ 1 ].size( real_size );
if ( fn->result_size == 1 )
return std::max( s0, s1 );
else
return std::min( s0, s1 );
}
// Changes the size of the output value.
//
expression& resize( uint8_t size, bool sign_extend = false )
{
// Can't resize to <any size>
//
if ( size == 0 )
return *this;
// If variable, resize it:
//
if ( is_variable() )
{
if ( value->is_symbolic() )
{
if ( value->size == size )
return *this;
if ( value->size < size )
*this = expression( *this, sign_extend ? find_opr( "__sx" ) : find_opr( "__zx" ), variable( size ) );
else
value->resize( size, sign_extend );
}
else
{
value->resize( size, sign_extend );
}
return *this;
}
// If result of an operator:
//
else if( is_expression() )
{
// If function is an extender, apply to the argument.
//
if ( fn->function == "__zx" ||
fn->function == "__sx" )
{
*this = operands[ 0 ];
resize( size, sign_extend );
}
// If unary operator or result size is first operand,
// redirect to the first operand.
//
else if ( fn->is_unary || fn->result_size == 0 )
{
operands[ 0 ].resize( size, !fn->is_bitwise );
}
// Otherwise, resize both.
//
else
{
// Cannot shrink in a simple way if rotate or shift right
//
if( fn->function == "rol" || fn->function == "ror" || fn->function == "shr" )
fassert( size >= operands[ 0 ].size() );
operands[ 0 ].resize( size, !fn->is_bitwise );
operands[ 1 ].resize( size, !fn->is_bitwise );
}
// Declare that the expression was changed.
//
declare_changed();
}
return *this;
}
// Returns an arbitrary value that represents the "complexity"
// of the expression. It's used for the simplification algorithm.
//
size_t complexity() const
{
// If it's a variable:
//
if ( is_variable() )
return value->is_constant() ? 0 : 1;
// Exceptional operators:
//
if ( fn->function == "__bcntN" ||
fn->function == "__zx" ||
fn->function == "__sx" )
return operands[ 0 ].complexity();
if ( fn->function == "__bcnt" ||
fn->function == "__bcntN" ||
fn->function == "__bmask" )
return 0;
// Ideally we want less operations on symbolic variables,
// so create an exponentially increasing cost.
//
if ( fn->is_unary )
return operands[ 0 ].complexity() << 1;
else
return ( operands[ 0 ].complexity() + operands[ 1 ].complexity() ) << 1;
}
// Depth of the operation tree.
//
size_t depth() const
{
// If variable, return 1.
//
if ( is_variable() )
return 0;
// Exceptional operators:
//
if ( fn->function == "__bcntN" ||
fn->function == "__zx" ||
fn->function == "__sx" )
return operands[ 0 ].depth();
if ( fn->function == "__bcnt" ||
fn->function == "__bcntN" ||
fn->function == "__bmask" )
return 0;
// For each operand, recurse and sum.
//
size_t out = 1;
for ( auto& subexp : operands )
out += subexp.depth();
return out;
}
// Tries to evaluate the numeric value of a symbolic expression.
//
std::optional<variable> evaluate() const
{
// If expression is a boxed variable, return as is.
//
if ( is_variable() )
return is_constant() ? value : std::nullopt;
// Handle resizing.
//
if ( fn->function == "__zx" )
{
auto res = operands[ 0 ].evaluate();
if ( res )
res->resize( operands[ 1 ].value->get(), false );
return res;
}
else if ( fn->function == "__sx" )
{
auto res = operands[ 0 ].evaluate();
if ( res )
res->resize( operands[ 1 ].value->get(), true );
return res;
}
auto operands_n = operands;
size_t ns = size();
// ------- Unary operators ------- //
if ( fn->function == "__bcnt" )
return variable{ operands_n[ 0 ].size() * 8, ns };
else if ( fn->function == "__bmask" )
return variable{ ~0ull >> ( 64 - operands_n[ 0 ].size() * 8 ), ns };
variable o1;
if ( auto r = operands_n[ 0 ].evaluate() ) o1 = r.value();
else return {};
if ( fn->function == "neg" )
return variable{ -o1.get<true>(), ns };
else if ( fn->function == "not" )
return variable{ ~o1.get(), ns };
// ------- Binary operators ------- //
if ( fn->function == "__bcntN" )
return variable{ uint8_t( ( o1.get() ) % ( operands_n[ 1 ].size() * 8 ) ), operands_n[ 0 ].size() };
variable o2;
if ( auto r = operands_n[ 1 ].evaluate() ) o2 = r.value();
else return {};
if ( fn->function == "or" )
return variable{ o1.get() | o2.get(), ns };
else if ( fn->function == "and" )
return variable{ o1.get() & o2.get(), ns };
else if ( fn->function == "xor" )
return variable{ o1.get() ^ o2.get(), ns };
else if ( fn->function == "shr" )
return ( ns && o2.get() >= ( ns * 8 ) ) ? variable{ 0, ns } : variable{ o1.get( ns ) >> o2.get(), ns };
else if ( fn->function == "shl" )
return ( ns && o2.get() >= ( ns * 8 ) ) ? variable{ 0, ns } : variable{ o1.get( ns ) << o2.get(), ns };
else if ( fn->function == "ror" )
return variable{ ( o1.get( ns ) >> o2.get() ) | ( o1.get( ns ) << ( o1.size * 8 - o2.get() ) ), ns };
else if ( fn->function == "rol" )
return variable{ ( o1.get( ns ) << o2.get() ) | ( o1.get( ns ) >> ( o1.size * 8 - o2.get() ) ), ns };
else if ( fn->function == "add" )
return variable{ o1.get<true>() + o2.get<true>(), ns };
else if ( fn->function == "sub" )
return variable{ o1.get<true>() - o2.get<true>(), ns };
// Other operators should not reach here.
return {};
}
// Conversion to human readable format.
//
std::string to_string() const
{
// If variable redirect to it's own ::to_string.
//
if ( is_variable() )
return value->to_string();
fassert( fn );
// If unary function:
//
if ( fn->is_unary )
{
fassert( operands.size() == 1 );
return fn->symbol.size()
? fn->symbol + operands[ 0 ].to_string()
: fn->function + "(" + operands[ 0 ].to_string() + ")";
}
// If binary function:
//
fassert( operands.size() == 2 );
return fn->symbol.size()
? "(" + operands[ 0 ].to_string() + fn->symbol + operands[ 1 ].to_string() + ")"
: fn->function + "(" + operands[ 0 ].to_string() + ", " + operands[ 1 ].to_string() + ")";
}
// Basic comparison operators.
//
bool operator==( const expression& o ) const
{
if ( is_expression() && ( fn->function == "__zx" || fn->function == "__sx" ) )
return o == operands[ 0 ];
else if ( o.is_variable() )
return is_variable() && value == o.value;
else
return is_expression() && fn == o.fn && operands == o.operands;
}
bool operator!=( const expression& o ) const { return !operator==( o ); }
bool operator<( const expression& o ) const { return !operator==( o ) && to_string() < o.to_string(); }
// Convinience wrapper for operand access.
// - Use the .operands container manually when changing
// any operands and clear the simplest form hint.
//
const expression& operator[]( size_t i ) const { return operands[ i ]; }
// Convinience wrappers around common operations.
//
expression operator+() const { return expression( *this ); }
expression operator~() const { return expression( find_opr( "not" ), *this ); }
expression operator-() const { return expression( find_opr( "neg" ), *this ); }
template<typename T> expression ror( T y ) const { return expression( *this, find_opr( "ror" ), expression{ y } ); }
template<typename T> expression rol( T y ) const { return expression( *this, find_opr( "rol" ), expression{ y } ); }
};
template<typename T> static expression operator+( const expression& x, T y ) { return { x, find_opr( "add" ), expression{ y } }; }
template<typename T> static expression operator-( const expression& x, T y ) { return { x, find_opr( "sub" ), expression{ y } }; }
template<typename T> static expression operator|( const expression& x, T y ) { return { x, find_opr( "or" ), expression{ y } }; }
template<typename T> static expression operator&( const expression& x, T y ) { return { x, find_opr( "and" ), expression{ y } }; }
template<typename T> static expression operator^( const expression& x, T y ) { return { x, find_opr( "xor" ), expression{ y } }; }
template<typename T> static expression operator>>( const expression& x, T y ) { return { x, find_opr( "shr" ), expression{ y } }; }
template<typename T> static expression operator<<( const expression& x, T y ) { return { x, find_opr( "shl" ), expression{ y } }; }
template<typename T, std::enable_if_t<!std::is_same_v<std::remove_cvref_t<T>, expression>, int> = 0>
static expression operator+( T x, const expression& y ) { return expression{ x } + y; }
template<typename T, std::enable_if_t<!std::is_same_v<std::remove_cvref_t<T>, expression>, int> = 0>
static expression operator-( T x, const expression& y ) { return expression{ x } - y; }
template<typename T, std::enable_if_t<!std::is_same_v<std::remove_cvref_t<T>, expression>, int> = 0>
static expression operator|( T x, const expression& y ) { return expression{ x } | y; }
template<typename T, std::enable_if_t<!std::is_same_v<std::remove_cvref_t<T>, expression>, int> = 0>
static expression operator&( T x, const expression& y ) { return expression{ x } & y; }
template<typename T, std::enable_if_t<!std::is_same_v<std::remove_cvref_t<T>, expression>, int> = 0>
static expression operator^( T x, const expression& y ) { return expression{ x } ^ y; }
template<typename T, std::enable_if_t<!std::is_same_v<std::remove_cvref_t<T>, expression>, int> = 0>
static expression operator>>( T x, const expression& y ) { return expression{ x } >> y; }
template<typename T, std::enable_if_t<!std::is_same_v<std::remove_cvref_t<T>, expression>, int> = 0>
static expression operator<<( T x, const expression& y ) { return expression{ x } << y; }
};

View file

@ -1,550 +0,0 @@
#pragma once
#define SYMEX_EVALTIME_SIMPLIFY 1
#include <optional>
#include <iterator>
#include <functional>
#include <algorithm>
#include "variable.hpp"
#include "expression.hpp"
#include "simplifier.hpp"
#include "..\query\view.hpp"
#include "..\routine\instruction.hpp"
#include "..\routine\basic_block.hpp"
// Symbolic expression generator is essentialy the core of almost every optimization
// pass since it is used to create simplified equivalents for the given
// variable, which can be easily created from and cast back to any register
// descriptor, view, or a memory pointer including external memory.
//
namespace vtil::symbolic
{
// Generates a simplified expression for the given interator-bound variable
// according to the instruction stream in the basic block, and any block that
// jumps to it if recurse flag is set.
//
// - Virtual-SP should be set if the caller wishes to receive
// virtual stack pointers such as the operands of STR and
// LDR where the memory offset behaves indepdentent to the
// the stack pointer itself.
//
// - Handles branching and loops internally.
//
template<bool verbose = false>
static expression generate( const variable& lookup,
bool virtual_sp = false,
bool recurse = false,
int32_t max_op_depth = INT32_MAX,
std::map<std::pair<const basic_block*, const basic_block*>, uint32_t> visited = {} )
{
// If we're not tracing a register or a memory value, return as is.
//
if ( !lookup.is_register() && !lookup.is_memory() )
return lookup;
// If we are tracing a control register return the symbol as is.
//
if ( lookup.is_register() )
{
register_view rw = lookup.get_reg();
if ( rw.base.maps_to >= X86_REG_VCR0 )
return lookup;
}
// Fail if max operation depth was reached.
//
if ( max_op_depth < 0 )
return {};
// Resolve query offset, size and iterator.
//
int32_t query_offset = lookup.is_register() ? lookup.get_reg().offset : 0;
uint8_t query_size = lookup.size;
fassert( query_size != 0 );
// Log the beginning of the trace if verbose.
//
if constexpr ( verbose )
{
io::log( "=> Tracing %s", lookup.to_string() );
io::log_padding++; io::log( "\n" );
}
// Craete the base query.
//
auto query_base = query::create( lookup.uid.origin, -1 ).unproject();
expression pointer_exp = {};
uint8_t current_size;
int64_t current_offset;
// If looking up register value:
//
if ( lookup.is_register() )
{
// | Filter to instructions that write to the register we're trying to resolve.
query_base
.where( [ & ] ( const ilstream_const_iterator& it ) { return it->writes_to( lookup.get_reg() ); } );
}
else
{
query_base
// | Filter to instructions that write to memory.
.where( [ ] ( const ilstream_const_iterator& it ) { return it->base->writes_memory(); } )
// | Filter to instructions that write over our pointer.
.where( [ & ] ( const ilstream_const_iterator& it )
{
auto [mem_base, mem_off] = it->get_mem_loc();
// Check if both are reading from the same stack instance to optimize
// the amount of time this lookup takes by avoiding another recursive call.
//
if ( mem_base == X86_REG_RSP &&
lookup.uid.get_mem().first == X86_REG_RSP &&
lookup.uid.memory_base_idx == it->sp_index )
{
// Offset is equivalent to the delta of their offset operands.
//
current_offset = mem_off - lookup.uid.get_mem().second;
// Log write resolved if verbose.
//
if constexpr ( verbose )
io::log<CON_RED>( "Write resolved to [@+%s]\n", format::hex( current_offset ) );
}
else
{
// If pointer expression generation was deferred, generate it now.
//
if ( !pointer_exp.is_valid() )
{
pointer_exp = generate( { { lookup.uid.get_mem().first, lookup.uid.origin }, 8 }, true ) + lookup.uid.get_mem().second;
// Log pointer resolved if verbose.
//
if constexpr ( verbose )
io::log<CON_GRN>( "Pointer resolved to [=%s]\n", pointer_exp.to_string() );
}
// Try to simplify the expression for [dst-lookup].
//
auto offset_exp = simplify( generate( { { it, it->base->memory_operand_index }, 8 }, true ) + mem_off - pointer_exp );
// Log write resolved if verbose.
//
if constexpr ( verbose )
io::log<CON_RED>( "Write resolved to [@+%s]\n", offset_exp.to_string() );
// If it does not evaluate to a constant value, skip.
//
auto offset = offset_exp.evaluate();
if ( !offset.has_value() || !offset->is_constant() )
return false;
// Offset is equivalent to the evaluated constant.
//
current_offset = offset->get<true>();
}
// Apply simple boundary check.
//
current_size = it->access_size();
return -current_size < current_offset && current_offset < lookup.size;
} );
}
// Define our query's main logic.
//
const auto to_result = [ & ] ( ilstream_const_iterator it ) -> expression
{
auto gen = [ & ] ( const variable& var, bool is_op = false ) { return generate<verbose>( var, false, recurse, max_op_depth - is_op, visited ); };
// If we are looking up a stack value:
//
if ( lookup.is_memory() )
{
// If an unknown/non-representable operation is being executed on
// the operand we're tracing, fail the trace.
//
if ( it->base != &ins::str )
return variable( lookup ).bind( it );
// If size and offset match:
//
if ( query_offset == current_offset &&
query_size == current_size )
{
// Generate symbolic variable for the result.
//
return gen( { it, 2 } );
}
}
// Else, if we are looking up a register value:
//
else
{
// If an unknown/non-representable operation is being executed on
// the operand we're tracing, fail the trace.
//
if ( it->base != &ins::mov && it->base != &ins::ldd && it->base->symbolic_operator.empty() )
return variable( lookup ).bind( it );
// Generic check for whether the symbol offset/size matches or not.
// Will work as every instruction above writes to operand 1.
//
current_size = it->operands[ 0 ].reg.size;
current_offset = it->operands[ 0 ].reg.offset;
fassert( it->operands[ 0 ].is_register() );
if ( query_offset == current_offset &&
query_size == current_size )
{
// If result is an immediate or simply another register:
//
if ( it->base == &ins::mov )
{
// Generate symbolic variable for the result.
//
return gen( { it, 1 } );
}
// If result is simply being loaded from external memory:
//
else if ( it->base == &ins::ldd )
{
// Generate symbolic variable for the result.
//
return gen( { it, -1 } );
}
// If it's the result of a symbolic operator:
//
else if ( !it->base->symbolic_operator.empty() )
{
const operator_desc* opr = find_opr( it->base->symbolic_operator );
// OP1 = F(OP1)
//
if ( it->operands.size() == 1 )
{
fassert( opr->is_unary );
// Resolve the value of OP1 prior to this instruction,
// fail if recursive call fails.
//
expression op1e = gen( { it, 0 }, true );
if ( !op1e.is_valid() )
return variable( lookup ).bind( it );
// Describe the operation.
//
#if SYMEX_EVALTIME_SIMPLIFY
return simplify( expression( opr, op1e ) );
#else
return expression( opr, op1e );
#endif
}
// OP1 = F(OP1, OP2)
//
else if ( it->operands.size() == 2 )
{
fassert( !opr->is_unary );
// Resolve the value of OP1 and OP2 prior to this
// instruction, fail if recursive call fails.
//
expression op1e = gen( { it, 0 }, true );
if ( !op1e.is_valid() )
return variable( lookup ).bind( it );
expression op2e = gen( { it, 1 }, true );
if ( !op2e.is_valid() )
return variable( lookup ).bind( it );
// Describe the operation.
//
#if SYMEX_EVALTIME_SIMPLIFY
return simplify( expression( op1e, opr, op2e ) );
#else
return expression( op1e, opr, op2e );
#endif
}
}
unreachable();
}
}
/////////////////////////////////////////////
// >>> Begin size / offset mismatch logic <<<
/////////////////////////////////////////////
// Helper to calculate the exact symbol we would have resolved.
//
const auto query_sub = [ & ] ( int64_t offset, uint8_t size )
{
// Create the symbolic identifier for the segment
// and try to generate an expresion.
//
variable sub_lookup = lookup;
sub_lookup.size = size;
if ( lookup.is_register() )
sub_lookup.uid.register_id->offset = offset, sub_lookup.uid.register_id->size = size;
else if ( lookup.is_memory() )
sub_lookup.uid.memory_id = { sub_lookup.uid.memory_id->first, sub_lookup.uid.memory_id->second + offset };
else
unreachable();
sub_lookup.uid.origin = std::next( sub_lookup.uid.origin );
sub_lookup.uid.refresh();
expression exp = gen( sub_lookup );
// If we failed, return as is.
//
if ( !exp.is_valid() )
return exp;
exp.resize( query_size );
// Mask the result (and resize, abusing size == max(x.size, y.size))
//
exp = exp & variable( ~0ull >> ( 64 - size * 8 ), query_size );
// If result belongs to low segment, shift left:
//
if ( offset > query_offset )
exp = exp << variable( ( offset - query_offset ) * 8, query_size );
// If result belongs to high segment, shift right:
//
else if ( offset < query_offset )
exp = exp >> variable( ( query_offset - offset ) * 8, query_size );
// Return the new expression.
//
return exp;
};
// Query current data we have with no mistmatch, fail if query fails.
//
expression result = query_sub( current_offset, current_size );
if ( !result.is_valid() )
return result;
// If we have enough information to return as is do not query any other segments
//
if ( ( query_size + query_offset ) <= ( current_offset + current_size ) && query_offset >= current_offset )
{
// <No operation>
}
// If the offsets match, we need to merge a high segment to the current result.
//
else if ( current_offset == query_offset )
{
fassert( current_size < query_size );
// Query high and merge it to the result.
//
expression high_res = query_sub( current_offset + current_size, query_size - current_size );
if ( !high_res.is_valid() )
return high_res;
result = result | high_res;
}
// If offsets do not match, we need to 2 <= x <= 3 segments
//
else
{
// Query low (if relevant) and merge it to the result.
//
if ( query_offset < current_offset )
{
expression low_res = query_sub( query_offset, current_offset - query_offset );
if ( !low_res.is_valid() )
return low_res;
result = result | low_res;
}
// Query high (if relevant) and merge it to the result.
//
int32_t off_query_end = query_offset + query_size;
int32_t off_current_end = current_offset + current_size;
if ( off_query_end > off_current_end )
{
expression high_res = query_sub( off_current_end, off_query_end - off_current_end );
if ( !high_res.is_valid() )
return high_res;
result = result | high_res;
}
}
// Return the result.
//
#if SYMEX_EVALTIME_SIMPLIFY
return simplify( result );
#else
return result;
#endif
};
// Declare the output expression and the default expression.
//
expression result = {};
expression default_result = variable( lookup ).unbind();
// If looking up stack pointer and flag to adjust is set:
//
int64_t adjustment_offset = 0;
if ( lookup.is_register() && lookup.get_reg() == X86_REG_RSP && !virtual_sp )
adjustment_offset = lookup.uid.origin.is_end() ? lookup.uid.origin.container->sp_offset : lookup.uid.origin->sp_offset;
// If we could find a local result within the current block assign it as is.
//
if ( std::optional result_p = query_base.reproject( to_result ).first() )
{
result = result_p.value();
}
// If recursive scanning is allowed, try recursing into previous blocks.
//
else if ( recurse )
{
// Generate the list of iterators we could continue from.
//
std::vector it_list = query_base.query.iterator.recurse( false );
// If only valid previous block is self, one self-referencing result is valid.
//
bool expect_self_ref = false;
if ( it_list.size() == 1 && it_list[ 0 ].container == lookup.uid.origin.container )
{
expect_self_ref = lookup.uid.origin.is_begin();
it_list.push_back( { lookup.uid.origin.container->begin() } );
}
// For each possible route:
//
for ( auto it : it_list )
{
// Create a local copy for the visited list for this path
// and increment the visit counter.
//
std::map visited_local = visited;
uint32_t& visit_counter = visited_local[ { lookup.uid.origin.container, it.container } ];
int32_t blk_sp_offset = it.is_begin() ? 0 : it.container->sp_offset;
// If we've taken this route no more than once:
//
if ( visit_counter++ <= 1 )
{
// Transform the variable over to the destination block.
//
variable var_inherited = variable( lookup ).bind( it );
if ( lookup.is_memory() )
{
// If pointer expression generation was deferred, generate it now.
//
if ( !pointer_exp.is_valid() )
pointer_exp = generate( { { lookup.uid.get_mem().first, lookup.uid.origin }, 8 }, true ) + lookup.uid.get_mem().second;
// Log pointer resolved if verbose.
//
if constexpr ( verbose )
io::log<CON_CYN>( "Pointer adjusted to [=%s]\n", pointer_exp.to_string() );
// If the pointer can be rewritten in the form of [RSP + C]:
//
if ( auto off = simplify( pointer_exp - variable{ { register_view{ X86_REG_RSP } }, 8 } ).evaluate() )
{
// Assign the new adjusted offset.
//
var_inherited.uid.assign( X86_REG_RSP, blk_sp_offset - off->get<true>() );
}
// If the delta does not simplify to a constant stop recursing and fail.
//
else
{
result = {};
break;
}
}
// Generate a expression for the variable in the destination block.
// - Read note below to understand why virtual sp == true.
//
expression exp = generate<verbose>( var_inherited, true, recurse, max_op_depth, visited_local );
// Skip if we traced back to the lookup variable
//
if( !expect_self_ref && is_equivalent( exp, default_result + adjustment_offset ) )
{
// Log decision if verbose.
//
if constexpr ( verbose )
io::log<CON_YLW>( "Candidate [%s] was rejected as it's self-referencing.\n", exp.to_string() );
continue;
}
// If we are tracing the value of RSP, add the stack pointer delta between blocks since
// that will be the adjustment offset of the next block and we will request a virtual
// stack pointer to keep things in balance.
//
if ( lookup.is_register() && lookup.get_reg() == X86_REG_RSP )
exp = exp + blk_sp_offset;
// If no result is set yet, assign the current expression:
//
if ( !result.is_valid() )
{
// Log decision if verbose.
//
if constexpr ( verbose )
io::log<CON_GRN>( "Using [%s] as primary candidate.\n", exp.to_string() );
result = exp;
}
// If previously set result is not equivalent to current expression
// hint branch dependency and return default result:
//
else if( !is_equivalent( exp, result ) )
{
// Log decision if verbose.
//
if constexpr ( verbose )
io::log<CON_RED>( "Cancelling query as candidate [%s] differs from previously set candidate.\n", exp.to_string() );
result = {};
default_result.enum_symbols( [ & ] ( expression& v )
{
v.value->uid.set_branch_dependency( true );
v.value->uid.bind( lookup.uid.origin.container->begin() );
} );
break;
}
else
{
// Log decision if verbose.
//
if constexpr ( verbose )
io::log<CON_YLW>( "Continuing query since [%s] matched primary candidate.\n", exp.to_string() );
}
}
else
{
// Log skipping of path if verbose.
//
if constexpr ( verbose )
io::log<CON_CYN>( "Path [%llx->%llx] is not taken as it's n-looping.\n", lookup.uid.origin.container->entry_vip, it.container->entry_vip );
}
}
}
// If resolved result is not valid, assign the default result.
//
if ( !result.is_valid() )
result = default_result;
// Propagate the stack adjustment.
//
if ( adjustment_offset != 0 )
result = simplify( result + variable( adjustment_offset, 8 ) );
// Log the final result if verbose.
//
if constexpr ( verbose )
{
io::log_padding--;
io::log( "= %s\n", result.to_string() );
}
return result.resize( lookup.size );
}
};

View file

@ -1,130 +0,0 @@
#pragma once
#include <map>
#include <string>
#include <vector>
#include <platform.hpp>
namespace vtil::symbolic
{
// Generic operator description.
//
struct operator_desc
{
// Symbolic identifier for this operator.
//
// If [symbol] is empty:
// to_string(...) => function(split(..., ','))
// else:
// to_string(x) => concat(symbol, x)
// to_string(...) => split(..., symbol)
//
std::string function;
std::string symbol;
// Whether this is a unary operator or not.
//
bool is_unary;
// Whether this is a bitwise operator or not.
//
bool is_bitwise;
// Whether (anti-)commutative property is applicable or not.
// N/A if 0, anti-commutative if -1, commutative if +1.
int8_t commutative;
// Type of result size, min if -1, max if +1, first operand if 0.
//
int8_t result_size;
// Null and identity operands in terms of x.
//
std::vector<std::string> null_operand;
std::vector<std::string> identity_operand;
};
// List of all operators.
//
static const std::vector<operator_desc> operator_map =
{
// [Name] [Symbol] [Unary?] [Bitwise?] [Commutative] [RSize] //
{ "neg", "-", true, false, 0, 0, },
{ "not", "~", true, true, 0, 0, },
{ "add", "+", false, false, +1, +1, },
{ "sub", "-", false, false, -1, +1, },
{ "or" , "|", false, true, +1, +1, },
{ "and", "&", false, true, +1, -1, },
{ "xor", "^", false, true, +1, +1, },
{ "shr", ">>", false, true, 0, 0, },
{ "shl", "<<", false, true, 0, 0, },
{ "ror", ">]", false, true, 0, 0, },
{ "rol", "[<", false, true, 0, 0, },
// Special operands for simplification instructions:
//
// Bit-Count Normalize:
// - Evaluates to op#1 % bcnt(op#2) [Note: Will only match if op#1 >= bcnt(op#2) || op#1 < 0]
//
{ "__bcntN", "", false, true, 0, 0, },
// Bit-Mask:
// - Evaluates to ~0{ of size op#1}
//
{ "__bmask", "", true, true, 0, 0, },
// Bit-Mask:
// - Evaluates to size op#1
//
{ "__bcnt", "", true, true, 0, 0, },
// Extension:
// - Hints that op#1 was extended.
//
{ "__sx", "", false, true, 0, 0, },
{ "__zx", "", false, true, 0, 0, },
};
// Searcher for an operator within the string provided.
//
static const operator_desc* lookup( const std::string& s, bool partial, bool prefix )
{
// For each operator in the list:
//
for ( auto& desc : operator_map )
{
// Match symbols only if we're looking for a suffix or
// this is a unary operator.
//
if ( !prefix || desc.is_unary )
{
if ( !desc.symbol.empty() && ( partial ? s.starts_with( desc.symbol ) : s == desc.symbol ) )
return &desc;
}
// Match function names only if we're looking for a prefix.
//
if ( prefix && ( partial ? s.starts_with( desc.function ) : s == desc.function ) )
{
return &desc;
}
}
return nullptr;
}
// Convinience wrapper for (what would be, if the map was not const) operator_map[name].
//
static const operator_desc* find_opr( const std::string& name )
{
// For each operator in the list:
//
for ( auto& desc : operator_map )
{
if ( desc.function == name )
return &desc;
}
unreachable();
}
};

View file

@ -1,495 +0,0 @@
#pragma once
#include <tuple>
#include <algorithm>
#include <unordered_map>
#include <numeric>
#include "expression.hpp"
#include "variable.hpp"
namespace vtil::symbolic::rules
{
// Describes an entry in the rule tables.
//
struct rule_entry
{
// Number of times this rule entry was used to produce optimal result.
//
volatile uint32_t points = 0;
// Base expression containing symbolic expression tree to match against.
//
const expression base_expression;
// Function that performs any additional checks/processing after initial matching.
//
std::function<bool( symbol_map& )> extension = {};
// Constructors for simple and complex logic.
//
rule_entry( expression exp ) : base_expression( exp ) {}
template<typename T>
rule_entry( expression exp, T extension ) : base_expression( exp ), extension( extension ) {}
// Basic hash function implementation so we can place it in an unordered map.
//
struct hash { size_t operator()( const rule_entry& self ) const { return std::hash<std::string>()( self.base_expression.to_string() ); } };
bool operator==( const rule_entry& o ) const { return base_expression == o.base_expression; }
};
// Symbolic variables used in rule creation:
//
static const expression A = { { "α", 0 } };
static const expression B = { { "β", 0 } };
static const expression C = { { "λ", 0 } };
// Special variables:
//
static const expression X = { { "Σ", 0 } };
static const expression Q = { { "Ω", 0 } };
static const expression V = { { "π", 0 } }; // Does not accept constants.
static const expression U = { { "μ", 0 } }; // Only accepts constants.
// Special functions used in rule creation:
//
static const auto sx = [ ] ( const expression& a, const expression& b ) { return expression( a, find_opr( "__sx" ), b ); };
static const auto zx = [ ] ( const expression& a, const expression& b ) { return expression( a, find_opr( "__zx" ), b ); };
static const auto bmask = [ ] ( const expression& a ) { return expression( find_opr( "__bmask" ), a ); };
static const auto bcnt = [ ] ( const expression& a ) { return expression( find_opr( "__bcnt" ), a ); };
static const auto bcntN = [ ] ( const expression& a, const expression& b ) { return expression( a, find_opr( "__bcntN" ), b ); };
// All simpilfications:
// - Note! Must not contain ( simplified[simplified[x]] == y ).
//
static std::unordered_map<rule_entry, expression, rule_entry::hash> simplified_form =
{
// Inverse operations
//
{ -(-A), A },
{ ~(~A), A },
{ -(~A), A+1 },
{ ~(-A), A-1 },
// Identity constant
//
{ A+0, A },
{ A-0, A },
{ A|A, A },
{ A|0, A },
{ A&A, A },
{ A^0, A },
{ A&bmask(A), A },
// Shift normalization
//
{ (A>>bcntN(B,A))|(A<<(bcntN(-B,A))), A.ror(B) }, // [imm shift -> imm rotation]
{ (A<<bcntN(B,A))|(A>>(bcntN(-B,A))), A.rol(B) }, //
{ (A>>bcntN(Q,A))>>bcntN(X,A), A>>(X+Q) }, // merge {imm shift x2}
{ (A<<bcntN(Q,A))<<bcntN(X,A), A<<(X+Q) }, //
{ (A>>V)|(A<<(bcnt(A)-V)), A.ror(V) }, // [var shift -> var rotation]
{ (A<<V)|(A>>(bcnt(A)-V)), A.rol(V) }, //
{ A.rol(bcntN(Q,A)), A.rol(Q) }, // normalize {imm rotation}
{ A.ror(bcntN(Q,A)), A.ror(Q) }, //
{ A>>bcntN(Q,A), {0} }, // noramlize {imm shift}
{ A<<bcntN(Q,A), {0} }, //
{ (A<<B)>>B, A&(bmask(A)>>B)},
{ (A>>B)<<B, A&(bmask(A)<<B)},
// Special extended rules
//
{ zx(A,B)>>bcntN(Q,A), {0} }, // take in the real size into account when shifting
{ sx(A,B)>>bcntN(Q,A), {-1>>Q} }, // take in the real size into account when shifting
{ { { A & U }, [ ] ( symbol_map& sym ) // convert mask to __zx
{
uint64_t mask = sym[ *U.value ].evaluate()->get();
auto& in = sym[ *A.value ];
auto& out = sym[ *Q.value ];
switch ( mask )
{
case 0: out = { 0 }; break;
case 0xFF: out = { expression( in ).resize( 1 ) }; break;
case 0xFFFF: out = { expression( in ).resize( 2 ) }; break;
case 0xFFFFFFFF: out = { expression( in ).resize( 4 ) }; break;
case 0xFFFFFFFFFFFFFFFF: out = { expression( in ).resize( 8 ) }; break;
default: return false;
}
return true;
} }, { Q } },
// Constant result
//
{ A-A, {0} },
{ A+(-A), {0} },
{ A&0, {0} },
{ A^A, {0} },
{ A&(~A), {0} },
{ A|bmask(A), bmask(A) },
{ A^(~A), bmask(A) },
{ A|(~A), bmask(A) },
{ A.rol(0), A },
{ A.ror(0), A },
{ A>>0, A },
{ A<<0, A },
// SUB conversion
//
{ (~A)+B, ~(A-B) },
// NEG conversion
//
{ ~(A+bmask(A)), -A },
{ 0-A, -A },
// Simplify AND OR
//
{ A&(A|B), A },
{ A|(A&B), A },
// XOR|NAND|NOR -> NOT conversion
//
{ A^bmask(A), ~A },
// XOR / OR / AND conversion
//
{ (~A)&(~B), ~(A|B) },
{ (~A)|(~B), ~(A&B) },
{ (A|B)&(~(A&B)), A^B },
{ (A&(~B))|((~A)&B), A^B },
{ ~((~(A|B))|(A&B)), A^B },
// Prefer SUB over NEG
//
{ A+(-B), A-B },
// ADD to OR
//
{ ((~A)&B)+(A&C), ((~A)&B)|(A&C) }
};
// All alternate forms:
// - Note: Both sides should contain the same amount of unknowns.
//
static std::unordered_map<rule_entry, std::vector<expression>, rule_entry::hash> alternate_forms =
{
// Convert between SUB and ADD
//
{ A-B, { A+(-B) }},
// Convert between bitwise and arithmetic negation
//
{ ~A, { -(A+1) } },
{ -A, { ~(A-1) } },
// Distribute bitwise operators
//
{ ~(A^B), { (~A)^B } },
{ ~(A^B), { A^(~B) } },
{ ~(A&B), { (~A)|(~B) } },
{ ~(A|B), { (~A)&(~B) } },
{ A&(B|C),{ (A&B)|(A&C) } },
{ A|(B&C),{ (A|B)&(A|C) } },
{ A&(B^C),{ (A&B)^(A&C) } },
{ (A&B)>>C, { (A>>C)&(B>>C) } },
{ (A&B)<<C, { (A<<C)&(B<<C) } },
{ (A|B)>>C, { (A>>C)|(B>>C) } },
{ (A|B)<<C, { (A<<C)|(B<<C) } },
{ (A^B)>>C, { (A>>C)^(B>>C) } },
{ (A^B)<<C, { (A<<C)^(B<<C) } },
{ A^(B|C), {(A&(~(B|C)))|((~A)&(B|C)), (A&(~(C|B)))|((~A)&(C|B))} },
// All commutative laws.
// - Certain instances are commented out since only node #0 and node #1 are reversed,
// and since they are not of the same type, matcher will apply the commutative law
// automatically anyways to match the other.
//
{ A+(B+C), { (A+B)+C, (A+C)+B } },
//{ (A+B)+C, { A+(B+C), B+(A+C) } },
{ A+(B-C), { (A+B)-C, (A-C)+B } },
//{ (A-B)+C, { (A-C)-B, A+(C-B) } },
{ A-(B+C), { (A-B)-C, (A-C)-B } },
{ (A+B)-C, { (A-C)+B, A+(B-C) } },
{ A-(B-C), { (A-B)+C, (A+C)-B } },
{ (A-B)-C, { (A-C)-B, A-(B+C) } },
{ A|(B|C), { (A|B)|C, (A|C)|B } },
//{ (A|B)|C, { A|(B|C), B|(A|C) } },
{ A&(B&C), { (A&B)&C, (A&C)&B } },
//{ (A&B)&C, { A&(B&C), B&(A&C) } },
{ A^(B^C), { (A^B)^C, (A^C)^B } },
//{ (A^B)^C, { A^(B^C), B^(A^C) } },
};
// A handy helper that invokes enumerator for each entry in the
// given map in the order of points and increments the points
// and breaks out of the loop if callback returns true.
//
template<typename T, typename Z>
static auto for_each( std::unordered_map<rule_entry, T, rule_entry::hash>& map,
const Z& enumerator )
{
using iterator_type = typename std::unordered_map<rule_entry, T, rule_entry::hash>::iterator;
std::vector<iterator_type> ref_vec( map.size() );
std::iota( ref_vec.begin(), ref_vec.end(), map.begin() );
std::sort( ref_vec.begin(), ref_vec.end(), [ ] ( const iterator_type& a, const iterator_type& b ) { return a->first.points > b->first.points; } );
using ret_type = decltype( enumerator( ref_vec[ 0 ]->first, ref_vec[ 0 ]->second ) );
if constexpr ( std::is_same_v<ret_type, void> )
{
for ( auto& it : ref_vec )
enumerator( it->first, it->second );
}
else
{
for ( auto& it : ref_vec )
{
if ( enumerator( it->first, it->second ) )
{
++( *( volatile uint32_t* ) &it->first.points );
return true;
}
}
return false;
}
}
// Checks if the provided expression tree matches that of a symbolic
// tree simplification/alternate form and returns the table to map it
// so that they are equivalent.
//
template<bool bcnt_strict = true>
static std::optional<symbol_map> match( const expression& input, const expression& target, const symbol_map& sym_map = {}, uint8_t op_size = 0 )
{
// If target is a variable.
//
if ( target.is_variable() )
{
// If constant, simply compare the values.
//
if ( target.value->is_constant() )
{
// Fail if input is not a constant.
//
if ( !input.is_variable() || !input.value->is_constant() )
return {};
// Determine operation size where possible.
//
if ( !op_size ) op_size = input.size();
if ( target.value->get( op_size ) == input.value->get( op_size ) )
return { sym_map };
else
return {};
}
// If symbolic map contains the target variable:
//
auto it = sym_map.find( *target.value );
if ( it == sym_map.end() )
{
// Check special conditions:
//
if ( target.value->uid == V.value->uid && input.is_constant() )
return {};
if ( target.value->uid == U.value->uid && !input.is_constant() )
return {};
symbol_map sym_map_new = sym_map;
sym_map_new[ *target.value ] = input;
return { sym_map_new };
}
else
{
if ( it->second == input )
return { sym_map };
else
return {};
}
}
// If input is a variable and target is an expression.
//
else if ( input.is_variable() )
{
// If special functor:
//
if ( target.fn->function == "__bcntN" &&
input.is_constant() )
{
// Find which variable we're calculating this for.
// - Referencing unknown variable in simplification condition if assert fail raises.
//
auto it = sym_map.find( *target[ 1 ].value );
fassert( it != sym_map.end() );
// Find the In/Out operand.
//
symbol_map sym_map_new = sym_map;
expression* exp_out;
int8_t sign = +1;
if ( target[ 0 ].is_expression() )
{
// Unknown operation in simplification condition if this is hit.
//
fassert( target[ 0 ].fn->function == "neg" );
sign = -1;
exp_out = &sym_map_new[ *target[ 0 ][ 0 ].value ];
}
else
{
exp_out = &sym_map_new[ *target[ 0 ].value ];
}
// Calculate number of bits in the variable.
//
int8_t bit_count = it->second.size() * 8;
fassert( bit_count != 0 );
// If known variable, we're being asked to check if normalized form matches:
//
if ( exp_out->is_valid() )
{
// Fail if not constant.
//
if ( !exp_out->is_constant() )
return {};
int64_t value_a = ( ( bit_count + exp_out->value->get() ) % bit_count );
int64_t value_b = ( ( bit_count + sign * input.value->get() ) % bit_count );
if ( value_a == value_b )
return { sym_map };
else
return {};
}
// If unknown variable, it's asking for normalized form:
//
else
{
// Skip if already normalized
//
int64_t value = sign * input.value->get();
if ( 0 <= value && value < bit_count && bcnt_strict )
return {};
// Write normalized value and indicate success.
//
*exp_out = variable( ( ( bit_count + value ) % bit_count ), op_size );
return { sym_map_new };
}
}
// If constant maps to expression, try remapping
// and checking if it evaluates to the same value.
//
else if ( input.is_constant() )
{
expression copy = target;
copy.remap_symbols( sym_map );
if( copy.evaluate() == input.value )
return { sym_map };
}
return {};
}
// If both are expressions.
//
else
{
// If operators mismatch:
//
if ( input.fn != target.fn )
{
return {};
}
// If matching operators, compare operands:
//
else
{
// Determine expected size:
//
op_size = input.size();
// If unary operator:
//
std::optional<symbol_map> result;
if ( input.fn->is_unary )
{
result = match<bcnt_strict>( input[ 0 ], target[ 0 ], sym_map, op_size );
}
// If binary operator:
//
else
{
// Check if operands match, in order:
//
result = match<bcnt_strict>( input[ 0 ], target[ 0 ], sym_map, op_size );
if ( result.has_value() ) result = match<bcnt_strict>( input[ 1 ], target[ 1 ], result.value(), op_size );
// Otherwise check if operator is commutative and operands match in reverse:
//
if ( !result.has_value() && input.fn->commutative == +1 )
{
result = match<bcnt_strict>( input[ 0 ], target[ 1 ], sym_map, op_size );
if ( result.has_value() ) result = match<bcnt_strict>( input[ 1 ], target[ 0 ], result.value(), op_size );
}
}
// Return the final result.
//
return result;
}
}
}
template<bool bcnt_strict = true>
static std::optional<symbol_map> match( const expression& input, const rule_entry& rule )
{
// All variables should be of the same size.
//
fassert( input.is_normalized() );
// Check if equivalent, if not return invalid expression.
//
auto sym_map = match<bcnt_strict>( input, rule.base_expression );
if ( !sym_map )
return {};
if ( rule.extension && !rule.extension( *sym_map ) )
return {};
return sym_map;
}
// Transforms between equivalent expression trees.
//
static expression remap( const expression& input, const symbol_map& sym_map, const expression& to )
{
// Remap symbol.
//
expression new_expression = to;
new_expression.remap_symbols( sym_map );
// Remove any remaining special instructions
//
std::function<void( expression& )> remove_special = [ & ] ( expression& r )
{
if ( r.fn && r.fn->function[ 0 ] == '_' )
{
if ( auto val = r.evaluate() )
{
r = *val;
return;
}
}
for ( auto& op : r.operands )
remove_special( op );
};
remove_special( new_expression );
return new_expression.resize( input.size() );
}
template<bool bcnt_strict = true>
static std::optional<expression> apply( const expression& input, const rule_entry& rule, const expression& target )
{
auto sym_map = match<bcnt_strict>( input, rule );
if ( !sym_map )
return {};
return remap( input, sym_map.value(), target );
}
}

View file

@ -1,147 +0,0 @@
#pragma once
#include <map>
#include <optional>
#include "rules.hpp"
#include "expression.hpp"
#include "variable.hpp"
#include "operators.hpp"
namespace vtil::symbolic
{
// Tries to simplify the given symbolic expression as much as possible.
//
static std::optional<expression> try_simplify( const expression& input, bool skip_top_level = false )
{
// Assert we received a valid expression.
//
fassert( input.is_valid() );
// If expression is already in the simplest form possible, return as is.
//
if ( input.is_simplest_form )
return {};
// Simplify children.
//
expression exp = input;
bool simplified_child = false;
for ( auto& op : exp.operands )
{
if ( op.is_simplest_form )
continue;
if ( auto r = try_simplify( op ) )
simplified_child = true, op = r.value();
op.declare_simple();
}
// Try evaluating current expression, if we could
// return it as is.
//
if ( auto eval = input.evaluate() )
return eval.value();
// If top level is to be simplified:
//
if ( !skip_top_level )
{
// For each alternate form:
//
size_t complexity_0 = exp.complexity();
bool success = rules::for_each( rules::alternate_forms, [ & ] ( const rules::rule_entry& rule, const std::vector<expression>& forms )
{
// If we match the rules:
//
if ( auto sym_map = rules::match( exp, rule ) )
{
// For each form:
//
for ( auto& form : forms )
{
// Try simplifying, if we could not, continue onto next one.
//
auto new_exp = try_simplify( rules::remap( exp, *sym_map, form ), true );
if ( !new_exp ) continue;
// If complexity was reduced:
//
size_t complexity_1 = new_exp->complexity();
if ( complexity_1 < complexity_0 )
{
// Recurse, write the result at exp, indicate success.
//
exp = try_simplify( *new_exp ).value_or( *new_exp ).declare_simple();
return true;
}
}
}
return false;
} );
if ( success ) return exp;
}
// For each simplified form:
//
bool success = rules::for_each( rules::simplified_form, [ & ] ( const rules::rule_entry& rule, const expression& form )
{
// If we could apply the simplification rule:
//
if ( auto new_exp = rules::apply( exp, rule, form ) )
{
// Recurse, write the result at exp, indicate success.
//
exp = try_simplify( *new_exp, skip_top_level ).value_or( *new_exp ).declare_simple();
return true;
}
return false;
} );
if ( success ) return exp;
// Try evaluating new expression, if we could
// return it as is.
//
if ( auto eval = exp.evaluate() )
return eval.value();
// If we simplified a child, still declare simple and return.
//
if ( simplified_child )
return exp.declare_simple();
return {};
}
static expression simplify( expression input )
{
// TODO: Fix equivalent registers of different size being in the variable
// Fail if input is invalid.
//
if ( !input.is_valid() )
return {};
// Explicitly resize the expression to output size.
//
input.resize( input.size() );
// Try simplifying the expression, and declare simple.
//
if ( auto r = try_simplify( input ) )
input = r.value();
return input.resize( input.size() ).declare_simple();
}
// Checks whether the two given expressions are equivalent in a more reliable
// fashion when compared to simply invoking expression::operator==(...)
//
static bool is_equivalent( const expression& a, const expression& b )
{
// If naive-comparison returns equivalent, return so.
//
if ( a == b )
return true;
// Try matching the simplification of an expression that
// would be zero if a and be were equivalent instead
// otherwise to cause a much more complex evaluation.
//
return simplify( a - b ) == variable{ 0 };
}
};

View file

@ -1,407 +0,0 @@
#pragma once
#define SYMEX_CONST_SIZE_DEFAULT(x) 0
#include <string>
#include <codecvt>
#include <type_traits>
#include "..\arch\operands.hpp"
#include "..\arch\instruction_set.hpp"
#include "..\routine\basic_block.hpp"
#include "..\misc\format.hpp"
namespace vtil::symbolic
{
// Dictionary for the names of reserved unique identifiers. (For simplifier)
//
static const std::wstring uid_reserved_dictionary =
L"ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩαβγδεζηθικλμνξοπρστυφχψω"
L"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
// Unique identifier for a variable.
//
struct unique_identifier
{
// Conversion between the internal unicode type and the UTF8 output expected.
//
using utf_cvt_t = std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>;
// Unique name of the variable.
//
std::string name;
// Origin of the variable if relevant.
//
ilstream_const_iterator origin = {};
std::optional<register_view> register_id = {};
std::optional<std::pair<register_view, int64_t>> memory_id = {};
uint32_t memory_base_idx = 0;
bool branch_dependant = false;
// Default constructors for simple identifiers.
//
unique_identifier() {}
unique_identifier( const std::string& name ) : name( name ) { fassert( is_valid() ); }
// Constructors for unique identifiers created from stream iterators.
//
unique_identifier( const register_view& base, int64_t offset, ilstream_const_iterator at = {} )
{
assign( base, offset ).bind( at );
}
unique_identifier( const register_view& reg, ilstream_const_iterator at = {} )
{
assign( reg ).bind( at );
}
unique_identifier( ilstream_const_iterator origin, int operand_index )
{
// Identifier for memory:
//
if ( operand_index == -1 )
{
fassert( origin->base->accesses_memory() );
auto [mem_base, mem_loc] = origin->get_mem_loc();
assign( mem_base, mem_loc ).bind( origin );
}
// Identifier for register/temporary:
//
else
{
fassert( origin->operands[ operand_index ].is_register() );
assign( origin->operands[ operand_index ].reg ).bind( origin );
}
}
// Refreshes the unique identifier if it's bound to a register value or a pointer.
//
unique_identifier& refresh()
{
if ( register_id.has_value() )
{
name = register_id->to_string();
if ( !origin.is_end() )
{
name += '@';
if ( origin->vip != invalid_vip )
name += format::str( "%llx", origin->vip );
else
name += format::str( "%llx", origin.container->entry_vip ) + "#" + std::to_string( std::distance( origin.container->begin(), origin ) );
}
else
{
name += origin.is_begin() ? "?" : "*";
}
}
else if( memory_id.has_value() )
{
if ( memory_id->first.base == X86_REG_RSP )
{
name = memory_id->second >= 0 ? "arg" : "var";
name += format::hex( abs( memory_id->second ) );
if( memory_base_idx != 0 )
name += format::str( "#%d", memory_base_idx );
}
else
{
name = "[";
name += memory_id->first.base.to_string();
name += "+" + format::hex( memory_id->second ) + "]";
}
if ( !origin.is_end() )
{
name += '@';
if ( origin->vip != invalid_vip )
name += format::str( "%llx", origin->vip );
else
name += format::str( "%llx", origin.container->entry_vip ) + "#" + std::to_string( std::distance( origin.container->begin(), origin ) );
}
else
{
name += origin.is_begin() ? "?" : "*";
}
}
if( branch_dependant )
name += "...";
return *this;
}
// Assigns unique identifier the value of a register or a pointer.
//
unique_identifier& assign( const register_view& reg )
{
register_id = { reg };
memory_id = {};
return refresh();
}
unique_identifier& assign( const register_view& base, int64_t offset )
{
register_id = {};
memory_id = { base, offset };
return refresh();
}
// Binds/unbinds the identifier from/to the origin.
//
unique_identifier& unbind() { origin = {}; return refresh(); }
unique_identifier& bind( ilstream_const_iterator it )
{
origin = it;
if ( it.is_valid() && memory_id )
memory_base_idx = it.is_end() ? it.container->sp_index : it->sp_index;
return refresh();
}
// Declares value branch dependant/not.
//
unique_identifier& set_branch_dependency( bool v = true )
{
if ( branch_dependant != v )
{
branch_dependant = v;
return refresh();
}
return *this;
}
// Helpers used to resolve the actual operand being traced.
//
bool is_valid() const { return name.size() && !iswdigit( name[ 0 ] ); }
bool is_reserved() const { return name.size() == 1 && uid_reserved_dictionary.find( name[ 0 ] ) != std::wstring::npos; }
bool is_memory() const { return is_valid() && memory_id.has_value(); }
bool is_register() const { return is_valid() && register_id.has_value(); }
bool is_arbitrary() const { return is_valid() && ( memory_id.has_value() || register_id.has_value() ); }
// Returns the pointer associated with the variable.
//
auto get_mem() const
{
fassert( is_memory() );
return memory_id.value();
}
// Returns the register that is associated with the variable.
//
register_view get_reg() const
{
fassert( is_register() );
return register_id.value();
}
// Conversion to human readable format.
//
std::string to_string() const { return name; }
// Simple comparison operators.
//
bool operator==( const unique_identifier& o ) const
{
return name == o.name && origin == o.origin;
}
bool operator<( const unique_identifier& o ) const { return name < o.name; }
bool operator!=( const unique_identifier& o ) const { return name != o.name; }
};
// Describes a variable that will be used in a symbolic expression.
//
struct variable
{
// A unique identifier for the variable, if left empty
// this variable will be treated as a constant value.
//
unique_identifier uid;
// If constant, the value that is being represented
// by this variable. Do not ever access directly (not via .get).
//
union
{
uint64_t _u64;
int64_t _i64;
};
// Size of the variable, used for both constants and UID-bound variables.
// - If zero, implies any size.
//
uint8_t size;
// Default constructor, will make an invalid varaible.
//
variable() : size( 0 ) {}
// Constructor for uniquely variables.
//
variable( const std::string& uid, uint8_t size ) : uid( uid ), size( size ) { fassert( is_valid() ); }
variable( const unique_identifier& uid, uint8_t size ) : uid( uid ), size( size ) { fassert( is_valid() ); }
variable( ilstream_const_iterator origin, int operand_index )
{
// If operand is an immediate or a register:
//
if ( operand_index != -1 )
{
// If immediate, assign constant:
//
if ( origin->operands[ operand_index ].is_immediate() )
_u64 = origin->operands[ operand_index ].u64;
// If register, generate unique identifier:
//
else
uid = { origin, operand_index };
// Assing operand size as the variable size.
//
size = origin->operands[ operand_index ].size();
}
// If operand is a stack pointer:
//
else
{
// Generate a unique identifier and assign the size.
//
uid = { origin, operand_index };
size = origin->access_size();
}
fassert( is_valid() );
}
// Constructor for variables that represent constant values.
//
template<typename T, std::enable_if_t<std::is_arithmetic_v<T>, int> = 0>
variable( T imm, uint8_t size = SYMEX_CONST_SIZE_DEFAULT( T ) ) : size( size )
{
fassert( is_valid() );
// If a signed type was passed, sign extend, otherwise zero extend before storing.
//
if constexpr ( std::is_signed_v<T> )
_i64 = imm;
else
_u64 = imm;
}
// Simple helpers to determine the type of the variable.
//
bool is_valid() const { return size == 0 || size == 1 || size == 2 || size == 4 || size == 8; }
bool is_symbolic() const { return uid.is_valid(); }
bool is_constant() const { return !uid.is_valid(); }
// Wrappers around unique_identifer:: helpers used to resolve the actual operand being traced.
//
bool is_memory() const { return uid.is_memory(); }
bool is_register() const { return uid.is_register(); }
bool is_arbitrary() const { return uid.is_arbitrary(); }
auto get_mem() const { return uid.get_mem(); }
register_view get_reg() const { register_view reg = uid.get_reg(); fassert( size == reg.size ); return reg; }
auto& unbind() { uid.unbind(); return *this; }
auto& bind( ilstream_const_iterator it ) { uid.bind( it ); return *this; }
// Resizes the variable.
//
variable& resize( uint8_t new_size, bool sign_extend )
{
if ( is_constant() )
_u64 = sign_extend ? get<true>( new_size ) : get<false>( new_size );
else
fassert( size >= new_size && new_size != 0 );
size = new_size;
if ( is_register() ) uid.register_id->size = new_size;
fassert( is_valid() );
return *this;
}
// Instead of using the size variable as is, this function
// calculates minimum equivalent size if the constant is an
// any-size special.
//
uint8_t calc_size( bool sign ) const
{
if ( size || !is_constant() )
return size;
if ( sign )
{
if ( get<true>( 1 ) == _i64 ) return 1;
else if ( get<true>( 2 ) == _i64 ) return 2;
else if ( get<true>( 4 ) == _i64 ) return 4;
else if ( get<true>( 8 ) == _i64 ) return 8;
}
else
{
if ( get( 1 ) == _u64 ) return 1;
else if ( get( 2 ) == _u64 ) return 2;
else if ( get( 4 ) == _u64 ) return 4;
else if ( get( 8 ) == _u64 ) return 8;
}
unreachable();
}
// Getter for value:
//
template<bool sign = false>
auto get( uint8_t new_size = 0 ) const
{
fassert( is_constant() );
uint8_t out_size = size;
if ( out_size == 0 || ( new_size != 0 && new_size < out_size ) )
out_size = new_size;
if constexpr ( sign )
{
switch ( out_size )
{
case 0: case 8: return _i64; break;
case 1: return ( int64_t ) *( int8_t* ) &_i64; break;
case 2: return ( int64_t ) *( int16_t* ) &_i64; break;
case 4: return ( int64_t ) *( int32_t* ) &_i64; break;
}
}
else
{
switch ( out_size )
{
case 0: case 8: return _u64; break;
case 1: return ( uint64_t ) *( uint8_t* ) &_u64; break;
case 2: return ( uint64_t ) *( uint16_t* ) &_u64; break;
case 4: return ( uint64_t ) *( uint32_t* ) &_u64; break;
}
}
unreachable();
}
// Converts the variable into human-readable format.
//
std::string to_string() const
{
if ( !is_symbolic() )
return format::hex( get<true>() );
else if ( size == 0 )
return uid.to_string();
else
return uid.to_string() + format::suffix_map[ size ];
}
// Basic comparison operators.
//
bool operator==( const variable& o ) const
{
if ( is_constant() )
return o.is_constant() && get( o.size ) == o.get( size );
else
return o.is_symbolic() && uid == o.uid && size == o.size;
}
bool operator!=( const variable& o ) const { return !operator==( o ); }
bool operator<( const variable& o ) const
{
if ( is_symbolic() != o.is_symbolic() )
return o.is_symbolic();
if ( is_symbolic() )
return uid < o.uid;
else
return get() < o.get();
}
};
};