parent
099a7e9c58
commit
08635457a7
@ -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
|
@ -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
|
@ -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…
Reference in new issue