diff --git a/include/vmlocate.hpp b/include/vmlocate.hpp index d96e484..4f54606 100644 --- a/include/vmlocate.hpp +++ b/include/vmlocate.hpp @@ -1,24 +1,36 @@ #pragma once -#include #include #include -#define ABS_TO_IMG( addr, mod_base, img_base ) ( addr - mod_base ) + img_base #define LEA_R12_SIG "\x4C\x8D\x25\x00\x00\x00\x00" #define LEA_R12_MASK "xxx????" #define PUSH_4B_IMM "\x68\x00\x00\x00\x00" #define PUSH_4B_MASK "x????" -namespace vm::locate -{ - struct vm_handler_table_info_t - { - std::uint32_t rva, lea_r12_rva; - zydis_decoded_instr_t lea_r12_instr; - }; +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(); +} - std::vector< vm_handler_table_info_t > all_handler_tables( std::uintptr_t module_base ); - std::vector< std::pair< std::uint32_t, std::uint32_t > > all_vm_enters( - std::uintptr_t module_base, std::vector< vm_handler_table_info_t > &vm_handler_tables ); -} // namespace vm::locate \ No newline at end of file +struct vm_enter_t { + std::uint32_t rva; + std::uint32_t encrypted_rva; + + struct { + std::uint32_t hndlr_tbl_rva; + zydis_instr_t lea_r12_instr; + } hndlr_tble; +}; + +std::uintptr_t sigscan(void *base, std::uint32_t size, const char *pattern, + const char *mask); + +// this routine will search the entire binary for all vm entries. It will apply +// all known axioms/constants/signatures which are detailed here: +// https://back.engineering/17/05/2021/#vm_entry +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/include/vmutils.hpp b/include/vmutils.hpp index 876c072..d3b783f 100644 --- a/include/vmutils.hpp +++ b/include/vmutils.hpp @@ -1,10 +1,10 @@ #pragma once #include #include +#include #include #include -#include #ifdef _MSC_VER @@ -40,8 +40,8 @@ #elif defined(__NetBSD__) -#include #include +#include #if defined(__BSWAP_RENAME) && !defined(__bswap_32) #define bswap_32(x) bswap32(x) #define bswap_64(x) bswap64(x) @@ -64,87 +64,96 @@ using zydis_register_t = ZydisRegister; using zydis_mnemonic_t = ZydisMnemonic; using zydis_decoded_operand_t = ZydisDecodedOperand; -struct zydis_instr_t -{ - zydis_decoded_instr_t instr; - std::vector< u8 > raw; - std::uintptr_t addr; +struct zydis_instr_t { + zydis_decoded_instr_t instr; + std::vector raw; + std::uintptr_t addr; }; -using zydis_routine_t = std::vector< zydis_instr_t >; +using zydis_routine_t = std::vector; /// /// utils used by the other cpp files... misc things that get used a lot... /// -namespace vm::util -{ - /// - /// utils pertaining to native registers... - /// - namespace reg - { - /// - /// converts say... AL to RAX... - /// - /// a zydis decoded register value... - /// returns the largest width register of the given register... AL gives RAX... - zydis_register_t to64( zydis_register_t reg ); - - /// - /// compares to registers with each other... calls to64 and compares... - /// - /// register a... - /// register b... - /// returns true if register to64(a) == to64(b)... - bool compare( zydis_register_t a, zydis_register_t b ); - } // namespace reg - - /// - /// get the instruction that fetches an operand out of VIP... - /// - /// this is a deobfuscated, flattened, view of any set of native instructions that read an operand out of VIP... can be calc_jmp, vm_entry, or vm handlers... - /// - /// returns true of the fetch operand native instruction is found... - bool get_fetch_operand( const zydis_routine_t &routine, zydis_instr_t &fetch_instr ); - - /// - /// gets the instruction that fetches an operand out of VIP and returns an iterator to it... - /// - /// this is a deobfuscated, flattened, view of any set of native instructions that read an operand out of VIP... can be calc_jmp, vm_entry, or vm handlers... - /// returns the iterator of the native instruction, else an empty std::optional... - std::optional< zydis_routine_t::iterator > get_fetch_operand( zydis_routine_t &routine ); - - /// - /// prints a disassembly view of a routine... - /// - /// reference to a zydis_routine_t to be printed... - void print( zydis_routine_t &routine ); - - /// - /// prints a single disassembly view of an instruction... - /// - /// instruction to print... - void print( const zydis_decoded_instr_t &instr ); - - /// - /// determines if a given decoded native instruction is a JCC... - /// - /// - /// - bool is_jmp( const zydis_decoded_instr_t &instr ); - - /// - /// flatten native instruction stream, takes every JCC (follows the branch)... - /// - /// filled with decoded instructions... - /// linear virtual address to start flattening from... - /// keep JCC's in the flattened instruction stream... - /// returns true if flattened was successful... - bool flatten( zydis_routine_t &routine, std::uintptr_t routine_addr, bool keep_jmps = false ); - - /// - /// deadstore deobfuscation of a flattened routine... - /// - /// reference to a flattened instruction vector... - void deobfuscate( zydis_routine_t &routine ); -} // namespace vm::util \ No newline at end of file +namespace vm::util { +/// +/// utils pertaining to native registers... +/// +namespace reg { +/// +/// converts say... AL to RAX... +/// +/// a zydis decoded register value... +/// returns the largest width register of the given register... AL +/// gives RAX... +zydis_register_t to64(zydis_register_t reg); + +/// +/// compares to registers with each other... calls to64 and compares... +/// +/// register a... +/// register b... +/// returns true if register to64(a) == to64(b)... +bool compare(zydis_register_t a, zydis_register_t b); +} // namespace reg + +/// +/// get the instruction that fetches an operand out of VIP... +/// +/// this is a deobfuscated, flattened, view of any set of +/// native instructions that read an operand out of VIP... can be calc_jmp, +/// vm_entry, or vm handlers... +/// returns true of the fetch operand native instruction is +/// found... +bool get_fetch_operand(const zydis_routine_t &routine, + zydis_instr_t &fetch_instr); + +/// +/// gets the instruction that fetches an operand out of VIP and returns an +/// iterator to it... +/// +/// this is a deobfuscated, flattened, view of any set of +/// native instructions that read an operand out of VIP... can be calc_jmp, +/// vm_entry, or vm handlers... returns the iterator of the +/// native instruction, else an empty std::optional... +std::optional get_fetch_operand( + zydis_routine_t &routine); + +/// +/// prints a disassembly view of a routine... +/// +/// reference to a zydis_routine_t to be +/// printed... +void print(zydis_routine_t &routine); + +/// +/// prints a single disassembly view of an instruction... +/// +/// instruction to print... +void print(const zydis_decoded_instr_t &instr); + +/// +/// determines if a given decoded native instruction is a JCC... +/// +/// +/// +bool is_jmp(const zydis_decoded_instr_t &instr); + +/// +/// flatten native instruction stream, takes every JCC (follows the branch)... +/// +/// filled with decoded instructions... +/// linear virtual address to start flattening +/// from... keep JCC's in the flattened +/// instruction stream... returns true if flattened was +/// successful... +bool flatten(zydis_routine_t &routine, std::uintptr_t routine_addr, + bool keep_jmps = false, std::uint32_t max_instrs = 500, + std::uintptr_t module_base = 0ull); + +/// +/// deadstore deobfuscation of a flattened routine... +/// +/// reference to a flattened instruction vector... +void deobfuscate(zydis_routine_t &routine); +} // namespace vm::util \ No newline at end of file diff --git a/src/vmlocate.cpp b/src/vmlocate.cpp index 0c6d4bf..f7db7d3 100644 --- a/src/vmlocate.cpp +++ b/src/vmlocate.cpp @@ -1,15 +1,211 @@ #include -namespace vm::locate -{ - std::vector< vm_handler_table_info_t > all_handler_tables( std::uintptr_t module_base ) - { - 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< std::pair< std::uint32_t, std::uint32_t > > all_vm_enters( - std::uintptr_t module_base, std::vector< vm_handler_table_info_t > &vm_handler_tables ) - { - 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; } -} // 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 (RCX OR RDX) + 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.operands[0].reg.value == ZYDIS_REGISTER_RDX || + last_instr.instr.operands[0].reg.value == ZYDIS_REGISTER_RCX))) + 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 > 2) 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 rax, 0ull + 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_RAX && + instr.instr.operands[1].type == ZYDIS_OPERAND_TYPE_IMMEDIATE && + instr.instr.operands[1].size == 64 && + instr.instr.operands[1].imm.value.u == 0ull; + })) + 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 rbp, 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[0].reg.value == ZYDIS_REGISTER_RBP && + instr.instr.operands[1].type == ZYDIS_OPERAND_TYPE_REGISTER && + instr.instr.operands[1].reg.value == ZYDIS_REGISTER_RSP; + })) + continue; + + // check for a mov rdi, 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[0].reg.value == ZYDIS_REGISTER_RDI && + instr.instr.operands[1].type == ZYDIS_OPERAND_TYPE_REGISTER && + instr.instr.operands[1].reg.value == ZYDIS_REGISTER_RSP; + })) + continue; + + // check for a mov esi, [rsp+0xA0] + if (!vm::locate::find(rtn, [&](const zydis_instr_t& instr) -> bool { + return instr.instr.mnemonic == ZYDIS_MNEMONIC_MOV && + instr.instr.operands[0].reg.value == ZYDIS_REGISTER_ESI && + 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 == 0xA0; + })) + continue; + + // check for a lea r12, [vm_handler_table] + if (!vm::locate::find(rtn, [&](const zydis_instr_t& instr) -> bool { + return instr.instr.mnemonic == ZYDIS_MNEMONIC_LEA && + instr.instr.operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER && + instr.instr.operands[0].reg.value == ZYDIS_REGISTER_R12 && + instr.instr.raw.sib.base == ZYDIS_REGISTER_NONE; + })) + 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; + + auto hndlr_tbl = std::find_if( + rtn.begin(), rtn.end(), [&](const zydis_instr_t& instr) -> bool { + return instr.instr.mnemonic == ZYDIS_MNEMONIC_LEA && + instr.instr.operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER && + instr.instr.operands[0].reg.value == ZYDIS_REGISTER_R12 && + instr.instr.operands[1].type == ZYDIS_OPERAND_TYPE_MEMORY && + instr.instr.operands[1].mem.base == ZYDIS_REGISTER_NONE; + }); + + vm_enter_t entry{(std::uint32_t)(result - module_base), push_val}; + entry.hndlr_tble.lea_r12_instr = *hndlr_tbl; + entry.hndlr_tble.hndlr_tbl_rva = + (hndlr_tbl->instr.operands[1].mem.disp.value + result + + hndlr_tbl->instr.length) - + module_base; + + entries.push_back(entry); + } while (result); + return entries; +} +} // namespace vm::locate \ No newline at end of file diff --git a/src/vmutils.cpp b/src/vmutils.cpp index b9be0e5..533ea0b 100644 --- a/src/vmutils.cpp +++ b/src/vmutils.cpp @@ -1,238 +1,244 @@ #include -namespace vm::util -{ - namespace reg - { - zydis_register_t to64( zydis_register_t reg ) - { - return ZydisRegisterGetLargestEnclosing( ZYDIS_MACHINE_MODE_LONG_64, reg ); - } - - bool compare( zydis_register_t a, zydis_register_t b ) - { - return to64( a ) == to64( b ); - } - } // namespace reg - - bool get_fetch_operand( const zydis_routine_t &routine, zydis_instr_t &fetch_instr ) - { - const auto result = - std::find_if( routine.begin(), routine.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; - } ); - - if ( result == routine.end() ) - return false; - - fetch_instr = *result; +namespace vm::util { +namespace reg { +zydis_register_t to64(zydis_register_t reg) { + return ZydisRegisterGetLargestEnclosing(ZYDIS_MACHINE_MODE_LONG_64, reg); +} + +bool compare(zydis_register_t a, zydis_register_t b) { + return to64(a) == to64(b); +} +} // namespace reg + +bool get_fetch_operand(const zydis_routine_t &routine, + zydis_instr_t &fetch_instr) { + const auto result = std::find_if( + routine.begin(), routine.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; + }); + + if (result == routine.end()) return false; + + fetch_instr = *result; + return true; +} + +std::optional get_fetch_operand( + zydis_routine_t &routine) { + auto result = std::find_if( + routine.begin(), routine.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; + }); + + if (result == routine.end()) return {}; + + return result; +} + +void print(const zydis_decoded_instr_t &instr) { + char buffer[256]; + ZydisFormatter formatter; + ZydisFormatterInit(&formatter, ZYDIS_FORMATTER_STYLE_INTEL); + ZydisFormatterFormatInstruction(&formatter, &instr, buffer, sizeof(buffer), + 0u); + std::puts(buffer); +} + +void print(zydis_routine_t &routine) { + char buffer[256]; + ZydisFormatter formatter; + ZydisFormatterInit(&formatter, ZYDIS_FORMATTER_STYLE_INTEL); + + for (auto [instr, raw, addr] : routine) { + ZydisFormatterFormatInstruction(&formatter, &instr, buffer, sizeof(buffer), + addr); + std::printf("> %p %s\n", addr, buffer); + } +} + +bool is_jmp(const zydis_decoded_instr_t &instr) { + return instr.mnemonic >= ZYDIS_MNEMONIC_JB && + instr.mnemonic <= ZYDIS_MNEMONIC_JZ; +} + +bool flatten(zydis_routine_t &routine, std::uintptr_t routine_addr, + bool keep_jmps, std::uint32_t max_instrs, + std::uintptr_t module_base) { + ZydisDecoder decoder; + zydis_decoded_instr_t instr; + + ZydisDecoderInit(&decoder, ZYDIS_MACHINE_MODE_LONG_64, + ZYDIS_ADDRESS_WIDTH_64); + + std::uint32_t instr_cnt = 0u; + while (ZYAN_SUCCESS(ZydisDecoderDecodeBuffer( + &decoder, reinterpret_cast(routine_addr), 0x1000, &instr))) { + if (++instr_cnt > max_instrs) return false; + // detect if we have already been at this instruction... if so that means + // there is a loop and we are going to just return... + if (std::find_if(routine.begin(), routine.end(), + [&](const zydis_instr_t &zydis_instr) -> bool { + return zydis_instr.addr == routine_addr; + }) != routine.end()) + return true; + + std::vector raw_instr; + raw_instr.insert(raw_instr.begin(), (u8 *)routine_addr, + (u8 *)routine_addr + instr.length); + + if (is_jmp(instr)) { + if (instr.operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER) { + routine.push_back({instr, raw_instr, routine_addr}); return true; + } + + if (keep_jmps) routine.push_back({instr, raw_instr, routine_addr}); + + ZydisCalcAbsoluteAddress(&instr, &instr.operands[0], routine_addr, + &routine_addr); + } else if (instr.mnemonic == ZYDIS_MNEMONIC_RET) { + routine.push_back({instr, raw_instr, routine_addr}); + return true; + } else { + routine.push_back({instr, raw_instr, routine_addr}); + routine_addr += instr.length; } - std::optional< zydis_routine_t::iterator > get_fetch_operand( zydis_routine_t &routine ) - { - auto result = std::find_if( routine.begin(), routine.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; - } ); - - if ( result == routine.end() ) - return {}; - - return result; - } - - void print( const zydis_decoded_instr_t &instr ) - { - char buffer[ 256 ]; - ZydisFormatter formatter; - ZydisFormatterInit( &formatter, ZYDIS_FORMATTER_STYLE_INTEL ); - ZydisFormatterFormatInstruction( &formatter, &instr, buffer, sizeof( buffer ), 0u ); - std::puts( buffer ); - } - - void print( zydis_routine_t &routine ) - { - char buffer[ 256 ]; - ZydisFormatter formatter; - ZydisFormatterInit( &formatter, ZYDIS_FORMATTER_STYLE_INTEL ); - - for ( auto [ instr, raw, addr ] : routine ) - { - ZydisFormatterFormatInstruction( &formatter, &instr, buffer, sizeof( buffer ), addr ); - std::printf( "> 0x%p %s\n", addr, buffer ); - } - } - - bool is_jmp( const zydis_decoded_instr_t &instr ) - { - return instr.mnemonic >= ZYDIS_MNEMONIC_JB && instr.mnemonic <= ZYDIS_MNEMONIC_JZ; + // optional sanity checking... + if (module_base && !scn::executable(module_base, routine_addr)) + return false; + } + return false; +} + +void deobfuscate(zydis_routine_t &routine) { + static const auto _uses_reg = [](zydis_decoded_operand_t &op, + zydis_register_t reg) -> bool { + switch (op.type) { + case ZYDIS_OPERAND_TYPE_MEMORY: { + return reg::compare(op.mem.base, reg) || + reg::compare(op.mem.index, reg); + } + case ZYDIS_OPERAND_TYPE_REGISTER: { + return reg::compare(op.reg.value, reg); + } + default: + break; } - - bool flatten( zydis_routine_t &routine, std::uintptr_t routine_addr, bool keep_jmps ) - { - ZydisDecoder decoder; - zydis_decoded_instr_t instr; - ZydisDecoderInit( &decoder, ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_ADDRESS_WIDTH_64 ); - - while ( ZYAN_SUCCESS( - ZydisDecoderDecodeBuffer( &decoder, reinterpret_cast< void * >( routine_addr ), 0x1000, &instr ) ) ) - { - // detect if we have already been at this instruction... if so that means there is a loop and we are - // going to just return... - if ( std::find_if( routine.begin(), routine.end(), [ & ]( const zydis_instr_t &zydis_instr ) -> bool { - return zydis_instr.addr == routine_addr; - } ) != routine.end() ) - return true; - - std::vector< u8 > raw_instr; - raw_instr.insert( raw_instr.begin(), ( u8 * )routine_addr, ( u8 * )routine_addr + instr.length ); - - if ( is_jmp( instr ) ) - { - if ( instr.operands[ 0 ].type == ZYDIS_OPERAND_TYPE_REGISTER ) - { - routine.push_back( { instr, raw_instr, routine_addr } ); - return true; - } - - if ( keep_jmps ) - routine.push_back( { instr, raw_instr, routine_addr } ); - - ZydisCalcAbsoluteAddress( &instr, &instr.operands[ 0 ], routine_addr, &routine_addr ); - } - else if ( instr.mnemonic == ZYDIS_MNEMONIC_RET ) - { - routine.push_back( { instr, raw_instr, routine_addr } ); - return true; - } - else - { - routine.push_back( { instr, raw_instr, routine_addr } ); - routine_addr += instr.length; - } + return false; + }; + + static const auto _reads = [](zydis_decoded_instr_t &instr, + zydis_register_t reg) -> bool { + if (instr.operands[0].type == ZYDIS_OPERAND_TYPE_MEMORY && + reg::compare(instr.operands[0].mem.base, reg)) + return true; + + for (auto op_idx = 0u; op_idx < instr.operand_count; ++op_idx) + if (instr.operands[op_idx].actions & ZYDIS_OPERAND_ACTION_READ && + _uses_reg(instr.operands[op_idx], reg)) + return true; + return false; + }; + + static const auto _writes = [](zydis_decoded_instr_t &instr, + zydis_register_t reg) -> bool { + for (auto op_idx = 0u; op_idx < instr.operand_count; ++op_idx) + // if instruction writes to the specific register... + if (instr.operands[op_idx].type == ZYDIS_OPERAND_TYPE_REGISTER && + instr.operands[op_idx].actions & ZYDIS_OPERAND_ACTION_WRITE && + !(instr.operands[op_idx].actions & ZYDIS_OPERAND_ACTION_READ) && + reg::compare(instr.operands[op_idx].reg.value, reg)) + return true; + return false; + }; + + std::uint32_t last_size = 0u; + + do { + last_size = routine.size(); + + for (auto itr = routine.begin(); itr != routine.end(); ++itr) { + // dont remove these... at all... + if (itr->instr.mnemonic == ZYDIS_MNEMONIC_PUSH || + itr->instr.mnemonic == ZYDIS_MNEMONIC_POP || + itr->instr.mnemonic == ZYDIS_MNEMONIC_CALL) + continue; + + static const std::vector blacklist = { + ZYDIS_MNEMONIC_CLC, ZYDIS_MNEMONIC_BT, ZYDIS_MNEMONIC_TEST, + ZYDIS_MNEMONIC_CMP, ZYDIS_MNEMONIC_CMC, ZYDIS_MNEMONIC_STC}; + + if (std::find(blacklist.begin(), blacklist.end(), itr->instr.mnemonic) != + blacklist.end()) { + routine.erase(itr); + break; + } + + zydis_register_t reg = ZYDIS_REGISTER_NONE; + // look for operands with writes to a register... + for (auto op_idx = 0u; op_idx < itr->instr.operand_count; ++op_idx) + if (itr->instr.operands[op_idx].type == ZYDIS_OPERAND_TYPE_REGISTER && + itr->instr.operands[op_idx].actions & ZYDIS_OPERAND_ACTION_WRITE) + reg = reg::to64(itr->instr.operands[0].reg.value); + + // if this current instruction writes to a register, look ahead in the + // instruction stream to see if it gets written too before it gets read... + if (reg != ZYDIS_REGISTER_NONE) { + // find the next place that this register is written too... + auto write_result = std::find_if(itr + 1, routine.end(), + [&](zydis_instr_t &instr) -> bool { + return _writes(instr.instr, reg); + }); + + auto read_result = std::find_if(itr + 1, write_result, + [&](zydis_instr_t &instr) -> bool { + return _reads(instr.instr, reg); + }); + + // if there is neither a read or a write to this register in the + // instruction stream then we are going to be safe and leave the + // instruction in the stream... + if (read_result == routine.end() && write_result == routine.end()) + continue; + + // if there is no read of the register before the next write... and + // there is a known next write, then remove the instruction from the + // stream... + if (read_result == write_result && write_result != routine.end()) { + // if the instruction reads and writes the same register than skip... + if (_reads(read_result->instr, reg) && + _writes(read_result->instr, reg)) + continue; + + routine.erase(itr); + break; } - return false; - } - - void deobfuscate( zydis_routine_t &routine ) - { - static const auto _uses_reg = []( zydis_decoded_operand_t &op, zydis_register_t reg ) -> bool { - switch ( op.type ) - { - case ZYDIS_OPERAND_TYPE_MEMORY: - { - return reg::compare( op.mem.base, reg ) || reg::compare( op.mem.index, reg ); - } - case ZYDIS_OPERAND_TYPE_REGISTER: - { - return reg::compare( op.reg.value, reg ); - } - default: - break; - } - return false; - }; - - static const auto _reads = []( zydis_decoded_instr_t &instr, zydis_register_t reg ) -> bool { - if ( instr.operands[ 0 ].type == ZYDIS_OPERAND_TYPE_MEMORY && - reg::compare( instr.operands[ 0 ].mem.base, reg ) ) - return true; - - for ( auto op_idx = 0u; op_idx < instr.operand_count; ++op_idx ) - if ( instr.operands[ op_idx ].actions & ZYDIS_OPERAND_ACTION_READ && - _uses_reg( instr.operands[ op_idx ], reg ) ) - return true; - return false; - }; - - static const auto _writes = []( zydis_decoded_instr_t &instr, zydis_register_t reg ) -> bool { - for ( auto op_idx = 0u; op_idx < instr.operand_count; ++op_idx ) - // if instruction writes to the specific register... - if ( instr.operands[ op_idx ].type == ZYDIS_OPERAND_TYPE_REGISTER && - instr.operands[ op_idx ].actions & ZYDIS_OPERAND_ACTION_WRITE && - !( instr.operands[ op_idx ].actions & ZYDIS_OPERAND_ACTION_READ ) && - reg::compare( instr.operands[ op_idx ].reg.value, reg ) ) - return true; - return false; - }; - - std::uint32_t last_size = 0u; - - do - { - last_size = routine.size(); - - for ( auto itr = routine.begin(); itr != routine.end(); ++itr ) - { - // dont remove these... at all... - if ( itr->instr.mnemonic == ZYDIS_MNEMONIC_PUSH || itr->instr.mnemonic == ZYDIS_MNEMONIC_POP || - itr->instr.mnemonic == ZYDIS_MNEMONIC_CALL ) - continue; - - static const std::vector< ZydisMnemonic > blacklist = { ZYDIS_MNEMONIC_CLC, ZYDIS_MNEMONIC_BT, - ZYDIS_MNEMONIC_TEST, ZYDIS_MNEMONIC_CMP, - ZYDIS_MNEMONIC_CMC, ZYDIS_MNEMONIC_STC }; - - if ( std::find( blacklist.begin(), blacklist.end(), itr->instr.mnemonic ) != blacklist.end() ) - { - routine.erase( itr ); - break; - } - - zydis_register_t reg = ZYDIS_REGISTER_NONE; - // look for operands with writes to a register... - for ( auto op_idx = 0u; op_idx < itr->instr.operand_count; ++op_idx ) - if ( itr->instr.operands[ op_idx ].type == ZYDIS_OPERAND_TYPE_REGISTER && - itr->instr.operands[ op_idx ].actions & ZYDIS_OPERAND_ACTION_WRITE ) - reg = reg::to64( itr->instr.operands[ 0 ].reg.value ); - - // if this current instruction writes to a register, look ahead in the instruction stream to see - // if it gets written too before it gets read... - if ( reg != ZYDIS_REGISTER_NONE ) - { - // find the next place that this register is written too... - auto write_result = std::find_if( itr + 1, routine.end(), [ & ]( zydis_instr_t &instr ) -> bool { - return _writes( instr.instr, reg ); - } ); - - auto read_result = std::find_if( itr + 1, write_result, [ & ]( zydis_instr_t &instr ) -> bool { - return _reads( instr.instr, reg ); - } ); - - // if there is neither a read or a write to this register in the instruction stream - // then we are going to be safe and leave the instruction in the stream... - if ( read_result == routine.end() && write_result == routine.end() ) - continue; - - // if there is no read of the register before the next write... and there is - // a known next write, then remove the instruction from the stream... - if ( read_result == write_result && write_result != routine.end() ) - { - // if the instruction reads and writes the same register than skip... - if ( _reads( read_result->instr, reg ) && _writes( read_result->instr, reg ) ) - continue; - - routine.erase( itr ); - break; - } - } - } - } while ( last_size != routine.size() ); + } } -} // namespace vm::util \ No newline at end of file + } while (last_size != routine.size()); +} +} // namespace vm::util \ No newline at end of file