fixed broke ass vmlocate algo so it hums like a v8

master
IDontCode 3 years ago
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;
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 } // namespace vm::locate

@ -1,10 +1,10 @@
#pragma once #pragma once
#include <Zydis/Utils.h> #include <Zydis/Utils.h>
#include <Zydis/Zydis.h> #include <Zydis/Zydis.h>
#include <xmmintrin.h>
#include <optional> #include <optional>
#include <vector> #include <vector>
#include <xmmintrin.h>
#ifdef _MSC_VER #ifdef _MSC_VER
@ -40,8 +40,8 @@
#elif defined(__NetBSD__) #elif defined(__NetBSD__)
#include <sys/types.h>
#include <machine/bswap.h> #include <machine/bswap.h>
#include <sys/types.h>
#if defined(__BSWAP_RENAME) && !defined(__bswap_32) #if defined(__BSWAP_RENAME) && !defined(__bswap_32)
#define bswap_32(x) bswap32(x) #define bswap_32(x) bswap32(x)
#define bswap_64(x) bswap64(x) #define bswap_64(x) bswap64(x)
@ -64,87 +64,96 @@ using zydis_register_t = ZydisRegister;
using zydis_mnemonic_t = ZydisMnemonic; using zydis_mnemonic_t = ZydisMnemonic;
using zydis_decoded_operand_t = ZydisDecodedOperand; using zydis_decoded_operand_t = ZydisDecodedOperand;
struct zydis_instr_t struct zydis_instr_t {
{
zydis_decoded_instr_t instr; zydis_decoded_instr_t instr;
std::vector< u8 > raw; std::vector<u8> raw;
std::uintptr_t addr; std::uintptr_t addr;
}; };
using zydis_routine_t = std::vector< zydis_instr_t >; using zydis_routine_t = std::vector<zydis_instr_t>;
/// <summary> /// <summary>
/// utils used by the other cpp files... misc things that get used a lot... /// utils used by the other cpp files... misc things that get used a lot...
/// </summary> /// </summary>
namespace vm::util namespace vm::util {
{ /// <summary>
/// <summary> /// utils pertaining to native registers...
/// utils pertaining to native registers... /// </summary>
/// </summary> namespace reg {
namespace reg /// <summary>
{ /// converts say... AL to RAX...
/// <summary> /// </summary>
/// converts say... AL to RAX... /// <param name="reg">a zydis decoded register value...</param>
/// </summary> /// <returns>returns the largest width register of the given register... AL
/// <param name="reg">a zydis decoded register value...</param> /// gives RAX...</returns>
/// <returns>returns the largest width register of the given register... AL gives RAX...</returns> zydis_register_t to64(zydis_register_t reg);
zydis_register_t to64( zydis_register_t reg );
/// <summary>
/// <summary> /// compares to registers with each other... calls to64 and compares...
/// compares to registers with each other... calls to64 and compares... /// </summary>
/// </summary> /// <param name="a">register a...</param>
/// <param name="a">register a...</param> /// <param name="b">register b...</param>
/// <param name="b">register b...</param> /// <returns>returns true if register to64(a) == to64(b)...</returns>
/// <returns>returns true if register to64(a) == to64(b)...</returns> bool compare(zydis_register_t a, zydis_register_t b);
bool compare( zydis_register_t a, zydis_register_t b ); } // namespace reg
} // namespace reg
/// <summary>
/// <summary> /// get the instruction that fetches an operand out of VIP...
/// get the instruction that fetches an operand out of VIP... /// </summary>
/// </summary> /// <param name="routine">this is a deobfuscated, flattened, view of any set of
/// <param name="routine">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...</param> /// native instructions that read an operand out of VIP... can be calc_jmp,
/// <param name="fetch_instr"></param> /// vm_entry, or vm handlers...</param> <param name="fetch_instr"></param>
/// <returns>returns true of the fetch operand native instruction is found...</returns> /// <returns>returns true of the fetch operand native instruction is
bool get_fetch_operand( const zydis_routine_t &routine, zydis_instr_t &fetch_instr ); /// found...</returns>
bool get_fetch_operand(const zydis_routine_t &routine,
/// <summary> zydis_instr_t &fetch_instr);
/// gets the instruction that fetches an operand out of VIP and returns an iterator to it...
/// </summary> /// <summary>
/// <param name="routine">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...</param> /// gets the instruction that fetches an operand out of VIP and returns an
/// <returns>returns the iterator of the native instruction, else an empty std::optional...</returns> /// iterator to it...
std::optional< zydis_routine_t::iterator > get_fetch_operand( zydis_routine_t &routine ); /// </summary>
/// <param name="routine">this is a deobfuscated, flattened, view of any set of
/// <summary> /// native instructions that read an operand out of VIP... can be calc_jmp,
/// prints a disassembly view of a routine... /// vm_entry, or vm handlers...</param> <returns>returns the iterator of the
/// </summary> /// native instruction, else an empty std::optional...</returns>
/// <param name="routine">reference to a zydis_routine_t to be printed...</param> std::optional<zydis_routine_t::iterator> get_fetch_operand(
void print( zydis_routine_t &routine ); zydis_routine_t &routine);
/// <summary> /// <summary>
/// prints a single disassembly view of an instruction... /// prints a disassembly view of a routine...
/// </summary> /// </summary>
/// <param name="instr">instruction to print...</param> /// <param name="routine">reference to a zydis_routine_t to be
void print( const zydis_decoded_instr_t &instr ); /// printed...</param>
void print(zydis_routine_t &routine);
/// <summary>
/// determines if a given decoded native instruction is a JCC... /// <summary>
/// </summary> /// prints a single disassembly view of an instruction...
/// <param name="instr"></param> /// </summary>
/// <returns></returns> /// <param name="instr">instruction to print...</param>
bool is_jmp( const zydis_decoded_instr_t &instr ); void print(const zydis_decoded_instr_t &instr);
/// <summary> /// <summary>
/// flatten native instruction stream, takes every JCC (follows the branch)... /// determines if a given decoded native instruction is a JCC...
/// </summary> /// </summary>
/// <param name="routine">filled with decoded instructions...</param> /// <param name="instr"></param>
/// <param name="routine_addr">linear virtual address to start flattening from...</param> /// <returns></returns>
/// <param name="keep_jmps">keep JCC's in the flattened instruction stream...</param> bool is_jmp(const zydis_decoded_instr_t &instr);
/// <returns>returns true if flattened was successful...</returns>
bool flatten( zydis_routine_t &routine, std::uintptr_t routine_addr, bool keep_jmps = false ); /// <summary>
/// flatten native instruction stream, takes every JCC (follows the branch)...
/// <summary> /// </summary>
/// deadstore deobfuscation of a flattened routine... /// <param name="routine">filled with decoded instructions...</param>
/// </summary> /// <param name="routine_addr">linear virtual address to start flattening
/// <param name="routine">reference to a flattened instruction vector...</param> /// from...</param> <param name="keep_jmps">keep JCC's in the flattened
void deobfuscate( zydis_routine_t &routine ); /// instruction stream...</param> <returns>returns true if flattened was
/// successful...</returns>
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);
/// <summary>
/// deadstore deobfuscation of a flattened routine...
/// </summary>
/// <param name="routine">reference to a flattened instruction vector...</param>
void deobfuscate(zydis_routine_t &routine);
} // namespace vm::util } // namespace vm::util

@ -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);
} }
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 {}; 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 (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 } // namespace vm::locate

@ -1,147 +1,149 @@
#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 {
{
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] // mov/movsx/movzx rax/eax/ax/al, [rsi]
return instr_data.instr.operand_count > 1 && return instr_data.instr.operand_count > 1 &&
( instr_data.instr.mnemonic == ZYDIS_MNEMONIC_MOV || (instr_data.instr.mnemonic == ZYDIS_MNEMONIC_MOV ||
instr_data.instr.mnemonic == ZYDIS_MNEMONIC_MOVSX || instr_data.instr.mnemonic == ZYDIS_MNEMONIC_MOVSX ||
instr_data.instr.mnemonic == ZYDIS_MNEMONIC_MOVZX ) && instr_data.instr.mnemonic == ZYDIS_MNEMONIC_MOVZX) &&
instr_data.instr.operands[ 0 ].type == ZYDIS_OPERAND_TYPE_REGISTER && instr_data.instr.operands[0].type ==
util::reg::to64( instr_data.instr.operands[ 0 ].reg.value ) == ZYDIS_REGISTER_RAX && ZYDIS_OPERAND_TYPE_REGISTER &&
instr_data.instr.operands[ 1 ].type == ZYDIS_OPERAND_TYPE_MEMORY && util::reg::to64(instr_data.instr.operands[0].reg.value) ==
instr_data.instr.operands[ 1 ].mem.base == ZYDIS_REGISTER_RSI; 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;
if (result == routine.end()) return false;
fetch_instr = *result; fetch_instr = *result;
return true; return true;
} }
std::optional< zydis_routine_t::iterator > get_fetch_operand( zydis_routine_t &routine ) 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 { auto result = std::find_if(
routine.begin(), routine.end(),
[](const zydis_instr_t &instr_data) -> bool {
// mov/movsx/movzx rax/eax/ax/al, [rsi] // mov/movsx/movzx rax/eax/ax/al, [rsi]
return instr_data.instr.operand_count > 1 && return instr_data.instr.operand_count > 1 &&
( instr_data.instr.mnemonic == ZYDIS_MNEMONIC_MOV || (instr_data.instr.mnemonic == ZYDIS_MNEMONIC_MOV ||
instr_data.instr.mnemonic == ZYDIS_MNEMONIC_MOVSX || instr_data.instr.mnemonic == ZYDIS_MNEMONIC_MOVSX ||
instr_data.instr.mnemonic == ZYDIS_MNEMONIC_MOVZX ) && instr_data.instr.mnemonic == ZYDIS_MNEMONIC_MOVZX) &&
instr_data.instr.operands[ 0 ].type == ZYDIS_OPERAND_TYPE_REGISTER && instr_data.instr.operands[0].type ==
util::reg::to64( instr_data.instr.operands[ 0 ].reg.value ) == ZYDIS_REGISTER_RAX && ZYDIS_OPERAND_TYPE_REGISTER &&
instr_data.instr.operands[ 1 ].type == ZYDIS_OPERAND_TYPE_MEMORY && util::reg::to64(instr_data.instr.operands[0].reg.value) ==
instr_data.instr.operands[ 1 ].mem.base == ZYDIS_REGISTER_RSI; 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() ) if (result == routine.end()) return {};
return {};
return result; return result;
} }
void print( const zydis_decoded_instr_t &instr ) void print(const zydis_decoded_instr_t &instr) {
{ char buffer[256];
char buffer[ 256 ];
ZydisFormatter formatter; ZydisFormatter formatter;
ZydisFormatterInit( &formatter, ZYDIS_FORMATTER_STYLE_INTEL ); ZydisFormatterInit(&formatter, ZYDIS_FORMATTER_STYLE_INTEL);
ZydisFormatterFormatInstruction( &formatter, &instr, buffer, sizeof( buffer ), 0u ); ZydisFormatterFormatInstruction(&formatter, &instr, buffer, sizeof(buffer),
std::puts( buffer ); 0u);
} std::puts(buffer);
}
void print( zydis_routine_t &routine )
{ void print(zydis_routine_t &routine) {
char buffer[ 256 ]; char buffer[256];
ZydisFormatter formatter; ZydisFormatter formatter;
ZydisFormatterInit( &formatter, ZYDIS_FORMATTER_STYLE_INTEL ); ZydisFormatterInit(&formatter, ZYDIS_FORMATTER_STYLE_INTEL);
for ( auto [ instr, raw, addr ] : routine ) for (auto [instr, raw, addr] : routine) {
{ ZydisFormatterFormatInstruction(&formatter, &instr, buffer, sizeof(buffer),
ZydisFormatterFormatInstruction( &formatter, &instr, buffer, sizeof( buffer ), addr ); addr);
std::printf( "> 0x%p %s\n", addr, buffer ); std::printf("> %p %s\n", addr, buffer);
}
} }
}
bool is_jmp( const zydis_decoded_instr_t &instr ) bool is_jmp(const zydis_decoded_instr_t &instr) {
{ return instr.mnemonic >= ZYDIS_MNEMONIC_JB &&
return instr.mnemonic >= ZYDIS_MNEMONIC_JB && instr.mnemonic <= ZYDIS_MNEMONIC_JZ; instr.mnemonic <= ZYDIS_MNEMONIC_JZ;
} }
bool flatten( zydis_routine_t &routine, std::uintptr_t routine_addr, bool keep_jmps ) 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; ZydisDecoder decoder;
zydis_decoded_instr_t instr; zydis_decoded_instr_t instr;
ZydisDecoderInit( &decoder, ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_ADDRESS_WIDTH_64 );
ZydisDecoderInit(&decoder, ZYDIS_MACHINE_MODE_LONG_64,
while ( ZYAN_SUCCESS( ZYDIS_ADDRESS_WIDTH_64);
ZydisDecoderDecodeBuffer( &decoder, reinterpret_cast< void * >( routine_addr ), 0x1000, &instr ) ) )
{ std::uint32_t instr_cnt = 0u;
// detect if we have already been at this instruction... if so that means there is a loop and we are while (ZYAN_SUCCESS(ZydisDecoderDecodeBuffer(
// going to just return... &decoder, reinterpret_cast<void *>(routine_addr), 0x1000, &instr))) {
if ( std::find_if( routine.begin(), routine.end(), [ & ]( const zydis_instr_t &zydis_instr ) -> bool { 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; return zydis_instr.addr == routine_addr;
} ) != routine.end() ) }) != routine.end())
return true; return true;
std::vector< u8 > raw_instr; std::vector<u8> raw_instr;
raw_instr.insert( raw_instr.begin(), ( u8 * )routine_addr, ( u8 * )routine_addr + instr.length ); raw_instr.insert(raw_instr.begin(), (u8 *)routine_addr,
(u8 *)routine_addr + instr.length);
if ( is_jmp( instr ) ) if (is_jmp(instr)) {
{ if (instr.operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER) {
if ( instr.operands[ 0 ].type == ZYDIS_OPERAND_TYPE_REGISTER ) routine.push_back({instr, raw_instr, routine_addr});
{
routine.push_back( { instr, raw_instr, routine_addr } );
return true; return true;
} }
if ( keep_jmps ) if (keep_jmps) routine.push_back({instr, raw_instr, routine_addr});
routine.push_back( { instr, raw_instr, routine_addr } );
ZydisCalcAbsoluteAddress( &instr, &instr.operands[ 0 ], routine_addr, &routine_addr ); ZydisCalcAbsoluteAddress(&instr, &instr.operands[0], routine_addr,
} &routine_addr);
else if ( instr.mnemonic == ZYDIS_MNEMONIC_RET ) } else if (instr.mnemonic == ZYDIS_MNEMONIC_RET) {
{ routine.push_back({instr, raw_instr, routine_addr});
routine.push_back( { instr, raw_instr, routine_addr } );
return true; return true;
} } else {
else routine.push_back({instr, raw_instr, routine_addr});
{
routine.push_back( { instr, raw_instr, routine_addr } );
routine_addr += instr.length; routine_addr += instr.length;
} }
}
// optional sanity checking...
if (module_base && !scn::executable(module_base, routine_addr))
return false; return false;
} }
return false;
}
void deobfuscate( zydis_routine_t &routine ) void deobfuscate(zydis_routine_t &routine) {
{ static const auto _uses_reg = [](zydis_decoded_operand_t &op,
static const auto _uses_reg = []( zydis_decoded_operand_t &op, zydis_register_t reg ) -> bool { zydis_register_t reg) -> bool {
switch ( op.type ) switch (op.type) {
{ case ZYDIS_OPERAND_TYPE_MEMORY: {
case ZYDIS_OPERAND_TYPE_MEMORY: return reg::compare(op.mem.base, reg) ||
{ reg::compare(op.mem.index, reg);
return reg::compare( op.mem.base, reg ) || reg::compare( op.mem.index, reg );
} }
case ZYDIS_OPERAND_TYPE_REGISTER: case ZYDIS_OPERAND_TYPE_REGISTER: {
{ return reg::compare(op.reg.value, reg);
return reg::compare( op.reg.value, reg );
} }
default: default:
break; break;
@ -149,90 +151,94 @@ namespace vm::util
return false; return false;
}; };
static const auto _reads = []( zydis_decoded_instr_t &instr, zydis_register_t reg ) -> bool { static const auto _reads = [](zydis_decoded_instr_t &instr,
if ( instr.operands[ 0 ].type == ZYDIS_OPERAND_TYPE_MEMORY && zydis_register_t reg) -> bool {
reg::compare( instr.operands[ 0 ].mem.base, reg ) ) if (instr.operands[0].type == ZYDIS_OPERAND_TYPE_MEMORY &&
reg::compare(instr.operands[0].mem.base, reg))
return true; return true;
for ( auto op_idx = 0u; op_idx < instr.operand_count; ++op_idx ) for (auto op_idx = 0u; op_idx < instr.operand_count; ++op_idx)
if ( instr.operands[ op_idx ].actions & ZYDIS_OPERAND_ACTION_READ && if (instr.operands[op_idx].actions & ZYDIS_OPERAND_ACTION_READ &&
_uses_reg( instr.operands[ op_idx ], reg ) ) _uses_reg(instr.operands[op_idx], reg))
return true; return true;
return false; return false;
}; };
static const auto _writes = []( zydis_decoded_instr_t &instr, zydis_register_t reg ) -> bool { static const auto _writes = [](zydis_decoded_instr_t &instr,
for ( auto op_idx = 0u; op_idx < instr.operand_count; ++op_idx ) 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 instruction writes to the specific register...
if ( instr.operands[ op_idx ].type == ZYDIS_OPERAND_TYPE_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_WRITE &&
!( instr.operands[ op_idx ].actions & ZYDIS_OPERAND_ACTION_READ ) && !(instr.operands[op_idx].actions & ZYDIS_OPERAND_ACTION_READ) &&
reg::compare( instr.operands[ op_idx ].reg.value, reg ) ) reg::compare(instr.operands[op_idx].reg.value, reg))
return true; return true;
return false; return false;
}; };
std::uint32_t last_size = 0u; std::uint32_t last_size = 0u;
do do {
{
last_size = routine.size(); last_size = routine.size();
for ( auto itr = routine.begin(); itr != routine.end(); ++itr ) for (auto itr = routine.begin(); itr != routine.end(); ++itr) {
{
// dont remove these... at all... // dont remove these... at all...
if ( itr->instr.mnemonic == ZYDIS_MNEMONIC_PUSH || itr->instr.mnemonic == ZYDIS_MNEMONIC_POP || if (itr->instr.mnemonic == ZYDIS_MNEMONIC_PUSH ||
itr->instr.mnemonic == ZYDIS_MNEMONIC_CALL ) itr->instr.mnemonic == ZYDIS_MNEMONIC_POP ||
itr->instr.mnemonic == ZYDIS_MNEMONIC_CALL)
continue; continue;
static const std::vector< ZydisMnemonic > blacklist = { ZYDIS_MNEMONIC_CLC, ZYDIS_MNEMONIC_BT, static const std::vector<ZydisMnemonic> blacklist = {
ZYDIS_MNEMONIC_TEST, ZYDIS_MNEMONIC_CMP, ZYDIS_MNEMONIC_CLC, ZYDIS_MNEMONIC_BT, ZYDIS_MNEMONIC_TEST,
ZYDIS_MNEMONIC_CMC, ZYDIS_MNEMONIC_STC }; ZYDIS_MNEMONIC_CMP, ZYDIS_MNEMONIC_CMC, ZYDIS_MNEMONIC_STC};
if ( std::find( blacklist.begin(), blacklist.end(), itr->instr.mnemonic ) != blacklist.end() ) if (std::find(blacklist.begin(), blacklist.end(), itr->instr.mnemonic) !=
{ blacklist.end()) {
routine.erase( itr ); routine.erase(itr);
break; break;
} }
zydis_register_t reg = ZYDIS_REGISTER_NONE; zydis_register_t reg = ZYDIS_REGISTER_NONE;
// look for operands with writes to a register... // look for operands with writes to a register...
for ( auto op_idx = 0u; op_idx < itr->instr.operand_count; ++op_idx ) for (auto op_idx = 0u; op_idx < itr->instr.operand_count; ++op_idx)
if ( itr->instr.operands[ op_idx ].type == ZYDIS_OPERAND_TYPE_REGISTER && if (itr->instr.operands[op_idx].type == ZYDIS_OPERAND_TYPE_REGISTER &&
itr->instr.operands[ op_idx ].actions & ZYDIS_OPERAND_ACTION_WRITE ) itr->instr.operands[op_idx].actions & ZYDIS_OPERAND_ACTION_WRITE)
reg = reg::to64( itr->instr.operands[ 0 ].reg.value ); 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 this current instruction writes to a register, look ahead in the
// if it gets written too before it gets read... // instruction stream to see if it gets written too before it gets read...
if ( reg != ZYDIS_REGISTER_NONE ) if (reg != ZYDIS_REGISTER_NONE) {
{
// find the next place that this register is written too... // 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 { auto write_result = std::find_if(itr + 1, routine.end(),
return _writes( instr.instr, reg ); [&](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 ); 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() ) // 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; continue;
// if there is no read of the register before the next write... and there is // if there is no read of the register before the next write... and
// a known next write, then remove the instruction from the stream... // there is a known next write, then remove the instruction from the
if ( read_result == write_result && write_result != routine.end() ) // stream...
{ if (read_result == write_result && write_result != routine.end()) {
// if the instruction reads and writes the same register than skip... // if the instruction reads and writes the same register than skip...
if ( _reads( read_result->instr, reg ) && _writes( read_result->instr, reg ) ) if (_reads(read_result->instr, reg) &&
_writes(read_result->instr, reg))
continue; continue;
routine.erase( itr ); routine.erase(itr);
break; break;
} }
} }
} }
} while ( last_size != routine.size() ); } while (last_size != routine.size());
} }
} // namespace vm::util } // namespace vm::util
Loading…
Cancel
Save