diff --git a/CMakeLists.txt b/CMakeLists.txt index 1d4a1ff..e9f7176 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,9 +72,14 @@ list(APPEND vmprofiler_SOURCES "dependencies/vmprofiler/src/vmprofiles/vmexit.cpp" "dependencies/vmprofiler/src/vmprofiles/write.cpp" "dependencies/vmprofiler/src/vmprofiles/writecr3.cpp" + "dependencies/vmprofiler/src/vmutils.cpp" + "dependencies/vmprofiler/src/scn.cpp" "dependencies/vmprofiler/include/transform.hpp" "dependencies/vmprofiler/include/vmprofiles.hpp" "dependencies/vmprofiler/include/vmutils.hpp" + "dependencies/vmprofiler/include/scn.hpp" + "src/vmlocate.cpp" + "include/vmlocate.hpp" "include/vmprofiler.hpp" ) diff --git a/cmake.toml b/cmake.toml index 7fd609b..c2a1c3c 100644 --- a/cmake.toml +++ b/cmake.toml @@ -7,9 +7,13 @@ compile-features = ["cxx_std_20"] sources = [ "dependencies/vmprofiler/src/vmprofiles/**.cpp", + "dependencies/vmprofiler/src/vmutils.cpp", + "dependencies/vmprofiler/src/scn.cpp", "dependencies/vmprofiler/include/transform.hpp", "dependencies/vmprofiler/include/vmprofiles.hpp", "dependencies/vmprofiler/include/vmutils.hpp", + "dependencies/vmprofiler/include/scn.hpp", + "src/**.cpp", "include/**.hpp" ] diff --git a/include/vmlocate.hpp b/include/vmlocate.hpp new file mode 100644 index 0000000..b2844f4 --- /dev/null +++ b/include/vmlocate.hpp @@ -0,0 +1,24 @@ +#pragma once +#include +#include + +#define PUSH_4B_IMM "\x68\x00\x00\x00\x00" +#define PUSH_4B_MASK "x????" + +namespace vm::locate +{ + inline bool find( const zydis_routine_t &rtn, std::function< bool( const zydis_instr_t & ) > callback ) + { + auto res = std::find_if( rtn.begin(), rtn.end(), callback ); + return res != rtn.end(); + } + + struct vm_enter_t + { + std::uint32_t rva; + std::uint32_t encrypted_rva; + }; + + std::uintptr_t sigscan( void *base, std::uint32_t size, const char *pattern, const char *mask ); + std::vector< vm_enter_t > get_vm_entries( std::uintptr_t module_base, std::uint32_t module_size ); +} // namespace vm::locate \ No newline at end of file diff --git a/include/vmprofiler.hpp b/include/vmprofiler.hpp index 9de4b81..5305960 100644 --- a/include/vmprofiler.hpp +++ b/include/vmprofiler.hpp @@ -1,3 +1,4 @@ #pragma once #include -#include \ No newline at end of file +#include +#include \ No newline at end of file diff --git a/src/vmlocate.cpp b/src/vmlocate.cpp new file mode 100644 index 0000000..36e4c0a --- /dev/null +++ b/src/vmlocate.cpp @@ -0,0 +1,170 @@ +#include + +namespace vm::locate +{ + std::uintptr_t sigscan( void *base, std::uint32_t size, const char *pattern, const char *mask ) + { + static const auto check_mask = [ & ]( const char *base, const char *pattern, const char *mask ) -> bool { + for ( ; *mask; ++base, ++pattern, ++mask ) + if ( *mask == 'x' && *base != *pattern ) + return false; + return true; + }; + + size -= std::strlen( mask ); + for ( auto i = 0; i <= size; ++i ) + { + void *addr = ( void * )&( ( ( char * )base )[ i ] ); + if ( check_mask( ( char * )addr, pattern, mask ) ) + return reinterpret_cast< std::uintptr_t >( addr ); + } + + return {}; + } + + std::vector< vm_enter_t > get_vm_entries( std::uintptr_t module_base, std::uint32_t module_size ) + { + std::uintptr_t result = module_base; + std::vector< vm_enter_t > entries; + + static const auto push_regs = [ & ]( const zydis_routine_t &rtn ) -> bool { + for ( unsigned reg = ZYDIS_REGISTER_RAX; reg < ZYDIS_REGISTER_R15; ++reg ) + { + auto res = std::find_if( rtn.begin(), rtn.end(), [ & ]( const zydis_instr_t &instr ) -> bool { + return instr.instr.mnemonic == ZYDIS_MNEMONIC_PUSH && + instr.instr.operands[ 0 ].type == ZYDIS_OPERAND_TYPE_REGISTER && + instr.instr.operands[ 0 ].reg.value == reg; + } ); + + // skip RSP push... + if ( res == rtn.end() && reg != ZYDIS_REGISTER_RSP ) + return false; + } + return true; + }; + + do + { + result = sigscan( ( void * )++result, module_size - ( result - module_base ), PUSH_4B_IMM, PUSH_4B_MASK ); + + zydis_routine_t rtn; + if ( !scn::executable( module_base, result ) ) + continue; + + if ( !vm::util::flatten( rtn, result, false, 500, module_base ) ) + continue; + + // the last instruction in the stream should be a JMP to a register or a return instruction... + const auto &last_instr = rtn[ rtn.size() - 1 ]; + if ( !( ( last_instr.instr.mnemonic == ZYDIS_MNEMONIC_JMP && + last_instr.instr.operands[ 0 ].type == ZYDIS_OPERAND_TYPE_REGISTER ) || + last_instr.instr.mnemonic == ZYDIS_MNEMONIC_RET ) ) + continue; + + std::uint8_t num_pushs = 0u; + std::for_each( rtn.begin(), rtn.end(), [ & ]( const zydis_instr_t &instr ) { + if ( instr.instr.mnemonic == ZYDIS_MNEMONIC_PUSH && + instr.instr.operands[ 0 ].type == ZYDIS_OPERAND_TYPE_IMMEDIATE ) + ++num_pushs; + } ); + + /* + only two legit imm pushes for every vm entry... + > 0x822c : push 0xFFFFFFFF890001FA <--- + > 0x7fc9 : push 0x45D3BF1F <--- + > 0x48e4 : push r13 + > 0x4690 : push rsi + > 0x4e53 : push r14 + > 0x74fb : push rcx + > 0x607c : push rsp + > 0x4926 : pushfq + > 0x4dc2 : push rbp + > 0x5c8c : push r12 + > 0x52ac : push r10 + > 0x51a5 : push r9 + > 0x5189 : push rdx + > 0x7d5f : push r8 + > 0x4505 : push rdi + > 0x4745 : push r11 + > 0x478b : push rax + > 0x7a53 : push rbx + > 0x500d : push r15 + */ + if ( num_pushs != 1 ) + continue; + + // check for a pushfq... + // > 0x4926 : pushfq <--- + if ( !vm::locate::find( rtn, [ & ]( const zydis_instr_t &instr ) -> bool { + return instr.instr.mnemonic == ZYDIS_MNEMONIC_PUSHFQ; + } ) ) + continue; + + /* + check to see if we push all of these registers... + > 0x48e4 : push r13 + > 0x4690 : push rsi + > 0x4e53 : push r14 + > 0x74fb : push rcx + > 0x607c : push rsp + > 0x4926 : pushfq + > 0x4dc2 : push rbp + > 0x5c8c : push r12 + > 0x52ac : push r10 + > 0x51a5 : push r9 + > 0x5189 : push rdx + > 0x7d5f : push r8 + > 0x4505 : push rdi + > 0x4745 : push r11 + > 0x478b : push rax + > 0x7a53 : push rbx + > 0x500d : push r15 + */ + if ( !push_regs( rtn ) ) + continue; + + // check for a mov r13, rax... + if ( !vm::locate::find( rtn, [ & ]( const zydis_instr_t &instr ) -> bool { + return instr.instr.mnemonic == ZYDIS_MNEMONIC_MOV && + instr.instr.operands[ 0 ].type == ZYDIS_OPERAND_TYPE_REGISTER && + instr.instr.operands[ 0 ].reg.value == ZYDIS_REGISTER_R13 && + instr.instr.operands[ 1 ].type == ZYDIS_OPERAND_TYPE_REGISTER && + instr.instr.operands[ 1 ].reg.value == ZYDIS_REGISTER_RAX; + } ) ) + continue; + + // check for a mov reg, rsp + if ( !vm::locate::find( rtn, [ & ]( const zydis_instr_t &instr ) -> bool { + return instr.instr.mnemonic == ZYDIS_MNEMONIC_MOV && + instr.instr.operands[ 0 ].type == ZYDIS_OPERAND_TYPE_REGISTER && + instr.instr.operands[ 1 ].type == ZYDIS_OPERAND_TYPE_REGISTER && + instr.instr.operands[ 1 ].reg.value == ZYDIS_REGISTER_RSP; + } ) ) + continue; + + // check for a mov reg, [rsp+0x90] + if ( !vm::locate::find( rtn, [ & ]( const zydis_instr_t &instr ) -> bool { + return instr.instr.mnemonic == ZYDIS_MNEMONIC_MOV && + instr.instr.operands[ 0 ].type == ZYDIS_OPERAND_TYPE_REGISTER && + instr.instr.operands[ 1 ].type == ZYDIS_OPERAND_TYPE_MEMORY && + instr.instr.operands[ 1 ].mem.base == ZYDIS_REGISTER_RSP && + instr.instr.operands[ 1 ].mem.disp.value == 0x90; + } ) ) + continue; + + // if code execution gets to here then we can assume this is a legit vm + // entry... its time to build a vm_enter_t... first we check to see if an + // existing entry already exits... + + auto push_val = ( std::uint32_t )rtn[ 0 ].instr.operands[ 0 ].imm.value.u; + if ( std::find_if( entries.begin(), entries.end(), [ & ]( const vm_enter_t &vm_enter ) -> bool { + return vm_enter.encrypted_rva == push_val; + } ) != entries.end() ) + continue; + + vm_enter_t entry{ ( std::uint32_t )( result - module_base ), push_val }; + entries.push_back( entry ); + } while ( result ); + return entries; + } +} // namespace vm::locate \ No newline at end of file