diff --git a/include/vmprofiler.hpp b/include/vmprofiler.hpp
index 9cbc78e..bbc0519 100644
--- a/include/vmprofiler.hpp
+++ b/include/vmprofiler.hpp
@@ -172,11 +172,13 @@ namespace vm
class ctx_t
{
public:
- explicit ctx_t( std::uintptr_t module_base, std::uintptr_t image_base, std::uint32_t vm_entry_rva );
- ctx_t( std::vector< vm::handler::handler_t > &vm_handlers, zydis_routine_t &vm_entry, zydis_routine_t &calc_jmp,
- vmp2::exec_type_t exec_type );
+ explicit ctx_t( std::uintptr_t module_base, std::uintptr_t image_base, std::uintptr_t image_size,
+ std::uintptr_t vm_entry_rva );
+
+ // never change...
+ const std::uintptr_t module_base, image_base, vm_entry_rva, image_size;
+ const vmp2::exec_type_t exec_type;
- vmp2::exec_type_t exec_type;
zydis_routine_t vm_entry, calc_jmp;
std::vector< vm::handler::handler_t > vm_handlers;
};
@@ -213,17 +215,18 @@ namespace vm
absolute
};
- struct code_block_t
+ struct jcc_data
{
- struct
- {
- bool has_jcc;
- jcc_type type;
- std::uint32_t block_rva[ 2 ];
- } jcc;
+ bool has_jcc;
+ jcc_type type;
+ std::uint32_t block_rva[ 2 ];
+ };
- std::uint32_t code_block_rva;
+ struct code_block_t
+ {
+ std::uintptr_t vip_begin;
std::vector< virt_instr_t > vinstrs;
+ jcc_data jcc;
};
// decrypt transformations for encrypted virtual instruction rva...
@@ -245,6 +248,39 @@ namespace vm
/// virtual instruction pointer, linear virtual address...
/// returns immediate value if imm_size is not 0...
std::optional< std::uint64_t > get_imm( vm::ctx_t &ctx, std::uint8_t imm_size, std::uintptr_t vip );
+
+ ///
+ /// get jcc data out of a code block... this function will loop over the code block
+ /// and look for the last 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...
+ ///
+ /// vm context
+ /// code block that does not have its jcc_data yet
+ /// if last lconstdw is found, return filled in jcc_data structure...
+ std::optional< jcc_data > get_jcc_data( vm::ctx_t &ctx, code_block_t &code_block );
+
+ ///
+ /// the top of the stack will contain the lower 32bits of the RVA to the virtual instructions
+ /// that will be jumping too... the RVA is image based (not module based, but optional header image
+ /// based)... this means the value ontop of the stack could be "40007fd8" with image base being
+ /// 0x140000000... as you can see the 0x100000000 is missing... the below statement deals with this...
+ ///
+ /// vm context
+ /// current trace entry for virtual JMP instruction
+ /// returns linear virtual address of the next code block...
+ std::uintptr_t code_block_addr( const vm::ctx_t &ctx, const vmp2::v2::entry_t &entry );
+
+ ///
+ /// same routine as above except lower_32bits is passed directly and not extracted from the stack...
+ ///
+ /// vm context
+ /// lower 32bits of the relative virtual address...
+ /// returns full linear virtual address of code block...
+ std::uintptr_t code_block_addr( const vm::ctx_t &ctx, const std::uint32_t lower_32bits );
} // namespace instrs
namespace calc_jmp
diff --git a/src/vmctx.cpp b/src/vmctx.cpp
index 0b7e1ef..145a1ec 100644
--- a/src/vmctx.cpp
+++ b/src/vmctx.cpp
@@ -2,7 +2,9 @@
namespace vm
{
- ctx_t::ctx_t( std::uintptr_t module_base, std::uintptr_t image_base, std::uint32_t vm_entry_rva )
+ 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 )
{
vm::util::flatten( vm_entry, vm_entry_rva + module_base );
vm::util::deobfuscate( vm_entry );
@@ -11,10 +13,4 @@ namespace vm
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 );
}
-
- ctx_t::ctx_t( std::vector< vm::handler::handler_t > &vm_handlers, zydis_routine_t &vm_entry,
- zydis_routine_t &calc_jmp, vmp2::exec_type_t exec_type )
- : vm_handlers( vm_handlers ), vm_entry( vm_entry ), calc_jmp( calc_jmp ), exec_type( exec_type )
- {
- }
} // namespace vm
\ No newline at end of file
diff --git a/src/vminstrs.cpp b/src/vminstrs.cpp
index 066e159..f2d5df2 100644
--- a/src/vminstrs.cpp
+++ b/src/vminstrs.cpp
@@ -196,5 +196,78 @@ namespace vm
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 {
+ auto profile = vm::handler::get_profile( vinstr.mnemonic_t );
+ if ( profile && profile->mnemonic == vm::handler::PUSHVSP )
+ {
+ const auto possible_block_1 =
+ code_block_addr( vmctx, vinstr.trace_data.vsp.qword[ 0 ] ^ xor_key );
+
+ const auto 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_rva[ 0 ] = code_block_addr( vmctx, last_trace );
+
+ jcc.has_jcc = false;
+ jcc.type = jcc_type::absolute;
+ }
+ // else there are two branches...
+ else
+ {
+ jcc.block_rva[ 0 ] =
+ }
+
+ 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
\ No newline at end of file