diff --git a/.clang-format b/.clang-format index d7eeac7..e90a94d 100644 --- a/.clang-format +++ b/.clang-format @@ -1,4 +1,5 @@ --- BasedOnStyle: Chromium -... \ No newline at end of file +... + diff --git a/CMakeLists.txt b/CMakeLists.txt index 22a69fe..2b50a5d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -78,7 +78,6 @@ list(APPEND vmprofiler_SOURCES "dependencies/vmprofiler/include/vmprofiles.hpp" "dependencies/vmprofiler/include/vmutils.hpp" "dependencies/vmprofiler/include/scn.hpp" - "src/vmctx.cpp" "src/vmlocate.cpp" "include/vmctx.hpp" "include/vmlocate.hpp" diff --git a/include/vmctx.hpp b/include/vmctx.hpp index 880ef41..3e81070 100644 --- a/include/vmctx.hpp +++ b/include/vmctx.hpp @@ -3,40 +3,7 @@ #include namespace vm { -/// -/// vm::ctx_t class is used to auto generate vm_entry, calc_jmp, and other -/// per-vm entry information... creating a vm::ctx_t object can make it easier -/// to pass around information pertaining to a given vm entry... -/// -class ctx_t { - public: - /// - /// default constructor for vm::ctx_t... all information for a given vm entry - /// must be provided... - /// - /// the linear virtual address of the module - /// base... image base from optional nt - /// header... IMAGE_OPTIONAL_HEADER64... - /// image size from optional nt header... IMAGE_OPTIONAL_HEADER64... - /// relative virtual address from the module base - /// address to the first push prior to a vm entry... - explicit ctx_t(std::uintptr_t module_base, std::uintptr_t image_base, - std::uintptr_t image_size, std::uintptr_t vm_entry_rva); - - /// - /// init all per-vm entry data such as vm_entry, calc_jmp, and vm handlers... - /// - /// returns true if no errors... - bool init(); - +struct ctx_t { const std::uintptr_t module_base, image_base, vm_entry_rva, image_size; - - /// - /// the order in which VIP advances... - /// - vmp2::exec_type_t exec_type; - zydis_routine_t vm_entry; }; } // namespace vm \ No newline at end of file diff --git a/include/vmlocate.hpp b/include/vmlocate.hpp index b2844f4..983c7aa 100644 --- a/include/vmlocate.hpp +++ b/include/vmlocate.hpp @@ -5,20 +5,23 @@ #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(); - } +namespace vm::locate { +inline bool find(const zydis_routine_t& rtn, + std::function 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; - }; +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 +std::uintptr_t sigscan(void* base, + std::uint32_t size, + const char* pattern, + const char* mask); + +std::vector 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/src/vmctx.cpp b/src/vmctx.cpp deleted file mode 100644 index ca2bd68..0000000 --- a/src/vmctx.cpp +++ /dev/null @@ -1,19 +0,0 @@ -#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 ); - return true; - } -} // namespace vm \ No newline at end of file diff --git a/src/vmlocate.cpp b/src/vmlocate.cpp index 58ae390..b1e0fd6 100644 --- a/src/vmlocate.cpp +++ b/src/vmlocate.cpp @@ -1,170 +1,164 @@ #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 {}; +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(addr); + } + + return {}; +} + +std::vector get_vm_entries(std::uintptr_t module_base, + std::uint32_t module_size) { + std::uintptr_t result = module_base; + std::vector 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; } - - 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 one legit imm pushes for every vm entry... - > 0x822c : push 0xFFFFFFFF890001FA <--- - > 0x7fc9 : call xxxxx - > 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 + 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 one legit imm pushes for every vm entry... + > 0x822c : push 0xFFFFFFFF890001FA <--- + > 0x7fc9 : call xxxxx + > 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 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