still adding doxygen comments...

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

@ -2,11 +2,22 @@
#include <transform.hpp> #include <transform.hpp>
#include <vmp2.hpp> #include <vmp2.hpp>
namespace vm namespace vm::calc_jmp
{ {
namespace calc_jmp /// <summary>
{ /// extracts calc_jmp out of vm_entry... you can learn about calc_jmp <a
bool get( zydis_routine_t &vm_entry, zydis_routine_t &calc_jmp ); /// href="https://back.engineering/17/05/2021/#calc_jmp">here</a>.
std::optional< vmp2::exec_type_t > get_advancement( const zydis_routine_t &calc_jmp ); /// </summary>
} // namespace calc_jmp /// <param name="vm_entry">pass by reference vm entry...</param>
} // namespace vm /// <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 <stdexcept>
#include <vmutils.hpp> #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... const unsigned int nbits = sizeof( T ) * 8;
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;
}
// taken from ida... if ( count > 0 )
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 )
{ {
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 value;
{ }
return __ROL__( ( u8 )value, -count );
} /// <summary>
inline u16 __ROR2__( u16 value, int count ) /// rotate left a one byte value...
{ /// </summary>
return __ROL__( ( u16 )value, -count ); /// <param name="value">byte value</param>
} /// <param name="count">number of bits to rotate</param>
inline u32 __ROR4__( u32 value, int count ) /// <returns>return rotated value...</returns>
{ inline u8 __ROL1__( u8 value, int count )
return __ROL__( ( u32 )value, -count ); {
} return __ROL__( ( u8 )value, count );
inline u64 __ROR8__( u64 value, int count ) }
{
return __ROL__( ( u64 )value, -count ); /// <summary>
} /// rotate left a two byte value...
/// </summary>
template < typename T > using transform_t = std::function< T( T, T ) >; /// <param name="value">two byte value to rotate...</param>
/// <param name="count">number of bits to rotate...</param>
enum class type /// <returns>return rotated value...</returns>
{ inline u16 __ROL2__( u16 value, int count )
generic0, {
rolling_key, return __ROL__( ( u16 )value, count );
generic1, }
generic2,
generic3, /// <summary>
update_key /// rotate left a four byte value...
}; /// </summary>
/// <param name="value">four byte value to rotate...</param>
using map_t = std::map< transform::type, zydis_decoded_instr_t >; /// <param name="count">number of bits to shift...</param>
/// <returns>return rotated value...</returns>
template < class T > inline u32 __ROL4__( u32 value, int count )
inline const auto _bswap = []( T a, T b ) -> T { {
if constexpr ( std::is_same_v< T, std::uint64_t > ) return __ROL__( ( u32 )value, count );
return _byteswap_uint64( a ); }
if constexpr ( std::is_same_v< T, std::uint32_t > )
return _byteswap_ulong( a ); /// <summary>
if constexpr ( std::is_same_v< T, std::uint16_t > ) /// rotate left an eight byte value...
return _byteswap_ushort( a ); /// </summary>
/// <param name="value">eight byte value...</param>
throw std::invalid_argument( "invalid type size..." ); /// <param name="count">number of bits to shift...</param>
}; /// <returns>return rotated value...</returns>
inline u64 __ROL8__( u64 value, int count )
template < class T > inline const auto _add = []( T a, T b ) -> T { return a + b; }; {
return __ROL__( ( u64 )value, count );
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; }; /// <summary>
/// rotate right a one byte value...
template < class T > inline const auto _neg = []( T a, T b ) -> T { return a * -1; }; /// </summary>
/// <param name="value">one byte value...</param>
template < class T > inline const auto _not = []( T a, T b ) -> T { return ~a; }; /// <param name="count">number of bits to shift...</param>
/// <returns>return rotated value...</returns>
template < class T > inline u8 __ROR1__( u8 value, int count )
inline const auto _ror = []( T a, T b ) -> T { {
if constexpr ( std::is_same_v< T, std::uint64_t > ) return __ROL__( ( u8 )value, -count );
return __ROR8__( a, b ); }
if constexpr ( std::is_same_v< T, std::uint32_t > )
return __ROR4__( a, b ); /// <summary>
if constexpr ( std::is_same_v< T, std::uint16_t > ) /// rotate right a two byte value...
return __ROR2__( a, b ); /// </summary>
if constexpr ( std::is_same_v< T, std::uint8_t > ) /// <param name="value">two byte value to rotate...</param>
return __ROR1__( a, b ); /// <param name="count">number of bits to shift...</param>
/// <returns></returns>
throw std::invalid_argument( "invalid type size..." ); inline u16 __ROR2__( u16 value, int count )
}; {
return __ROL__( ( u16 )value, -count );
template < class T > }
inline const auto _rol = []( T a, T b ) -> T {
if constexpr ( std::is_same_v< T, std::uint64_t > ) /// <summary>
return __ROL8__( a, b ); /// rotate right a four byte value...
if constexpr ( std::is_same_v< T, std::uint32_t > ) /// </summary>
return __ROL4__( a, b ); /// <param name="value">four byte value to rotate...</param>
if constexpr ( std::is_same_v< T, std::uint16_t > ) /// <param name="count">number of bits to rotate...</param>
return __ROL2__( a, b ); /// <returns>return rotated value...</returns>
if constexpr ( std::is_same_v< T, std::uint8_t > ) inline u32 __ROR4__( u32 value, int count )
return __ROL1__( a, b ); {
return __ROL__( ( u32 )value, -count );
throw std::invalid_argument( "invalid type size..." ); }
};
/// <summary>
template < class T > inline const auto _inc = []( T a, T b ) -> T { return a + 1; }; /// rotate right an eight byte value...
/// </summary>
template < class T > inline const auto _dec = []( T a, T b ) -> T { return a - 1; }; /// <param name="value">eight byte value</param>
/// <param name="count">number of bits to rotate...</param>
template < class T > /// <returns>return rotated value...</returns>
inline std::map< zydis_mnemonic_t, transform_t< T > > transforms = { inline u64 __ROR8__( u64 value, int count )
{ 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 > }, return __ROL__( ( u64 )value, -count );
{ ZYDIS_MNEMONIC_ROR, _ror< T > }, { ZYDIS_MNEMONIC_ROL, _rol< T > }, { ZYDIS_MNEMONIC_INC, _inc< T > }, }
{ ZYDIS_MNEMONIC_DEC, _dec< T > } };
/// <summary>
inline std::map< zydis_mnemonic_t, zydis_mnemonic_t > inverse = { /// transform function, such as ADD, SUB, BSWAP... etc...
{ ZYDIS_MNEMONIC_ADD, ZYDIS_MNEMONIC_SUB }, { ZYDIS_MNEMONIC_XOR, ZYDIS_MNEMONIC_XOR }, /// </summary>
{ ZYDIS_MNEMONIC_BSWAP, ZYDIS_MNEMONIC_BSWAP }, { ZYDIS_MNEMONIC_SUB, ZYDIS_MNEMONIC_ADD }, /// <typeparam name="T">returns the transform result...</typeparam>
{ ZYDIS_MNEMONIC_NEG, ZYDIS_MNEMONIC_NEG }, { ZYDIS_MNEMONIC_NOT, ZYDIS_MNEMONIC_NOT }, template < typename T > using transform_t = std::function< T( T, T ) >;
{ ZYDIS_MNEMONIC_ROR, ZYDIS_MNEMONIC_ROL }, { ZYDIS_MNEMONIC_ROL, ZYDIS_MNEMONIC_ROR },
{ ZYDIS_MNEMONIC_INC, ZYDIS_MNEMONIC_DEC }, { ZYDIS_MNEMONIC_DEC, ZYDIS_MNEMONIC_INC } }; /// <summary>
/// type of transformation...
inline bool valid( zydis_mnemonic_t op ) /// </summary>
{ enum class type
return transforms< std::uint64_t >.find( op ) != transforms< std::uint64_t >.end(); {
} generic0,
rolling_key,
inline void inverse_transforms( transform::map_t &transforms, transform::map_t &inverse ) generic1,
{ generic2,
inverse[ transform::type::generic0 ] = transforms[ transform::type::generic0 ]; generic3,
inverse[ transform::type::generic0 ].mnemonic = update_key
transform::inverse[ transforms[ transform::type::generic0 ].mnemonic ]; };
inverse[ transform::type::rolling_key ] = transforms[ transform::type::rolling_key ]; /// <summary>
inverse[ transform::type::rolling_key ].mnemonic = /// map of transform type to zydis decoded instruction of the transform...
transform::inverse[ transforms[ transform::type::rolling_key ].mnemonic ]; /// </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 ]; inline void inverse_transforms( transform::map_t &transforms, transform::map_t &inverse )
inverse[ transform::type::generic1 ].mnemonic = {
transform::inverse[ transforms[ transform::type::generic1 ].mnemonic ]; 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::rolling_key ] = transforms[ transform::type::rolling_key ];
inverse[ transform::type::generic2 ].mnemonic = inverse[ transform::type::rolling_key ].mnemonic =
transform::inverse[ transforms[ transform::type::generic2 ].mnemonic ]; transform::inverse[ transforms[ transform::type::rolling_key ].mnemonic ];
inverse[ transform::type::generic3 ] = transforms[ transform::type::generic3 ]; inverse[ transform::type::generic1 ] = transforms[ transform::type::generic1 ];
inverse[ transform::type::generic3 ].mnemonic = inverse[ transform::type::generic1 ].mnemonic =
transform::inverse[ transforms[ transform::type::generic3 ].mnemonic ]; transform::inverse[ transforms[ transform::type::generic1 ].mnemonic ];
inverse[ transform::type::update_key ] = transforms[ transform::type::update_key ]; inverse[ transform::type::generic2 ] = transforms[ transform::type::generic2 ];
inverse[ transform::type::update_key ].mnemonic = inverse[ transform::type::generic2 ].mnemonic =
transform::inverse[ transforms[ transform::type::update_key ].mnemonic ]; transform::inverse[ transforms[ transform::type::generic2 ].mnemonic ];
}
inline auto inverse_transforms( std::vector< zydis_decoded_instr_t > &instrs ) -> bool inverse[ transform::type::generic3 ] = transforms[ transform::type::generic3 ];
{ inverse[ transform::type::generic3 ].mnemonic =
for ( auto idx = 0u; idx < instrs.size(); idx++ ) transform::inverse[ transforms[ transform::type::generic3 ].mnemonic ];
if ( !( instrs[ idx ].mnemonic = inverse[ instrs[ idx ].mnemonic ] ) )
return false;
std::reverse( instrs.begin(), instrs.end() ); inverse[ transform::type::update_key ] = transforms[ transform::type::update_key ];
return true; 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 inline auto inverse_transforms( std::vector< zydis_decoded_instr_t > &instrs ) -> bool
// the number of bits in bitsize, the transformation is applied, {
// finally the result is converted back to 64bits... zero extended... for ( auto idx = 0u; idx < instrs.size(); idx++ )
inline auto apply( std::uint8_t bitsize, ZydisMnemonic op, std::uint64_t a, std::uint64_t b ) -> std::uint64_t 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 8: case 16:
return transforms< std::uint8_t >[ op ]( a, b ); return transforms< std::uint16_t >[ op ]( a, b );
case 16: case 32:
return transforms< std::uint16_t >[ op ]( a, b ); return transforms< std::uint32_t >[ op ]( a, b );
case 32: case 64:
return transforms< std::uint32_t >[ op ]( a, b ); return transforms< std::uint64_t >[ op ]( a, b );
case 64: default:
return transforms< std::uint64_t >[ op ]( a, b ); throw std::invalid_argument( "invalid bit size..." );
default:
throw std::invalid_argument( "invalid bit size..." );
}
} }
}
inline bool has_imm( const zydis_decoded_instr_t *instr ) inline bool has_imm( const zydis_decoded_instr_t *instr )
{ {
return instr->operand_count > 1 && ( instr->operands[ 1 ].type == ZYDIS_OPERAND_TYPE_IMMEDIATE ); return instr->operand_count > 1 && ( instr->operands[ 1 ].type == ZYDIS_OPERAND_TYPE_IMMEDIATE );
} }
} // namespace transform } // namespace vm::transform
} // namespace vm

@ -1,20 +1,47 @@
#pragma once #pragma once
#include <transform.hpp> #include <transform.hpp>
#include <vmp2.hpp>
#include <vmhandlers.hpp> #include <vmhandlers.hpp>
#include <vmp2.hpp>
namespace vm 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 class ctx_t
{ {
public: 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, explicit ctx_t( std::uintptr_t module_base, std::uintptr_t image_base, std::uintptr_t image_size,
std::uintptr_t vm_entry_rva ); 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(); bool init();
const std::uintptr_t module_base, image_base, vm_entry_rva, image_size; 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; vmp2::exec_type_t exec_type;
zydis_routine_t vm_entry, calc_jmp; 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; std::vector< vm::handler::handler_t > vm_handlers;
}; };
} // namespace vm } // namespace vm

@ -2,37 +2,134 @@
#include <transform.hpp> #include <transform.hpp>
#include <vmprofiles.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 /// <summary>
{ /// imm size in bits, zero if no imm...
u8 imm_size; // size in bits... /// </summary>
vm::transform::map_t transforms; u8 imm_size;
vm::handler::profile_t *profile;
zydis_routine_t instrs; /// <summary>
std::uintptr_t address; /// transformations to decrypt imm...
}; /// </summary>
vm::transform::map_t transforms;
bool has_imm( const zydis_routine_t &vm_handler );
std::optional< std::uint8_t > imm_size( const zydis_routine_t &vm_handler ); /// <summary>
bool get( zydis_routine_t &vm_entry, zydis_routine_t &vm_handler, std::uintptr_t handler_addr ); /// pointer to the profile, nullptr if none...
/// </summary>
bool get_all( std::uintptr_t module_base, std::uintptr_t image_base, zydis_routine_t &vm_entry, vm::handler::profile_t *profile;
std::uintptr_t *vm_handler_table, std::vector< handler_t > &vm_handlers );
/// <summary>
bool get_operand_transforms( zydis_routine_t &vm_handler, transform::map_t &transforms ); /// native instructions of the vm handler... (calc_jmp/check_vsp is removed from this)...
vm::handler::profile_t *get_profile( handler_t &vm_handler ); /// </summary>
vm::handler::profile_t *get_profile( vm::handler::mnemonic_t mnemonic ); zydis_routine_t instrs;
namespace table /// <summary>
{ /// linear virtual address to the vm handler...
std::uintptr_t *get( const zydis_routine_t &vm_entry ); /// </summary>
bool get_transform( const zydis_routine_t &vm_entry, zydis_decoded_instr_t *transform_instr ); std::uintptr_t address;
};
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 ); /// <summary>
} // namespace table /// given a vm handler returns true if the vm handler decrypts an operand...
} // namespace handler /// </summary>
} // namespace vm /// <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 <vmhandlers.hpp>
#include <vmp2.hpp> #include <vmp2.hpp>
namespace vm namespace vm::instrs
{ {
namespace instrs /// <summary>
{ /// gets the native instructions that are used to decrypt the relative virtual address to virtual instructions
// decrypt transformations for encrypted virtual instruction rva... /// located on the stack at RSP+0xA0... you can learn about this @link https://back.engineering/17/05/2021/#vm_entry
bool get_rva_decrypt( const zydis_routine_t &vm_entry, std::vector< zydis_decoded_instr_t > &transform_instrs ); /// </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, /// <summary>
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
/// </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, /// <summary>
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
/// </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> /// <summary>
/// get virt_instr_t filled in with data given a vmp2 trace entry and vm context... /// get virt_instr_t filled in with data given a vmp2 trace entry and vm context...
/// </summary> /// </summary>
/// <param name="ctx">current vm context</param> /// <param name="ctx">current vm context</param>
/// <param name="entry">vmp2 trace entry containing all of the native/virtual register/stack values...</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> /// <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 ); std::optional< virt_instr_t > get( vm::ctx_t &ctx, vmp2::v2::entry_t &entry );
/// <summary> /// <summary>
/// gets the second operand (imm) given vip and vm::ctx_t... /// gets the second operand (imm) given vip and vm::ctx_t...
/// </summary> /// </summary>
/// <param name="ctx">vm context</param> /// <param name="ctx">vm context</param>
/// <param name="imm_size">immediate value size in bits...</param> /// <param name="imm_size">immediate value size in bits...</param>
/// <param name="vip">virtual instruction pointer, linear virtual address...</param> /// <param name="vip">virtual instruction pointer, linear virtual address...</param>
/// <returns>returns immediate value if imm_size is not 0...</returns> /// <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 ); std::optional< std::uint64_t > get_imm( vm::ctx_t &ctx, std::uint8_t imm_size, std::uintptr_t vip );
/// <summary> /// <summary>
/// get jcc data out of a code block... this function will loop over the 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. /// 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 /// 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 /// 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... /// then the virtual jmp instruction only has one dest...
/// </summary> /// </summary>
/// <param name="ctx">vm context</param> /// <param name="ctx">vm context</param>
/// <param name="code_block">code block that does not have its jcc_data yet</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> /// <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 ); std::optional< jcc_data > get_jcc_data( vm::ctx_t &ctx, code_block_t &code_block );
/// <summary> /// <summary>
/// the top of the stack will contain the lower 32bits of the RVA to the virtual instructions /// 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 /// 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 /// 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... /// 0x140000000... as you can see the 0x100000000 is missing... the below statement deals with this...
/// </summary> /// </summary>
/// <param name="ctx">vm context</param> /// <param name="ctx">vm context</param>
/// <param name="entry">current trace entry for virtual JMP instruction</param> /// <param name="entry">current trace entry for virtual JMP instruction</param>
/// <returns>returns linear virtual address of the next code block...</returns> /// <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 ); std::uintptr_t code_block_addr( const vm::ctx_t &ctx, const vmp2::v2::entry_t &entry );
/// <summary> /// <summary>
/// same routine as above except lower_32bits is passed directly and not extracted from the stack... /// same routine as above except lower_32bits is passed directly and not extracted from the stack...
/// </summary> /// </summary>
/// <param name="ctx">vm context</param> /// <param name="ctx">vm context</param>
/// <param name="lower_32bits">lower 32bits of the relative virtual address...</param> /// <param name="lower_32bits">lower 32bits of the relative virtual address...</param>
/// <returns>returns full linear virtual address of code block...</returns> /// <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 ); std::uintptr_t code_block_addr( const vm::ctx_t &ctx, const std::uint32_t lower_32bits );
} // namespace instrs } // namespace vm::instrs
} // namespace vm

@ -1,138 +1,175 @@
#pragma once #pragma once
#include <transform.hpp> #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,
INVALID, PUSHVSP,
LRFLAGS, MULQ,
PUSHVSP, DIVQ,
MULQ, CALL,
DIVQ, JMP,
CALL, VMEXIT,
JMP,
VMEXIT, SREGQ,
SREGDW,
SREGQ, SREGW,
SREGDW,
SREGW, LREGQ,
LREGDW,
LREGQ,
LREGDW, LCONSTQ,
LCONSTBZXW,
LCONSTQ, LCONSTBSXQ,
LCONSTBZXW, LCONSTBSXDW,
LCONSTBSXQ, LCONSTDWSXQ,
LCONSTBSXDW, LCONSTWSXQ,
LCONSTDWSXQ, LCONSTWSXDW,
LCONSTWSXQ, LCONSTDW,
LCONSTWSXDW, LCONSTW,
LCONSTDW,
LCONSTW, READQ,
READDW,
READQ, READW,
READDW,
READW, WRITEQ,
WRITEDW,
WRITEQ, WRITEW,
WRITEDW, WRITEB,
WRITEW,
WRITEB, ADDQ,
ADDDW,
ADDQ, ADDW,
ADDDW,
ADDW, SHLQ,
SHLDW,
SHLQ,
SHLDW, SHRQ,
SHRW,
SHRQ,
SHRW, NANDQ,
NANDDW,
NANDQ, NANDW
NANDDW, };
NANDW
}; /// <summary>
/// zydis callback lambda used to pattern match native instructions...
using zydis_callback_t = std::function<bool( const zydis_decoded_instr_t &instr )>; /// </summary>
using zydis_callback_t = std::function< bool( const zydis_decoded_instr_t &instr ) >;
enum extention_t
{ /// <summary>
none, /// how sign extention is handled...
sign_extend, /// </summary>
zero_extend enum extention_t
}; {
none,
struct profile_t sign_extend,
{ zero_extend
const char *name; };
mnemonic_t mnemonic;
u8 imm_size; /// <summary>
std::vector< zydis_callback_t > signature; /// pre defined vm handler profile containing all compiled time known information about a vm handler...
extention_t extention; /// </summary>
}; struct profile_t
{
namespace profile /// <summary>
{ /// name of the vm handler, such as JMP or LCONST...
extern vm::handler::profile_t sregq; /// </summary>
extern vm::handler::profile_t sregdw; const char *name;
extern vm::handler::profile_t sregw;
/// <summary>
extern vm::handler::profile_t lregq; /// the mnemonic of the vm handler... so you dont need to compare strings...
extern vm::handler::profile_t lregdw; /// </summary>
mnemonic_t mnemonic;
extern vm::handler::profile_t lconstq;
extern vm::handler::profile_t lconstdw; /// <summary>
extern vm::handler::profile_t lconstw; /// size, in bits, of the operand (imm)... if there is none then this will be zero...
/// </summary>
extern vm::handler::profile_t lconstbzxw; u8 imm_size;
extern vm::handler::profile_t lconstbsxdw;
extern vm::handler::profile_t lconstbsxq; /// <summary>
extern vm::handler::profile_t lconstdwsxq; /// a vector of signatures used to compare native instructions against zydis aided signatures...
extern vm::handler::profile_t lconstwsxq; /// </summary>
extern vm::handler::profile_t lconstwsxdw; std::vector< zydis_callback_t > signature;
extern vm::handler::profile_t addq; /// <summary>
extern vm::handler::profile_t adddw; /// how sign extention of operands are handled...
extern vm::handler::profile_t addw; /// </summary>
extention_t extention;
extern vm::handler::profile_t shlq; };
extern vm::handler::profile_t shldw;
/// <summary>
extern vm::handler::profile_t nandq; /// contains all profiles defined, as well as a vector of all of the defined profiles...
extern vm::handler::profile_t nanddw; /// </summary>
extern vm::handler::profile_t nandw; namespace profile
{
extern vm::handler::profile_t writeq; extern vm::handler::profile_t sregq;
extern vm::handler::profile_t writedw; extern vm::handler::profile_t sregdw;
extern vm::handler::profile_t writeb; extern vm::handler::profile_t sregw;
extern vm::handler::profile_t readq; extern vm::handler::profile_t lregq;
extern vm::handler::profile_t readdw; extern vm::handler::profile_t lregdw;
extern vm::handler::profile_t shrq; extern vm::handler::profile_t lconstq;
extern vm::handler::profile_t shrw; extern vm::handler::profile_t lconstdw;
extern vm::handler::profile_t lconstw;
extern vm::handler::profile_t lrflags;
extern vm::handler::profile_t call; extern vm::handler::profile_t lconstbzxw;
extern vm::handler::profile_t pushvsp; extern vm::handler::profile_t lconstbsxdw;
extern vm::handler::profile_t mulq; extern vm::handler::profile_t lconstbsxq;
extern vm::handler::profile_t divq; extern vm::handler::profile_t lconstdwsxq;
extern vm::handler::profile_t jmp; extern vm::handler::profile_t lconstwsxq;
extern vm::handler::profile_t vmexit; extern vm::handler::profile_t lconstwsxdw;
inline std::vector< vm::handler::profile_t * > all = { extern vm::handler::profile_t addq;
&sregq, &sregdw, &sregw, &lregq, &lregdw, &lconstq, &lconstbzxw, &lconstbsxdw, extern vm::handler::profile_t adddw;
&lconstbsxq, &lconstdwsxq, &lconstwsxq, &lconstwsxdw, &lconstdw, &lconstw, &addq, &adddw, extern vm::handler::profile_t addw;
&addw,
extern vm::handler::profile_t shlq;
&shlq, &shldw, &writeq, &writedw, &writeb, &nandq, &nanddw, &nandw, extern vm::handler::profile_t shldw;
&shrq, &shrw, &readq, &readdw, &mulq, &pushvsp, &divq, &jmp, extern vm::handler::profile_t nandq;
&lrflags, &vmexit, &call }; extern vm::handler::profile_t nanddw;
} // namespace profile extern vm::handler::profile_t nandw;
} // namespace handler
} // namespace vm 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 >; 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 );
// converts say... AL to RAX... bool compare( zydis_register_t a, zydis_register_t b );
zydis_register_t to64( zydis_register_t reg ); } // namespace 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 );
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 );
void print( zydis_routine_t &routine ); bool is_jmp( const zydis_decoded_instr_t &instr );
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 );
bool flatten( zydis_routine_t &routine, std::uintptr_t routine_addr, bool keep_jmps = false ); } // namespace vm::util
void deobfuscate( zydis_routine_t &routine );
} // namespace util
} // namespace vm

@ -1,279 +1,274 @@
#include <vmprofiler.hpp> #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, const auto &generic_decrypt_0 = transforms[ transform::type::generic0 ];
std::uint64_t rolling_key ) 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 ]; operand = transform::apply(
const auto &key_decrypt = transforms[ transform::type::rolling_key ]; generic_decrypt_0.operands[ 0 ].size, generic_decrypt_0.mnemonic, operand,
const auto &generic_decrypt_1 = transforms[ transform::type::generic1 ]; // check to see if this instruction has an IMM...
const auto &generic_decrypt_2 = transforms[ transform::type::generic2 ]; transform::has_imm( &generic_decrypt_0 ) ? generic_decrypt_0.operands[ 1 ].imm.value.u : 0 );
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 };
} }
std::pair< std::uint64_t, std::uint64_t > encrypt_operand( transform::map_t &transforms, std::uint64_t operand, // apply transformation with rolling decrypt key...
std::uint64_t rolling_key ) operand = transform::apply( key_decrypt.operands[ 0 ].size, key_decrypt.mnemonic, operand, rolling_key );
// apply three generic transformations...
{ {
transform::map_t inverse; operand = transform::apply(
inverse_transforms( transforms, inverse ); generic_decrypt_1.operands[ 0 ].size, generic_decrypt_1.mnemonic, operand,
const auto apply_key = rolling_key; // check to see if this instruction has an IMM...
transform::has_imm( &generic_decrypt_1 ) ? generic_decrypt_1.operands[ 1 ].imm.value.u : 0 );
const auto &generic_decrypt_0 = inverse[ transform::type::generic0 ];
const auto &key_decrypt = inverse[ transform::type::rolling_key ]; operand = transform::apply(
const auto &generic_decrypt_1 = inverse[ transform::type::generic1 ]; generic_decrypt_2.operands[ 0 ].size, generic_decrypt_2.mnemonic, operand,
const auto &generic_decrypt_2 = inverse[ transform::type::generic2 ]; // check to see if this instruction has an IMM...
const auto &generic_decrypt_3 = inverse[ transform::type::generic3 ]; transform::has_imm( &generic_decrypt_2 ) ? generic_decrypt_2.operands[ 1 ].imm.value.u : 0 );
const auto &update_key = transforms[ transform::type::update_key ];
operand = transform::apply(
auto result = transform::apply( update_key.operands[ 0 ].size, update_key.mnemonic, rolling_key, operand ); generic_decrypt_3.operands[ 0 ].size, generic_decrypt_3.mnemonic, operand,
// check to see if this instruction has an IMM...
// mov rax, al does not clear the top bits... transform::has_imm( &generic_decrypt_3 ) ? generic_decrypt_3.operands[ 1 ].imm.value.u : 0 );
// 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 };
} }
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] case 8:
auto result = rolling_key = ( rolling_key & ~std::numeric_limits< u8 >::max() ) + result;
std::find_if( vm_entry.begin(), vm_entry.end(), []( const zydis_instr_t &instr_data ) -> bool { break;
return instr_data.instr.mnemonic == ZYDIS_MNEMONIC_MOV && case 16:
instr_data.instr.operands[ 0 ].type == ZYDIS_OPERAND_TYPE_REGISTER && rolling_key = ( rolling_key & ~std::numeric_limits< u16 >::max() ) + result;
instr_data.instr.operands[ 0 ].reg.value == ZYDIS_REGISTER_ESI && break;
instr_data.instr.operands[ 1 ].type == ZYDIS_OPERAND_TYPE_MEMORY && default:
instr_data.instr.operands[ 1 ].mem.base == ZYDIS_REGISTER_RSP && rolling_key = result;
instr_data.instr.operands[ 1 ].mem.disp.value == 0xA0; break;
} ); }
if ( result == vm_entry.end() ) return { operand, rolling_key };
return false; }
// find the next three instructions with ESI as std::pair< std::uint64_t, std::uint64_t > encrypt_operand( transform::map_t &transforms, std::uint64_t operand,
// the first operand... and make sure actions & writes... std::uint64_t rolling_key )
for ( auto idx = 0u; idx < 3; ++idx ) {
{ transform::map_t inverse;
result = std::find_if( ++result, vm_entry.end(), []( const zydis_instr_t &instr_data ) -> bool { inverse_transforms( transforms, inverse );
return vm::transform::valid( instr_data.instr.mnemonic ) && const auto apply_key = rolling_key;
instr_data.instr.operands[ 0 ].actions & ZYDIS_OPERAND_ACTION_WRITE &&
instr_data.instr.operands[ 0 ].reg.value == ZYDIS_REGISTER_ESI; 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 ) result = std::find_if( ++result, vm_entry.end(), []( const zydis_instr_t &instr_data ) -> bool {
return {}; 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; if ( result == vm_entry.end() )
ctx.exec_type == vmp2::exec_type_t::forward return false;
? std::memcpy( &result, reinterpret_cast< void * >( vip ), imm_size / 8 )
: std::memcpy( &result, reinterpret_cast< void * >( vip - ( imm_size / 8 ) ), imm_size / 8 );
return result; transform_instrs.push_back( result->instr );
} }
std::optional< virt_instr_t > get( vm::ctx_t &ctx, vmp2::v2::entry_t &entry ) return true;
{ }
virt_instr_t result;
auto &vm_handler = ctx.vm_handlers[ entry.handler_idx ];
const auto profile = vm_handler.profile;
result.mnemonic_t = profile ? profile->mnemonic : vm::handler::INVALID; std::optional< std::uint64_t > get_imm( vm::ctx_t &ctx, std::uint8_t imm_size, std::uintptr_t vip )
result.opcode = entry.handler_idx; {
result.trace_data = entry; if ( !imm_size )
result.operand.has_imm = false; return {};
if ( vm_handler.imm_size ) auto result = 0ull;
{ ctx.exec_type == vmp2::exec_type_t::forward
result.operand.has_imm = true; ? std::memcpy( &result, reinterpret_cast< void * >( vip ), imm_size / 8 )
result.operand.imm.imm_size = vm_handler.imm_size; : std::memcpy( &result, reinterpret_cast< void * >( vip - ( imm_size / 8 ) ), imm_size / 8 );
const auto imm_val = get_imm( ctx, vm_handler.imm_size, entry.vip );
if ( !imm_val.has_value() ) return result;
return {}; }
result.operand.imm.u = std::optional< virt_instr_t > get( vm::ctx_t &ctx, vmp2::v2::entry_t &entry )
vm::instrs::decrypt_operand( vm_handler.transforms, imm_val.value(), entry.decrypt_key ).first; {
} 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... result.operand.has_imm = true;
if ( code_block.vinstrs.back().mnemonic_t == vm::handler::VMEXIT ) 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 {}; return {};
// find the last LCONSTDW... the imm value is the JMP xor decrypt key... result.operand.imm.u =
// we loop backwards here (using rbegin and rend)... vm::instrs::decrypt_operand( vm_handler.transforms, imm_val.value(), entry.decrypt_key ).first;
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;
} }
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() ) - jcc.block_addr[ 0 ] = code_block_addr( vmctx, last_trace );
( ctx.image_base & std::numeric_limits< u32 >::max() ) ) + jcc.has_jcc = false;
ctx.module_base; jcc.type = jcc_type::absolute;
} }
// else there are two branches...
std::uintptr_t code_block_addr( const vm::ctx_t &ctx, const std::uint32_t lower_32bits ) 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