Skip to content
Permalink
master
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
// Hex-Rays VMX intrinsics plugin
// Copyright (c) Dougall Johnson, 2018
// NOTE: this generates "*a1 = __vmread(0x681Cui64)" instead of the
// correct "__vmx_vmread(0x681Eui64, a1)", both because I prefer the
// output, and because I had miscompilation problems when I generated
// "get address of register" operand arguments.
#include <hexrays.hpp>
#include <intel.hpp>
// Hex-Rays API pointer
hexdsp_t* hexdsp = NULL;
// Found by calling mop_t::dstr on sequential registers - it might be more
// portable to find it that way each time the plugin loads?
const mreg_t mr_tt = mreg_t(0xC0);
//--------------------------------------------------------------------------
class helpercall_builder_t {
public:
helpercall_builder_t(codegen_t& _cdg, const char* name, tinfo_t return_type = tinfo_t(BT_VOID))
: cdg(_cdg)
{
emitted = false;
funcinfo = new mfuncinfo_t();
funcinfo->callee = BADADDR;
funcinfo->solid_args = 0;
funcinfo->call_spd = 0;
funcinfo->stkargs_top = 0;
funcinfo->cc = CM_CC_FASTCALL,
funcinfo->return_type = return_type;
// FCI_PROP is used to avoid return-value verification,
// since we may have already "propagated" it into a "mov"
// instruction, I think.
funcinfo->flags = FCI_FINAL | FCI_PROP;
funcinfo->role = ROLE_UNK;
// Prevent optimizing this away, even if the return value is unused.
// unfortunately I'm not sure how to correctly spoil the regions
// described in the documentation, so I just guess that this is enough.
ivl_t glblow(0, 0x100000);
funcinfo->spoiled.mem.add(glblow);
callins = new_minsn(m_call);
callins->l.make_helper(name);
callins->d.t = mop_f;
callins->d.size = 0;
callins->d.f = funcinfo;
if (return_type.is_void()) {
ins = callins;
} else {
callins->d.size = (int)return_type.get_size();
ins = new_minsn(m_mov);
ins->l.t = mop_d;
ins->l.d = callins;
ins->l.size = callins->d.size;
ins->d.t = mop_r;
ins->d.r = 0;
ins->d.size = callins->d.size;
}
}
void add_register_argument(tinfo_t type, mreg_t reg)
{
mfuncarg_t* fa = &funcinfo->args.push_back();
fa->t = mop_r;
fa->r = reg;
fa->type = type;
fa->size = type.get_size();
funcinfo->solid_args++;
}
void set_return_register(mreg_t reg)
{
if (ins->opcode != m_mov) {
warning("helpercall_builder_t: cannot set_return_register for void return type");
return;
}
ins->d.r = reg;
}
void emit()
{
if (emitted) {
warning("helpercall_builder_t: cannot emit twice");
return;
}
cdg.mb->insert_into_block(ins, cdg.mb->tail);
emitted = true;
}
void emit_und_reg(mreg_t reg, int size)
{
minsn_t* ud_cf = new_minsn(m_und);
ud_cf->d.t = mop_r;
ud_cf->d.r = reg;
ud_cf->d.size = size;
cdg.mb->insert_into_block(ud_cf, cdg.mb->tail);
}
void emit_reg_equals_number(mreg_t result_reg, mreg_t reg, uint64 number, int size)
{
minsn_t* insn = new_minsn(m_setz);
insn->l.t = mop_r;
insn->l.r = reg;
insn->l.size = size;
insn->r.make_number(number, size);
insn->d.t = mop_r;
insn->d.r = result_reg;
insn->d.size = 1;
cdg.mb->insert_into_block(insn, cdg.mb->tail);
}
~helpercall_builder_t()
{
// TODO: I guess if we didn't emit, we should delete these?
cdg.mb->mark_lists_dirty();
}
private:
minsn_t* new_minsn(mcode_t opcode)
{
minsn_t* i = new minsn_t(cdg.insn.ea);
i->opcode = opcode;
// not sure if this is necessary, but to be safe:
i->l.zero();
i->r.zero();
i->d.zero();
return i;
}
bool emitted;
codegen_t& cdg;
mfuncinfo_t* funcinfo;
minsn_t* callins;
minsn_t* ins;
};
//--------------------------------------------------------------------------
static mreg_t hacky_store_operand(codegen_t& cdg, int operand)
{
// "hacky" as this assumes load_operand either returns a register
// operand, which is a valid destination register, or a temporary
// register, which is the result of a load operand, and isn't used
// in computing the address for that load operand.
minsn_t* old_tail = cdg.mb->tail;
mreg_t outreg = cdg.load_operand(operand);
if (cdg.mb->tail == old_tail || !cdg.mb->tail || cdg.mb->tail->opcode != m_ldx) {
// register destination
return outreg;
}
// memory destination
minsn_t* memop = cdg.mb->tail;
memop->opcode = m_stx;
mop_t sel = memop->l; // left
mop_t off = memop->r; // right
mop_t value = memop->d; // destination
memop->l = value; // left
memop->r = sel; // right
memop->d = off; // destination
return value.r;
}
//--------------------------------------------------------------------------
static mreg_t hacky_get_operand_address(codegen_t& cdg, int operand)
{
minsn_t* old_tail = cdg.mb->tail;
mreg_t outreg = cdg.load_operand(operand);
if (cdg.mb->tail == old_tail || !cdg.mb->tail || cdg.mb->tail->opcode != m_ldx) {
warning("hacky_get_operand_address failed! compilation output will be incorrect!");
return outreg;
}
minsn_t* tail = cdg.mb->tail;
// convert the m_ldx to a m_mov
// TODO: is it safe to ignore the segment?
tail->opcode = m_mov;
tail->l = tail->r;
tail->r.zero();
tail->d.size = tail->l.size;
return outreg;
}
//--------------------------------------------------------------------------
// Microsoft's VMX intrinsics return a byte:
//
// 0 = succeeded
// 1 = failed with status in VMCS
// 2 = failed with no status available
//
// So we generate:
//
// mov call !__vmx_vmwrite<fast:"unsigned __int64" r9.8,"unsigned __int64" rax.8>.1, tt.1 ; 180001018 u=rax.8,r9.8 d=tt.1,(GLBLOW)
// setz tt.1, #1.1, zf.1 ; 180001018 u=tt.1 d=zf.1
// setz tt.1, #2.1, cf.1 ; 180001018 u=tt.1 d=cf.1
//
// However, their compiler generates the idiom:
//
// setz cl
// setb al
// adc cl, al
//
// Leading to the following pattern in the output:
//
// v1 = __vmx_vmwrite(...);
// return (v1 == 2) + (v1 == 2) + (v1 == 1);
//
// This is technically correct, but we could do better. Possibly by matching
// the idiom, possibly by matching the AST (would need the 0 <= v1 < 3 data),
// possibly by generating it in a way that the existing optimizer could turn
// into (v1 & 3).
void do_ms_vmx_intrinsic_return(helpercall_builder_t& builder)
{
builder.set_return_register(mr_tt);
builder.emit_reg_equals_number(mr_zf, mr_tt, 1, 1);
builder.emit_reg_equals_number(mr_cf, mr_tt, 2, 1);
}
//--------------------------------------------------------------------------
class vmread_filter_t : public microcode_filter_t {
public:
virtual bool match(codegen_t& cdg)
{
return cdg.insn.itype == NN_vmread;
}
virtual merror_t apply(codegen_t& cdg)
{
helpercall_builder_t builder(cdg, "__vmread", tinfo_t(BT_INT64 | BTMT_USIGNED));
builder.add_register_argument(tinfo_t(BT_INT64 | BTMT_USIGNED), cdg.load_operand(1));
builder.emit();
builder.set_return_register(hacky_store_operand(cdg, 0));
builder.emit_und_reg(mr_cf, 1);
builder.emit_und_reg(mr_zf, 1);
return MERR_OK;
}
virtual ~vmread_filter_t() {}
};
static vmread_filter_t g_vmread_filter;
//--------------------------------------------------------------------------
class vmwrite_filter_t : public microcode_filter_t {
public:
virtual bool match(codegen_t& cdg)
{
return cdg.insn.itype == NN_vmwrite;
}
virtual merror_t apply(codegen_t& cdg)
{
helpercall_builder_t builder(cdg, "__vmx_vmwrite", tinfo_t(BT_INT8 | BTMT_UNSIGNED));
builder.add_register_argument(tinfo_t(BT_INT64 | BTMT_UNSIGNED), cdg.load_operand(0));
builder.add_register_argument(tinfo_t(BT_INT64 | BTMT_UNSIGNED), cdg.load_operand(1));
builder.emit();
do_ms_vmx_intrinsic_return(builder);
return MERR_OK;
}
virtual ~vmwrite_filter_t() {}
};
static vmwrite_filter_t g_vmwrite_filter;
//--------------------------------------------------------------------------
class vmcs_instr_filter_t : public microcode_filter_t {
public:
virtual bool match(codegen_t& cdg)
{
return cdg.insn.itype == NN_vmptrld
|| cdg.insn.itype == NN_vmptrst
|| cdg.insn.itype == NN_vmxon
|| cdg.insn.itype == NN_vmclear;
}
virtual merror_t apply(codegen_t& cdg)
{
// TODO: __vmx_vmptrst doesn't return a value (but this shouldn't be a
// problem in well-formed code)
const char* name = NULL;
switch (cdg.insn.itype) {
case NN_vmptrld:
name = "__vmx_vmptrld";
break;
case NN_vmptrst:
name = "__vmx_vmptrst";
break;
case NN_vmxon:
name = "__vmx_on";
break;
case NN_vmclear:
name = "__vmx_vmclear";
break;
}
helpercall_builder_t builder(cdg, name, tinfo_t(BT_INT8 | BTMT_UNSIGNED));
// TODO: should be "unsigned __int64*"
builder.add_register_argument(tinfo_t::get_stock(STI_PVOID), hacky_get_operand_address(cdg, 0));
builder.emit();
do_ms_vmx_intrinsic_return(builder);
return MERR_OK;
}
virtual ~vmcs_instr_filter_t() {}
};
static vmcs_instr_filter_t g_vmcs_instr_filter;
//--------------------------------------------------------------------------
class vm_void_instr_filter_t : public microcode_filter_t {
public:
virtual bool match(codegen_t& cdg)
{
// hex-rays already supports vmxoff/__vmx_off, as it doesn't have a return value
return cdg.insn.itype == NN_vmlaunch
|| cdg.insn.itype == NN_vmresume;
}
virtual merror_t apply(codegen_t& cdg)
{
const char* name = NULL;
switch (cdg.insn.itype) {
case NN_vmlaunch:
name = "__vmx_vmlaunch";
break;
case NN_vmresume:
name = "__vmx_vmresume";
break;
}
helpercall_builder_t builder(cdg, name, tinfo_t(BT_INT8 | BTMT_UNSIGNED));
builder.emit();
do_ms_vmx_intrinsic_return(builder);
return MERR_OK;
}
virtual ~vm_void_instr_filter_t() {}
};
static vm_void_instr_filter_t g_vm_void_instr_filter;
//--------------------------------------------------------------------------
int idaapi init(void)
{
if (ph.id != PLFM_386 || !inf.is_64bit())
return false; // for x64 only
if (!init_hexrays_plugin())
return PLUGIN_SKIP; // no decompiler
const char* hxver = get_hexrays_version();
msg("Hex-rays version %s has been detected, %s ready to use\n", hxver, PLUGIN.wanted_name);
install_microcode_filter(&g_vmread_filter, true);
install_microcode_filter(&g_vmwrite_filter, true);
install_microcode_filter(&g_vmcs_instr_filter, true);
install_microcode_filter(&g_vm_void_instr_filter, true);
return PLUGIN_KEEP;
}
//--------------------------------------------------------------------------
void idaapi term(void)
{
if (hexdsp != NULL) {
install_microcode_filter(&g_vm_void_instr_filter, false);
install_microcode_filter(&g_vmcs_instr_filter, false);
install_microcode_filter(&g_vmwrite_filter, false);
install_microcode_filter(&g_vmread_filter, false);
term_hexrays_plugin();
}
}
//--------------------------------------------------------------------------
bool idaapi run(size_t)
{
warning("The '%s' plugin is fully automatic", PLUGIN.wanted_name);
return false;
}
//--------------------------------------------------------------------------
static const char comment[] = "VMX intrinsics plugin for Hex-Rays decompiler";
//--------------------------------------------------------------------------
//
// PLUGIN DESCRIPTION BLOCK
//
//--------------------------------------------------------------------------
plugin_t PLUGIN = {
IDP_INTERFACE_VERSION,
PLUGIN_HIDE, // plugin flags
init, // initialize
term, // terminate. this pointer may be NULL.
run, // invoke plugin
comment,
"VMX intrinsics", // the preferred short name of the plugin
"" // the preferred hotkey to run the plugin
};