parent
300eed7a62
commit
ade6b0fdd7
@ -1,24 +1,36 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <Zydis/Zydis.h>
|
|
||||||
#include <nt/image.hpp>
|
#include <nt/image.hpp>
|
||||||
#include <vmprofiler.hpp>
|
#include <vmprofiler.hpp>
|
||||||
|
|
||||||
#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_SIG "\x4C\x8D\x25\x00\x00\x00\x00"
|
||||||
#define LEA_R12_MASK "xxx????"
|
#define LEA_R12_MASK "xxx????"
|
||||||
|
|
||||||
#define PUSH_4B_IMM "\x68\x00\x00\x00\x00"
|
#define PUSH_4B_IMM "\x68\x00\x00\x00\x00"
|
||||||
#define PUSH_4B_MASK "x????"
|
#define PUSH_4B_MASK "x????"
|
||||||
|
|
||||||
namespace vm::locate
|
namespace vm::locate {
|
||||||
{
|
inline bool find(const zydis_routine_t &rtn,
|
||||||
struct vm_handler_table_info_t
|
std::function<bool(const zydis_instr_t &)> callback) {
|
||||||
{
|
auto res = std::find_if(rtn.begin(), rtn.end(), callback);
|
||||||
std::uint32_t rva, lea_r12_rva;
|
return res != rtn.end();
|
||||||
zydis_decoded_instr_t lea_r12_instr;
|
}
|
||||||
};
|
|
||||||
|
|
||||||
std::vector< vm_handler_table_info_t > all_handler_tables( std::uintptr_t module_base );
|
struct vm_enter_t {
|
||||||
std::vector< std::pair< std::uint32_t, std::uint32_t > > all_vm_enters(
|
std::uint32_t rva;
|
||||||
std::uintptr_t module_base, std::vector< vm_handler_table_info_t > &vm_handler_tables );
|
std::uint32_t encrypted_rva;
|
||||||
} // namespace vm::locate
|
|
||||||
|
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<vm_enter_t> get_vm_entries(std::uintptr_t module_base,
|
||||||
|
std::uint32_t module_size);
|
||||||
|
} // namespace vm::locate
|
@ -1,15 +1,211 @@
|
|||||||
#include <vmlocate.hpp>
|
#include <vmlocate.hpp>
|
||||||
|
|
||||||
namespace vm::locate
|
namespace vm::locate {
|
||||||
{
|
std::uintptr_t sigscan(void* base, std::uint32_t size, const char* pattern,
|
||||||
std::vector< vm_handler_table_info_t > all_handler_tables( std::uintptr_t module_base )
|
const char* mask) {
|
||||||
{
|
static const auto check_mask = [&](const char* base, const char* pattern,
|
||||||
return {};
|
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< std::pair< std::uint32_t, std::uint32_t > > all_vm_enters(
|
std::vector<vm_enter_t> get_vm_entries(std::uintptr_t module_base,
|
||||||
std::uintptr_t module_base, std::vector< vm_handler_table_info_t > &vm_handler_tables )
|
std::uint32_t module_size) {
|
||||||
{
|
std::uintptr_t result = module_base;
|
||||||
return {};
|
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;
|
||||||
}
|
}
|
||||||
} // namespace vm::locate
|
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
|
@ -1,238 +1,244 @@
|
|||||||
#include <vmprofiler.hpp>
|
#include <vmprofiler.hpp>
|
||||||
|
|
||||||
namespace vm::util
|
namespace vm::util {
|
||||||
{
|
namespace reg {
|
||||||
namespace reg
|
zydis_register_t to64(zydis_register_t reg) {
|
||||||
{
|
return ZydisRegisterGetLargestEnclosing(ZYDIS_MACHINE_MODE_LONG_64, 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);
|
||||||
|
}
|
||||||
bool compare( zydis_register_t a, zydis_register_t b )
|
} // namespace reg
|
||||||
{
|
|
||||||
return to64( a ) == to64( b );
|
bool get_fetch_operand(const zydis_routine_t &routine,
|
||||||
}
|
zydis_instr_t &fetch_instr) {
|
||||||
} // namespace reg
|
const auto result = std::find_if(
|
||||||
|
routine.begin(), routine.end(),
|
||||||
bool get_fetch_operand( const zydis_routine_t &routine, zydis_instr_t &fetch_instr )
|
[](const zydis_instr_t &instr_data) -> bool {
|
||||||
{
|
// mov/movsx/movzx rax/eax/ax/al, [rsi]
|
||||||
const auto result =
|
return instr_data.instr.operand_count > 1 &&
|
||||||
std::find_if( routine.begin(), routine.end(), []( const zydis_instr_t &instr_data ) -> bool {
|
(instr_data.instr.mnemonic == ZYDIS_MNEMONIC_MOV ||
|
||||||
// mov/movsx/movzx rax/eax/ax/al, [rsi]
|
instr_data.instr.mnemonic == ZYDIS_MNEMONIC_MOVSX ||
|
||||||
return instr_data.instr.operand_count > 1 &&
|
instr_data.instr.mnemonic == ZYDIS_MNEMONIC_MOVZX) &&
|
||||||
( instr_data.instr.mnemonic == ZYDIS_MNEMONIC_MOV ||
|
instr_data.instr.operands[0].type ==
|
||||||
instr_data.instr.mnemonic == ZYDIS_MNEMONIC_MOVSX ||
|
ZYDIS_OPERAND_TYPE_REGISTER &&
|
||||||
instr_data.instr.mnemonic == ZYDIS_MNEMONIC_MOVZX ) &&
|
util::reg::to64(instr_data.instr.operands[0].reg.value) ==
|
||||||
instr_data.instr.operands[ 0 ].type == ZYDIS_OPERAND_TYPE_REGISTER &&
|
ZYDIS_REGISTER_RAX &&
|
||||||
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 ].type == ZYDIS_OPERAND_TYPE_MEMORY &&
|
instr_data.instr.operands[1].mem.base == ZYDIS_REGISTER_RSI;
|
||||||
instr_data.instr.operands[ 1 ].mem.base == ZYDIS_REGISTER_RSI;
|
});
|
||||||
} );
|
|
||||||
|
if (result == routine.end()) return false;
|
||||||
if ( result == routine.end() )
|
|
||||||
return false;
|
fetch_instr = *result;
|
||||||
|
return true;
|
||||||
fetch_instr = *result;
|
}
|
||||||
|
|
||||||
|
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("> %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<void *>(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<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;
|
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 )
|
// optional sanity checking...
|
||||||
{
|
if (module_base && !scn::executable(module_base, routine_addr))
|
||||||
auto result = std::find_if( routine.begin(), routine.end(), []( const zydis_instr_t &instr_data ) -> bool {
|
return false;
|
||||||
// mov/movsx/movzx rax/eax/ax/al, [rsi]
|
}
|
||||||
return instr_data.instr.operand_count > 1 &&
|
return false;
|
||||||
( instr_data.instr.mnemonic == ZYDIS_MNEMONIC_MOV ||
|
}
|
||||||
instr_data.instr.mnemonic == ZYDIS_MNEMONIC_MOVSX ||
|
|
||||||
instr_data.instr.mnemonic == ZYDIS_MNEMONIC_MOVZX ) &&
|
void deobfuscate(zydis_routine_t &routine) {
|
||||||
instr_data.instr.operands[ 0 ].type == ZYDIS_OPERAND_TYPE_REGISTER &&
|
static const auto _uses_reg = [](zydis_decoded_operand_t &op,
|
||||||
util::reg::to64( instr_data.instr.operands[ 0 ].reg.value ) == ZYDIS_REGISTER_RAX &&
|
zydis_register_t reg) -> bool {
|
||||||
instr_data.instr.operands[ 1 ].type == ZYDIS_OPERAND_TYPE_MEMORY &&
|
switch (op.type) {
|
||||||
instr_data.instr.operands[ 1 ].mem.base == ZYDIS_REGISTER_RSI;
|
case ZYDIS_OPERAND_TYPE_MEMORY: {
|
||||||
} );
|
return reg::compare(op.mem.base, reg) ||
|
||||||
|
reg::compare(op.mem.index, reg);
|
||||||
if ( result == routine.end() )
|
}
|
||||||
return {};
|
case ZYDIS_OPERAND_TYPE_REGISTER: {
|
||||||
|
return reg::compare(op.reg.value, reg);
|
||||||
return result;
|
}
|
||||||
}
|
default:
|
||||||
|
break;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
bool flatten( zydis_routine_t &routine, std::uintptr_t routine_addr, bool keep_jmps )
|
};
|
||||||
{
|
|
||||||
ZydisDecoder decoder;
|
static const auto _reads = [](zydis_decoded_instr_t &instr,
|
||||||
zydis_decoded_instr_t instr;
|
zydis_register_t reg) -> bool {
|
||||||
ZydisDecoderInit( &decoder, ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_ADDRESS_WIDTH_64 );
|
if (instr.operands[0].type == ZYDIS_OPERAND_TYPE_MEMORY &&
|
||||||
|
reg::compare(instr.operands[0].mem.base, reg))
|
||||||
while ( ZYAN_SUCCESS(
|
return true;
|
||||||
ZydisDecoderDecodeBuffer( &decoder, reinterpret_cast< void * >( routine_addr ), 0x1000, &instr ) ) )
|
|
||||||
{
|
for (auto op_idx = 0u; op_idx < instr.operand_count; ++op_idx)
|
||||||
// detect if we have already been at this instruction... if so that means there is a loop and we are
|
if (instr.operands[op_idx].actions & ZYDIS_OPERAND_ACTION_READ &&
|
||||||
// going to just return...
|
_uses_reg(instr.operands[op_idx], reg))
|
||||||
if ( std::find_if( routine.begin(), routine.end(), [ & ]( const zydis_instr_t &zydis_instr ) -> bool {
|
return true;
|
||||||
return zydis_instr.addr == routine_addr;
|
return false;
|
||||||
} ) != routine.end() )
|
};
|
||||||
return true;
|
|
||||||
|
static const auto _writes = [](zydis_decoded_instr_t &instr,
|
||||||
std::vector< u8 > raw_instr;
|
zydis_register_t reg) -> bool {
|
||||||
raw_instr.insert( raw_instr.begin(), ( u8 * )routine_addr, ( u8 * )routine_addr + instr.length );
|
for (auto op_idx = 0u; op_idx < instr.operand_count; ++op_idx)
|
||||||
|
// if instruction writes to the specific register...
|
||||||
if ( is_jmp( instr ) )
|
if (instr.operands[op_idx].type == ZYDIS_OPERAND_TYPE_REGISTER &&
|
||||||
{
|
instr.operands[op_idx].actions & ZYDIS_OPERAND_ACTION_WRITE &&
|
||||||
if ( instr.operands[ 0 ].type == ZYDIS_OPERAND_TYPE_REGISTER )
|
!(instr.operands[op_idx].actions & ZYDIS_OPERAND_ACTION_READ) &&
|
||||||
{
|
reg::compare(instr.operands[op_idx].reg.value, reg))
|
||||||
routine.push_back( { instr, raw_instr, routine_addr } );
|
return true;
|
||||||
return true;
|
return false;
|
||||||
}
|
};
|
||||||
|
|
||||||
if ( keep_jmps )
|
std::uint32_t last_size = 0u;
|
||||||
routine.push_back( { instr, raw_instr, routine_addr } );
|
|
||||||
|
do {
|
||||||
ZydisCalcAbsoluteAddress( &instr, &instr.operands[ 0 ], routine_addr, &routine_addr );
|
last_size = routine.size();
|
||||||
}
|
|
||||||
else if ( instr.mnemonic == ZYDIS_MNEMONIC_RET )
|
for (auto itr = routine.begin(); itr != routine.end(); ++itr) {
|
||||||
{
|
// dont remove these... at all...
|
||||||
routine.push_back( { instr, raw_instr, routine_addr } );
|
if (itr->instr.mnemonic == ZYDIS_MNEMONIC_PUSH ||
|
||||||
return true;
|
itr->instr.mnemonic == ZYDIS_MNEMONIC_POP ||
|
||||||
}
|
itr->instr.mnemonic == ZYDIS_MNEMONIC_CALL)
|
||||||
else
|
continue;
|
||||||
{
|
|
||||||
routine.push_back( { instr, raw_instr, routine_addr } );
|
static const std::vector<ZydisMnemonic> blacklist = {
|
||||||
routine_addr += instr.length;
|
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
|
} while (last_size != routine.size());
|
||||||
|
}
|
||||||
|
} // namespace vm::util
|
Loading…
Reference in new issue