still adding doxygen comments...

merge-requests/11/head
_xeroxz 3 years ago
parent 099a7e9c58
commit 08635457a7

@ -2,11 +2,22 @@
#include <transform.hpp>
#include <vmp2.hpp>
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
/// <summary>
/// extracts calc_jmp out of vm_entry... you can learn about calc_jmp <a
/// href="https://back.engineering/17/05/2021/#calc_jmp">here</a>.
/// </summary>
/// <param name="vm_entry">pass by reference vm entry...</param>
/// <param name="calc_jmp">zydis_routine_t filled up with native instructions by this routine...</param>
/// <returns>returns truee if no errors happen...</returns>
bool get( zydis_routine_t &vm_entry, zydis_routine_t &calc_jmp );
/// <summary>
/// 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...
/// </summary>
/// <param name="calc_jmp"></param>
/// <returns></returns>
std::optional< vmp2::exec_type_t > get_advancement( const zydis_routine_t &calc_jmp );
} // namespace vm::calc_jmp

@ -5,215 +5,282 @@
#include <stdexcept>
#include <vmutils.hpp>
namespace vm
namespace vm::transform
{
namespace transform
/// <summary>
/// rotate left template function take from IDA SDK...
/// </summary>
/// <typeparam name="T">type of data to rotate left...</typeparam>
/// <param name="value">value to rotate left</param>
/// <param name="count">number of bits to rotate left...</param>
/// <returns>returns the rotated value...</returns>
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;
}
/// <summary>
/// rotate left a one byte value...
/// </summary>
/// <param name="value">byte value</param>
/// <param name="count">number of bits to rotate</param>
/// <returns>return rotated value...</returns>
inline u8 __ROL1__( u8 value, int count )
{
return __ROL__( ( u8 )value, count );
}
/// <summary>
/// rotate left a two byte value...
/// </summary>
/// <param name="value">two byte value to rotate...</param>
/// <param name="count">number of bits to rotate...</param>
/// <returns>return rotated value...</returns>
inline u16 __ROL2__( u16 value, int count )
{
return __ROL__( ( u16 )value, count );
}
/// <summary>
/// rotate left a four byte value...
/// </summary>
/// <param name="value">four byte value to rotate...</param>
/// <param name="count">number of bits to shift...</param>
/// <returns>return rotated value...</returns>
inline u32 __ROL4__( u32 value, int count )
{
return __ROL__( ( u32 )value, count );
}
/// <summary>
/// rotate left an eight byte value...
/// </summary>
/// <param name="value">eight byte value...</param>
/// <param name="count">number of bits to shift...</param>
/// <returns>return rotated value...</returns>
inline u64 __ROL8__( u64 value, int count )
{
return __ROL__( ( u64 )value, count );
}
/// <summary>
/// rotate right a one byte value...
/// </summary>
/// <param name="value">one byte value...</param>
/// <param name="count">number of bits to shift...</param>
/// <returns>return rotated value...</returns>
inline u8 __ROR1__( u8 value, int count )
{
return __ROL__( ( u8 )value, -count );
}
/// <summary>
/// rotate right a two byte value...
/// </summary>
/// <param name="value">two byte value to rotate...</param>
/// <param name="count">number of bits to shift...</param>
/// <returns></returns>
inline u16 __ROR2__( u16 value, int count )
{
return __ROL__( ( u16 )value, -count );
}
/// <summary>
/// rotate right a four byte value...
/// </summary>
/// <param name="value">four byte value to rotate...</param>
/// <param name="count">number of bits to rotate...</param>
/// <returns>return rotated value...</returns>
inline u32 __ROR4__( u32 value, int count )
{
return __ROL__( ( u32 )value, -count );
}
/// <summary>
/// rotate right an eight byte value...
/// </summary>
/// <param name="value">eight byte value</param>
/// <param name="count">number of bits to rotate...</param>
/// <returns>return rotated value...</returns>
inline u64 __ROR8__( u64 value, int count )
{
return __ROL__( ( u64 )value, -count );
}
/// <summary>
/// transform function, such as ADD, SUB, BSWAP... etc...
/// </summary>
/// <typeparam name="T">returns the transform result...</typeparam>
template < typename T > using transform_t = std::function< T( T, T ) >;
/// <summary>
/// type of transformation...
/// </summary>
enum class type
{
generic0,
rolling_key,
generic1,
generic2,
generic3,
update_key
};
/// <summary>
/// map of transform type to zydis decoded instruction of the transform...
/// </summary>
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
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

@ -1,20 +1,47 @@
#pragma once
#include <transform.hpp>
#include <vmp2.hpp>
#include <vmhandlers.hpp>
#include <vmp2.hpp>
namespace vm
{
/// <summary>
/// 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...
/// </summary>
class ctx_t
{
public:
/// <summary>
/// default constructor for vm::ctx_t... all information for a given vm entry must be provided...
/// </summary>
/// <param name="module_base">the linear virtual address of the module base...</param>
/// <param name="image_base">image base from optional nt header... <a
/// href="https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_optional_header64">IMAGE_OPTIONAL_HEADER64</a>...</param>
/// <param name="image_size">image size from optional nt header... <a
/// href="https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_optional_header64">IMAGE_OPTIONAL_HEADER64</a>...</param>
/// <param name="vm_entry_rva">relative virtual address from the module base address to the first push prior to
/// a vm entry...</param>
explicit ctx_t( std::uintptr_t module_base, std::uintptr_t image_base, std::uintptr_t image_size,
std::uintptr_t vm_entry_rva );
/// <summary>
/// init all per-vm entry data such as vm_entry, calc_jmp, and vm handlers...
/// </summary>
/// <returns>returns true if no errors...</returns>
bool init();
const std::uintptr_t module_base, image_base, vm_entry_rva, image_size;
/// <summary>
/// the order in which VIP advances...
/// </summary>
vmp2::exec_type_t exec_type;
zydis_routine_t vm_entry, calc_jmp;
/// <summary>
/// all the vm handlers for the given vm entry...
/// </summary>
std::vector< vm::handler::handler_t > vm_handlers;
};
} // namespace vm

@ -2,37 +2,134 @@
#include <transform.hpp>
#include <vmprofiles.hpp>
namespace vm
namespace vm::handler
{
namespace handler
/// <summary>
/// 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...
/// </summary>
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
/// <summary>
/// imm size in bits, zero if no imm...
/// </summary>
u8 imm_size;
/// <summary>
/// transformations to decrypt imm...
/// </summary>
vm::transform::map_t transforms;
/// <summary>
/// pointer to the profile, nullptr if none...
/// </summary>
vm::handler::profile_t *profile;
/// <summary>
/// native instructions of the vm handler... (calc_jmp/check_vsp is removed from this)...
/// </summary>
zydis_routine_t instrs;
/// <summary>
/// linear virtual address to the vm handler...
/// </summary>
std::uintptr_t address;
};
/// <summary>
/// given a vm handler returns true if the vm handler decrypts an operand...
/// </summary>
/// <param name="vm_handler">const reference to a vm handler...</param>
/// <returns>returns true if the vm handler decrypts an operand, else false...</returns>
bool has_imm( const zydis_routine_t &vm_handler );
/// <summary>
/// gets the imm size of a vm handler...
/// </summary>
/// <param name="vm_handler">const reference to a vm handler...</param>
/// <returns>returns the imm size, otherwise returns an empty optional value...</returns>
std::optional< std::uint8_t > imm_size( const zydis_routine_t &vm_handler );
/// <summary>
/// gets a vm handler, puts all of the native instructions inside of the vm_handler param...
/// </summary>
/// <param name="vm_entry">reference to a zydis_routine_t containing the native instructions of a vm
/// entry...</param> <param name="vm_handler">reference to a zydis_routine_t that will get filled with the
/// native instructions of the vm handler...</param> <param name="handler_addr">linear virtual address to the
/// first instruction of the vm handler...</param> <returns>returns true if the native instructions of the vm
/// handler was extracted...</returns>
bool get( zydis_routine_t &vm_entry, zydis_routine_t &vm_handler, std::uintptr_t handler_addr );
/// <summary>
/// get all 256 vm handlers...
/// </summary>
/// <param name="module_base">linear virtual address of the module base...</param>
/// <param name="image_base">image base from optional nt header... <a
/// href="https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_optional_header64">IMAGE_OPTIONAL_HEADER64</a>...</param>
/// <param name="vm_entry">zydis_routine_t containing the deobfuscated and flattened vm entry native
/// instructions...</param> <param name="vm_handler_table">linear virtual address to the vm handler
/// table...</param> <param name="vm_handlers">vector of handler_t's that will be filled with the vm
/// handlers...</param> <returns>returns true if all vm handlers were extracted, else false...</returns>
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 );
/// <summary>
/// get operand decryption instructions given a vm handler...
/// </summary>
/// <param name="vm_handler">reference to a zydis_routine_t containing the deobfuscated and flattened vm handler
/// native instructions...</param> <param name="transforms">reference to a transform::map_t that will get filled
/// up with the transforms needed to decrypt operands...</param> <returns>returns true if the transformations
/// were extracted successfully</returns>
bool get_operand_transforms( zydis_routine_t &vm_handler, transform::map_t &transforms );
/// <summary>
/// get a vm handler profile given a handler_t...
/// </summary>
/// <param name="vm_handler">reference to a handler_t structure that contains all the information of a given vm
/// handler...</param> <returns>returns a pointer to the vm profile, else a nullptr...</returns>
vm::handler::profile_t *get_profile( handler_t &vm_handler );
/// <summary>
/// get a vm handler profile given the mnemonic of the vm handler...
/// </summary>
/// <param name="mnemonic">mnemonic of the vm handler...</param>
/// <returns>returns a pointer to the profile if the given menmonic is implimented, else a nullptr...</returns>
vm::handler::profile_t *get_profile( vm::handler::mnemonic_t mnemonic );
namespace table
{
/// <summary>
/// get the linear virtual address of the vm handler table give a deobfuscated, flattened, vm entry...
/// </summary>
/// <param name="vm_entry">deobfuscated, flattened, vm entry...</param>
/// <returns>returns the linear virtual address of the vm handler table...</returns>
std::uintptr_t *get( const zydis_routine_t &vm_entry );
/// <summary>
/// get the single native instruction used to decrypt vm handler entries...
/// </summary>
/// <param name="vm_entry">reference to the deobfuscated, flattened, vm entry...</param>
/// <param name="transform_instr"></param>
/// <returns></returns>
bool get_transform( const zydis_routine_t &vm_entry, zydis_decoded_instr_t *transform_instr );
/// <summary>
/// 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...
/// </summary>
/// <param name="transform_instr">reference to the transformation native instruction...</param>
/// <param name="val">value to be encrypted (linear virtual address)</param>
/// <returns>returns the encrypted value...</returns>
std::uint64_t encrypt( zydis_decoded_instr_t &transform_instr, std::uint64_t val );
/// <summary>
/// decrypts a vm handler table entry...
/// </summary>
/// <param name="transform_instr">transformation extracted from vm_entry that decrypts vm handler table
/// entries...</param> <param name="val">encrypted value to be decrypted...</param> <returns>returns the
/// decrypted value...</returns>
std::uint64_t decrypt( zydis_decoded_instr_t &transform_instr, std::uint64_t val );
} // namespace table
} // namespace vm::handler

@ -4,66 +4,87 @@
#include <vmhandlers.hpp>
#include <vmp2.hpp>
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 );
/// <summary>
/// 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
/// </summary>
/// <param name="vm_entry">pass by reference of the specific vm entry you want to get the decryption instructions
/// from...</param> <param name="transform_instrs">pass by reference vector that will be filled with the decryption
/// instructions...</param> <returns>returns true if the decryption instructions are extracted...</returns>
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 );
/// <summary>
/// decrypt virtual instruction operand given the decryption transformations... you can read about these
/// transformations
/// @link https://back.engineering/17/05/2021/#operand-decryption
/// </summary>
/// <param name="transforms">decryption transformations...</param>
/// <param name="operand">encrypted virtual instruction operand...</param>
/// <param name="rolling_key">the decryption key (RBX)...</param>
/// <returns></returns>
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 );
/// <summary>
/// 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
/// </summary>
/// <param name="transforms">transformations to decrypt operand, these transformations are inversed by the
/// function...</param> <param name="operand">operand to be encrypted...</param> <param
/// name="rolling_key">encryption key... (RBX)...</param> <returns></returns>
std::pair< std::uint64_t, std::uint64_t > encrypt_operand( transform::map_t &transforms, std::uint64_t operand,
std::uint64_t rolling_key );
/// <summary>
/// get virt_instr_t filled in with data given a vmp2 trace entry and vm context...
/// </summary>
/// <param name="ctx">current vm context</param>
/// <param name="entry">vmp2 trace entry containing all of the native/virtual register/stack values...</param>
/// <returns>returns a filled in virt_instr_t on success...</returns>
std::optional< virt_instr_t > get( vm::ctx_t &ctx, vmp2::v2::entry_t &entry );
/// <summary>
/// get virt_instr_t filled in with data given a vmp2 trace entry and vm context...
/// </summary>
/// <param name="ctx">current vm context</param>
/// <param name="entry">vmp2 trace entry containing all of the native/virtual register/stack values...</param>
/// <returns>returns a filled in virt_instr_t on success...</returns>
std::optional< virt_instr_t > get( vm::ctx_t &ctx, vmp2::v2::entry_t &entry );
/// <summary>
/// gets the second operand (imm) given vip and vm::ctx_t...
/// </summary>
/// <param name="ctx">vm context</param>
/// <param name="imm_size">immediate value size in bits...</param>
/// <param name="vip">virtual instruction pointer, linear virtual address...</param>
/// <returns>returns immediate value if imm_size is not 0...</returns>
std::optional< std::uint64_t > get_imm( vm::ctx_t &ctx, std::uint8_t imm_size, std::uintptr_t vip );
/// <summary>
/// gets the second operand (imm) given vip and vm::ctx_t...
/// </summary>
/// <param name="ctx">vm context</param>
/// <param name="imm_size">immediate value size in bits...</param>
/// <param name="vip">virtual instruction pointer, linear virtual address...</param>
/// <returns>returns immediate value if imm_size is not 0...</returns>
std::optional< std::uint64_t > get_imm( vm::ctx_t &ctx, std::uint8_t imm_size, std::uintptr_t vip );
/// <summary>
/// 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...
/// </summary>
/// <param name="ctx">vm context</param>
/// <param name="code_block">code block that does not have its jcc_data yet</param>
/// <returns>if last lconstdw is found, return filled in jcc_data structure...</returns>
std::optional< jcc_data > get_jcc_data( vm::ctx_t &ctx, code_block_t &code_block );
/// <summary>
/// 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...
/// </summary>
/// <param name="ctx">vm context</param>
/// <param name="code_block">code block that does not have its jcc_data yet</param>
/// <returns>if last lconstdw is found, return filled in jcc_data structure...</returns>
std::optional< jcc_data > get_jcc_data( vm::ctx_t &ctx, code_block_t &code_block );
/// <summary>
/// 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...
/// </summary>
/// <param name="ctx">vm context</param>
/// <param name="entry">current trace entry for virtual JMP instruction</param>
/// <returns>returns linear virtual address of the next code block...</returns>
std::uintptr_t code_block_addr( const vm::ctx_t &ctx, const vmp2::v2::entry_t &entry );
/// <summary>
/// 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...
/// </summary>
/// <param name="ctx">vm context</param>
/// <param name="entry">current trace entry for virtual JMP instruction</param>
/// <returns>returns linear virtual address of the next code block...</returns>
std::uintptr_t code_block_addr( const vm::ctx_t &ctx, const vmp2::v2::entry_t &entry );
/// <summary>
/// same routine as above except lower_32bits is passed directly and not extracted from the stack...
/// </summary>
/// <param name="ctx">vm context</param>
/// <param name="lower_32bits">lower 32bits of the relative virtual address...</param>
/// <returns>returns full linear virtual address of code block...</returns>
std::uintptr_t code_block_addr( const vm::ctx_t &ctx, const std::uint32_t lower_32bits );
} // namespace instrs
} // namespace vm
/// <summary>
/// same routine as above except lower_32bits is passed directly and not extracted from the stack...
/// </summary>
/// <param name="ctx">vm context</param>
/// <param name="lower_32bits">lower 32bits of the relative virtual address...</param>
/// <returns>returns full linear virtual address of code block...</returns>
std::uintptr_t code_block_addr( const vm::ctx_t &ctx, const std::uint32_t lower_32bits );
} // namespace vm::instrs

@ -1,138 +1,175 @@
#pragma once
#include <transform.hpp>
namespace vm
/// <summary>
/// contains all information pertaining to vm handler identification...
/// </summary>
namespace vm::handler
{
namespace handler
/// <summary>
/// vm handler mnemonic... so you dont need to compare strings!
/// </summary>
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<bool( const zydis_decoded_instr_t &instr )>;
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
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
};
/// <summary>
/// zydis callback lambda used to pattern match native instructions...
/// </summary>
using zydis_callback_t = std::function< bool( const zydis_decoded_instr_t &instr ) >;
/// <summary>
/// how sign extention is handled...
/// </summary>
enum extention_t
{
none,
sign_extend,
zero_extend
};
/// <summary>
/// pre defined vm handler profile containing all compiled time known information about a vm handler...
/// </summary>
struct profile_t
{
/// <summary>
/// name of the vm handler, such as JMP or LCONST...
/// </summary>
const char *name;
/// <summary>
/// the mnemonic of the vm handler... so you dont need to compare strings...
/// </summary>
mnemonic_t mnemonic;
/// <summary>
/// size, in bits, of the operand (imm)... if there is none then this will be zero...
/// </summary>
u8 imm_size;
/// <summary>
/// a vector of signatures used to compare native instructions against zydis aided signatures...
/// </summary>
std::vector< zydis_callback_t > signature;
/// <summary>
/// how sign extention of operands are handled...
/// </summary>
extention_t extention;
};
/// <summary>
/// contains all profiles defined, as well as a vector of all of the defined profiles...
/// </summary>
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;
/// <summary>
/// a vector of pointers to all defined vm handler profiles...
/// </summary>
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

@ -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
// 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

@ -1,279 +1,274 @@
#include <vmprofiler.hpp>
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
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
Loading…
Cancel
Save