├── README.md └── dj_vmx_intrinsics ├── dj_vmx_intrinsics.cpp └── makefile /README.md: -------------------------------------------------------------------------------- 1 | # dj_ida_plugins 2 | Plugins for IDA Pro and Hex-Rays 3 | 4 | ## dj_vmx_intrinsics 5 | 6 | This adds VMX intrinsics to the Hex-Rays decompiler. 7 | -------------------------------------------------------------------------------- /dj_vmx_intrinsics/dj_vmx_intrinsics.cpp: -------------------------------------------------------------------------------- 1 | // Hex-Rays VMX intrinsics plugin 2 | // Copyright (c) Dougall Johnson, 2018 3 | 4 | // NOTE: this generates "*a1 = __vmread(0x681Cui64)" instead of the 5 | // correct "__vmx_vmread(0x681Eui64, a1)", both because I prefer the 6 | // output, and because I had miscompilation problems when I generated 7 | // "get address of register" operand arguments. 8 | 9 | #include 10 | #include 11 | 12 | // Hex-Rays API pointer 13 | hexdsp_t* hexdsp = NULL; 14 | 15 | // Found by calling mop_t::dstr on sequential registers - it might be more 16 | // portable to find it that way each time the plugin loads? 17 | const mreg_t mr_tt = mreg_t(0xC0); 18 | 19 | //-------------------------------------------------------------------------- 20 | class helpercall_builder_t { 21 | public: 22 | helpercall_builder_t(codegen_t& _cdg, const char* name, tinfo_t return_type = tinfo_t(BT_VOID)) 23 | : cdg(_cdg) 24 | { 25 | emitted = false; 26 | 27 | funcinfo = new mfuncinfo_t(); 28 | funcinfo->callee = BADADDR; 29 | funcinfo->solid_args = 0; 30 | funcinfo->call_spd = 0; 31 | funcinfo->stkargs_top = 0; 32 | funcinfo->cc = CM_CC_FASTCALL, 33 | funcinfo->return_type = return_type; 34 | 35 | // FCI_PROP is used to avoid return-value verification, 36 | // since we may have already "propagated" it into a "mov" 37 | // instruction, I think. 38 | funcinfo->flags = FCI_FINAL | FCI_PROP; 39 | 40 | funcinfo->role = ROLE_UNK; 41 | 42 | // Prevent optimizing this away, even if the return value is unused. 43 | // unfortunately I'm not sure how to correctly spoil the regions 44 | // described in the documentation, so I just guess that this is enough. 45 | ivl_t glblow(0, 0x100000); 46 | funcinfo->spoiled.mem.add(glblow); 47 | 48 | callins = new_minsn(m_call); 49 | callins->l.make_helper(name); 50 | callins->d.t = mop_f; 51 | callins->d.size = 0; 52 | callins->d.f = funcinfo; 53 | 54 | if (return_type.is_void()) { 55 | ins = callins; 56 | } else { 57 | callins->d.size = (int)return_type.get_size(); 58 | 59 | ins = new_minsn(m_mov); 60 | 61 | ins->l.t = mop_d; 62 | ins->l.d = callins; 63 | ins->l.size = callins->d.size; 64 | 65 | ins->d.t = mop_r; 66 | ins->d.r = 0; 67 | ins->d.size = callins->d.size; 68 | } 69 | } 70 | 71 | void add_register_argument(tinfo_t type, mreg_t reg) 72 | { 73 | mfuncarg_t* fa = &funcinfo->args.push_back(); 74 | fa->t = mop_r; 75 | fa->r = reg; 76 | fa->type = type; 77 | fa->size = type.get_size(); 78 | 79 | funcinfo->solid_args++; 80 | } 81 | 82 | void set_return_register(mreg_t reg) 83 | { 84 | if (ins->opcode != m_mov) { 85 | warning("helpercall_builder_t: cannot set_return_register for void return type"); 86 | return; 87 | } 88 | ins->d.r = reg; 89 | } 90 | 91 | void emit() 92 | { 93 | if (emitted) { 94 | warning("helpercall_builder_t: cannot emit twice"); 95 | return; 96 | } 97 | cdg.mb->insert_into_block(ins, cdg.mb->tail); 98 | emitted = true; 99 | } 100 | 101 | void emit_und_reg(mreg_t reg, int size) 102 | { 103 | minsn_t* ud_cf = new_minsn(m_und); 104 | ud_cf->d.t = mop_r; 105 | ud_cf->d.r = reg; 106 | ud_cf->d.size = size; 107 | 108 | cdg.mb->insert_into_block(ud_cf, cdg.mb->tail); 109 | } 110 | 111 | void emit_reg_equals_number(mreg_t result_reg, mreg_t reg, uint64 number, int size) 112 | { 113 | minsn_t* insn = new_minsn(m_setz); 114 | 115 | insn->l.t = mop_r; 116 | insn->l.r = reg; 117 | insn->l.size = size; 118 | 119 | insn->r.make_number(number, size); 120 | 121 | insn->d.t = mop_r; 122 | insn->d.r = result_reg; 123 | insn->d.size = 1; 124 | cdg.mb->insert_into_block(insn, cdg.mb->tail); 125 | } 126 | 127 | ~helpercall_builder_t() 128 | { 129 | // TODO: I guess if we didn't emit, we should delete these? 130 | cdg.mb->mark_lists_dirty(); 131 | } 132 | 133 | private: 134 | minsn_t* new_minsn(mcode_t opcode) 135 | { 136 | minsn_t* i = new minsn_t(cdg.insn.ea); 137 | i->opcode = opcode; 138 | 139 | // not sure if this is necessary, but to be safe: 140 | i->l.zero(); 141 | i->r.zero(); 142 | i->d.zero(); 143 | return i; 144 | } 145 | 146 | bool emitted; 147 | codegen_t& cdg; 148 | mfuncinfo_t* funcinfo; 149 | minsn_t* callins; 150 | minsn_t* ins; 151 | }; 152 | 153 | //-------------------------------------------------------------------------- 154 | static mreg_t hacky_store_operand(codegen_t& cdg, int operand) 155 | { 156 | // "hacky" as this assumes load_operand either returns a register 157 | // operand, which is a valid destination register, or a temporary 158 | // register, which is the result of a load operand, and isn't used 159 | // in computing the address for that load operand. 160 | 161 | minsn_t* old_tail = cdg.mb->tail; 162 | 163 | mreg_t outreg = cdg.load_operand(operand); 164 | 165 | if (cdg.mb->tail == old_tail || !cdg.mb->tail || cdg.mb->tail->opcode != m_ldx) { 166 | // register destination 167 | return outreg; 168 | } 169 | 170 | // memory destination 171 | minsn_t* memop = cdg.mb->tail; 172 | 173 | memop->opcode = m_stx; 174 | 175 | mop_t sel = memop->l; // left 176 | mop_t off = memop->r; // right 177 | mop_t value = memop->d; // destination 178 | 179 | memop->l = value; // left 180 | memop->r = sel; // right 181 | memop->d = off; // destination 182 | 183 | return value.r; 184 | } 185 | 186 | //-------------------------------------------------------------------------- 187 | static mreg_t hacky_get_operand_address(codegen_t& cdg, int operand) 188 | { 189 | minsn_t* old_tail = cdg.mb->tail; 190 | 191 | mreg_t outreg = cdg.load_operand(operand); 192 | 193 | if (cdg.mb->tail == old_tail || !cdg.mb->tail || cdg.mb->tail->opcode != m_ldx) { 194 | warning("hacky_get_operand_address failed! compilation output will be incorrect!"); 195 | return outreg; 196 | } 197 | 198 | minsn_t* tail = cdg.mb->tail; 199 | 200 | // convert the m_ldx to a m_mov 201 | // TODO: is it safe to ignore the segment? 202 | tail->opcode = m_mov; 203 | tail->l = tail->r; 204 | tail->r.zero(); 205 | tail->d.size = tail->l.size; 206 | 207 | return outreg; 208 | } 209 | 210 | //-------------------------------------------------------------------------- 211 | // Microsoft's VMX intrinsics return a byte: 212 | // 213 | // 0 = succeeded 214 | // 1 = failed with status in VMCS 215 | // 2 = failed with no status available 216 | // 217 | // So we generate: 218 | // 219 | // mov call !__vmx_vmwrite.1, tt.1 ; 180001018 u=rax.8,r9.8 d=tt.1,(GLBLOW) 220 | // setz tt.1, #1.1, zf.1 ; 180001018 u=tt.1 d=zf.1 221 | // setz tt.1, #2.1, cf.1 ; 180001018 u=tt.1 d=cf.1 222 | // 223 | // However, their compiler generates the idiom: 224 | // 225 | // setz cl 226 | // setb al 227 | // adc cl, al 228 | // 229 | // Leading to the following pattern in the output: 230 | // 231 | // v1 = __vmx_vmwrite(...); 232 | // return (v1 == 2) + (v1 == 2) + (v1 == 1); 233 | // 234 | // This is technically correct, but we could do better. Possibly by matching 235 | // the idiom, possibly by matching the AST (would need the 0 <= v1 < 3 data), 236 | // possibly by generating it in a way that the existing optimizer could turn 237 | // into (v1 & 3). 238 | void do_ms_vmx_intrinsic_return(helpercall_builder_t& builder) 239 | { 240 | builder.set_return_register(mr_tt); 241 | builder.emit_reg_equals_number(mr_zf, mr_tt, 1, 1); 242 | builder.emit_reg_equals_number(mr_cf, mr_tt, 2, 1); 243 | } 244 | 245 | //-------------------------------------------------------------------------- 246 | class vmread_filter_t : public microcode_filter_t { 247 | public: 248 | virtual bool match(codegen_t& cdg) 249 | { 250 | return cdg.insn.itype == NN_vmread; 251 | } 252 | 253 | virtual merror_t apply(codegen_t& cdg) 254 | { 255 | helpercall_builder_t builder(cdg, "__vmread", tinfo_t(BT_INT64 | BTMT_USIGNED)); 256 | builder.add_register_argument(tinfo_t(BT_INT64 | BTMT_USIGNED), cdg.load_operand(1)); 257 | builder.emit(); 258 | builder.set_return_register(hacky_store_operand(cdg, 0)); 259 | builder.emit_und_reg(mr_cf, 1); 260 | builder.emit_und_reg(mr_zf, 1); 261 | return MERR_OK; 262 | } 263 | 264 | virtual ~vmread_filter_t() {} 265 | }; 266 | static vmread_filter_t g_vmread_filter; 267 | 268 | //-------------------------------------------------------------------------- 269 | class vmwrite_filter_t : public microcode_filter_t { 270 | public: 271 | virtual bool match(codegen_t& cdg) 272 | { 273 | return cdg.insn.itype == NN_vmwrite; 274 | } 275 | 276 | virtual merror_t apply(codegen_t& cdg) 277 | { 278 | helpercall_builder_t builder(cdg, "__vmx_vmwrite", tinfo_t(BT_INT8 | BTMT_UNSIGNED)); 279 | builder.add_register_argument(tinfo_t(BT_INT64 | BTMT_UNSIGNED), cdg.load_operand(0)); 280 | builder.add_register_argument(tinfo_t(BT_INT64 | BTMT_UNSIGNED), cdg.load_operand(1)); 281 | builder.emit(); 282 | do_ms_vmx_intrinsic_return(builder); 283 | return MERR_OK; 284 | } 285 | 286 | virtual ~vmwrite_filter_t() {} 287 | }; 288 | static vmwrite_filter_t g_vmwrite_filter; 289 | 290 | //-------------------------------------------------------------------------- 291 | class vmcs_instr_filter_t : public microcode_filter_t { 292 | public: 293 | virtual bool match(codegen_t& cdg) 294 | { 295 | return cdg.insn.itype == NN_vmptrld 296 | || cdg.insn.itype == NN_vmptrst 297 | || cdg.insn.itype == NN_vmxon 298 | || cdg.insn.itype == NN_vmclear; 299 | } 300 | 301 | virtual merror_t apply(codegen_t& cdg) 302 | { 303 | // TODO: __vmx_vmptrst doesn't return a value (but this shouldn't be a 304 | // problem in well-formed code) 305 | 306 | const char* name = NULL; 307 | switch (cdg.insn.itype) { 308 | case NN_vmptrld: 309 | name = "__vmx_vmptrld"; 310 | break; 311 | case NN_vmptrst: 312 | name = "__vmx_vmptrst"; 313 | break; 314 | case NN_vmxon: 315 | name = "__vmx_on"; 316 | break; 317 | case NN_vmclear: 318 | name = "__vmx_vmclear"; 319 | break; 320 | } 321 | 322 | helpercall_builder_t builder(cdg, name, tinfo_t(BT_INT8 | BTMT_UNSIGNED)); 323 | // TODO: should be "unsigned __int64*" 324 | builder.add_register_argument(tinfo_t::get_stock(STI_PVOID), hacky_get_operand_address(cdg, 0)); 325 | builder.emit(); 326 | do_ms_vmx_intrinsic_return(builder); 327 | 328 | return MERR_OK; 329 | } 330 | 331 | virtual ~vmcs_instr_filter_t() {} 332 | }; 333 | static vmcs_instr_filter_t g_vmcs_instr_filter; 334 | 335 | //-------------------------------------------------------------------------- 336 | class vm_void_instr_filter_t : public microcode_filter_t { 337 | public: 338 | virtual bool match(codegen_t& cdg) 339 | { 340 | // hex-rays already supports vmxoff/__vmx_off, as it doesn't have a return value 341 | return cdg.insn.itype == NN_vmlaunch 342 | || cdg.insn.itype == NN_vmresume; 343 | } 344 | 345 | virtual merror_t apply(codegen_t& cdg) 346 | { 347 | const char* name = NULL; 348 | switch (cdg.insn.itype) { 349 | case NN_vmlaunch: 350 | name = "__vmx_vmlaunch"; 351 | break; 352 | case NN_vmresume: 353 | name = "__vmx_vmresume"; 354 | break; 355 | } 356 | 357 | helpercall_builder_t builder(cdg, name, tinfo_t(BT_INT8 | BTMT_UNSIGNED)); 358 | builder.emit(); 359 | do_ms_vmx_intrinsic_return(builder); 360 | 361 | return MERR_OK; 362 | } 363 | 364 | virtual ~vm_void_instr_filter_t() {} 365 | }; 366 | static vm_void_instr_filter_t g_vm_void_instr_filter; 367 | 368 | //-------------------------------------------------------------------------- 369 | int idaapi init(void) 370 | { 371 | if (ph.id != PLFM_386 || !inf.is_64bit()) 372 | return false; // for x64 only 373 | 374 | if (!init_hexrays_plugin()) 375 | return PLUGIN_SKIP; // no decompiler 376 | 377 | const char* hxver = get_hexrays_version(); 378 | msg("Hex-rays version %s has been detected, %s ready to use\n", hxver, PLUGIN.wanted_name); 379 | 380 | install_microcode_filter(&g_vmread_filter, true); 381 | install_microcode_filter(&g_vmwrite_filter, true); 382 | install_microcode_filter(&g_vmcs_instr_filter, true); 383 | install_microcode_filter(&g_vm_void_instr_filter, true); 384 | return PLUGIN_KEEP; 385 | } 386 | 387 | //-------------------------------------------------------------------------- 388 | void idaapi term(void) 389 | { 390 | if (hexdsp != NULL) { 391 | install_microcode_filter(&g_vm_void_instr_filter, false); 392 | install_microcode_filter(&g_vmcs_instr_filter, false); 393 | install_microcode_filter(&g_vmwrite_filter, false); 394 | install_microcode_filter(&g_vmread_filter, false); 395 | term_hexrays_plugin(); 396 | } 397 | } 398 | 399 | //-------------------------------------------------------------------------- 400 | bool idaapi run(size_t) 401 | { 402 | warning("The '%s' plugin is fully automatic", PLUGIN.wanted_name); 403 | return false; 404 | } 405 | 406 | //-------------------------------------------------------------------------- 407 | static const char comment[] = "VMX intrinsics plugin for Hex-Rays decompiler"; 408 | 409 | //-------------------------------------------------------------------------- 410 | // 411 | // PLUGIN DESCRIPTION BLOCK 412 | // 413 | //-------------------------------------------------------------------------- 414 | plugin_t PLUGIN = { 415 | IDP_INTERFACE_VERSION, 416 | PLUGIN_HIDE, // plugin flags 417 | init, // initialize 418 | term, // terminate. this pointer may be NULL. 419 | run, // invoke plugin 420 | comment, 421 | "VMX intrinsics", // the preferred short name of the plugin 422 | "" // the preferred hotkey to run the plugin 423 | }; 424 | -------------------------------------------------------------------------------- /dj_vmx_intrinsics/makefile: -------------------------------------------------------------------------------- 1 | PROC=dj_vmx_intrinsics 2 | include ../plugin.mak 3 | 4 | # MAKEDEP dependency list ------------------ 5 | $(F)dj_vmx_intrinsics$(O): $(I)bitrange.hpp $(I)bytes.hpp $(I)config.hpp \ 6 | $(I)fpro.h $(I)funcs.hpp $(I)gdl.hpp $(I)hexrays.hpp \ 7 | $(I)ida.hpp $(I)idp.hpp $(I)ieee.h $(I)kernwin.hpp \ 8 | $(I)lines.hpp $(I)llong.hpp $(I)loader.hpp $(I)nalt.hpp \ 9 | $(I)name.hpp $(I)netnode.hpp $(I)pro.h $(I)range.hpp \ 10 | $(I)segment.hpp $(I)typeinf.hpp $(I)ua.hpp $(I)xref.hpp \ 11 | dj_vmx_intrinsics.cpp 12 | --------------------------------------------------------------------------------