├── README.md └── mcexplorer.py /README.md: -------------------------------------------------------------------------------- 1 | # MCExplorer 2 | 3 | This is a Python portage of the Microcode Explorer plugin created by @[RolfRolles](https://github.com/RolfRolles). 4 | 5 | ## Disclaimer 6 | 7 | Because the Microcode API isn't exported to Python, I had to make extensive use of the `ctypes` module. As a result, the plugin is only compatible with **IDA 7.2** and on **Windows**. You probably have no use for it, sorry. 8 | 9 | You might be wondering why I created it though. I simply wanted to play around with Hex-Rays decompiler's micro-code and with the IDA Pro's internals more generally. As such, the plugin can serve as a reference on how to use unexported APIs (I'm thinking of you dispatcher), and to showcase why it is not a bright idea. You have been warned! 10 | 11 | Nevertheless, I'm still satisfied with my little experiment and learned a ton of things. Maybe you will too... 12 | 13 | ## Credits 14 | 15 | * Original repository: https://github.com/RolfRolles/HexRaysDeob 16 | * Related blog-post: http://www.hexblog.com/?p=1248 17 | -------------------------------------------------------------------------------- /mcexplorer.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | import os 3 | import sys 4 | 5 | import ida_diskio 6 | import ida_funcs 7 | import ida_graph 8 | import ida_hexrays 9 | import ida_idaapi 10 | import ida_kernwin 11 | import ida_lines 12 | import ida_pro 13 | 14 | 15 | LEVELS = ["MMAT_GENERATED", "MMAT_PREOPTIMIZED", "MMAT_LOCOPT", "MMAT_CALLS", 16 | "MMAT_GLBOPT1", "MMAT_GLBOPT2", "MMAT_GLBOPT3", "MMAT_LVARS"] 17 | 18 | MCODES = ["m_nop", "m_stx", "m_ldx", "m_ldc", "m_mov", "m_neg", "m_lnot", 19 | "m_bnot", "m_xds", "m_xdu", "m_low", "m_high", "m_add", "m_sub", 20 | "m_mul", "m_udiv", "m_sdiv", "m_umod", "m_smod", "m_or", "m_and", 21 | "m_xor", "m_shl", "m_shr", "m_sar", "m_cfadd", "m_ofadd", "m_cfshl", 22 | "m_cfshr", "m_sets", "m_seto", "m_setp", "m_setnz", "m_setz", 23 | "m_setae", "m_setb", "m_seta", "m_setbe", "m_setg", "m_setge", 24 | "m_setl", "m_setle", "m_jcnd", "m_jnz", "m_jz", "m_jae", "m_jb", 25 | "m_ja", "m_jbe", "m_jg", "m_jge", "m_jl", "m_jle", "m_jtbl", 26 | "m_ijmp", "m_goto", "m_call", "m_icall", "m_ret", "m_push", "m_pop", 27 | "m_und", "m_ext", "m_f2i", "m_f2u", "m_i2f", "m_u2f", "m_f2f", 28 | "m_fneg", "m_fadd", "m_fsub", "m_fmul", "m_fdiv"] 29 | 30 | MOPTS = ["mop_z", "mop_r", "mop_n", "mop_str", "mop_d", "mop_S", "mop_v", 31 | "mop_b", "mop_f", "mop_l", "mop_a", "mop_h", "mop_c", "mop_fn", 32 | "mop_p", "mop_sc"] 33 | 34 | 35 | class Native(object): 36 | VALUES = { 37 | 720: { 38 | "magic": 0x00DEC0DE00000003, 39 | "ui_broadcast": 7, 40 | "hx_mop_t_print": 260, 41 | "hx_minsn_t_print": 316, 42 | "hx_mblock_t_print": 338, 43 | "hx_mbl_array_t_print": 369, 44 | "hx_gen_microcode": 506, 45 | "offsetof_mbl_array_t_qty": (52, 64), 46 | "offsetof_mbl_array_t_natural": (464, 560), 47 | "offsetof_mblock_t_head": (40, 48), 48 | "offsetof_mblock_t_succset": (352, 368), 49 | "offsetof_minsn_t_opcode": (0, 0), 50 | "offsetof_minsn_t_next": (8, 8), 51 | "offsetof_minsn_t_l": (32, 32), 52 | "offsetof_minsn_t_r": (48, 48), 53 | "offsetof_minsn_t_d": (64, 64), 54 | "offsetof_mop_t_t": (0, 0), 55 | "offsetof_mop_t_union": (8, 8), 56 | "offsetof_mcallinfo_t_args": (24, 24), 57 | "sizeof_mcallarg_t": (64, 72), 58 | "offsetof_mop_pair_t_lop": (0, 0), 59 | "offsetof_mop_pair_t_hop": (16, 16), 60 | } 61 | } 62 | 63 | @staticmethod 64 | def get_library(): 65 | dllname = "ida64" if ida_idaapi.__EA64__ else "ida" 66 | if sys.platform == "win32": 67 | dllname, dlltype = dllname + ".dll", ctypes.windll 68 | elif sys.platform == "linux2": 69 | dllname, dlltype = "lib" + dllname + ".so", ctypes.cdll 70 | elif sys.platform == "darwin": 71 | dllname, dlltype = "lib" + dllname + ".dylib", ctypes.cdll 72 | return dlltype[os.path.join(ida_diskio.idadir(None), dllname)] 73 | 74 | _cfg, _lib, _dsp, _rch = None, None, None, None 75 | 76 | @classmethod 77 | def init(cls): 78 | if ida_hexrays.init_hexrays_plugin(): 79 | version = ida_pro.IDA_SDK_VERSION 80 | if version in Native.VALUES: 81 | cls._cfg = Native.VALUES[version] 82 | cls._lib = Native.get_library() 83 | cls._dsp = cls.get_dispatcher() 84 | cls._rch = 1 if ida_idaapi.__EA64__ else 0 85 | return True 86 | return False 87 | 88 | @classmethod 89 | def get_dispatcher(cls): 90 | callui = ctypes.c_void_p.in_dll(cls._lib, "callui") 91 | 92 | def broadcast(magic, *args): 93 | func_type = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_int, 94 | ctypes.c_ulonglong, 95 | ctypes.POINTER(ctypes.c_void_p)) 96 | func_code = cls._cfg["ui_broadcast"] 97 | return func_type(callui.value)(func_code, magic, *args) 98 | 99 | hexdsp = ctypes.c_void_p() 100 | broadcast(cls._cfg["magic"], ctypes.byref(hexdsp)) 101 | return hexdsp 102 | 103 | @classmethod 104 | def term(cls): 105 | return ida_hexrays.term_hexrays_plugin() 106 | 107 | class qvector(ctypes.Structure): 108 | _fields_ = [("array", ctypes.c_void_p), 109 | ("n", ctypes.c_size_t), 110 | ("alloc", ctypes.c_size_t)] 111 | 112 | @classmethod 113 | def qvector_str(cls, v): 114 | return "" if v.n == 0 else ctypes.cast(v.array, ctypes.c_char_p).value 115 | 116 | @classmethod 117 | def gen_microcode(cls, fn, hf, retlist, flags, reqmat): 118 | class mba_ranges_t(ctypes.Structure): 119 | _fields_ = [("pfn", ctypes.c_void_p), 120 | ("ranges", cls.qvector)] 121 | 122 | fn = ctypes.c_void_p(int(fn.this)) 123 | mbr = mba_ranges_t() 124 | mbr.pfn = fn 125 | mbr.ranges = cls.qvector() 126 | mbr = ctypes.c_void_p(ctypes.addressof(mbr)) 127 | hf = ctypes.c_void_p(int(hf.this)) 128 | retlist = ctypes.c_void_p(retlist) 129 | 130 | func_type = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_int, 131 | ctypes.c_void_p, ctypes.c_void_p, 132 | ctypes.c_void_p, ctypes.c_int, 133 | ctypes.c_int) 134 | func_code = cls._cfg["hx_gen_microcode"] 135 | return func_type(cls._dsp.value)(func_code, 136 | mbr, hf, retlist, flags, reqmat) 137 | 138 | @classmethod 139 | def mbl_array_t_print(cls, mba, vp): 140 | vp = ctypes.c_void_p(int(vp.this)) 141 | func_type = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_void_p, 142 | ctypes.c_void_p) 143 | func_code = cls._cfg["hx_mbl_array_t_print"] 144 | return func_type(cls._dsp.value)(func_code, mba, vp) 145 | 146 | @classmethod 147 | def mbl_array_t_qty(cls, mba): 148 | offset = cls._cfg["offsetof_mbl_array_t_qty"][cls._rch] 149 | return ctypes.c_int.from_address(mba + offset) 150 | 151 | @classmethod 152 | def mbl_array_t_get_mblock(cls, mba, n): 153 | offset = cls._cfg["offsetof_mbl_array_t_natural"][cls._rch] 154 | qty = cls.mbl_array_t_qty(mba).value 155 | field_type = ctypes.POINTER(ctypes.c_void_p * qty) 156 | return field_type.from_address(mba + offset).contents[n] 157 | 158 | @classmethod 159 | def mblock_t_succset(cls, mblock): 160 | offset = cls._cfg["offsetof_mblock_t_succset"][cls._rch] 161 | succset = cls.qvector.from_address(mblock + offset) 162 | if succset.n > 0: 163 | array = (ctypes.c_int * succset.n).from_address(succset.array) 164 | for i in range(succset.n): 165 | yield array[i] 166 | 167 | @classmethod 168 | def mblock_t_print(cls, mblock, vp): 169 | vp = ctypes.c_void_p(int(vp.this)) 170 | func_type = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_void_p, 171 | ctypes.c_void_p) 172 | func_code = cls._cfg["hx_mblock_t_print"] 173 | return func_type(cls._dsp.value)(func_code, mblock, vp) 174 | 175 | @classmethod 176 | def mblock_t_get_minsn(cls, mblock, serial): 177 | offset = cls._cfg["offsetof_mblock_t_head"][cls._rch] 178 | minsn = ctypes.c_void_p.from_address(mblock + offset) 179 | for i in range(serial): 180 | if not minsn: 181 | break 182 | offset = cls._cfg["offsetof_minsn_t_next"][cls._rch] 183 | minsn = ctypes.c_void_p.from_address(minsn.value + offset) 184 | return minsn 185 | 186 | @classmethod 187 | def minsn_t_opcode(cls, minsn): 188 | offset = cls._cfg["offsetof_minsn_t_opcode"][cls._rch] 189 | return ctypes.c_int.from_address(minsn.value + offset) 190 | 191 | @classmethod 192 | def minsn_t_l(cls, minsn): 193 | offset = cls._cfg["offsetof_minsn_t_l"][cls._rch] 194 | return ctypes.c_void_p(minsn.value + offset) 195 | 196 | @classmethod 197 | def minsn_t_r(cls, minsn): 198 | offset = cls._cfg["offsetof_minsn_t_r"][cls._rch] 199 | return ctypes.c_void_p(minsn.value + offset) 200 | 201 | @classmethod 202 | def minsn_t_d(cls, minsn): 203 | offset = cls._cfg["offsetof_minsn_t_d"][cls._rch] 204 | return ctypes.c_void_p(minsn.value + offset) 205 | 206 | @classmethod 207 | def minsn_t_print(cls, minsn, shins_flags=ida_hexrays.SHINS_SHORT | 208 | ida_hexrays.SHINS_VALNUM): 209 | py_vout = cls.qvector() 210 | vout = ctypes.cast(ctypes.addressof(py_vout), ctypes.c_void_p) 211 | func_type = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_void_p, 212 | ctypes.c_void_p, ctypes.c_int) 213 | func_code = cls._cfg["hx_minsn_t_print"] 214 | func_type(cls._dsp.value)(func_code, minsn, vout, shins_flags) 215 | return cls.qvector_str(py_vout) 216 | 217 | @classmethod 218 | def mop_t_t(cls, mop): 219 | offset = cls._cfg["offsetof_mop_t_t"][cls._rch] 220 | return ctypes.c_uint8.from_address(mop.value + offset) 221 | 222 | @classmethod 223 | def mop_t_union(cls, mop): 224 | offset = cls._cfg["offsetof_mop_t_union"][cls._rch] 225 | return ctypes.c_void_p.from_address(mop.value + offset) 226 | 227 | @classmethod 228 | def mop_t_print(cls, mop, shins_flags=ida_hexrays.SHINS_SHORT | 229 | ida_hexrays.SHINS_VALNUM): 230 | py_vout = cls.qvector() 231 | vout = ctypes.cast(ctypes.addressof(py_vout), ctypes.c_void_p) 232 | func_type = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_void_p, 233 | ctypes.c_void_p, ctypes.c_int) 234 | func_code = cls._cfg["hx_mop_t_print"] 235 | func_type(cls._dsp.value)(func_code, mop, vout, shins_flags) 236 | return cls.qvector_str(py_vout) 237 | 238 | @classmethod 239 | def mcallinfo_t_args(cls, f): 240 | offset = cls._cfg["offsetof_mcallinfo_t_args"][cls._rch] 241 | return ctypes.c_void_p(f.value + offset) 242 | 243 | @classmethod 244 | def mcallargs_t_iter(cls, args): 245 | args = ctypes.cast(args, ctypes.POINTER(cls.qvector)).contents 246 | for i in range(args.n): 247 | size = cls._cfg["sizeof_mcallarg_t"][cls._rch] 248 | yield ctypes.c_void_p(args.array + size * i) 249 | 250 | @classmethod 251 | def mop_pair_t_lop(cls, pair): 252 | offset = cls._cfg["offsetof_mop_pair_t_lop"][cls._rch] 253 | return ctypes.c_void_p(pair.value + offset) 254 | 255 | @classmethod 256 | def mop_pair_t_hop(cls, pair): 257 | offset = cls._cfg["offsetof_mop_pair_t_hop"][cls._rch] 258 | return ctypes.c_void_p(pair.value + offset) 259 | 260 | 261 | class MCInsnView(ida_graph.GraphViewer): 262 | def __init__(self, mba, func, mmat, block, serial): 263 | title = "MCInsn View - %s at %s / %d.%d" % (func, mmat, block, serial) 264 | ida_graph.GraphViewer.__init__(self, title, True) 265 | 266 | self.mblock = Native.mbl_array_t_get_mblock(mba, block) 267 | self.minsn = Native.mblock_t_get_minsn(self.mblock, serial) 268 | 269 | def _insert_minsn(self, minsn): 270 | text = MCODES[Native.minsn_t_opcode(minsn).value] 271 | text += '\n' + Native.minsn_t_print(minsn) 272 | node_id = self.AddNode(text) 273 | 274 | self._insert_mop(Native.minsn_t_l(minsn), node_id) 275 | self._insert_mop(Native.minsn_t_r(minsn), node_id) 276 | self._insert_mop(Native.minsn_t_d(minsn), node_id) 277 | return node_id 278 | 279 | def _insert_mop(self, mop, parent): 280 | t = Native.mop_t_t(mop).value 281 | if t == 0: 282 | return -1 283 | 284 | text = MOPTS[t] + '\n' + Native.mop_t_print(mop) 285 | node_id = self.AddNode(text) 286 | self.AddEdge(parent, node_id) 287 | 288 | if t == MOPTS.index("mop_d"): 289 | dst = self._insert_minsn(Native.mop_t_union(mop)) 290 | self.AddEdge(node_id, dst) 291 | elif t == MOPTS.index("mop_f"): 292 | f = Native.mop_t_union(mop) 293 | args = Native.mcallinfo_t_args(f) 294 | for arg in Native.mcallargs_t_iter(args): 295 | self._insert_mop(arg, node_id) 296 | elif t == MOPTS.index("mop_a"): 297 | self._insert_mop(Native.mop_t_union(mop), node_id) 298 | elif t == MOPTS.index("mop_p"): 299 | pair = Native.mop_t_union(mop) 300 | self._insert_mop(Native.mop_pair_t_lop(pair), node_id) 301 | self._insert_mop(Native.mop_pair_t_hop(pair), node_id) 302 | return node_id 303 | 304 | def OnRefresh(self): 305 | self.Clear() 306 | self._insert_minsn(self.minsn) 307 | return True 308 | 309 | def OnGetText(self, node_id): 310 | return self._nodes[node_id] 311 | 312 | 313 | class MCGraphView(ida_graph.GraphViewer): 314 | def __init__(self, mba, func, mmat): 315 | title = "MCGraph View - %s at %s" % (func, mmat) 316 | ida_graph.GraphViewer.__init__(self, title, True) 317 | self._mba = mba 318 | 319 | def OnRefresh(self): 320 | self.Clear() 321 | qty = Native.mbl_array_t_qty(self._mba).value 322 | for src in range(qty): 323 | self.AddNode(src) 324 | for src in range(qty): 325 | mblock = Native.mbl_array_t_get_mblock(self._mba, src) 326 | for dest in Native.mblock_t_succset(mblock): 327 | self.AddEdge(src, dest) 328 | return True 329 | 330 | def OnGetText(self, node): 331 | mblock = Native.mbl_array_t_get_mblock(self._mba, node) 332 | vp = ida_hexrays.qstring_printer_t(None, True) 333 | Native.mblock_t_print(mblock, vp) 334 | return vp.s 335 | 336 | 337 | class MCTextView(ida_kernwin.simplecustviewer_t): 338 | def __init__(self, mba, func, mmat): 339 | ida_kernwin.simplecustviewer_t.__init__(self) 340 | self._mba = mba 341 | self._func = func 342 | self._mmat = mmat 343 | title = "MCText View - %s at %s" % (func, mmat) 344 | self.Create(title) 345 | 346 | vp = ida_hexrays.qstring_printer_t(None, True) 347 | Native.mbl_array_t_print(mba, vp) 348 | for line in vp.s.split('\n'): 349 | self.AddLine(line) 350 | 351 | def OnKeydown(self, vkey, shift): 352 | if shift == 0 and vkey == ord("G"): 353 | MCGraphView(self._mba, self._func, self._mmat).Show() 354 | return True 355 | elif shift == 0 and vkey == ord("I"): 356 | widget = self.GetWidget() 357 | line = ida_kernwin.get_custom_viewer_curline(widget, False) 358 | line = ida_lines.tag_remove(line) 359 | if '.' in line: 360 | block, serial = line.split('.')[:2] 361 | serial = serial.strip().split(' ')[0] 362 | MCInsnView(self._mba, self._func, self._mmat, 363 | int(block), int(serial)).Show() 364 | return True 365 | return False 366 | 367 | 368 | class MCExplorer(ida_idaapi.plugin_t): 369 | flags = 0 370 | comment = "Microcode Explorer" 371 | help = "" 372 | wanted_name = "MCExplorer" 373 | wanted_hotkey = "Ctrl+Shift+M" 374 | 375 | @staticmethod 376 | def ask_desired_maturity(): 377 | class MaturityForm(ida_kernwin.Form): 378 | def __init__(self): 379 | ctrl = ida_kernwin.Form.DropdownListControl(LEVELS) 380 | form = """Select maturity level 381 |