From 08635457a776d4bd142367eabe9b486d47649f24 Mon Sep 17 00:00:00 2001 From: _xeroxz Date: Wed, 9 Jun 2021 22:39:38 -0700 Subject: [PATCH] still adding doxygen comments... --- include/calc_jmp.hpp | 25 ++- include/transform.hpp | 457 ++++++++++++++++++++++----------------- include/vmctx.hpp | 29 ++- include/vmhandlers.hpp | 161 +++++++++++--- include/vminstrs.hpp | 131 ++++++----- include/vmprofiles.hpp | 303 ++++++++++++++------------ include/vmutils.hpp | 37 ++-- src/vminstrs.cpp | 477 ++++++++++++++++++++--------------------- 8 files changed, 936 insertions(+), 684 deletions(-) diff --git a/include/calc_jmp.hpp b/include/calc_jmp.hpp index 973efdf..5df23f6 100644 --- a/include/calc_jmp.hpp +++ b/include/calc_jmp.hpp @@ -2,11 +2,22 @@ #include #include -namespace vm +namespace vm::calc_jmp { - namespace calc_jmp - { - bool get( zydis_routine_t &vm_entry, zydis_routine_t &calc_jmp ); - std::optional< vmp2::exec_type_t > get_advancement( const zydis_routine_t &calc_jmp ); - } // namespace calc_jmp -} // namespace vm \ No newline at end of file + /// + /// extracts calc_jmp out of vm_entry... you can learn about calc_jmp here. + /// + /// pass by reference vm entry... + /// zydis_routine_t filled up with native instructions by this routine... + /// returns truee if no errors happen... + bool get( zydis_routine_t &vm_entry, zydis_routine_t &calc_jmp ); + + /// + /// gets the advancement of the virtual instruction pointer... iterates over calc_jmp for LEA, MOV, INC, DEC, SUB, + /// ADD, ETC instructions and then decides which way VIP advances based upon this information... + /// + /// + /// + std::optional< vmp2::exec_type_t > get_advancement( const zydis_routine_t &calc_jmp ); +} // namespace vm::calc_jmp \ No newline at end of file diff --git a/include/transform.hpp b/include/transform.hpp index 2bfe3c0..e754c63 100644 --- a/include/transform.hpp +++ b/include/transform.hpp @@ -5,215 +5,282 @@ #include #include -namespace vm +namespace vm::transform { - namespace transform + /// + /// rotate left template function take from IDA SDK... + /// + /// type of data to rotate left... + /// value to rotate left + /// number of bits to rotate left... + /// returns the rotated value... + template < class T > inline T __ROL__( T value, int count ) { - // taken from ida... - template < class T > inline T __ROL__( T value, int count ) - { - const unsigned int nbits = sizeof( T ) * 8; - - if ( count > 0 ) - { - count %= nbits; - T high = value >> ( nbits - count ); - if ( T( -1 ) < 0 ) // signed value - high &= ~( ( T( -1 ) << count ) ); - value <<= count; - value |= high; - } - else - { - count = -count % nbits; - T low = value << ( nbits - count ); - value >>= count; - value |= low; - } - return value; - } + const unsigned int nbits = sizeof( T ) * 8; - // taken from ida... - inline u8 __ROL1__( u8 value, int count ) - { - return __ROL__( ( u8 )value, count ); - } - inline u16 __ROL2__( u16 value, int count ) - { - return __ROL__( ( u16 )value, count ); - } - inline u32 __ROL4__( u32 value, int count ) + if ( count > 0 ) { - return __ROL__( ( u32 )value, count ); + count %= nbits; + T high = value >> ( nbits - count ); + if ( T( -1 ) < 0 ) // signed value + high &= ~( ( T( -1 ) << count ) ); + value <<= count; + value |= high; } - inline u64 __ROL8__( u64 value, int count ) + else { - return __ROL__( ( u64 )value, count ); + count = -count % nbits; + T low = value << ( nbits - count ); + value >>= count; + value |= low; } - inline u8 __ROR1__( u8 value, int count ) - { - return __ROL__( ( u8 )value, -count ); - } - inline u16 __ROR2__( u16 value, int count ) - { - return __ROL__( ( u16 )value, -count ); - } - inline u32 __ROR4__( u32 value, int count ) - { - return __ROL__( ( u32 )value, -count ); - } - inline u64 __ROR8__( u64 value, int count ) - { - return __ROL__( ( u64 )value, -count ); - } - - template < typename T > using transform_t = std::function< T( T, T ) >; - - enum class type - { - generic0, - rolling_key, - generic1, - generic2, - generic3, - update_key - }; - - using map_t = std::map< transform::type, zydis_decoded_instr_t >; - - template < class T > - inline const auto _bswap = []( T a, T b ) -> T { - if constexpr ( std::is_same_v< T, std::uint64_t > ) - return _byteswap_uint64( a ); - if constexpr ( std::is_same_v< T, std::uint32_t > ) - return _byteswap_ulong( a ); - if constexpr ( std::is_same_v< T, std::uint16_t > ) - return _byteswap_ushort( a ); - - throw std::invalid_argument( "invalid type size..." ); - }; - - template < class T > inline const auto _add = []( T a, T b ) -> T { return a + b; }; - - template < class T > inline const auto _xor = []( T a, T b ) -> T { return a ^ b; }; - - template < class T > inline const auto _sub = []( T a, T b ) -> T { return a - b; }; - - template < class T > inline const auto _neg = []( T a, T b ) -> T { return a * -1; }; - - template < class T > inline const auto _not = []( T a, T b ) -> T { return ~a; }; - - template < class T > - inline const auto _ror = []( T a, T b ) -> T { - if constexpr ( std::is_same_v< T, std::uint64_t > ) - return __ROR8__( a, b ); - if constexpr ( std::is_same_v< T, std::uint32_t > ) - return __ROR4__( a, b ); - if constexpr ( std::is_same_v< T, std::uint16_t > ) - return __ROR2__( a, b ); - if constexpr ( std::is_same_v< T, std::uint8_t > ) - return __ROR1__( a, b ); - - throw std::invalid_argument( "invalid type size..." ); - }; - - template < class T > - inline const auto _rol = []( T a, T b ) -> T { - if constexpr ( std::is_same_v< T, std::uint64_t > ) - return __ROL8__( a, b ); - if constexpr ( std::is_same_v< T, std::uint32_t > ) - return __ROL4__( a, b ); - if constexpr ( std::is_same_v< T, std::uint16_t > ) - return __ROL2__( a, b ); - if constexpr ( std::is_same_v< T, std::uint8_t > ) - return __ROL1__( a, b ); - - throw std::invalid_argument( "invalid type size..." ); - }; - - template < class T > inline const auto _inc = []( T a, T b ) -> T { return a + 1; }; - - template < class T > inline const auto _dec = []( T a, T b ) -> T { return a - 1; }; - - template < class T > - inline std::map< zydis_mnemonic_t, transform_t< T > > transforms = { - { ZYDIS_MNEMONIC_ADD, _add< T > }, { ZYDIS_MNEMONIC_XOR, _xor< T > }, { ZYDIS_MNEMONIC_BSWAP, _bswap< T > }, - { ZYDIS_MNEMONIC_SUB, _sub< T > }, { ZYDIS_MNEMONIC_NEG, _neg< T > }, { ZYDIS_MNEMONIC_NOT, _not< T > }, - { ZYDIS_MNEMONIC_ROR, _ror< T > }, { ZYDIS_MNEMONIC_ROL, _rol< T > }, { ZYDIS_MNEMONIC_INC, _inc< T > }, - { ZYDIS_MNEMONIC_DEC, _dec< T > } }; - - inline std::map< zydis_mnemonic_t, zydis_mnemonic_t > inverse = { - { ZYDIS_MNEMONIC_ADD, ZYDIS_MNEMONIC_SUB }, { ZYDIS_MNEMONIC_XOR, ZYDIS_MNEMONIC_XOR }, - { ZYDIS_MNEMONIC_BSWAP, ZYDIS_MNEMONIC_BSWAP }, { ZYDIS_MNEMONIC_SUB, ZYDIS_MNEMONIC_ADD }, - { ZYDIS_MNEMONIC_NEG, ZYDIS_MNEMONIC_NEG }, { ZYDIS_MNEMONIC_NOT, ZYDIS_MNEMONIC_NOT }, - { ZYDIS_MNEMONIC_ROR, ZYDIS_MNEMONIC_ROL }, { ZYDIS_MNEMONIC_ROL, ZYDIS_MNEMONIC_ROR }, - { ZYDIS_MNEMONIC_INC, ZYDIS_MNEMONIC_DEC }, { ZYDIS_MNEMONIC_DEC, ZYDIS_MNEMONIC_INC } }; - - inline bool valid( zydis_mnemonic_t op ) - { - return transforms< std::uint64_t >.find( op ) != transforms< std::uint64_t >.end(); - } - - inline void inverse_transforms( transform::map_t &transforms, transform::map_t &inverse ) - { - inverse[ transform::type::generic0 ] = transforms[ transform::type::generic0 ]; - inverse[ transform::type::generic0 ].mnemonic = - transform::inverse[ transforms[ transform::type::generic0 ].mnemonic ]; - - inverse[ transform::type::rolling_key ] = transforms[ transform::type::rolling_key ]; - inverse[ transform::type::rolling_key ].mnemonic = - transform::inverse[ transforms[ transform::type::rolling_key ].mnemonic ]; + return value; + } + + /// + /// rotate left a one byte value... + /// + /// byte value + /// number of bits to rotate + /// return rotated value... + inline u8 __ROL1__( u8 value, int count ) + { + return __ROL__( ( u8 )value, count ); + } + + /// + /// rotate left a two byte value... + /// + /// two byte value to rotate... + /// number of bits to rotate... + /// return rotated value... + inline u16 __ROL2__( u16 value, int count ) + { + return __ROL__( ( u16 )value, count ); + } + + /// + /// rotate left a four byte value... + /// + /// four byte value to rotate... + /// number of bits to shift... + /// return rotated value... + inline u32 __ROL4__( u32 value, int count ) + { + return __ROL__( ( u32 )value, count ); + } + + /// + /// rotate left an eight byte value... + /// + /// eight byte value... + /// number of bits to shift... + /// return rotated value... + inline u64 __ROL8__( u64 value, int count ) + { + return __ROL__( ( u64 )value, count ); + } + + /// + /// rotate right a one byte value... + /// + /// one byte value... + /// number of bits to shift... + /// return rotated value... + inline u8 __ROR1__( u8 value, int count ) + { + return __ROL__( ( u8 )value, -count ); + } + + /// + /// rotate right a two byte value... + /// + /// two byte value to rotate... + /// number of bits to shift... + /// + inline u16 __ROR2__( u16 value, int count ) + { + return __ROL__( ( u16 )value, -count ); + } + + /// + /// rotate right a four byte value... + /// + /// four byte value to rotate... + /// number of bits to rotate... + /// return rotated value... + inline u32 __ROR4__( u32 value, int count ) + { + return __ROL__( ( u32 )value, -count ); + } + + /// + /// rotate right an eight byte value... + /// + /// eight byte value + /// number of bits to rotate... + /// return rotated value... + inline u64 __ROR8__( u64 value, int count ) + { + return __ROL__( ( u64 )value, -count ); + } + + /// + /// transform function, such as ADD, SUB, BSWAP... etc... + /// + /// returns the transform result... + template < typename T > using transform_t = std::function< T( T, T ) >; + + /// + /// type of transformation... + /// + enum class type + { + generic0, + rolling_key, + generic1, + generic2, + generic3, + update_key + }; + + /// + /// map of transform type to zydis decoded instruction of the transform... + /// + using map_t = std::map< transform::type, zydis_decoded_instr_t >; + + template < class T > + inline const auto _bswap = []( T a, T b ) -> T { + if constexpr ( std::is_same_v< T, std::uint64_t > ) + return _byteswap_uint64( a ); + if constexpr ( std::is_same_v< T, std::uint32_t > ) + return _byteswap_ulong( a ); + if constexpr ( std::is_same_v< T, std::uint16_t > ) + return _byteswap_ushort( a ); + + throw std::invalid_argument( "invalid type size..." ); + }; + + template < class T > inline const auto _add = []( T a, T b ) -> T { return a + b; }; + + template < class T > inline const auto _xor = []( T a, T b ) -> T { return a ^ b; }; + + template < class T > inline const auto _sub = []( T a, T b ) -> T { return a - b; }; + + template < class T > inline const auto _neg = []( T a, T b ) -> T { return a * -1; }; + + template < class T > inline const auto _not = []( T a, T b ) -> T { return ~a; }; + + template < class T > + inline const auto _ror = []( T a, T b ) -> T { + if constexpr ( std::is_same_v< T, std::uint64_t > ) + return __ROR8__( a, b ); + if constexpr ( std::is_same_v< T, std::uint32_t > ) + return __ROR4__( a, b ); + if constexpr ( std::is_same_v< T, std::uint16_t > ) + return __ROR2__( a, b ); + if constexpr ( std::is_same_v< T, std::uint8_t > ) + return __ROR1__( a, b ); + + throw std::invalid_argument( "invalid type size..." ); + }; + + template < class T > + inline const auto _rol = []( T a, T b ) -> T { + if constexpr ( std::is_same_v< T, std::uint64_t > ) + return __ROL8__( a, b ); + if constexpr ( std::is_same_v< T, std::uint32_t > ) + return __ROL4__( a, b ); + if constexpr ( std::is_same_v< T, std::uint16_t > ) + return __ROL2__( a, b ); + if constexpr ( std::is_same_v< T, std::uint8_t > ) + return __ROL1__( a, b ); + + throw std::invalid_argument( "invalid type size..." ); + }; + + template < class T > inline const auto _inc = []( T a, T b ) -> T { return a + 1; }; + + template < class T > inline const auto _dec = []( T a, T b ) -> T { return a - 1; }; + + template < class T > + inline std::map< zydis_mnemonic_t, transform_t< T > > transforms = { + { ZYDIS_MNEMONIC_ADD, _add< T > }, { ZYDIS_MNEMONIC_XOR, _xor< T > }, { ZYDIS_MNEMONIC_BSWAP, _bswap< T > }, + { ZYDIS_MNEMONIC_SUB, _sub< T > }, { ZYDIS_MNEMONIC_NEG, _neg< T > }, { ZYDIS_MNEMONIC_NOT, _not< T > }, + { ZYDIS_MNEMONIC_ROR, _ror< T > }, { ZYDIS_MNEMONIC_ROL, _rol< T > }, { ZYDIS_MNEMONIC_INC, _inc< T > }, + { ZYDIS_MNEMONIC_DEC, _dec< T > } }; + + inline std::map< zydis_mnemonic_t, zydis_mnemonic_t > inverse = { + { ZYDIS_MNEMONIC_ADD, ZYDIS_MNEMONIC_SUB }, { ZYDIS_MNEMONIC_XOR, ZYDIS_MNEMONIC_XOR }, + { ZYDIS_MNEMONIC_BSWAP, ZYDIS_MNEMONIC_BSWAP }, { ZYDIS_MNEMONIC_SUB, ZYDIS_MNEMONIC_ADD }, + { ZYDIS_MNEMONIC_NEG, ZYDIS_MNEMONIC_NEG }, { ZYDIS_MNEMONIC_NOT, ZYDIS_MNEMONIC_NOT }, + { ZYDIS_MNEMONIC_ROR, ZYDIS_MNEMONIC_ROL }, { ZYDIS_MNEMONIC_ROL, ZYDIS_MNEMONIC_ROR }, + { ZYDIS_MNEMONIC_INC, ZYDIS_MNEMONIC_DEC }, { ZYDIS_MNEMONIC_DEC, ZYDIS_MNEMONIC_INC } }; + + inline bool valid( zydis_mnemonic_t op ) + { + return transforms< std::uint64_t >.find( op ) != transforms< std::uint64_t >.end(); + } - inverse[ transform::type::generic1 ] = transforms[ transform::type::generic1 ]; - inverse[ transform::type::generic1 ].mnemonic = - transform::inverse[ transforms[ transform::type::generic1 ].mnemonic ]; + inline void inverse_transforms( transform::map_t &transforms, transform::map_t &inverse ) + { + inverse[ transform::type::generic0 ] = transforms[ transform::type::generic0 ]; + inverse[ transform::type::generic0 ].mnemonic = + transform::inverse[ transforms[ transform::type::generic0 ].mnemonic ]; - inverse[ transform::type::generic2 ] = transforms[ transform::type::generic2 ]; - inverse[ transform::type::generic2 ].mnemonic = - transform::inverse[ transforms[ transform::type::generic2 ].mnemonic ]; + inverse[ transform::type::rolling_key ] = transforms[ transform::type::rolling_key ]; + inverse[ transform::type::rolling_key ].mnemonic = + transform::inverse[ transforms[ transform::type::rolling_key ].mnemonic ]; - inverse[ transform::type::generic3 ] = transforms[ transform::type::generic3 ]; - inverse[ transform::type::generic3 ].mnemonic = - transform::inverse[ transforms[ transform::type::generic3 ].mnemonic ]; + inverse[ transform::type::generic1 ] = transforms[ transform::type::generic1 ]; + inverse[ transform::type::generic1 ].mnemonic = + transform::inverse[ transforms[ transform::type::generic1 ].mnemonic ]; - inverse[ transform::type::update_key ] = transforms[ transform::type::update_key ]; - inverse[ transform::type::update_key ].mnemonic = - transform::inverse[ transforms[ transform::type::update_key ].mnemonic ]; - } + inverse[ transform::type::generic2 ] = transforms[ transform::type::generic2 ]; + inverse[ transform::type::generic2 ].mnemonic = + transform::inverse[ transforms[ transform::type::generic2 ].mnemonic ]; - inline auto inverse_transforms( std::vector< zydis_decoded_instr_t > &instrs ) -> bool - { - for ( auto idx = 0u; idx < instrs.size(); idx++ ) - if ( !( instrs[ idx ].mnemonic = inverse[ instrs[ idx ].mnemonic ] ) ) - return false; + inverse[ transform::type::generic3 ] = transforms[ transform::type::generic3 ]; + inverse[ transform::type::generic3 ].mnemonic = + transform::inverse[ transforms[ transform::type::generic3 ].mnemonic ]; - std::reverse( instrs.begin(), instrs.end() ); - return true; - } + inverse[ transform::type::update_key ] = transforms[ transform::type::update_key ]; + inverse[ transform::type::update_key ].mnemonic = + transform::inverse[ transforms[ transform::type::update_key ].mnemonic ]; + } - // max size of a and b is 64 bits, a and b is then converted to - // the number of bits in bitsize, the transformation is applied, - // finally the result is converted back to 64bits... zero extended... - inline auto apply( std::uint8_t bitsize, ZydisMnemonic op, std::uint64_t a, std::uint64_t b ) -> std::uint64_t + inline auto inverse_transforms( std::vector< zydis_decoded_instr_t > &instrs ) -> bool + { + for ( auto idx = 0u; idx < instrs.size(); idx++ ) + if ( !( instrs[ idx ].mnemonic = inverse[ instrs[ idx ].mnemonic ] ) ) + return false; + + std::reverse( instrs.begin(), instrs.end() ); + return true; + } + + // max size of a and b is 64 bits, a and b is then converted to + // the number of bits in bitsize, the transformation is applied, + // finally the result is converted back to 64bits... zero extended... + inline auto apply( std::uint8_t bitsize, ZydisMnemonic op, std::uint64_t a, std::uint64_t b ) -> std::uint64_t + { + switch ( bitsize ) { - switch ( bitsize ) - { - case 8: - return transforms< std::uint8_t >[ op ]( a, b ); - case 16: - return transforms< std::uint16_t >[ op ]( a, b ); - case 32: - return transforms< std::uint32_t >[ op ]( a, b ); - case 64: - return transforms< std::uint64_t >[ op ]( a, b ); - default: - throw std::invalid_argument( "invalid bit size..." ); - } + case 8: + return transforms< std::uint8_t >[ op ]( a, b ); + case 16: + return transforms< std::uint16_t >[ op ]( a, b ); + case 32: + return transforms< std::uint32_t >[ op ]( a, b ); + case 64: + return transforms< std::uint64_t >[ op ]( a, b ); + default: + throw std::invalid_argument( "invalid bit size..." ); } + } - inline bool has_imm( const zydis_decoded_instr_t *instr ) - { - return instr->operand_count > 1 && ( instr->operands[ 1 ].type == ZYDIS_OPERAND_TYPE_IMMEDIATE ); - } - } // namespace transform -} // namespace vm \ No newline at end of file + inline bool has_imm( const zydis_decoded_instr_t *instr ) + { + return instr->operand_count > 1 && ( instr->operands[ 1 ].type == ZYDIS_OPERAND_TYPE_IMMEDIATE ); + } +} // namespace vm::transform \ No newline at end of file diff --git a/include/vmctx.hpp b/include/vmctx.hpp index e47403f..ea7c174 100644 --- a/include/vmctx.hpp +++ b/include/vmctx.hpp @@ -1,20 +1,47 @@ #pragma once #include -#include #include +#include namespace vm { + /// + /// vm::ctx_t class is used to auto generate vm_entry, calc_jmp, and other per-vm entry information... + /// creating a vm::ctx_t object can make it easier to pass around information pertaining to a given vm entry... + /// class ctx_t { public: + /// + /// default constructor for vm::ctx_t... all information for a given vm entry must be provided... + /// + /// the linear virtual address of the module base... + /// image base from optional nt header... IMAGE_OPTIONAL_HEADER64... + /// image size from optional nt header... IMAGE_OPTIONAL_HEADER64... + /// relative virtual address from the module base address to the first push prior to + /// a vm entry... explicit ctx_t( std::uintptr_t module_base, std::uintptr_t image_base, std::uintptr_t image_size, std::uintptr_t vm_entry_rva ); + /// + /// init all per-vm entry data such as vm_entry, calc_jmp, and vm handlers... + /// + /// returns true if no errors... bool init(); + const std::uintptr_t module_base, image_base, vm_entry_rva, image_size; + + /// + /// the order in which VIP advances... + /// vmp2::exec_type_t exec_type; zydis_routine_t vm_entry, calc_jmp; + + /// + /// all the vm handlers for the given vm entry... + /// std::vector< vm::handler::handler_t > vm_handlers; }; } // namespace vm \ No newline at end of file diff --git a/include/vmhandlers.hpp b/include/vmhandlers.hpp index a2927bf..ae59ebe 100644 --- a/include/vmhandlers.hpp +++ b/include/vmhandlers.hpp @@ -2,37 +2,134 @@ #include #include -namespace vm +namespace vm::handler { - namespace handler + /// + /// handler_t contains all the information for a vm handler such as its immidate value size (zero if there is no + /// imm), the transformations applied to the imm to decrypt it (if any), a pointer to the profile (nullptr if + /// there is none), and other meta data... + /// + struct handler_t { - struct handler_t - { - u8 imm_size; // size in bits... - vm::transform::map_t transforms; - vm::handler::profile_t *profile; - zydis_routine_t instrs; - std::uintptr_t address; - }; - - bool has_imm( const zydis_routine_t &vm_handler ); - std::optional< std::uint8_t > imm_size( const zydis_routine_t &vm_handler ); - bool get( zydis_routine_t &vm_entry, zydis_routine_t &vm_handler, std::uintptr_t handler_addr ); - - bool get_all( std::uintptr_t module_base, std::uintptr_t image_base, zydis_routine_t &vm_entry, - std::uintptr_t *vm_handler_table, std::vector< handler_t > &vm_handlers ); - - bool get_operand_transforms( zydis_routine_t &vm_handler, transform::map_t &transforms ); - vm::handler::profile_t *get_profile( handler_t &vm_handler ); - vm::handler::profile_t *get_profile( vm::handler::mnemonic_t mnemonic ); - - namespace table - { - std::uintptr_t *get( const zydis_routine_t &vm_entry ); - bool get_transform( const zydis_routine_t &vm_entry, zydis_decoded_instr_t *transform_instr ); - - std::uint64_t encrypt( zydis_decoded_instr_t &transform_instr, std::uint64_t val ); - std::uint64_t decrypt( zydis_decoded_instr_t &transform_instr, std::uint64_t val ); - } // namespace table - } // namespace handler -} // namespace vm \ No newline at end of file + /// + /// imm size in bits, zero if no imm... + /// + u8 imm_size; + + /// + /// transformations to decrypt imm... + /// + vm::transform::map_t transforms; + + /// + /// pointer to the profile, nullptr if none... + /// + vm::handler::profile_t *profile; + + /// + /// native instructions of the vm handler... (calc_jmp/check_vsp is removed from this)... + /// + zydis_routine_t instrs; + + /// + /// linear virtual address to the vm handler... + /// + std::uintptr_t address; + }; + + /// + /// given a vm handler returns true if the vm handler decrypts an operand... + /// + /// const reference to a vm handler... + /// returns true if the vm handler decrypts an operand, else false... + bool has_imm( const zydis_routine_t &vm_handler ); + + /// + /// gets the imm size of a vm handler... + /// + /// const reference to a vm handler... + /// returns the imm size, otherwise returns an empty optional value... + std::optional< std::uint8_t > imm_size( const zydis_routine_t &vm_handler ); + + /// + /// gets a vm handler, puts all of the native instructions inside of the vm_handler param... + /// + /// reference to a zydis_routine_t containing the native instructions of a vm + /// entry... reference to a zydis_routine_t that will get filled with the + /// native instructions of the vm handler... linear virtual address to the + /// first instruction of the vm handler... returns true if the native instructions of the vm + /// handler was extracted... + bool get( zydis_routine_t &vm_entry, zydis_routine_t &vm_handler, std::uintptr_t handler_addr ); + + /// + /// get all 256 vm handlers... + /// + /// linear virtual address of the module base... + /// image base from optional nt header... IMAGE_OPTIONAL_HEADER64... + /// zydis_routine_t containing the deobfuscated and flattened vm entry native + /// instructions... linear virtual address to the vm handler + /// table... vector of handler_t's that will be filled with the vm + /// handlers... returns true if all vm handlers were extracted, else false... + bool get_all( std::uintptr_t module_base, std::uintptr_t image_base, zydis_routine_t &vm_entry, + std::uintptr_t *vm_handler_table, std::vector< handler_t > &vm_handlers ); + + /// + /// get operand decryption instructions given a vm handler... + /// + /// reference to a zydis_routine_t containing the deobfuscated and flattened vm handler + /// native instructions... reference to a transform::map_t that will get filled + /// up with the transforms needed to decrypt operands... returns true if the transformations + /// were extracted successfully + bool get_operand_transforms( zydis_routine_t &vm_handler, transform::map_t &transforms ); + + /// + /// get a vm handler profile given a handler_t... + /// + /// reference to a handler_t structure that contains all the information of a given vm + /// handler... returns a pointer to the vm profile, else a nullptr... + vm::handler::profile_t *get_profile( handler_t &vm_handler ); + + /// + /// get a vm handler profile given the mnemonic of the vm handler... + /// + /// mnemonic of the vm handler... + /// returns a pointer to the profile if the given menmonic is implimented, else a nullptr... + vm::handler::profile_t *get_profile( vm::handler::mnemonic_t mnemonic ); + + namespace table + { + /// + /// get the linear virtual address of the vm handler table give a deobfuscated, flattened, vm entry... + /// + /// deobfuscated, flattened, vm entry... + /// returns the linear virtual address of the vm handler table... + std::uintptr_t *get( const zydis_routine_t &vm_entry ); + + /// + /// get the single native instruction used to decrypt vm handler entries... + /// + /// reference to the deobfuscated, flattened, vm entry... + /// + /// + bool get_transform( const zydis_routine_t &vm_entry, zydis_decoded_instr_t *transform_instr ); + + /// + /// encrypt a linear virtual address given the transformation that is used to decrypt the vm handler table + /// entry... this function will apply the inverse of the transformation so you dont need to get the inverse + /// yourself... + /// + /// reference to the transformation native instruction... + /// value to be encrypted (linear virtual address) + /// returns the encrypted value... + std::uint64_t encrypt( zydis_decoded_instr_t &transform_instr, std::uint64_t val ); + + /// + /// decrypts a vm handler table entry... + /// + /// transformation extracted from vm_entry that decrypts vm handler table + /// entries... encrypted value to be decrypted... returns the + /// decrypted value... + std::uint64_t decrypt( zydis_decoded_instr_t &transform_instr, std::uint64_t val ); + } // namespace table +} // namespace vm::handler \ No newline at end of file diff --git a/include/vminstrs.hpp b/include/vminstrs.hpp index 8e98a28..aa51aa8 100644 --- a/include/vminstrs.hpp +++ b/include/vminstrs.hpp @@ -4,66 +4,87 @@ #include #include -namespace vm +namespace vm::instrs { - namespace instrs - { - // decrypt transformations for encrypted virtual instruction rva... - bool get_rva_decrypt( const zydis_routine_t &vm_entry, std::vector< zydis_decoded_instr_t > &transform_instrs ); + /// + /// gets the native instructions that are used to decrypt the relative virtual address to virtual instructions + /// located on the stack at RSP+0xA0... you can learn about this @link https://back.engineering/17/05/2021/#vm_entry + /// + /// pass by reference of the specific vm entry you want to get the decryption instructions + /// from... pass by reference vector that will be filled with the decryption + /// instructions... returns true if the decryption instructions are extracted... + bool get_rva_decrypt( const zydis_routine_t &vm_entry, std::vector< zydis_decoded_instr_t > &transform_instrs ); - std::pair< std::uint64_t, std::uint64_t > decrypt_operand( transform::map_t &transforms, std::uint64_t operand, - std::uint64_t rolling_key ); + /// + /// decrypt virtual instruction operand given the decryption transformations... you can read about these + /// transformations + /// @link https://back.engineering/17/05/2021/#operand-decryption + /// + /// decryption transformations... + /// encrypted virtual instruction operand... + /// the decryption key (RBX)... + /// + std::pair< std::uint64_t, std::uint64_t > decrypt_operand( transform::map_t &transforms, std::uint64_t operand, + std::uint64_t rolling_key ); - std::pair< std::uint64_t, std::uint64_t > encrypt_operand( transform::map_t &transforms, std::uint64_t operand, - std::uint64_t rolling_key ); + /// + /// encrypt a virtual instructions operand given the transformations to decrypt the operand... the transformations + /// are inversed by this functions so you dont need to worry about doing that. + /// + /// you can learn about transformations @link https://back.engineering/17/05/2021/#operand-decryption + /// + /// transformations to decrypt operand, these transformations are inversed by the + /// function... operand to be encrypted... encryption key... (RBX)... + std::pair< std::uint64_t, std::uint64_t > encrypt_operand( transform::map_t &transforms, std::uint64_t operand, + std::uint64_t rolling_key ); - /// - /// get virt_instr_t filled in with data given a vmp2 trace entry and vm context... - /// - /// current vm context - /// vmp2 trace entry containing all of the native/virtual register/stack values... - /// returns a filled in virt_instr_t on success... - std::optional< virt_instr_t > get( vm::ctx_t &ctx, vmp2::v2::entry_t &entry ); + /// + /// get virt_instr_t filled in with data given a vmp2 trace entry and vm context... + /// + /// current vm context + /// vmp2 trace entry containing all of the native/virtual register/stack values... + /// returns a filled in virt_instr_t on success... + std::optional< virt_instr_t > get( vm::ctx_t &ctx, vmp2::v2::entry_t &entry ); - /// - /// gets the second operand (imm) given vip and vm::ctx_t... - /// - /// vm context - /// immediate value size in bits... - /// virtual instruction pointer, linear virtual address... - /// returns immediate value if imm_size is not 0... - std::optional< std::uint64_t > get_imm( vm::ctx_t &ctx, std::uint8_t imm_size, std::uintptr_t vip ); + /// + /// gets the second operand (imm) given vip and vm::ctx_t... + /// + /// vm context + /// immediate value size in bits... + /// virtual instruction pointer, linear virtual address... + /// returns immediate value if imm_size is not 0... + std::optional< std::uint64_t > get_imm( vm::ctx_t &ctx, std::uint8_t imm_size, std::uintptr_t vip ); - /// - /// get jcc data out of a code block... this function will loop over the code block - /// and look for the last LCONSTDW in the virtual instructions. - /// - /// it will then loop and look for all PUSHVSP's, checking each to see if the stack - /// contains two encrypted rva's to each branch.. if there is not two encrypted rva's - /// then the virtual jmp instruction only has one dest... - /// - /// vm context - /// code block that does not have its jcc_data yet - /// if last lconstdw is found, return filled in jcc_data structure... - std::optional< jcc_data > get_jcc_data( vm::ctx_t &ctx, code_block_t &code_block ); + /// + /// get jcc data out of a code block... this function will loop over the code block + /// and look for the last LCONSTDW in the virtual instructions. + /// + /// it will then loop and look for all PUSHVSP's, checking each to see if the stack + /// contains two encrypted rva's to each branch.. if there is not two encrypted rva's + /// then the virtual jmp instruction only has one dest... + /// + /// vm context + /// code block that does not have its jcc_data yet + /// if last lconstdw is found, return filled in jcc_data structure... + std::optional< jcc_data > get_jcc_data( vm::ctx_t &ctx, code_block_t &code_block ); - /// - /// the top of the stack will contain the lower 32bits of the RVA to the virtual instructions - /// that will be jumping too... the RVA is image based (not module based, but optional header image - /// based)... this means the value ontop of the stack could be "40007fd8" with image base being - /// 0x140000000... as you can see the 0x100000000 is missing... the below statement deals with this... - /// - /// vm context - /// current trace entry for virtual JMP instruction - /// returns linear virtual address of the next code block... - std::uintptr_t code_block_addr( const vm::ctx_t &ctx, const vmp2::v2::entry_t &entry ); + /// + /// the top of the stack will contain the lower 32bits of the RVA to the virtual instructions + /// that will be jumping too... the RVA is image based (not module based, but optional header image + /// based)... this means the value ontop of the stack could be "40007fd8" with image base being + /// 0x140000000... as you can see the 0x100000000 is missing... the below statement deals with this... + /// + /// vm context + /// current trace entry for virtual JMP instruction + /// returns linear virtual address of the next code block... + std::uintptr_t code_block_addr( const vm::ctx_t &ctx, const vmp2::v2::entry_t &entry ); - /// - /// same routine as above except lower_32bits is passed directly and not extracted from the stack... - /// - /// vm context - /// lower 32bits of the relative virtual address... - /// returns full linear virtual address of code block... - std::uintptr_t code_block_addr( const vm::ctx_t &ctx, const std::uint32_t lower_32bits ); - } // namespace instrs -} // namespace vm \ No newline at end of file + /// + /// same routine as above except lower_32bits is passed directly and not extracted from the stack... + /// + /// vm context + /// lower 32bits of the relative virtual address... + /// returns full linear virtual address of code block... + std::uintptr_t code_block_addr( const vm::ctx_t &ctx, const std::uint32_t lower_32bits ); +} // namespace vm::instrs \ No newline at end of file diff --git a/include/vmprofiles.hpp b/include/vmprofiles.hpp index 34d3d32..25f6f58 100644 --- a/include/vmprofiles.hpp +++ b/include/vmprofiles.hpp @@ -1,138 +1,175 @@ #pragma once #include -namespace vm +/// +/// contains all information pertaining to vm handler identification... +/// +namespace vm::handler { - namespace handler + /// + /// vm handler mnemonic... so you dont need to compare strings! + /// + enum mnemonic_t { - enum mnemonic_t - { - INVALID, - LRFLAGS, - PUSHVSP, - MULQ, - DIVQ, - CALL, - JMP, - VMEXIT, - - SREGQ, - SREGDW, - SREGW, - - LREGQ, - LREGDW, - - LCONSTQ, - LCONSTBZXW, - LCONSTBSXQ, - LCONSTBSXDW, - LCONSTDWSXQ, - LCONSTWSXQ, - LCONSTWSXDW, - LCONSTDW, - LCONSTW, - - READQ, - READDW, - READW, - - WRITEQ, - WRITEDW, - WRITEW, - WRITEB, - - ADDQ, - ADDDW, - ADDW, - - SHLQ, - SHLDW, - - SHRQ, - SHRW, - - NANDQ, - NANDDW, - NANDW - }; - - using zydis_callback_t = std::function; - - enum extention_t - { - none, - sign_extend, - zero_extend - }; - - struct profile_t - { - const char *name; - mnemonic_t mnemonic; - u8 imm_size; - std::vector< zydis_callback_t > signature; - extention_t extention; - }; - - namespace profile - { - extern vm::handler::profile_t sregq; - extern vm::handler::profile_t sregdw; - extern vm::handler::profile_t sregw; - - extern vm::handler::profile_t lregq; - extern vm::handler::profile_t lregdw; - - extern vm::handler::profile_t lconstq; - extern vm::handler::profile_t lconstdw; - extern vm::handler::profile_t lconstw; - - extern vm::handler::profile_t lconstbzxw; - extern vm::handler::profile_t lconstbsxdw; - extern vm::handler::profile_t lconstbsxq; - extern vm::handler::profile_t lconstdwsxq; - extern vm::handler::profile_t lconstwsxq; - extern vm::handler::profile_t lconstwsxdw; - - extern vm::handler::profile_t addq; - extern vm::handler::profile_t adddw; - extern vm::handler::profile_t addw; - - extern vm::handler::profile_t shlq; - extern vm::handler::profile_t shldw; - - extern vm::handler::profile_t nandq; - extern vm::handler::profile_t nanddw; - extern vm::handler::profile_t nandw; - - extern vm::handler::profile_t writeq; - extern vm::handler::profile_t writedw; - extern vm::handler::profile_t writeb; - - extern vm::handler::profile_t readq; - extern vm::handler::profile_t readdw; - - extern vm::handler::profile_t shrq; - extern vm::handler::profile_t shrw; - - extern vm::handler::profile_t lrflags; - extern vm::handler::profile_t call; - extern vm::handler::profile_t pushvsp; - extern vm::handler::profile_t mulq; - extern vm::handler::profile_t divq; - extern vm::handler::profile_t jmp; - extern vm::handler::profile_t vmexit; - - inline std::vector< vm::handler::profile_t * > all = { - &sregq, &sregdw, &sregw, &lregq, &lregdw, &lconstq, &lconstbzxw, &lconstbsxdw, - &lconstbsxq, &lconstdwsxq, &lconstwsxq, &lconstwsxdw, &lconstdw, &lconstw, &addq, &adddw, - &addw, - - &shlq, &shldw, &writeq, &writedw, &writeb, &nandq, &nanddw, &nandw, - - &shrq, &shrw, &readq, &readdw, &mulq, &pushvsp, &divq, &jmp, - &lrflags, &vmexit, &call }; - } // namespace profile - } // namespace handler -} // namespace vm \ No newline at end of file + INVALID, + LRFLAGS, + PUSHVSP, + MULQ, + DIVQ, + CALL, + JMP, + VMEXIT, + + SREGQ, + SREGDW, + SREGW, + + LREGQ, + LREGDW, + + LCONSTQ, + LCONSTBZXW, + LCONSTBSXQ, + LCONSTBSXDW, + LCONSTDWSXQ, + LCONSTWSXQ, + LCONSTWSXDW, + LCONSTDW, + LCONSTW, + + READQ, + READDW, + READW, + + WRITEQ, + WRITEDW, + WRITEW, + WRITEB, + + ADDQ, + ADDDW, + ADDW, + + SHLQ, + SHLDW, + + SHRQ, + SHRW, + + NANDQ, + NANDDW, + NANDW + }; + + /// + /// zydis callback lambda used to pattern match native instructions... + /// + using zydis_callback_t = std::function< bool( const zydis_decoded_instr_t &instr ) >; + + /// + /// how sign extention is handled... + /// + enum extention_t + { + none, + sign_extend, + zero_extend + }; + + /// + /// pre defined vm handler profile containing all compiled time known information about a vm handler... + /// + struct profile_t + { + /// + /// name of the vm handler, such as JMP or LCONST... + /// + const char *name; + + /// + /// the mnemonic of the vm handler... so you dont need to compare strings... + /// + mnemonic_t mnemonic; + + /// + /// size, in bits, of the operand (imm)... if there is none then this will be zero... + /// + u8 imm_size; + + /// + /// a vector of signatures used to compare native instructions against zydis aided signatures... + /// + std::vector< zydis_callback_t > signature; + + /// + /// how sign extention of operands are handled... + /// + extention_t extention; + }; + + /// + /// contains all profiles defined, as well as a vector of all of the defined profiles... + /// + namespace profile + { + extern vm::handler::profile_t sregq; + extern vm::handler::profile_t sregdw; + extern vm::handler::profile_t sregw; + + extern vm::handler::profile_t lregq; + extern vm::handler::profile_t lregdw; + + extern vm::handler::profile_t lconstq; + extern vm::handler::profile_t lconstdw; + extern vm::handler::profile_t lconstw; + + extern vm::handler::profile_t lconstbzxw; + extern vm::handler::profile_t lconstbsxdw; + extern vm::handler::profile_t lconstbsxq; + extern vm::handler::profile_t lconstdwsxq; + extern vm::handler::profile_t lconstwsxq; + extern vm::handler::profile_t lconstwsxdw; + + extern vm::handler::profile_t addq; + extern vm::handler::profile_t adddw; + extern vm::handler::profile_t addw; + + extern vm::handler::profile_t shlq; + extern vm::handler::profile_t shldw; + + extern vm::handler::profile_t nandq; + extern vm::handler::profile_t nanddw; + extern vm::handler::profile_t nandw; + + extern vm::handler::profile_t writeq; + extern vm::handler::profile_t writedw; + extern vm::handler::profile_t writeb; + + extern vm::handler::profile_t readq; + extern vm::handler::profile_t readdw; + + extern vm::handler::profile_t shrq; + extern vm::handler::profile_t shrw; + + extern vm::handler::profile_t lrflags; + extern vm::handler::profile_t call; + extern vm::handler::profile_t pushvsp; + extern vm::handler::profile_t mulq; + extern vm::handler::profile_t divq; + extern vm::handler::profile_t jmp; + extern vm::handler::profile_t vmexit; + + /// + /// a vector of pointers to all defined vm handler profiles... + /// + inline std::vector< vm::handler::profile_t * > all = { + &sregq, &sregdw, &sregw, &lregq, &lregdw, &lconstq, &lconstbzxw, &lconstbsxdw, + &lconstbsxq, &lconstdwsxq, &lconstwsxq, &lconstwsxdw, &lconstdw, &lconstw, &addq, &adddw, + &addw, + + &shlq, &shldw, &writeq, &writedw, &writeb, &nandq, &nanddw, &nandw, + + &shrq, &shrw, &readq, &readdw, &mulq, &pushvsp, &divq, &jmp, + &lrflags, &vmexit, &call }; + } // namespace profile +} // namespace vm::handler \ No newline at end of file diff --git a/include/vmutils.hpp b/include/vmutils.hpp index a118b16..afc877c 100644 --- a/include/vmutils.hpp +++ b/include/vmutils.hpp @@ -28,25 +28,22 @@ struct zydis_instr_t using zydis_routine_t = std::vector< zydis_instr_t >; -namespace vm +namespace vm::util { - namespace util + namespace reg { - namespace reg - { - // converts say... AL to RAX... - zydis_register_t to64( zydis_register_t reg ); - bool compare( zydis_register_t a, zydis_register_t b ); - } // namespace reg - - bool get_fetch_operand( const zydis_routine_t &routine, zydis_instr_t &fetch_instr ); - std::optional< zydis_routine_t::iterator > get_fetch_operand( zydis_routine_t &routine ); - - void print( zydis_routine_t &routine ); - void print( const zydis_decoded_instr_t &instr ); - bool is_jmp( const zydis_decoded_instr_t &instr ); - - bool flatten( zydis_routine_t &routine, std::uintptr_t routine_addr, bool keep_jmps = false ); - void deobfuscate( zydis_routine_t &routine ); - } // namespace util -} // namespace vm \ No newline at end of file + // converts say... AL to RAX... + zydis_register_t to64( zydis_register_t reg ); + bool compare( zydis_register_t a, zydis_register_t b ); + } // namespace reg + + bool get_fetch_operand( const zydis_routine_t &routine, zydis_instr_t &fetch_instr ); + std::optional< zydis_routine_t::iterator > get_fetch_operand( zydis_routine_t &routine ); + + void print( zydis_routine_t &routine ); + void print( const zydis_decoded_instr_t &instr ); + bool is_jmp( const zydis_decoded_instr_t &instr ); + + bool flatten( zydis_routine_t &routine, std::uintptr_t routine_addr, bool keep_jmps = false ); + void deobfuscate( zydis_routine_t &routine ); +} // namespace vm::util \ No newline at end of file diff --git a/src/vminstrs.cpp b/src/vminstrs.cpp index fb241a5..f6cef09 100644 --- a/src/vminstrs.cpp +++ b/src/vminstrs.cpp @@ -1,279 +1,274 @@ #include -namespace vm +namespace vm::instrs { - namespace instrs + std::pair< std::uint64_t, std::uint64_t > decrypt_operand( transform::map_t &transforms, std::uint64_t operand, + std::uint64_t rolling_key ) { - std::pair< std::uint64_t, std::uint64_t > decrypt_operand( transform::map_t &transforms, std::uint64_t operand, - std::uint64_t rolling_key ) + const auto &generic_decrypt_0 = transforms[ transform::type::generic0 ]; + const auto &key_decrypt = transforms[ transform::type::rolling_key ]; + const auto &generic_decrypt_1 = transforms[ transform::type::generic1 ]; + const auto &generic_decrypt_2 = transforms[ transform::type::generic2 ]; + const auto &generic_decrypt_3 = transforms[ transform::type::generic3 ]; + const auto &update_key = transforms[ transform::type::update_key ]; + + if ( generic_decrypt_0.mnemonic != ZYDIS_MNEMONIC_INVALID ) { - const auto &generic_decrypt_0 = transforms[ transform::type::generic0 ]; - const auto &key_decrypt = transforms[ transform::type::rolling_key ]; - const auto &generic_decrypt_1 = transforms[ transform::type::generic1 ]; - const auto &generic_decrypt_2 = transforms[ transform::type::generic2 ]; - const auto &generic_decrypt_3 = transforms[ transform::type::generic3 ]; - const auto &update_key = transforms[ transform::type::update_key ]; - - if ( generic_decrypt_0.mnemonic != ZYDIS_MNEMONIC_INVALID ) - { - operand = transform::apply( - generic_decrypt_0.operands[ 0 ].size, generic_decrypt_0.mnemonic, operand, - // check to see if this instruction has an IMM... - transform::has_imm( &generic_decrypt_0 ) ? generic_decrypt_0.operands[ 1 ].imm.value.u : 0 ); - } - - // apply transformation with rolling decrypt key... - operand = transform::apply( key_decrypt.operands[ 0 ].size, key_decrypt.mnemonic, operand, rolling_key ); - - // apply three generic transformations... - { - operand = transform::apply( - generic_decrypt_1.operands[ 0 ].size, generic_decrypt_1.mnemonic, operand, - // check to see if this instruction has an IMM... - transform::has_imm( &generic_decrypt_1 ) ? generic_decrypt_1.operands[ 1 ].imm.value.u : 0 ); - - operand = transform::apply( - generic_decrypt_2.operands[ 0 ].size, generic_decrypt_2.mnemonic, operand, - // check to see if this instruction has an IMM... - transform::has_imm( &generic_decrypt_2 ) ? generic_decrypt_2.operands[ 1 ].imm.value.u : 0 ); - - operand = transform::apply( - generic_decrypt_3.operands[ 0 ].size, generic_decrypt_3.mnemonic, operand, - // check to see if this instruction has an IMM... - transform::has_imm( &generic_decrypt_3 ) ? generic_decrypt_3.operands[ 1 ].imm.value.u : 0 ); - } - - // update rolling key... - auto result = transform::apply( update_key.operands[ 0 ].size, update_key.mnemonic, rolling_key, operand ); - - // update decryption key correctly... - switch ( update_key.operands[ 0 ].size ) - { - case 8: - rolling_key = ( rolling_key & ~std::numeric_limits< u8 >::max() ) + result; - break; - case 16: - rolling_key = ( rolling_key & ~std::numeric_limits< u16 >::max() ) + result; - break; - default: - rolling_key = result; - break; - } - - return { operand, rolling_key }; + operand = transform::apply( + generic_decrypt_0.operands[ 0 ].size, generic_decrypt_0.mnemonic, operand, + // check to see if this instruction has an IMM... + transform::has_imm( &generic_decrypt_0 ) ? generic_decrypt_0.operands[ 1 ].imm.value.u : 0 ); } - std::pair< std::uint64_t, std::uint64_t > encrypt_operand( transform::map_t &transforms, std::uint64_t operand, - std::uint64_t rolling_key ) + // apply transformation with rolling decrypt key... + operand = transform::apply( key_decrypt.operands[ 0 ].size, key_decrypt.mnemonic, operand, rolling_key ); + + // apply three generic transformations... { - transform::map_t inverse; - inverse_transforms( transforms, inverse ); - const auto apply_key = rolling_key; - - const auto &generic_decrypt_0 = inverse[ transform::type::generic0 ]; - const auto &key_decrypt = inverse[ transform::type::rolling_key ]; - const auto &generic_decrypt_1 = inverse[ transform::type::generic1 ]; - const auto &generic_decrypt_2 = inverse[ transform::type::generic2 ]; - const auto &generic_decrypt_3 = inverse[ transform::type::generic3 ]; - const auto &update_key = transforms[ transform::type::update_key ]; - - auto result = transform::apply( update_key.operands[ 0 ].size, update_key.mnemonic, rolling_key, operand ); - - // mov rax, al does not clear the top bits... - // mov rax, ax does not clear the top bits... - // mov rax, eax does clear the top bits... - switch ( update_key.operands[ 0 ].size ) - { - case 8: - rolling_key = ( rolling_key & ~std::numeric_limits< u8 >::max() ) + result; - break; - case 16: - rolling_key = ( rolling_key & ~std::numeric_limits< u16 >::max() ) + result; - break; - default: - rolling_key = result; - break; - } - - { - operand = transform::apply( - generic_decrypt_3.operands[ 0 ].size, generic_decrypt_3.mnemonic, operand, - // check to see if this instruction has an IMM... - transform::has_imm( &generic_decrypt_3 ) ? generic_decrypt_3.operands[ 1 ].imm.value.u : 0 ); - - operand = transform::apply( - generic_decrypt_2.operands[ 0 ].size, generic_decrypt_2.mnemonic, operand, - // check to see if this instruction has an IMM... - transform::has_imm( &generic_decrypt_2 ) ? generic_decrypt_2.operands[ 1 ].imm.value.u : 0 ); - - operand = transform::apply( - generic_decrypt_1.operands[ 0 ].size, generic_decrypt_1.mnemonic, operand, - // check to see if this instruction has an IMM... - transform::has_imm( &generic_decrypt_1 ) ? generic_decrypt_1.operands[ 1 ].imm.value.u : 0 ); - } - - operand = transform::apply( key_decrypt.operands[ 0 ].size, key_decrypt.mnemonic, operand, apply_key ); - - if ( generic_decrypt_0.mnemonic != ZYDIS_MNEMONIC_INVALID ) - { - operand = transform::apply( - generic_decrypt_0.operands[ 0 ].size, generic_decrypt_0.mnemonic, operand, - // check to see if this instruction has an IMM... - transform::has_imm( &generic_decrypt_0 ) ? generic_decrypt_0.operands[ 1 ].imm.value.u : 0 ); - } - - return { operand, rolling_key }; + operand = transform::apply( + generic_decrypt_1.operands[ 0 ].size, generic_decrypt_1.mnemonic, operand, + // check to see if this instruction has an IMM... + transform::has_imm( &generic_decrypt_1 ) ? generic_decrypt_1.operands[ 1 ].imm.value.u : 0 ); + + operand = transform::apply( + generic_decrypt_2.operands[ 0 ].size, generic_decrypt_2.mnemonic, operand, + // check to see if this instruction has an IMM... + transform::has_imm( &generic_decrypt_2 ) ? generic_decrypt_2.operands[ 1 ].imm.value.u : 0 ); + + operand = transform::apply( + generic_decrypt_3.operands[ 0 ].size, generic_decrypt_3.mnemonic, operand, + // check to see if this instruction has an IMM... + transform::has_imm( &generic_decrypt_3 ) ? generic_decrypt_3.operands[ 1 ].imm.value.u : 0 ); } - bool get_rva_decrypt( const zydis_routine_t &vm_entry, std::vector< zydis_decoded_instr_t > &transform_instrs ) + // update rolling key... + auto result = transform::apply( update_key.operands[ 0 ].size, update_key.mnemonic, rolling_key, operand ); + + // update decryption key correctly... + switch ( update_key.operands[ 0 ].size ) { - // find mov esi, [rsp+0xA0] - auto result = - std::find_if( vm_entry.begin(), vm_entry.end(), []( const zydis_instr_t &instr_data ) -> bool { - return instr_data.instr.mnemonic == ZYDIS_MNEMONIC_MOV && - instr_data.instr.operands[ 0 ].type == ZYDIS_OPERAND_TYPE_REGISTER && - instr_data.instr.operands[ 0 ].reg.value == ZYDIS_REGISTER_ESI && - instr_data.instr.operands[ 1 ].type == ZYDIS_OPERAND_TYPE_MEMORY && - instr_data.instr.operands[ 1 ].mem.base == ZYDIS_REGISTER_RSP && - instr_data.instr.operands[ 1 ].mem.disp.value == 0xA0; - } ); + case 8: + rolling_key = ( rolling_key & ~std::numeric_limits< u8 >::max() ) + result; + break; + case 16: + rolling_key = ( rolling_key & ~std::numeric_limits< u16 >::max() ) + result; + break; + default: + rolling_key = result; + break; + } - if ( result == vm_entry.end() ) - return false; + return { operand, rolling_key }; + } - // find the next three instructions with ESI as - // the first operand... and make sure actions & writes... - for ( auto idx = 0u; idx < 3; ++idx ) - { - result = std::find_if( ++result, vm_entry.end(), []( const zydis_instr_t &instr_data ) -> bool { - return vm::transform::valid( instr_data.instr.mnemonic ) && - instr_data.instr.operands[ 0 ].actions & ZYDIS_OPERAND_ACTION_WRITE && - instr_data.instr.operands[ 0 ].reg.value == ZYDIS_REGISTER_ESI; - } ); + std::pair< std::uint64_t, std::uint64_t > encrypt_operand( transform::map_t &transforms, std::uint64_t operand, + std::uint64_t rolling_key ) + { + transform::map_t inverse; + inverse_transforms( transforms, inverse ); + const auto apply_key = rolling_key; + + const auto &generic_decrypt_0 = inverse[ transform::type::generic0 ]; + const auto &key_decrypt = inverse[ transform::type::rolling_key ]; + const auto &generic_decrypt_1 = inverse[ transform::type::generic1 ]; + const auto &generic_decrypt_2 = inverse[ transform::type::generic2 ]; + const auto &generic_decrypt_3 = inverse[ transform::type::generic3 ]; + const auto &update_key = transforms[ transform::type::update_key ]; + + auto result = transform::apply( update_key.operands[ 0 ].size, update_key.mnemonic, rolling_key, operand ); + + // mov rax, al does not clear the top bits... + // mov rax, ax does not clear the top bits... + // mov rax, eax does clear the top bits... + switch ( update_key.operands[ 0 ].size ) + { + case 8: + rolling_key = ( rolling_key & ~std::numeric_limits< u8 >::max() ) + result; + break; + case 16: + rolling_key = ( rolling_key & ~std::numeric_limits< u16 >::max() ) + result; + break; + default: + rolling_key = result; + break; + } - if ( result == vm_entry.end() ) - return false; + { + operand = transform::apply( + generic_decrypt_3.operands[ 0 ].size, generic_decrypt_3.mnemonic, operand, + // check to see if this instruction has an IMM... + transform::has_imm( &generic_decrypt_3 ) ? generic_decrypt_3.operands[ 1 ].imm.value.u : 0 ); + + operand = transform::apply( + generic_decrypt_2.operands[ 0 ].size, generic_decrypt_2.mnemonic, operand, + // check to see if this instruction has an IMM... + transform::has_imm( &generic_decrypt_2 ) ? generic_decrypt_2.operands[ 1 ].imm.value.u : 0 ); + + operand = transform::apply( + generic_decrypt_1.operands[ 0 ].size, generic_decrypt_1.mnemonic, operand, + // check to see if this instruction has an IMM... + transform::has_imm( &generic_decrypt_1 ) ? generic_decrypt_1.operands[ 1 ].imm.value.u : 0 ); + } - transform_instrs.push_back( result->instr ); - } + operand = transform::apply( key_decrypt.operands[ 0 ].size, key_decrypt.mnemonic, operand, apply_key ); - return true; + if ( generic_decrypt_0.mnemonic != ZYDIS_MNEMONIC_INVALID ) + { + operand = transform::apply( + generic_decrypt_0.operands[ 0 ].size, generic_decrypt_0.mnemonic, operand, + // check to see if this instruction has an IMM... + transform::has_imm( &generic_decrypt_0 ) ? generic_decrypt_0.operands[ 1 ].imm.value.u : 0 ); } - std::optional< std::uint64_t > get_imm( vm::ctx_t &ctx, std::uint8_t imm_size, std::uintptr_t vip ) + return { operand, rolling_key }; + } + + bool get_rva_decrypt( const zydis_routine_t &vm_entry, std::vector< zydis_decoded_instr_t > &transform_instrs ) + { + // find mov esi, [rsp+0xA0] + auto result = std::find_if( vm_entry.begin(), vm_entry.end(), []( const zydis_instr_t &instr_data ) -> bool { + return instr_data.instr.mnemonic == ZYDIS_MNEMONIC_MOV && + instr_data.instr.operands[ 0 ].type == ZYDIS_OPERAND_TYPE_REGISTER && + instr_data.instr.operands[ 0 ].reg.value == ZYDIS_REGISTER_ESI && + instr_data.instr.operands[ 1 ].type == ZYDIS_OPERAND_TYPE_MEMORY && + instr_data.instr.operands[ 1 ].mem.base == ZYDIS_REGISTER_RSP && + instr_data.instr.operands[ 1 ].mem.disp.value == 0xA0; + } ); + + if ( result == vm_entry.end() ) + return false; + + // find the next three instructions with ESI as + // the first operand... and make sure actions & writes... + for ( auto idx = 0u; idx < 3; ++idx ) { - if ( !imm_size ) - return {}; + result = std::find_if( ++result, vm_entry.end(), []( const zydis_instr_t &instr_data ) -> bool { + return vm::transform::valid( instr_data.instr.mnemonic ) && + instr_data.instr.operands[ 0 ].actions & ZYDIS_OPERAND_ACTION_WRITE && + instr_data.instr.operands[ 0 ].reg.value == ZYDIS_REGISTER_ESI; + } ); - auto result = 0ull; - ctx.exec_type == vmp2::exec_type_t::forward - ? std::memcpy( &result, reinterpret_cast< void * >( vip ), imm_size / 8 ) - : std::memcpy( &result, reinterpret_cast< void * >( vip - ( imm_size / 8 ) ), imm_size / 8 ); + if ( result == vm_entry.end() ) + return false; - return result; + transform_instrs.push_back( result->instr ); } - std::optional< virt_instr_t > get( vm::ctx_t &ctx, vmp2::v2::entry_t &entry ) - { - virt_instr_t result; - auto &vm_handler = ctx.vm_handlers[ entry.handler_idx ]; - const auto profile = vm_handler.profile; + return true; + } - result.mnemonic_t = profile ? profile->mnemonic : vm::handler::INVALID; - result.opcode = entry.handler_idx; - result.trace_data = entry; - result.operand.has_imm = false; + std::optional< std::uint64_t > get_imm( vm::ctx_t &ctx, std::uint8_t imm_size, std::uintptr_t vip ) + { + if ( !imm_size ) + return {}; - if ( vm_handler.imm_size ) - { - result.operand.has_imm = true; - result.operand.imm.imm_size = vm_handler.imm_size; - const auto imm_val = get_imm( ctx, vm_handler.imm_size, entry.vip ); + auto result = 0ull; + ctx.exec_type == vmp2::exec_type_t::forward + ? std::memcpy( &result, reinterpret_cast< void * >( vip ), imm_size / 8 ) + : std::memcpy( &result, reinterpret_cast< void * >( vip - ( imm_size / 8 ) ), imm_size / 8 ); - if ( !imm_val.has_value() ) - return {}; + return result; + } - result.operand.imm.u = - vm::instrs::decrypt_operand( vm_handler.transforms, imm_val.value(), entry.decrypt_key ).first; - } + std::optional< virt_instr_t > get( vm::ctx_t &ctx, vmp2::v2::entry_t &entry ) + { + virt_instr_t result; + auto &vm_handler = ctx.vm_handlers[ entry.handler_idx ]; + const auto profile = vm_handler.profile; - return result; - } + result.mnemonic_t = profile ? profile->mnemonic : vm::handler::INVALID; + result.opcode = entry.handler_idx; + result.trace_data = entry; + result.operand.has_imm = false; - std::optional< jcc_data > get_jcc_data( vm::ctx_t &vmctx, code_block_t &code_block ) + if ( vm_handler.imm_size ) { - // there is no branch for this as this is a vmexit... - if ( code_block.vinstrs.back().mnemonic_t == vm::handler::VMEXIT ) + result.operand.has_imm = true; + result.operand.imm.imm_size = vm_handler.imm_size; + const auto imm_val = get_imm( ctx, vm_handler.imm_size, entry.vip ); + + if ( !imm_val.has_value() ) return {}; - // find the last LCONSTDW... the imm value is the JMP xor decrypt key... - // we loop backwards here (using rbegin and rend)... - auto result = std::find_if( code_block.vinstrs.rbegin(), code_block.vinstrs.rend(), - []( const vm::instrs::virt_instr_t &vinstr ) -> bool { - auto profile = vm::handler::get_profile( vinstr.mnemonic_t ); - return profile && profile->mnemonic == vm::handler::LCONSTDW; - } ); - - jcc_data jcc; - const auto xor_key = static_cast< std::uint32_t >( result->operand.imm.u ); - const auto &last_trace = code_block.vinstrs.back().trace_data; - - // since result is already a variable and is a reverse itr - // im going to be using rbegin and rend here again... - // - // look for PUSHVSP virtual instructions with two encrypted virtual - // instruction rva's ontop of the virtual stack... - result = std::find_if( code_block.vinstrs.rbegin(), code_block.vinstrs.rend(), - [ & ]( const vm::instrs::virt_instr_t &vinstr ) -> bool { - if ( auto profile = vm::handler::get_profile( vinstr.mnemonic_t ); - profile && profile->mnemonic == vm::handler::PUSHVSP ) - { - const auto possible_block_1 = code_block_addr( - vmctx, vinstr.trace_data.vsp.qword[ 0 ] ^ xor_key ), - possible_block_2 = code_block_addr( - vmctx, vinstr.trace_data.vsp.qword[ 1 ] ^ xor_key ); - - // if this returns too many false positives we might have to get - // our hands dirty and look into trying to emulate each branch - // to see if the first instruction is an SREGQ... - return possible_block_1 > vmctx.module_base && - possible_block_1 < vmctx.module_base + vmctx.image_size && - possible_block_2 > vmctx.module_base && - possible_block_2 < vmctx.module_base + vmctx.image_size; - } - return false; - } ); - - // if there is not two branches... - if ( result == code_block.vinstrs.rend() ) - { - jcc.block_addr[ 0 ] = code_block_addr( vmctx, last_trace ); - jcc.has_jcc = false; - jcc.type = jcc_type::absolute; - } - // else there are two branches... - else - { - jcc.block_addr[ 0 ] = code_block_addr( vmctx, result->trace_data.vsp.qword[ 0 ] ^ xor_key ); - jcc.block_addr[ 1 ] = code_block_addr( vmctx, result->trace_data.vsp.qword[ 1 ] ^ xor_key ); - - jcc.has_jcc = true; - jcc.type = jcc_type::branching; - } - - return jcc; + result.operand.imm.u = + vm::instrs::decrypt_operand( vm_handler.transforms, imm_val.value(), entry.decrypt_key ).first; } - std::uintptr_t code_block_addr( const vm::ctx_t &ctx, const vmp2::v2::entry_t &entry ) + return result; + } + + std::optional< jcc_data > get_jcc_data( vm::ctx_t &vmctx, code_block_t &code_block ) + { + // there is no branch for this as this is a vmexit... + if ( code_block.vinstrs.back().mnemonic_t == vm::handler::VMEXIT ) + return {}; + + // find the last LCONSTDW... the imm value is the JMP xor decrypt key... + // we loop backwards here (using rbegin and rend)... + auto result = std::find_if( code_block.vinstrs.rbegin(), code_block.vinstrs.rend(), + []( const vm::instrs::virt_instr_t &vinstr ) -> bool { + auto profile = vm::handler::get_profile( vinstr.mnemonic_t ); + return profile && profile->mnemonic == vm::handler::LCONSTDW; + } ); + + jcc_data jcc; + const auto xor_key = static_cast< std::uint32_t >( result->operand.imm.u ); + const auto &last_trace = code_block.vinstrs.back().trace_data; + + // since result is already a variable and is a reverse itr + // im going to be using rbegin and rend here again... + // + // look for PUSHVSP virtual instructions with two encrypted virtual + // instruction rva's ontop of the virtual stack... + result = std::find_if( + code_block.vinstrs.rbegin(), code_block.vinstrs.rend(), + [ & ]( const vm::instrs::virt_instr_t &vinstr ) -> bool { + if ( auto profile = vm::handler::get_profile( vinstr.mnemonic_t ); + profile && profile->mnemonic == vm::handler::PUSHVSP ) + { + const auto possible_block_1 = code_block_addr( vmctx, vinstr.trace_data.vsp.qword[ 0 ] ^ xor_key ), + possible_block_2 = code_block_addr( vmctx, vinstr.trace_data.vsp.qword[ 1 ] ^ xor_key ); + + // if this returns too many false positives we might have to get + // our hands dirty and look into trying to emulate each branch + // to see if the first instruction is an SREGQ... + return possible_block_1 > vmctx.module_base && + possible_block_1 < vmctx.module_base + vmctx.image_size && + possible_block_2 > vmctx.module_base && + possible_block_2 < vmctx.module_base + vmctx.image_size; + } + return false; + } ); + + // if there is not two branches... + if ( result == code_block.vinstrs.rend() ) { - return ( ( entry.vsp.qword[ 0 ] & std::numeric_limits< u32 >::max() ) - - ( ctx.image_base & std::numeric_limits< u32 >::max() ) ) + - ctx.module_base; + jcc.block_addr[ 0 ] = code_block_addr( vmctx, last_trace ); + jcc.has_jcc = false; + jcc.type = jcc_type::absolute; } - - std::uintptr_t code_block_addr( const vm::ctx_t &ctx, const std::uint32_t lower_32bits ) + // else there are two branches... + else { - return ( lower_32bits - ( ctx.image_base & std::numeric_limits< u32 >::max() ) ) + ctx.module_base; + jcc.block_addr[ 0 ] = code_block_addr( vmctx, result->trace_data.vsp.qword[ 0 ] ^ xor_key ); + jcc.block_addr[ 1 ] = code_block_addr( vmctx, result->trace_data.vsp.qword[ 1 ] ^ xor_key ); + + jcc.has_jcc = true; + jcc.type = jcc_type::branching; } - } // namespace instrs -} // namespace vm \ No newline at end of file + + return jcc; + } + + std::uintptr_t code_block_addr( const vm::ctx_t &ctx, const vmp2::v2::entry_t &entry ) + { + return ( ( entry.vsp.qword[ 0 ] & std::numeric_limits< u32 >::max() ) - + ( ctx.image_base & std::numeric_limits< u32 >::max() ) ) + + ctx.module_base; + } + + std::uintptr_t code_block_addr( const vm::ctx_t &ctx, const std::uint32_t lower_32bits ) + { + return ( lower_32bits - ( ctx.image_base & std::numeric_limits< u32 >::max() ) ) + ctx.module_base; + } +} // namespace vm::instrs \ No newline at end of file