From c49210e150dbd8ce902724b4a2f2cfc76aaf250c Mon Sep 17 00:00:00 2001 From: _xeroxz Date: Thu, 3 Jun 2021 01:27:17 -0700 Subject: [PATCH 1/9] added an overloaded vm::handler::get_profile function which takes a mnemonic --- include/vmprofiler.hpp | 1 + src/vmhandler.cpp | 35 +++++++++++++++++++++++------------ 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/include/vmprofiler.hpp b/include/vmprofiler.hpp index 73a4a90..bf8d74b 100644 --- a/include/vmprofiler.hpp +++ b/include/vmprofiler.hpp @@ -116,6 +116,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 { diff --git a/src/vmhandler.cpp b/src/vmhandler.cpp index 0d67547..067187c 100644 --- a/src/vmhandler.cpp +++ b/src/vmhandler.cpp @@ -22,8 +22,8 @@ namespace vm // find LEA RAX, [RDI+0xE0], else determine if the instruction is inside of calc_jmp... auto result = std::find_if( vm_handler.begin(), vm_handler.end(), []( const zydis_instr_t &instr ) -> bool { return instr.instr.mnemonic == ZYDIS_MNEMONIC_LEA && - instr.instr.operands[ 0 ].reg.value == ZYDIS_REGISTER_RAX && - instr.instr.operands[ 1 ].mem.base == ZYDIS_REGISTER_RDI && + instr.instr.operands[ 0 ].reg.value == ZYDIS_REGISTER_RAX && + instr.instr.operands[ 1 ].mem.base == ZYDIS_REGISTER_RDI && instr.instr.operands[ 1 ].mem.disp.value == 0xE0 ? true : calc_jmp_check( instr.addr ); @@ -124,13 +124,13 @@ namespace vm 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 && - ( 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.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; } ); if ( result == vm_handler.end() ) @@ -196,7 +196,7 @@ namespace vm transform_instr = std::find_if( ++transform_instr, vm_handler.end(), []( const zydis_instr_t &instr_data ) -> bool { return vm::transform::valid( instr_data.instr.mnemonic ) && - instr_data.instr.operands[0].actions & ZYDIS_OPERAND_ACTION_WRITE && + instr_data.instr.operands[ 0 ].actions & ZYDIS_OPERAND_ACTION_WRITE && util::reg::compare( instr_data.instr.operands[ 0 ].reg.value, ZYDIS_REGISTER_RAX ); } ); @@ -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 ) From 1a0ba7ef56d06c459c79fe4cb334f5f7a916c558 Mon Sep 17 00:00:00 2001 From: _xeroxz Date: Wed, 2 Jun 2021 19:26:49 -0700 Subject: [PATCH 2/9] added vm::ctx_t which can be passed around >:) --- include/vmprofiler.hpp | 104 ++++++++++++++++++++++++++++++------- src/vmctx.cpp | 20 +++++++ src/vminstrs.cpp | 66 +++++++++++++++++------ vmprofiler.vcxproj | 1 + vmprofiler.vcxproj.filters | 3 ++ 5 files changed, 159 insertions(+), 35 deletions(-) create mode 100644 src/vmctx.cpp diff --git a/include/vmprofiler.hpp b/include/vmprofiler.hpp index bf8d74b..9cbc78e 100644 --- a/include/vmprofiler.hpp +++ b/include/vmprofiler.hpp @@ -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 ); @@ -187,4 +168,89 @@ 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::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 ); + + vmp2::exec_type_t exec_type; + zydis_routine_t vm_entry, calc_jmp; + std::vector< vm::handler::handler_t > vm_handlers; + }; + + 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 code_block_t + { + struct + { + bool has_jcc; + jcc_type type; + std::uint32_t block_rva[ 2 ]; + } jcc; + + std::uint32_t code_block_rva; + std::vector< virt_instr_t > vinstrs; + }; + + // 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 ); + + std::optional< virt_instr_t > get( vm::ctx_t &ctx, vmp2::v2::entry_t &entry ); + + /// + /// gets the second operand (imm) given vip and vm::ctx_t... + /// + /// vm context + /// immediate value size in bits... + /// 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 ); + } // 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 \ No newline at end of file diff --git a/src/vmctx.cpp b/src/vmctx.cpp new file mode 100644 index 0000000..0b7e1ef --- /dev/null +++ b/src/vmctx.cpp @@ -0,0 +1,20 @@ +#include + +namespace vm +{ + ctx_t::ctx_t( std::uintptr_t module_base, std::uintptr_t image_base, std::uint32_t vm_entry_rva ) + { + vm::util::flatten( vm_entry, vm_entry_rva + module_base ); + vm::util::deobfuscate( vm_entry ); + vm::calc_jmp::get( vm_entry, calc_jmp ); + + 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 9d92817..066e159 100644 --- a/src/vminstrs.cpp +++ b/src/vminstrs.cpp @@ -7,12 +7,12 @@ namespace vm std::pair< std::uint64_t, std::uint64_t > decrypt_operand( transform::map_t &transforms, std::uint64_t operand, std::uint64_t rolling_key ) { - const auto& generic_decrypt_0 = transforms[ transform::type::generic0 ]; - const auto& key_decrypt = transforms[ transform::type::rolling_key ]; - const auto& generic_decrypt_1 = transforms[ transform::type::generic1 ]; - const auto& generic_decrypt_2 = transforms[ transform::type::generic2 ]; - const auto& generic_decrypt_3 = transforms[ transform::type::generic3 ]; - const auto& update_key = transforms[ transform::type::update_key ]; + const auto &generic_decrypt_0 = transforms[ transform::type::generic0 ]; + const auto &key_decrypt = transforms[ transform::type::rolling_key ]; + const auto &generic_decrypt_1 = transforms[ transform::type::generic1 ]; + const auto &generic_decrypt_2 = transforms[ transform::type::generic2 ]; + const auto &generic_decrypt_3 = transforms[ transform::type::generic3 ]; + const auto &update_key = transforms[ transform::type::update_key ]; if ( generic_decrypt_0.mnemonic != ZYDIS_MNEMONIC_INVALID ) { @@ -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 ) @@ -70,15 +69,14 @@ namespace vm transform::map_t inverse; inverse_transforms( transforms, inverse ); - 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 = inverse[ transform::type::update_key ]; + const auto &generic_decrypt_0 = inverse[ transform::type::generic0 ]; + const auto &key_decrypt = inverse[ transform::type::rolling_key ]; + const auto &generic_decrypt_1 = inverse[ transform::type::generic1 ]; + const auto &generic_decrypt_2 = inverse[ transform::type::generic2 ]; + const auto &generic_decrypt_3 = inverse[ transform::type::generic3 ]; + const auto &update_key = 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 ) @@ -162,5 +160,41 @@ 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 {}; + + return ctx.exec_type == vmp2::exec_type_t::forward + ? *reinterpret_cast< std::uintptr_t * >( vip + ( imm_size / 8 ) ) + : *reinterpret_cast< std::uintptr_t * >( vip - ( imm_size / 8 ) ); + } + + std::optional< virt_instr_t > get( vm::ctx_t &ctx, vmp2::v2::entry_t &entry ) + { + virt_instr_t result; + const auto &vm_handler = ctx.vm_handlers[ entry.handler_idx ]; + const auto profile = vm_handler.profile; + + result.mnemonic_t = profile ? profile->mnemonic : vm::handler::mnemonic_t::INVALID; + result.opcode = entry.handler_idx; + result.trace_data = entry; + + if ( vm_handler.imm_size ) + { + result.operand.has_imm = true; + const auto imm_val = get_imm( ctx, vm_handler.imm_size, entry.vip ); + + if ( !imm_val.has_value() ) + return {}; + + result.operand.imm.u = imm_val.value(); + } + else + result.operand.has_imm = false; + + return result; + } } // namespace instrs } // namespace vm \ No newline at end of file diff --git a/vmprofiler.vcxproj b/vmprofiler.vcxproj index 760225c..e357f76 100644 --- a/vmprofiler.vcxproj +++ b/vmprofiler.vcxproj @@ -156,6 +156,7 @@ + diff --git a/vmprofiler.vcxproj.filters b/vmprofiler.vcxproj.filters index 99d0148..7c02f19 100644 --- a/vmprofiler.vcxproj.filters +++ b/vmprofiler.vcxproj.filters @@ -234,5 +234,8 @@ Source Files + + Source Files + \ No newline at end of file From ffd45ecb8a6a6a5d066a9b5e96c878ed9e09e243 Mon Sep 17 00:00:00 2001 From: _xeroxz Date: Wed, 2 Jun 2021 20:38:46 -0700 Subject: [PATCH 3/9] in the middle of something, adding branch detection cod --- include/vmprofiler.hpp | 60 +++++++++++++++++++++++++++------- src/vmctx.cpp | 10 ++---- src/vminstrs.cpp | 73 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 124 insertions(+), 19 deletions(-) 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 From cf403125643ac1e23391a36a3d8f484b33546a1f Mon Sep 17 00:00:00 2001 From: _xeroxz Date: Wed, 2 Jun 2021 21:39:42 -0700 Subject: [PATCH 4/9] added get_jcc_data functions v1.0, still testing... --- include/vmprofiler.hpp | 5 ++--- src/vmctx.cpp | 23 +++++++++++++++++++---- src/vminstrs.cpp | 6 +++++- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/include/vmprofiler.hpp b/include/vmprofiler.hpp index bbc0519..e9cd7ee 100644 --- a/include/vmprofiler.hpp +++ b/include/vmprofiler.hpp @@ -175,10 +175,9 @@ namespace vm 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... + bool init(); 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; }; diff --git a/src/vmctx.cpp b/src/vmctx.cpp index 145a1ec..e3742cc 100644 --- a/src/vmctx.cpp +++ b/src/vmctx.cpp @@ -6,11 +6,26 @@ namespace vm 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 ); + } + + bool ctx_t::init() + { + if ( !vm::util::flatten( vm_entry, vm_entry_rva + module_base ) ) + return false; + vm::util::deobfuscate( vm_entry ); - vm::calc_jmp::get( vm_entry, calc_jmp ); + 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; - 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 true; } } // namespace vm \ No newline at end of file diff --git a/src/vminstrs.cpp b/src/vminstrs.cpp index f2d5df2..09e0ac5 100644 --- a/src/vminstrs.cpp +++ b/src/vminstrs.cpp @@ -254,7 +254,11 @@ namespace vm // else there are two branches... else { - jcc.block_rva[ 0 ] = + jcc.block_rva[ 0 ] = code_block_addr( vmctx, result->trace_data.vsp.qword[ 0 ] ^ xor_key ); + jcc.block_rva[ 1 ] = code_block_addr( vmctx, result->trace_data.vsp.qword[ 1 ] ^ xor_key ); + + jcc.has_jcc = true; + jcc.type = jcc_type::branching; } return jcc; From 75ac25445644c4a6dbc1c9995bc3e571a5e1ceca Mon Sep 17 00:00:00 2001 From: _xeroxz Date: Wed, 2 Jun 2021 22:32:19 -0700 Subject: [PATCH 5/9] not sure what i did here, but i did something :) --- include/vmprofiler.hpp | 8 +++++++- src/vminstrs.cpp | 23 +++++++++++++++-------- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/include/vmprofiler.hpp b/include/vmprofiler.hpp index e9cd7ee..15bc224 100644 --- a/include/vmprofiler.hpp +++ b/include/vmprofiler.hpp @@ -218,7 +218,7 @@ namespace vm { bool has_jcc; jcc_type type; - std::uint32_t block_rva[ 2 ]; + std::uintptr_t block_rva[ 2 ]; }; struct code_block_t @@ -237,6 +237,12 @@ namespace vm std::pair< std::uint64_t, std::uint64_t > encrypt_operand( transform::map_t &transforms, std::uint64_t operand, std::uint64_t rolling_key ); + /// + /// get virt_instr_t filled in with data given a vmp2 trace entry and vm context... + /// + /// current vm context + /// vmp2 trace entry containing all of the native/virtual register/stack values... + /// returns a filled in virt_instr_t on success... std::optional< virt_instr_t > get( vm::ctx_t &ctx, vmp2::v2::entry_t &entry ); /// diff --git a/src/vminstrs.cpp b/src/vminstrs.cpp index 09e0ac5..3677db0 100644 --- a/src/vminstrs.cpp +++ b/src/vminstrs.cpp @@ -166,30 +166,36 @@ namespace vm if ( !imm_size ) return {}; - return ctx.exec_type == vmp2::exec_type_t::forward - ? *reinterpret_cast< std::uintptr_t * >( vip + ( imm_size / 8 ) ) - : *reinterpret_cast< std::uintptr_t * >( vip - ( imm_size / 8 ) ); + 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; - const auto &vm_handler = ctx.vm_handlers[ entry.handler_idx ]; + auto &vm_handler = ctx.vm_handlers[ entry.handler_idx ]; const auto profile = vm_handler.profile; - result.mnemonic_t = profile ? profile->mnemonic : vm::handler::mnemonic_t::INVALID; + 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 = imm_val.value(); + result.operand.imm.u = + vm::instrs::decrypt_operand( vm_handler.transforms, imm_val.value(), entry.decrypt_key ).first; } else result.operand.has_imm = false; @@ -266,12 +272,13 @@ namespace vm 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; + 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; + return ( ( lower_32bits + ( ctx.image_base & ~0xFFFFFFFFull ) ) - ctx.image_base ) + ctx.module_base; } } // namespace instrs } // namespace vm \ No newline at end of file From eb64f5d2c28a0296d2061de7fb4eb8eff3201529 Mon Sep 17 00:00:00 2001 From: _xeroxz Date: Thu, 3 Jun 2021 00:25:03 -0700 Subject: [PATCH 6/9] idk what i added but parsing jcc's work now --- include/vmprofiler.hpp | 2 +- src/vminstrs.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/vmprofiler.hpp b/include/vmprofiler.hpp index 15bc224..63a2f1c 100644 --- a/include/vmprofiler.hpp +++ b/include/vmprofiler.hpp @@ -218,7 +218,7 @@ namespace vm { bool has_jcc; jcc_type type; - std::uintptr_t block_rva[ 2 ]; + std::uintptr_t block_addr[ 2 ]; }; struct code_block_t diff --git a/src/vminstrs.cpp b/src/vminstrs.cpp index 3677db0..344b3e2 100644 --- a/src/vminstrs.cpp +++ b/src/vminstrs.cpp @@ -252,7 +252,7 @@ namespace vm // if there is not two branches... if ( result == code_block.vinstrs.rend() ) { - jcc.block_rva[ 0 ] = code_block_addr( vmctx, last_trace ); + jcc.block_addr[ 0 ] = code_block_addr( vmctx, last_trace ); jcc.has_jcc = false; jcc.type = jcc_type::absolute; @@ -260,8 +260,8 @@ namespace vm // else there are two branches... else { - jcc.block_rva[ 0 ] = code_block_addr( vmctx, result->trace_data.vsp.qword[ 0 ] ^ xor_key ); - jcc.block_rva[ 1 ] = code_block_addr( vmctx, result->trace_data.vsp.qword[ 1 ] ^ xor_key ); + 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; From fbe1c20772f7fe5a56733be253634c36738a76e7 Mon Sep 17 00:00:00 2001 From: _xeroxz Date: Thu, 3 Jun 2021 01:03:35 -0700 Subject: [PATCH 7/9] cleaned some code --- include/vmp2.hpp | 2 +- src/vmhandler.cpp | 16 ++++++++-------- src/vminstrs.cpp | 40 +++++++++++++++++----------------------- 3 files changed, 26 insertions(+), 32 deletions(-) diff --git a/include/vmp2.hpp b/include/vmp2.hpp index 0960920..5a25d53 100644 --- a/include/vmp2.hpp +++ b/include/vmp2.hpp @@ -136,5 +136,5 @@ namespace vmp2 u8 raw[ 0x100 ]; } vsp; }; - } + } // namespace v2 } // namespace vmp2 \ No newline at end of file diff --git a/src/vmhandler.cpp b/src/vmhandler.cpp index 067187c..546785a 100644 --- a/src/vmhandler.cpp +++ b/src/vmhandler.cpp @@ -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 && - ( 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 ); + 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; } ); if ( imm_fetch == vm_handler.end() ) diff --git a/src/vminstrs.cpp b/src/vminstrs.cpp index 344b3e2..ace5985 100644 --- a/src/vminstrs.cpp +++ b/src/vminstrs.cpp @@ -124,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 && - instr_data.instr.operands[ 0 ].reg.value == ZYDIS_REGISTER_ESI && - 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; + 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 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() ) @@ -228,14 +224,13 @@ namespace vm // 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 ) + 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 ); - - const auto possible_block_2 = - code_block_addr( vmctx, vinstr.trace_data.vsp.qword[ 1 ] ^ xor_key ); + 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 @@ -253,7 +248,6 @@ namespace vm 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; } From 0edcb36da928d9b43baeb549c395436782e79802 Mon Sep 17 00:00:00 2001 From: _xeroxz Date: Thu, 3 Jun 2021 15:35:02 -0700 Subject: [PATCH 8/9] added vmp2 file format v3 --- include/vmp2.hpp | 8 +++++--- include/vmprofiler.hpp | 45 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/include/vmp2.hpp b/include/vmp2.hpp index 0960920..82d0496 100644 --- a/include/vmp2.hpp +++ b/include/vmp2.hpp @@ -1,5 +1,6 @@ #pragma once #include +#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 vmp2 \ No newline at end of file + } // namespace v2 +} \ No newline at end of file diff --git a/include/vmprofiler.hpp b/include/vmprofiler.hpp index 63a2f1c..b1a5d9a 100644 --- a/include/vmprofiler.hpp +++ b/include/vmprofiler.hpp @@ -181,7 +181,10 @@ namespace vm 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 @@ -224,10 +227,50 @@ namespace vm struct code_block_t { std::uintptr_t vip_begin; - std::vector< virt_instr_t > vinstrs; 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... + 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 ); From 66bccb7596708516235f407e62544c423f5fc636 Mon Sep 17 00:00:00 2001 From: _xeroxz Date: Fri, 4 Jun 2021 23:01:03 +0000 Subject: [PATCH 9/9] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0f8bed8..aa020ed 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,6 @@ -# 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. \ No newline at end of file