diff --git a/VTIL-Architecture/.gitignore b/VTIL-Architecture/.gitignore new file mode 100644 index 0000000..39ac672 --- /dev/null +++ b/VTIL-Architecture/.gitignore @@ -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/ diff --git a/VTIL-Architecture/VTIL-Core.licenseheader b/VTIL-Architecture/VTIL-Core.licenseheader new file mode 100644 index 0000000..aca06d6 --- /dev/null +++ b/VTIL-Architecture/VTIL-Core.licenseheader @@ -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. +// \ No newline at end of file diff --git a/VTIL-Architecture/arch/control_registers.cpp b/VTIL-Architecture/arch/control_registers.cpp new file mode 100644 index 0000000..1585b25 --- /dev/null +++ b/VTIL-Architecture/arch/control_registers.cpp @@ -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_list = {}; + + // Looks up the descriptor for the given control register. + // + std::optional 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 ); + } +}; \ No newline at end of file diff --git a/VTIL-Architecture/arch/control_registers.hpp b/VTIL-Architecture/arch/control_registers.hpp index 713e87f..197d8e0 100644 --- a/VTIL-Architecture/arch/control_registers.hpp +++ b/VTIL-Architecture/arch/control_registers.hpp @@ -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 #include -#include +#include // 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_list; - // Looks up the descriptor for the given control register. // - static std::optional 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 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 ); }; \ No newline at end of file diff --git a/VTIL-Architecture/arch/instruction_desc.cpp b/VTIL-Architecture/arch/instruction_desc.cpp new file mode 100644 index 0000000..4ae4dc1 --- /dev/null +++ b/VTIL-Architecture/arch/instruction_desc.cpp @@ -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& access_types, + int access_size_index, + bool is_volatile, + const std::string& symbolic_operator, + std::vector branch_operands, + const std::pair& 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 ); + } + } +}; \ No newline at end of file diff --git a/VTIL-Architecture/arch/instruction_desc.hpp b/VTIL-Architecture/arch/instruction_desc.hpp new file mode 100644 index 0000000..4527d61 --- /dev/null +++ b/VTIL-Architecture/arch/instruction_desc.hpp @@ -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 +#include +#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 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 branch_operands_rip = {}; + std::vector 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& access_types, + int access_size_index, + bool is_volatile, + const std::string& symbolic_operator, + std::vector branch_operands, + const std::pair& 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 ]; + } + }; +}; \ No newline at end of file diff --git a/VTIL-Architecture/arch/instruction_set.hpp b/VTIL-Architecture/arch/instruction_set.hpp index e7836e9..810e9fa 100644 --- a/VTIL-Architecture/arch/instruction_set.hpp +++ b/VTIL-Architecture/arch/instruction_set.hpp @@ -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 #include -#include -#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 list; - - // Name of the instruction. - // - std::string name; - - // List of the access types for each operand. - // - std::vector 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 branch_operands_rip = {}; - std::vector 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& access_types, - int access_size_index, - bool is_volatile, - const std::string& symbolic_operator, - std::vector branch_operands, - const std::pair& 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 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 + }; }; \ No newline at end of file diff --git a/VTIL-Architecture/arch/operands.hpp b/VTIL-Architecture/arch/operands.hpp index 3525cc6..ec72f8a 100644 --- a/VTIL-Architecture/arch/operands.hpp +++ b/VTIL-Architecture/arch/operands.hpp @@ -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 -#include -#include "registers.hpp" -#include "..\misc\format.hpp" +#include +#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 ) {} diff --git a/VTIL-Architecture/arch/register_descriptor.hpp b/VTIL-Architecture/arch/register_descriptor.hpp new file mode 100644 index 0000000..2bcbe40 --- /dev/null +++ b/VTIL-Architecture/arch/register_descriptor.hpp @@ -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 +#include +#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; } + }; +}; \ No newline at end of file diff --git a/VTIL-Architecture/arch/register_details.hpp b/VTIL-Architecture/arch/register_details.hpp deleted file mode 100644 index e4861eb..0000000 --- a/VTIL-Architecture/arch/register_details.hpp +++ /dev/null @@ -1,182 +0,0 @@ -#pragma once -#include -#include -#include -#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> 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 - static std::pair 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 - 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 - 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 - 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; - } -}; \ No newline at end of file diff --git a/VTIL-Architecture/arch/register_view.cpp b/VTIL-Architecture/arch/register_view.cpp new file mode 100644 index 0000000..cb80eeb --- /dev/null +++ b/VTIL-Architecture/arch/register_view.cpp @@ -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 +#include + +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; + } +}; \ No newline at end of file diff --git a/VTIL-Architecture/arch/register_view.hpp b/VTIL-Architecture/arch/register_view.hpp new file mode 100644 index 0000000..56982bc --- /dev/null +++ b/VTIL-Architecture/arch/register_view.hpp @@ -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 +#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 }; +}; \ No newline at end of file diff --git a/VTIL-Architecture/arch/registers.hpp b/VTIL-Architecture/arch/registers.hpp deleted file mode 100644 index fc78bb1..0000000 --- a/VTIL-Architecture/arch/registers.hpp +++ /dev/null @@ -1,100 +0,0 @@ -#pragma once -#include -#include -#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; } - }; -}; \ No newline at end of file diff --git a/VTIL-Architecture/includes/capstone.hpp b/VTIL-Architecture/includes/capstone.hpp deleted file mode 100644 index 2344794..0000000 --- a/VTIL-Architecture/includes/capstone.hpp +++ /dev/null @@ -1,104 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include -#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 bytes; - std::string mnemonic; - std::string operand_string; - - // From ->detail - std::vector regs_read; - std::vector regs_write; - std::vector 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& 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 operator()( const void* bytes, uint64_t address, size_t size = 0, size_t count = 1 ) - { - std::vector 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 ); \ No newline at end of file diff --git a/VTIL-Architecture/includes/keystone.hpp b/VTIL-Architecture/includes/keystone.hpp deleted file mode 100644 index 157e0e4..0000000 --- a/VTIL-Architecture/includes/keystone.hpp +++ /dev/null @@ -1,48 +0,0 @@ -#pragma once -#include -#include -#include -#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 operator()( const std::string& src, uint64_t va = 0 ) - { - std::vector 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 ); \ No newline at end of file diff --git a/VTIL-Architecture/includes/platform.hpp b/VTIL-Architecture/includes/platform.hpp deleted file mode 100644 index 94c63ce..0000000 --- a/VTIL-Architecture/includes/platform.hpp +++ /dev/null @@ -1,137 +0,0 @@ -#pragma once - -#if _WIN64 - #include -#else - #include -#endif - -#include -#include -#include -#include -#include -#include -#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 - 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 - __forceinline static auto fix_format_paramter( const T& x ) - { - if constexpr ( std::is_same_v || - std::is_same_v ) - return x.data(); - else - return x; - } - - template - 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 - static void error( const char* fmt, params&&... ps ) - { - log( fmt, std::forward( ps )... ); - bp(); - } - - 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 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 bytes = std::vector( std::istreambuf_iterator( 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 ) \ No newline at end of file diff --git a/VTIL-Architecture/misc/debug.hpp b/VTIL-Architecture/misc/debug.hpp index 6458806..6fbfa09 100644 --- a/VTIL-Architecture/misc/debug.hpp +++ b/VTIL-Architecture/misc/debug.hpp @@ -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 #include -#include +#include #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( ">%c0x%-4x ", ins.sp_offset >= 0 ? '+' : '-', abs( ins.sp_offset ) ); + log( ">%c0x%-4x ", ins.sp_offset >= 0 ? '+' : '-', abs( ins.sp_offset ) ); else if ( ( prev ? prev->sp_offset : 0 ) == ins.sp_offset ) - io::log( "%c0x%-4x ", ins.sp_offset >= 0 ? '+' : '-', abs( ins.sp_offset ) ); + log( "%c0x%-4x ", ins.sp_offset >= 0 ? '+' : '-', abs( ins.sp_offset ) ); else if ( ( prev ? prev->sp_offset : 0 ) > ins.sp_offset ) - io::log( "%c0x%-4x ", ins.sp_offset >= 0 ? '+' : '-', abs( ins.sp_offset ) ); + log( "%c0x%-4x ", ins.sp_offset >= 0 ? '+' : '-', abs( ins.sp_offset ) ); else - io::log( "%c0x%-4x ", ins.sp_offset >= 0 ? '+' : '-', abs( ins.sp_offset ) ); + log( "%c0x%-4x ", ins.sp_offset >= 0 ? '+' : '-', abs( ins.sp_offset ) ); // Print name // if ( ins.is_volatile() ) - io::log( FMT_INS_MNM " ", ins.base->to_string( ins.access_size() ) ); // Volatile instruction + log( FMT_INS_MNM " ", ins.base->to_string( ins.access_size() ) ); // Volatile instruction else - io::log( FMT_INS_MNM " ", ins.base->to_string( ins.access_size() ) ); // Non-volatile instruction + log( 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( FMT_INS_OPR " ", op.reg.to_string() ); // Stack pointer + log( FMT_INS_OPR " ", op.reg.to_string() ); // Stack pointer else if ( op.reg.base.maps_to != X86_REG_INVALID ) - io::log( FMT_INS_OPR " ", op.reg.to_string() ); // Any hardware/special register + log( FMT_INS_OPR " ", op.reg.to_string() ); // Any hardware/special register else - io::log( FMT_INS_OPR " ", op.reg.to_string() ); // Virtual register + log( 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( FMT_INS_OPR " ", format::hex( op.i64 ) ); // External stack + log( FMT_INS_OPR " ", format::hex( op.i64 ) ); // External stack else - io::log( FMT_INS_OPR " ", format::hex( op.i64 ) ); // VM stack + log( FMT_INS_OPR " ", format::hex( op.i64 ) ); // VM stack } else { - io::log( FMT_INS_OPR " ", format::hex( op.i64 ) ); // Any immediate + log( 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* visited = nullptr ) + static void dump( const basic_block* blk, std::set* 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( "Y\n" ); - else io::log( "N\n" ); + if ( b ) log( "Y\n" ); + else log( "N\n" ); }; - io::log( "Entry point VIP: " ); - io::log( "0x%llx\n", blk->entry_vip ); - io::log( "Stack pointer: " ); + log( "Entry point VIP: " ); + log( "0x%llx\n", blk->entry_vip ); + log( "Stack pointer: " ); if ( blk->sp_offset < 0 ) - io::log( "%s\n", format::hex( blk->sp_offset ) ); + log( "%s\n", format::hex( blk->sp_offset ) ); else - io::log( "%s\n", format::hex( blk->sp_offset ) ); - io::log( "Already visited?: " ); + log( "%s\n", format::hex( blk->sp_offset ) ); + log( "Already visited?: " ); end_with_bool( blk_visited ); - io::log( "------------------------\n" ); + log( "------------------------\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( "%04d: ", ins_idx ); + log( "%04d: ", ins_idx ); if ( it->vip == invalid_vip ) - io::log( "[PSEUDO] " ); + log( "[PSEUDO] " ); else - io::log( "[%06x] ", it->vip ); + log( "[%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 vs; dump( routine->entry_point, &vs ); diff --git a/VTIL-Architecture/misc/format.hpp b/VTIL-Architecture/misc/format.hpp deleted file mode 100644 index b7d8214..0000000 --- a/VTIL-Architecture/misc/format.hpp +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once -#include -#include -#include - - -#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 - static std::string str( const char* fmt, params... ps ) - { - char buffer[ 512 ]; - sprintf_s( buffer, fmt, io::fix_format_paramter( ps )... ); - return buffer; - } - - template - static std::string hex( T value ) - { - if ( !std::is_signed_v || 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 ); - } -}; \ No newline at end of file diff --git a/VTIL-Architecture/query/query_desc.hpp b/VTIL-Architecture/query/query_desc.hpp deleted file mode 100644 index 4ea26df..0000000 --- a/VTIL-Architecture/query/query_desc.hpp +++ /dev/null @@ -1,183 +0,0 @@ -#pragma once -#include -#include -#include -#include -#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 - struct query_desc - { - using iterator_type = _iterator_type; - using reference_type = decltype( *std::declval() ); - - // 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; - 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 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_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; - } - }; -}; \ No newline at end of file diff --git a/VTIL-Architecture/query/ranges.hpp b/VTIL-Architecture/query/ranges.hpp deleted file mode 100644 index 80223ca..0000000 --- a/VTIL-Architecture/query/ranges.hpp +++ /dev/null @@ -1,63 +0,0 @@ -#pragma once -#include - -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 - 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 range_iterator( const range_iterator& 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 recurse( bool forward ) const { return {}; } - }; - - // Makes range iterator from any container and iterator combination based on basic_range_iterator. - // - template - static auto bind( container_type& container, typename container_type::iterator iterator ) - { - return range_iterator - { - &container, - iterator - }; - } - template - static auto bind( const container_type& container, typename container_type::const_iterator iterator ) - { - return range_iterator - { - &container, - iterator - }; - } -}; \ No newline at end of file diff --git a/VTIL-Architecture/query/recursive_view.hpp b/VTIL-Architecture/query/recursive_view.hpp deleted file mode 100644 index 05baf99..0000000 --- a/VTIL-Architecture/query/recursive_view.hpp +++ /dev/null @@ -1,356 +0,0 @@ -#pragma once -#include -#include -#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 - 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 paths; - - // Merges the results of all or extended basic-blocks. - // - template - struct is_std_vector : std::false_type {}; - template - struct is_std_vector> : 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::value ) - result.insert( result.end(), r.result.begin(), r.result.end() ); - else - result += r.result; - } - } - - // Return as is. - // - return *this; - } - }; - - template - 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; - 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 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 auto& run( T next ) { view.run( next ); return *this; } - template auto& with( T next ) { view.with( next ); return *this; } - template auto& where( T next ) { view.where( next ); return *this; } - template auto& until( T next ) { view.until( next ); return *this; } - template auto& whilst( T next ) { view.whilst( next ); return *this; } - - template - auto project( projector_type next ) { return recursive_view{ view.project( next ), it0.container != nullptr, filter }; } - template - auto reproject( projector_type next ) { return recursive_view{ view.reproject( next ), it0.container != nullptr, filter }; } - auto unproject() { return recursive_view{ 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()( std::declval() ) ), - typename result_type = std::conditional_t, size_t, std::vector> - > - recursive_result for_each( const enumerator_type& enumerator ) - { - // Begin the iteration loop. - // - recursive_result 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 ) - output.result++, enumerator( view.prev() ); - else if constexpr ( std::is_same_v ) - 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 = view_new.for_each( 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 - static auto recurse( view_type view, typename recursive_view::fn_container_filter filter = {}, bool partial_visits = true, bool safe = true ) - { - return recursive_view( 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::reference_type, query_desc> - > - static auto create_recursive( query_desc q, typename recursive_view::fn_container_filter filter = {}, bool partial_visits = true, bool safe = true ) - { - return recurse - ( - view_type( q ), - filter, - partial_visits, - safe - ); - } - - // Creates a reference-view query for the given range iterator. - // - template::reference_type, query_desc> - > - static auto create_recursive( iterator_type r, int8_t dir = 0, typename recursive_view::fn_container_filter filter = {}, bool partial_visits = true, bool safe = true ) - { - return create_recursive( query_desc{ r, dir }, filter, partial_visits, safe ); - } -}; \ No newline at end of file diff --git a/VTIL-Architecture/query/view.hpp b/VTIL-Architecture/query/view.hpp deleted file mode 100644 index bbf4c53..0000000 --- a/VTIL-Architecture/query/view.hpp +++ /dev/null @@ -1,444 +0,0 @@ -#pragma once -#include -#include -#include -#include -#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 - 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; - 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 - 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()( std::declval() ) )> - 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 - 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 - 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 ) - project_value = [ ] ( auto&, iterator_type i ) -> projected_type { return *i; }; - else if constexpr ( std::is_same_v ) - 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 - auto project( projector_type next ) - { - // Find out the new projected type - // - using projected_type_n = decltype( next( std::declval() ) ); - - // Save previous projector and create the new view - // - fn_projector prev = project_value; - return view - { - 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{ 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 - auto reproject( projector_type next ) - { - // Find out the new projected type - // - using projected_type_n = decltype( next( std::declval() ) ); - - // Save previous projector and create the new view - // - return view - { - 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 - 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 = { 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 - auto& until( argument_type arg ) - { - if constexpr ( !std::is_same_v, iterator_type> ) - { - // Allow callbacks taking non-projected type if they take - // the base iterator type. - // - fn_projector project = project_value; - callback_wrapper 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 - 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 = { 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 - 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 = { 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 - auto for_each( const enumerator_type& enumerator ) - { - using T = decltype( enumerator( std::declval() ) ); - - if constexpr ( std::is_same_v ) - { - size_t count = 0; - while ( query.forward() == 1 ) - count++, enumerator( prev() ); - return count; - } - else if constexpr ( std::is_same_v ) - { - size_t count = 0; - while ( query.forward() == 1 ) - count += enumerator( prev() ); - return count; - } - else - { - std::vector 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 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 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 last() - { - std::optional res; - while ( query.forward() == 1 ) - res = prev(); - return res; - } - }; - - // Creates a reference-view query for the given query base. - // - template - static auto create( query_desc q ) { return view::reference_type, query_desc>( q ); } - - // Creates a reference-view query for the given range iterator. - // - template - 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 - static auto dummy() { return create( query_desc{} ); } -}; \ No newline at end of file diff --git a/VTIL-Architecture/routine/basic_block.cpp b/VTIL-Architecture/routine/basic_block.cpp new file mode 100644 index 0000000..811f292 --- /dev/null +++ b/VTIL-Architecture/routine/basic_block.cpp @@ -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 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; + } +}; \ No newline at end of file diff --git a/VTIL-Architecture/routine/basic_block.hpp b/VTIL-Architecture/routine/basic_block.hpp index 543545f..0ef947e 100644 --- a/VTIL-Architecture/routine/basic_block.hpp +++ b/VTIL-Architecture/routine/basic_block.hpp @@ -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 #include #include #include #include -#include +#include #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 riterator_base( const riterator_base& 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 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 || std::is_same_v ) return operand( value ); + // If x86_reg, map to register_view // else if constexpr ( std::is_same_v ) { - 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 || std::is_same_v ) return operand( register_view( value ) ); + // Else, treat as immediate // else if constexpr ( std::is_integral_v ) @@ -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 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. diff --git a/VTIL-Architecture/routine/instruction.cpp b/VTIL-Architecture/routine/instruction.cpp new file mode 100644 index 0000000..667992b --- /dev/null +++ b/VTIL-Architecture/routine/instruction.cpp @@ -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 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 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 ] + ); + } +}; diff --git a/VTIL-Architecture/routine/instruction.hpp b/VTIL-Architecture/routine/instruction.hpp index f1bc896..80e57bf 100644 --- a/VTIL-Architecture/routine/instruction.hpp +++ b/VTIL-Architecture/routine/instruction.hpp @@ -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 #include #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 static operand make_imm( T value ) { return operand( value, sizeof( T ) ); } + template + 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& 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 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 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 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; }; }; diff --git a/VTIL-Architecture/routine/routine.hpp b/VTIL-Architecture/routine/routine.hpp index fd5e126..fd54189 100644 --- a/VTIL-Architecture/routine/routine.hpp +++ b/VTIL-Architecture/routine/routine.hpp @@ -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 #include diff --git a/VTIL-Architecture/symbolic/expression.hpp b/VTIL-Architecture/symbolic/expression.hpp deleted file mode 100644 index 6797f3a..0000000 --- a/VTIL-Architecture/symbolic/expression.hpp +++ /dev/null @@ -1,461 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include "variable.hpp" -#include "operators.hpp" - -namespace vtil::symbolic -{ - struct expression; - using symbol_set = std::set; - using symbol_map = std::map; - using fn_exp_callback = std::function; - using fn_const_exp_callback = std::function; - - // Describes an expression tree. - // - struct expression - { - // If variable, the value of the expression. - // - std::optional value; - - // If result, list of operands and the operator description. - // - const operator_desc* fn; - std::vector 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 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 - // - 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 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(), 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() + o2.get(), ns }; - else if ( fn->function == "sub" ) - return variable{ o1.get() - o2.get(), 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 expression ror( T y ) const { return expression( *this, find_opr( "ror" ), expression{ y } ); } - template expression rol( T y ) const { return expression( *this, find_opr( "rol" ), expression{ y } ); } - }; - - template static expression operator+( const expression& x, T y ) { return { x, find_opr( "add" ), expression{ y } }; } - template static expression operator-( const expression& x, T y ) { return { x, find_opr( "sub" ), expression{ y } }; } - template static expression operator|( const expression& x, T y ) { return { x, find_opr( "or" ), expression{ y } }; } - template static expression operator&( const expression& x, T y ) { return { x, find_opr( "and" ), expression{ y } }; } - template static expression operator^( const expression& x, T y ) { return { x, find_opr( "xor" ), expression{ y } }; } - template static expression operator>>( const expression& x, T y ) { return { x, find_opr( "shr" ), expression{ y } }; } - template static expression operator<<( const expression& x, T y ) { return { x, find_opr( "shl" ), expression{ y } }; } - - template, expression>, int> = 0> - static expression operator+( T x, const expression& y ) { return expression{ x } + y; } - template, expression>, int> = 0> - static expression operator-( T x, const expression& y ) { return expression{ x } - y; } - template, expression>, int> = 0> - static expression operator|( T x, const expression& y ) { return expression{ x } | y; } - template, expression>, int> = 0> - static expression operator&( T x, const expression& y ) { return expression{ x } & y; } - template, expression>, int> = 0> - static expression operator^( T x, const expression& y ) { return expression{ x } ^ y; } - template, expression>, int> = 0> - static expression operator>>( T x, const expression& y ) { return expression{ x } >> y; } - template, expression>, int> = 0> - static expression operator<<( T x, const expression& y ) { return expression{ x } << y; } -}; \ No newline at end of file diff --git a/VTIL-Architecture/symbolic/generator.hpp b/VTIL-Architecture/symbolic/generator.hpp deleted file mode 100644 index f161d48..0000000 --- a/VTIL-Architecture/symbolic/generator.hpp +++ /dev/null @@ -1,550 +0,0 @@ -#pragma once -#define SYMEX_EVALTIME_SIMPLIFY 1 -#include -#include -#include -#include -#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 - static expression generate( const variable& lookup, - bool virtual_sp = false, - bool recurse = false, - int32_t max_op_depth = INT32_MAX, - std::map, 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( "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( "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( "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(); - } - - // 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( 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 ) - { - // - } - // 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( "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() ); - } - // 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( 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( "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( "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( "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( "Continuing query since [%s] matched primary candidate.\n", exp.to_string() ); - } - } - else - { - // Log skipping of path if verbose. - // - if constexpr ( verbose ) - io::log( "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 ); - } -}; \ No newline at end of file diff --git a/VTIL-Architecture/symbolic/operators.hpp b/VTIL-Architecture/symbolic/operators.hpp deleted file mode 100644 index 7a41fb7..0000000 --- a/VTIL-Architecture/symbolic/operators.hpp +++ /dev/null @@ -1,130 +0,0 @@ -#pragma once -#include -#include -#include -#include - -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 null_operand; - std::vector identity_operand; - }; - - // List of all operators. - // - static const std::vector 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(); - } -}; \ No newline at end of file diff --git a/VTIL-Architecture/symbolic/rules.hpp b/VTIL-Architecture/symbolic/rules.hpp deleted file mode 100644 index 4ac145b..0000000 --- a/VTIL-Architecture/symbolic/rules.hpp +++ /dev/null @@ -1,495 +0,0 @@ -#pragma once -#include -#include -#include -#include -#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 extension = {}; - - // Constructors for simple and complex logic. - // - rule_entry( expression exp ) : base_expression( exp ) {} - template - 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()( 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 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.rol(B) }, // - { (A>>bcntN(Q,A))>>bcntN(X,A), A>>(X+Q) }, // merge {imm shift x2} - { (A<>V)|(A<<(bcnt(A)-V)), A.ror(V) }, // [var shift -> var rotation] - { (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<>B, A&(bmask(A)>>B)}, - { (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::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)< - static auto for_each( std::unordered_map& map, - const Z& enumerator ) - { - using iterator_type = typename std::unordered_map::iterator; - - std::vector 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 ) - { - 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 - static std::optional 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 result; - if ( input.fn->is_unary ) - { - result = match( input[ 0 ], target[ 0 ], sym_map, op_size ); - } - // If binary operator: - // - else - { - // Check if operands match, in order: - // - result = match( input[ 0 ], target[ 0 ], sym_map, op_size ); - if ( result.has_value() ) result = match( 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( input[ 0 ], target[ 1 ], sym_map, op_size ); - if ( result.has_value() ) result = match( input[ 1 ], target[ 0 ], result.value(), op_size ); - } - } - - // Return the final result. - // - return result; - } - } - } - template - static std::optional 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( 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 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 - static std::optional apply( const expression& input, const rule_entry& rule, const expression& target ) - { - auto sym_map = match( input, rule ); - if ( !sym_map ) - return {}; - return remap( input, sym_map.value(), target ); - } -} \ No newline at end of file diff --git a/VTIL-Architecture/symbolic/simplifier.hpp b/VTIL-Architecture/symbolic/simplifier.hpp deleted file mode 100644 index b1fe2ab..0000000 --- a/VTIL-Architecture/symbolic/simplifier.hpp +++ /dev/null @@ -1,147 +0,0 @@ -#pragma once -#include -#include -#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 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& 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 }; - } -}; diff --git a/VTIL-Architecture/symbolic/variable.hpp b/VTIL-Architecture/symbolic/variable.hpp deleted file mode 100644 index 669a4e7..0000000 --- a/VTIL-Architecture/symbolic/variable.hpp +++ /dev/null @@ -1,407 +0,0 @@ -#pragma once -#define SYMEX_CONST_SIZE_DEFAULT(x) 0 - -#include -#include -#include -#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>; - - // Unique name of the variable. - // - std::string name; - - // Origin of the variable if relevant. - // - ilstream_const_iterator origin = {}; - std::optional register_id = {}; - std::optional> 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, 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 ) - _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( new_size ) : get( 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( 1 ) == _i64 ) return 1; - else if ( get( 2 ) == _i64 ) return 2; - else if ( get( 4 ) == _i64 ) return 4; - else if ( get( 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 - 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() ); - 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(); - } - }; -}; \ No newline at end of file