|
|
|
#include <vmprofiler.hpp>
|
|
|
|
|
|
|
|
namespace vm
|
|
|
|
{
|
|
|
|
namespace handler
|
|
|
|
{
|
|
|
|
bool get(zydis_routine_t& calc_jmp, zydis_routine_t& vm_handler, std::uintptr_t handler_addr)
|
|
|
|
{
|
|
|
|
if (!vm::util::flatten(vm_handler, handler_addr))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
vm::util::deobfuscate(vm_handler);
|
|
|
|
|
|
|
|
static const auto calc_jmp_check =
|
|
|
|
[&](std::uintptr_t addr) -> bool
|
|
|
|
{
|
|
|
|
for (const auto& [instr, instr_raw, instr_addr] : calc_jmp)
|
|
|
|
if (instr_addr == addr)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
|
|
|
|
auto result = std::find_if(
|
|
|
|
vm_handler.begin(), vm_handler.end(),
|
|
|
|
[](const zydis_instr_t& instr) -> bool
|
|
|
|
{
|
|
|
|
if (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[1].mem.disp.value == 0xE0)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return calc_jmp_check(instr.addr);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
// remove calc_jmp from the vm handler vector...
|
|
|
|
if (result != vm_handler.end())
|
|
|
|
vm_handler.erase(result, vm_handler.end());
|
|
|
|
else // locate the last mov al, [rsi],
|
|
|
|
// then remove all instructions after that...
|
|
|
|
{
|
|
|
|
zydis_routine_t::iterator last = vm_handler.end();
|
|
|
|
result = vm_handler.begin();
|
|
|
|
|
|
|
|
while (result != vm_handler.end())
|
|
|
|
{
|
|
|
|
result = std::find_if(
|
|
|
|
++result, vm_handler.end(),
|
|
|
|
[](const zydis_instr_t& instr_data) -> bool
|
|
|
|
{
|
|
|
|
// mov/movsx/movzx rax/eax/ax/al, [rsi]
|
|
|
|
if (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 true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
if (result != vm_handler.end())
|
|
|
|
last = result;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (last != vm_handler.end())
|
|
|
|
vm_handler.erase(last, vm_handler.end());
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool get_all(std::uintptr_t module_base, std::uintptr_t image_base,
|
|
|
|
zydis_routine_t& vm_entry, std::uintptr_t* vm_handler_table, std::vector<vm::handler::handler_t>& vm_handlers)
|
|
|
|
{
|
|
|
|
zydis_decoded_instr_t instr;
|
|
|
|
if (!vm::handler::table::get_transform(vm_entry, &instr))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
zydis_routine_t calc_jmp;
|
|
|
|
if (!vm::calc_jmp::get(vm_entry, calc_jmp))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
for (auto idx = 0u; idx < 256; ++idx)
|
|
|
|
{
|
|
|
|
const auto decrypt_val =
|
|
|
|
vm::handler::table::decrypt(
|
|
|
|
instr, vm_handler_table[idx]);
|
|
|
|
|
|
|
|
handler_t vm_handler;
|
|
|
|
vm::transform::map_t transforms;
|
|
|
|
zydis_routine_t vm_handler_instrs;
|
|
|
|
|
|
|
|
if (!vm::handler::get(calc_jmp, vm_handler_instrs, (decrypt_val - image_base) + module_base))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
const auto has_imm =
|
|
|
|
vm::handler::has_imm(vm_handler_instrs);
|
|
|
|
|
|
|
|
const auto imm_size =
|
|
|
|
vm::handler::imm_size(vm_handler_instrs);
|
|
|
|
|
|
|
|
if (has_imm && !vm::handler::get_operand_transforms(vm_handler_instrs, transforms))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
vm_handler.address = (decrypt_val - image_base) + module_base;
|
|
|
|
vm_handler.instrs = vm_handler_instrs;
|
|
|
|
vm_handler.imm_size = imm_size;
|
|
|
|
vm_handler.transforms = transforms;
|
|
|
|
vm_handler.profile = vm::handler::get_profile(vm_handler);
|
|
|
|
vm_handlers.push_back(vm_handler);
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool has_imm(const zydis_routine_t& vm_handler)
|
|
|
|
{
|
|
|
|
const auto result = std::find_if(
|
|
|
|
vm_handler.begin(), vm_handler.end(),
|
|
|
|
[](const zydis_instr_t& instr_data) -> bool
|
|
|
|
{
|
|
|
|
// mov/movsx/movzx rax/eax/ax/al, [rsi]
|
|
|
|
if (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 true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
return result != vm_handler.end();
|
|
|
|
}
|
|
|
|
|
|
|
|
std::uint8_t imm_size(const zydis_routine_t& vm_handler)
|
|
|
|
{
|
|
|
|
const auto result = std::find_if(
|
|
|
|
vm_handler.begin(), vm_handler.end(),
|
|
|
|
[](const zydis_instr_t& instr_data) -> bool
|
|
|
|
{
|
|
|
|
// mov/movsx/movzx rax/eax/ax/al, [rsi]
|
|
|
|
if (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 true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
if (result == vm_handler.end())
|
|
|
|
return 0u;
|
|
|
|
|
|
|
|
return result->instr.operands[1].size;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool get_operand_transforms(const zydis_routine_t& vm_handler, transform::map_t& transforms)
|
|
|
|
{
|
|
|
|
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]
|
|
|
|
if (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 true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
if (imm_fetch == vm_handler.end())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// this finds the first transformation which looks like:
|
|
|
|
// transform rax, rbx <--- note these registers can be smaller so we to64 them...
|
|
|
|
auto key_transform = std::find_if(imm_fetch, vm_handler.end(),
|
|
|
|
[](const zydis_instr_t& instr_data) -> bool
|
|
|
|
{
|
|
|
|
if (util::reg::compare(instr_data.instr.operands[0].reg.value, ZYDIS_REGISTER_RAX) &&
|
|
|
|
util::reg::compare(instr_data.instr.operands[1].reg.value, ZYDIS_REGISTER_RBX))
|
|
|
|
return true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
if (key_transform == vm_handler.end())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// look for a primer/instruction that alters RAX prior to the 5 transformations...
|
|
|
|
auto generic0 = std::find_if(imm_fetch + 1, key_transform,
|
|
|
|
[](const zydis_instr_t& instr_data) -> bool
|
|
|
|
{
|
|
|
|
return util::reg::compare(
|
|
|
|
instr_data.instr.operands[0].reg.value, ZYDIS_REGISTER_RAX) &&
|
|
|
|
!util::reg::compare(instr_data.instr.operands[1].reg.value, ZYDIS_REGISTER_RBX);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
zydis_decoded_instr_t nogeneric0;
|
|
|
|
nogeneric0.mnemonic = ZYDIS_MNEMONIC_INVALID;
|
|
|
|
|
|
|
|
transforms[transform::type::generic0] =
|
|
|
|
generic0 != key_transform ? generic0->instr : nogeneric0;
|
|
|
|
|
|
|
|
// last transformation is the same as the first except src and dest are swwapped...
|
|
|
|
transforms[transform::type::rolling_key] = key_transform->instr;
|
|
|
|
auto instr_copy = key_transform->instr;
|
|
|
|
instr_copy.operands[0].reg.value = key_transform->instr.operands[1].reg.value;
|
|
|
|
instr_copy.operands[1].reg.value = key_transform->instr.operands[0].reg.value;
|
|
|
|
transforms[transform::type::update_key] = instr_copy;
|
|
|
|
|
|
|
|
// three generic transformations...
|
|
|
|
auto generic_transform = key_transform;
|
|
|
|
|
|
|
|
for (auto idx = 2u; idx < 5; ++idx)
|
|
|
|
{
|
|
|
|
generic_transform = std::find_if(++generic_transform, vm_handler.end(),
|
|
|
|
[](const zydis_instr_t& instr_data) -> bool
|
|
|
|
{
|
|
|
|
if (util::reg::compare(instr_data.instr.operands[0].reg.value, ZYDIS_REGISTER_RAX))
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
if (generic_transform == vm_handler.end())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
transforms[(transform::type)(idx)] = generic_transform->instr;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
vm::handler::profile_t* get_profile(handler_t& vm_handler)
|
|
|
|
{
|
|
|
|
static const auto vcontains =
|
|
|
|
[](vm::handler::profile_t* vprofile, handler_t* vm_handler) -> bool
|
|
|
|
{
|
|
|
|
if (vprofile->imm_size != vm_handler->imm_size)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
for (auto& instr : vprofile->signature)
|
|
|
|
{
|
|
|
|
const auto contains = std::find_if
|
|
|
|
(
|
|
|
|
vm_handler->instrs.begin(),
|
|
|
|
vm_handler->instrs.end(),
|
|
|
|
|
|
|
|
[&](zydis_instr_t& instr_data) -> bool
|
|
|
|
{ return instr(instr_data.instr); }
|
|
|
|
);
|
|
|
|
|
|
|
|
if (contains == vm_handler->instrs.end())
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
|
|
|
|
for (auto profile : vm::handler::profile::all)
|
|
|
|
if (vcontains(profile, &vm_handler))
|
|
|
|
return profile;
|
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
namespace table
|
|
|
|
{
|
|
|
|
std::uintptr_t* get(const zydis_routine_t& vm_entry)
|
|
|
|
{
|
|
|
|
const auto result = std::find_if(
|
|
|
|
vm_entry.begin(), vm_entry.end(),
|
|
|
|
[](const zydis_instr_t& instr_data) -> bool
|
|
|
|
{
|
|
|
|
const auto instr = &instr_data.instr;
|
|
|
|
// lea r12, vm_handlers... (always r12)...
|
|
|
|
if (instr->mnemonic == ZYDIS_MNEMONIC_LEA &&
|
|
|
|
instr->operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER &&
|
|
|
|
instr->operands[0].reg.value == ZYDIS_REGISTER_R12 &&
|
|
|
|
!instr->raw.sib.base) // no register used for the sib base...
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
if (result == vm_entry.end())
|
|
|
|
return nullptr;
|
|
|
|
|
|
|
|
std::uintptr_t ptr = 0u;
|
|
|
|
ZydisCalcAbsoluteAddress(&result->instr,
|
|
|
|
&result->instr.operands[1], result->addr, &ptr);
|
|
|
|
|
|
|
|
return reinterpret_cast<std::uintptr_t*>(ptr);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool get_transform(const zydis_routine_t& vm_entry, zydis_decoded_instr_t* transform_instr)
|
|
|
|
{
|
|
|
|
zydis_register_t rcx_or_rdx = ZYDIS_REGISTER_NONE;
|
|
|
|
|
|
|
|
auto handler_fetch = std::find_if(
|
|
|
|
vm_entry.begin(), vm_entry.end(),
|
|
|
|
[&](const zydis_instr_t& instr_data) -> bool
|
|
|
|
{
|
|
|
|
const auto instr = &instr_data.instr;
|
|
|
|
if (instr->mnemonic == ZYDIS_MNEMONIC_MOV &&
|
|
|
|
instr->operand_count == 2 &&
|
|
|
|
instr->operands[1].type == ZYDIS_OPERAND_TYPE_MEMORY &&
|
|
|
|
instr->operands[1].mem.base == ZYDIS_REGISTER_R12 &&
|
|
|
|
instr->operands[1].mem.index == ZYDIS_REGISTER_RAX &&
|
|
|
|
instr->operands[1].mem.scale == 8 &&
|
|
|
|
instr->operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER &&
|
|
|
|
(instr->operands[0].reg.value == ZYDIS_REGISTER_RDX ||
|
|
|
|
instr->operands[0].reg.value == ZYDIS_REGISTER_RCX))
|
|
|
|
{
|
|
|
|
rcx_or_rdx = instr->operands[0].reg.value;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
// check to see if we found the fetch instruction and if the next instruction
|
|
|
|
// is not the end of the vector...
|
|
|
|
if (handler_fetch == vm_entry.end() || ++handler_fetch == vm_entry.end() ||
|
|
|
|
// must be RCX or RDX... else something went wrong...
|
|
|
|
(rcx_or_rdx != ZYDIS_REGISTER_RCX && rcx_or_rdx != ZYDIS_REGISTER_RDX))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// find the next instruction that writes to RCX or RDX...
|
|
|
|
// the register is determined by the vm handler fetch above...
|
|
|
|
auto handler_transform = std::find_if(
|
|
|
|
handler_fetch, vm_entry.end(),
|
|
|
|
[&](const zydis_instr_t& instr_data) -> bool
|
|
|
|
{
|
|
|
|
if (instr_data.instr.operands[0].reg.value == rcx_or_rdx &&
|
|
|
|
instr_data.instr.operands[0].actions & ZYDIS_OPERAND_ACTION_WRITE)
|
|
|
|
return true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
if (handler_transform == vm_entry.end())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
*transform_instr = handler_transform->instr;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::uint64_t encrypt(zydis_decoded_instr_t& transform_instr, std::uint64_t val)
|
|
|
|
{
|
|
|
|
assert(transform_instr.operands[0].size == 64,
|
|
|
|
"invalid transformation for vm handler table entries...");
|
|
|
|
|
|
|
|
const auto operation = vm::transform::inverse[transform_instr.mnemonic];
|
|
|
|
const auto bitsize = transform_instr.operands[0].size;
|
|
|
|
const auto imm = vm::transform::has_imm(&transform_instr) ?
|
|
|
|
transform_instr.operands[1].imm.value.u : 0u;
|
|
|
|
|
|
|
|
return vm::transform::apply(bitsize, operation, val, imm);
|
|
|
|
}
|
|
|
|
|
|
|
|
std::uint64_t decrypt(zydis_decoded_instr_t& transform_instr, std::uint64_t val)
|
|
|
|
{
|
|
|
|
assert(transform_instr.operands[0].size == 64,
|
|
|
|
"invalid transformation for vm handler table entries...");
|
|
|
|
|
|
|
|
const auto operation = transform_instr.mnemonic;
|
|
|
|
const auto bitsize = transform_instr.operands[0].size;
|
|
|
|
const auto imm = vm::transform::has_imm(&transform_instr) ?
|
|
|
|
transform_instr.operands[1].imm.value.u : 0u;
|
|
|
|
|
|
|
|
return vm::transform::apply(bitsize, operation, val, imm);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|