Merge branch 'dev' into 'master'

tested and seems to be working great. mainly added virtual JCC support

See merge request vmp2/vmprofiler!9
merge-requests/10/head
_xeroxz 4 years ago
commit 5bb8029065

@ -4,6 +4,6 @@
</div>
</div>
# vmprofiler - Library To Profile VMProtect 2 Virtual Machines
# VMProfiler - Library To Profile VMProtect 2 Virtual Machines
vmprofiler is a c++ library which is used to statically analyze VMProtect 2 polymorphic virtual machines. This project is inherited in vmprofiler-qt, vmprofiler-cli, and vmemu. This is the base project for all other VMProtect 2 projects inside of this group on githacks.

@ -1,5 +1,6 @@
#pragma once
#include <transform.hpp>
#define VMP_MAGIC '2PMV'
namespace vmp2
{
@ -13,7 +14,8 @@ namespace vmp2
{
invalid,
v1 = 0x101,
v2 = 0x102
v2 = 0x102,
v3 = 0x103
};
namespace v1
@ -136,5 +138,5 @@ namespace vmp2
u8 raw[ 0x100 ];
} vsp;
};
} // namespace v2
}
} // namespace vmp2

@ -5,25 +5,6 @@
namespace vm
{
namespace calc_jmp
{
bool get( const zydis_routine_t &vm_entry, zydis_routine_t &calc_jmp );
std::optional< vmp2::exec_type_t > get_advancement( const zydis_routine_t &calc_jmp );
} // namespace calc_jmp
namespace instrs
{
// decrypt transformations for encrypted virtual instruction rva...
bool get_rva_decrypt( const zydis_routine_t &vm_entry, std::vector< zydis_decoded_instr_t > &transform_instrs );
std::pair< std::uint64_t, std::uint64_t > decrypt_operand( transform::map_t &transforms, std::uint64_t operand,
std::uint64_t rolling_key );
std::pair< std::uint64_t, std::uint64_t > encrypt_operand( transform::map_t &transforms, std::uint64_t operand,
std::uint64_t rolling_key );
} // namespace instrs
namespace handler
{
using instr_callback_t = bool ( * )( const zydis_decoded_instr_t &instr );
@ -116,6 +97,7 @@ namespace vm
// can be used on calc_jmp...
bool get_operand_transforms( const zydis_routine_t &vm_handler, transform::map_t &transforms );
vm::handler::profile_t *get_profile( handler_t &vm_handler );
vm::handler::profile_t *get_profile( vm::handler::mnemonic_t mnemonic );
namespace table
{
@ -186,4 +168,173 @@ namespace vm
&lrflags, &vmexit, &call };
} // namespace profile
} // namespace handler
class ctx_t
{
public:
explicit ctx_t( std::uintptr_t module_base, std::uintptr_t image_base, std::uintptr_t image_size,
std::uintptr_t vm_entry_rva );
bool init();
const std::uintptr_t module_base, image_base, vm_entry_rva, image_size;
vmp2::exec_type_t exec_type;
zydis_routine_t vm_entry, calc_jmp;
std::vector< vm::handler::handler_t > vm_handlers;
};
} // namespace vm
namespace vm
{
namespace instrs
{
struct virt_instr_t
{
vm::handler::mnemonic_t mnemonic_t;
std::uint8_t opcode; // aka vm handler idx...
// can be used to look at values on the stack...
vmp2::v2::entry_t trace_data;
struct
{
bool has_imm;
struct
{
std::uint8_t imm_size; // size in bits...
union
{
std::int64_t s;
std::uint64_t u;
};
} imm;
} operand;
};
enum class jcc_type
{
none,
branching,
absolute
};
struct jcc_data
{
bool has_jcc;
jcc_type type;
std::uintptr_t block_addr[ 2 ];
};
struct code_block_t
{
std::uintptr_t vip_begin;
jcc_data jcc;
std::vector< virt_instr_t > vinstrs;
};
} // namespace instrs
} // namespace vm
namespace vmp2
{
namespace v3
{
struct file_header
{
u32 magic; // VMP2
u64 epoch_time;
version_t version;
u64 module_base;
u64 image_base;
u64 vm_entry_rva;
u32 module_offset;
u32 module_size;
u32 code_block_offset;
u32 code_block_count;
};
struct code_block_t
{
std::uintptr_t vip_begin;
std::uintptr_t next_block_offset;
vm::instrs::jcc_data jcc;
// serialized from std::vector<virt_instr_t>...
std::uint32_t vinstr_count;
vm::instrs::virt_instr_t vinstr[];
};
} // namespace v3
} // namespace vmp2
namespace vm
{
namespace instrs
{
// decrypt transformations for encrypted virtual instruction rva...
bool get_rva_decrypt( const zydis_routine_t &vm_entry, std::vector< zydis_decoded_instr_t > &transform_instrs );
std::pair< std::uint64_t, std::uint64_t > decrypt_operand( transform::map_t &transforms, std::uint64_t operand,
std::uint64_t rolling_key );
std::pair< std::uint64_t, std::uint64_t > encrypt_operand( transform::map_t &transforms, std::uint64_t operand,
std::uint64_t rolling_key );
/// <summary>
/// get virt_instr_t filled in with data given a vmp2 trace entry and vm context...
/// </summary>
/// <param name="ctx">current vm context</param>
/// <param name="entry">vmp2 trace entry containing all of the native/virtual register/stack values...</param>
/// <returns>returns a filled in virt_instr_t on success...</returns>
std::optional< virt_instr_t > get( vm::ctx_t &ctx, vmp2::v2::entry_t &entry );
/// <summary>
/// gets the second operand (imm) given vip and vm::ctx_t...
/// </summary>
/// <param name="ctx">vm context</param>
/// <param name="imm_size">immediate value size in bits...</param>
/// <param name="vip">virtual instruction pointer, linear virtual address...</param>
/// <returns>returns immediate value if imm_size is not 0...</returns>
std::optional< std::uint64_t > get_imm( vm::ctx_t &ctx, std::uint8_t imm_size, std::uintptr_t vip );
/// <summary>
/// get jcc data out of a code block... this function will loop over the code block
/// and look for the last two NANDW in the virtual instructions, then it will look
/// for the last LCONSTW which is the xor key...
///
/// it will then loop and look for all PUSHVSP's, checking each to see if the stack
/// contains two encrypted rva's to each branch.. if there is not two encrypted rva's
/// then the virtual jmp instruction only has one dest...
/// </summary>
/// <param name="ctx">vm context</param>
/// <param name="code_block">code block that does not have its jcc_data yet</param>
/// <returns>if last lconstdw is found, return filled in jcc_data structure...</returns>
std::optional< jcc_data > get_jcc_data( vm::ctx_t &ctx, code_block_t &code_block );
/// <summary>
/// the top of the stack will contain the lower 32bits of the RVA to the virtual instructions
/// that will be jumping too... the RVA is image based (not module based, but optional header image
/// based)... this means the value ontop of the stack could be "40007fd8" with image base being
/// 0x140000000... as you can see the 0x100000000 is missing... the below statement deals with this...
/// </summary>
/// <param name="ctx">vm context</param>
/// <param name="entry">current trace entry for virtual JMP instruction</param>
/// <returns>returns linear virtual address of the next code block...</returns>
std::uintptr_t code_block_addr( const vm::ctx_t &ctx, const vmp2::v2::entry_t &entry );
/// <summary>
/// same routine as above except lower_32bits is passed directly and not extracted from the stack...
/// </summary>
/// <param name="ctx">vm context</param>
/// <param name="lower_32bits">lower 32bits of the relative virtual address...</param>
/// <returns>returns full linear virtual address of code block...</returns>
std::uintptr_t code_block_addr( const vm::ctx_t &ctx, const std::uint32_t lower_32bits );
} // namespace instrs
namespace calc_jmp
{
bool get( const zydis_routine_t &vm_entry, zydis_routine_t &calc_jmp );
std::optional< vmp2::exec_type_t > get_advancement( const zydis_routine_t &calc_jmp );
} // namespace calc_jmp
} // namespace vm

@ -0,0 +1,31 @@
#include <vmprofiler.hpp>
namespace vm
{
ctx_t::ctx_t( std::uintptr_t module_base, std::uintptr_t image_base, std::uintptr_t image_size,
std::uintptr_t vm_entry_rva )
: module_base( module_base ), image_base( image_base ), image_size( image_size ), vm_entry_rva( vm_entry_rva )
{
}
bool ctx_t::init()
{
if ( !vm::util::flatten( vm_entry, vm_entry_rva + module_base ) )
return false;
vm::util::deobfuscate( vm_entry );
if ( !vm::calc_jmp::get( vm_entry, calc_jmp ) )
return false;
if ( auto vm_handler_table = vm::handler::table::get( vm_entry );
!vm::handler::get_all( module_base, image_base, vm_entry, vm_handler_table, vm_handlers ) )
return false;
if ( auto advancement = vm::calc_jmp::get_advancement( calc_jmp ); advancement.has_value() )
exec_type = advancement.value();
else
return false;
return true;
}
} // namespace vm

@ -144,14 +144,14 @@ namespace vm
auto imm_fetch =
std::find_if( vm_handler.begin(), vm_handler.end(), []( const zydis_instr_t &instr_data ) -> bool {
// mov/movsx/movzx rax/eax/ax/al, [rsi]
return ( instr_data.instr.operand_count > 1 &&
return instr_data.instr.operand_count > 1 &&
( instr_data.instr.mnemonic == ZYDIS_MNEMONIC_MOV ||
instr_data.instr.mnemonic == ZYDIS_MNEMONIC_MOVSX ||
instr_data.instr.mnemonic == ZYDIS_MNEMONIC_MOVZX ) &&
instr_data.instr.operands[ 0 ].type == ZYDIS_OPERAND_TYPE_REGISTER &&
util::reg::to64( instr_data.instr.operands[ 0 ].reg.value ) == ZYDIS_REGISTER_RAX &&
instr_data.instr.operands[ 1 ].type == ZYDIS_OPERAND_TYPE_MEMORY &&
instr_data.instr.operands[ 1 ].mem.base == ZYDIS_REGISTER_RSI );
instr_data.instr.operands[ 1 ].mem.base == ZYDIS_REGISTER_RSI;
} );
if ( imm_fetch == vm_handler.end() )
@ -219,8 +219,10 @@ namespace vm
for ( auto &instr : vprofile->signature )
{
contains = std::find_if(contains, vm_handler->instrs.end(),
[ & ]( zydis_instr_t &instr_data ) -> bool { return instr( instr_data.instr ); } );
contains =
std::find_if( contains, vm_handler->instrs.end(), [ & ]( zydis_instr_t &instr_data ) -> bool {
return instr( instr_data.instr );
} );
if ( contains == vm_handler->instrs.end() )
return false;
@ -236,6 +238,15 @@ namespace vm
return nullptr;
}
vm::handler::profile_t *get_profile( vm::handler::mnemonic_t mnemonic )
{
auto result = std::find_if(
vm::handler::profile::all.begin(), vm::handler::profile::all.end(),
[ & ]( vm::handler::profile_t *profile ) -> bool { return profile->mnemonic == mnemonic; } );
return result != vm::handler::profile::all.end() ? *result : nullptr;
}
namespace table
{
std::uintptr_t *get( const zydis_routine_t &vm_entry )

@ -44,8 +44,7 @@ namespace vm
}
// update rolling key...
auto result =
transform::apply( update_key.operands[ 0 ].size, update_key.mnemonic, rolling_key, operand );
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 )
@ -77,8 +76,7 @@ namespace vm
const auto &generic_decrypt_3 = inverse[ transform::type::generic3 ];
const auto &update_key = inverse[ transform::type::update_key ];
auto result =
transform::apply( update_key.operands[ 0 ].size, update_key.mnemonic, rolling_key, operand );
auto result = transform::apply( update_key.operands[ 0 ].size, update_key.mnemonic, rolling_key, operand );
// make sure we update the rolling decryption key correctly...
switch ( update_key.operands[ 0 ].size )
@ -126,32 +124,28 @@ namespace vm
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 {
if ( instr_data.instr.mnemonic == ZYDIS_MNEMONIC_MOV && instr_data.instr.operand_count == 2 &&
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.has_displacement &&
instr_data.instr.operands[ 1 ].mem.disp.value == 0xA0 )
return true;
return false;
instr_data.instr.operands[ 1 ].mem.disp.value == 0xA0;
} );
if ( result == vm_entry.end() )
return false;
//
// find the next three instruction with ESI as the dest...
//
// find the next three instructions with ESI as
// the first operand... and make sure actions & writes...
for ( auto idx = 0u; idx < 3; ++idx )
{
result = std::find_if( ++result, vm_entry.end(), []( const zydis_instr_t &instr_data ) -> bool {
return instr_data.instr.operands[ 0 ].reg.value == ZYDIS_REGISTER_ESI;
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;
} );
if ( result == vm_entry.end() )
@ -162,5 +156,123 @@ namespace vm
return true;
}
std::optional< std::uint64_t > get_imm( vm::ctx_t &ctx, std::uint8_t imm_size, std::uintptr_t vip )
{
if ( !imm_size )
return {};
std::uint64_t result = 0u;
if ( ctx.exec_type == vmp2::exec_type_t::forward )
std::memcpy( &result, reinterpret_cast< void * >( vip ), imm_size / 8 );
else // else the second operand is below vip...
std::memcpy( &result, reinterpret_cast< void * >( vip - ( imm_size / 8 ) ), imm_size / 8 );
return result;
}
std::optional< virt_instr_t > get( vm::ctx_t &ctx, vmp2::v2::entry_t &entry )
{
virt_instr_t result;
auto &vm_handler = ctx.vm_handlers[ entry.handler_idx ];
const auto profile = vm_handler.profile;
result.mnemonic_t = profile ? profile->mnemonic : vm::handler::INVALID;
result.opcode = entry.handler_idx;
result.trace_data = entry;
if ( vm_handler.imm_size )
{
result.operand.has_imm = true;
result.operand.imm.imm_size = vm_handler.imm_size;
const auto imm_val = get_imm( ctx, vm_handler.imm_size, entry.vip );
if ( !imm_val.has_value() )
return {};
result.operand.imm.u =
vm::instrs::decrypt_operand( vm_handler.transforms, imm_val.value(), entry.decrypt_key ).first;
}
else
result.operand.has_imm = false;
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() )
{
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 ( ( entry.vsp.qword[ 0 ] + ( ctx.image_base & ~0xFFFFFFFFull ) ) - ctx.image_base ) +
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 & ~0xFFFFFFFFull ) ) - ctx.image_base ) + ctx.module_base;
}
} // namespace instrs
} // namespace vm

@ -156,6 +156,7 @@
</ItemGroup>
<ItemGroup>
<ClCompile Include="src\calc_jmp.cpp" />
<ClCompile Include="src\vmctx.cpp" />
<ClCompile Include="src\vmhandler.cpp" />
<ClCompile Include="src\vminstrs.cpp" />
<ClCompile Include="src\vmprofiles\add.cpp" />

@ -234,5 +234,8 @@
<ClCompile Include="src\vmutils.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\vmctx.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
</Project>
Loading…
Cancel
Save