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 diff --git a/include/vmp2.hpp b/include/vmp2.hpp index 0960920..decdb85 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 +} diff --git a/include/vmprofiler.hpp b/include/vmprofiler.hpp index 73a4a90..b1a5d9a 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 ); @@ -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... + 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 ); + + /// + /// 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 ); + + /// + /// 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 ); + + /// + /// 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 + { + 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..e3742cc --- /dev/null +++ b/src/vmctx.cpp @@ -0,0 +1,31 @@ +#include + +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 \ No newline at end of file diff --git a/src/vmhandler.cpp b/src/vmhandler.cpp index 0d67547..546785a 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() ) @@ -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() ) @@ -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 ) diff --git a/src/vminstrs.cpp b/src/vminstrs.cpp index 9d92817..ace5985 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 ) @@ -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 && - 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() ) @@ -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 \ 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