├── .gitignore ├── README.md ├── __init__.py ├── cpp_plugin ├── __init__.py ├── hooks.py └── plugin.py ├── cpp_utils.py ├── decompiler_utils.py ├── examples ├── a.cpp ├── a32_stripped └── a64_stripped ├── images ├── f_b_after.png ├── f_b_before.png ├── f_b_choose_vtable.png ├── f_b_union_choose.png ├── f_b_xrefs.png ├── main_after.png ├── main_before.png └── vtable_c_z.png ├── medigate_cpp_plugin.py ├── plugins └── ida-referee │ ├── LICENSE │ ├── README.md │ └── referee.py ├── rtti_parser.py └── utils.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | *.pyc 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ida_medigate C++ plugin for IDA Pro 2 | 3 | # 4 | 5 | [TOC] 6 | 7 | # Motivation And Background 8 | 9 | Reverse engineering of compiled C++ code is not fun. Static Reverse engineering of compiled C++ code is **frustrating.** The main reason which makes it so hard is virtual functions. In contrast to compiled C code, there is no clear code flow. Too many times one can spend much time trying to understand what is the next virtual function is called, rather than just see the function like in compiled C code. 10 | 11 | When one investigates a virtual function, the amount of time he or she needs to effort in order to find its xref, is not sense. 12 | 13 | After too many C++ RE projects, I gave up and decided I need a flexible (Python) and stable (which I can easily maintain) tool for this type of research. Most of this plugin was written in January 2018, and recently I decided to clean the dust and add support of the new IDA (7.2) classes support. 14 | 15 | This plugin isn't intended to work always "out of the box", but to be another tool for the reverser. 16 | 17 | # **About** 18 | 19 | The plugin consists of two parts: 20 | 21 | 1. Implementation of C++ classes and polymorphism over IDA 22 | 2. A RTTI parser which rebuilds the classes hierarchy 23 | 24 | This first part is not dependent on the second part, so it possible to use the plugin to reverse engineering a binary that doesn't contain RTTI, by defining those classes manually based on the plugin's API. 25 | 26 | What makes the plugin unique is the fact it uses the same environment the researcher is already familiar with, and doesn't add any new menu or object, and based on the known IDA building blocks (structure, union, type for structure's members, etc) - **This enable the plugin to support C++ abstracting for every architecture IDA supports**. 27 | 28 | **Note:** The RTTI parser parses x86/x64 g++ RTTI, but its structure enables to add support for more architectures and compilers **easily.** 29 | 30 | # Requirements 31 | 32 | * IDA 7.5 SP + Hex-Rays Decompiler + Python 3 33 | * This version we partially support disassembly with no decompiler 34 | * Linux - There is no anything that really depends on Linux, but the plugin was tested on IDA Linux version. 35 | * [ida-referee](https://github.com/joeleong/ida-referee): We use this useful plugin to save xrefs for struct's members uses in the decompiler. The original plugin doesn't support Python3 so we port it (under the directory `plugins/`) 36 | 37 | # Installation: 38 | 39 | Copy `medigate_cpp_plugin` to the `plugins` directory and add the source code path to your `idapythonrc.py` file 40 | 41 | Copy `plugins/ida-referee/referee.py`to the same directory. 42 | 43 | # Features: 44 | 45 | Assuming the binary original source code is the following (`examples/a.cpp`): 46 | 47 | ```c++ 48 | 49 | using namespace std; 50 | 51 | class A { 52 | public: 53 | int x_a; 54 | virtual int f_a()=0; 55 | }; 56 | 57 | class B : public A{ 58 | public: 59 | int x_b; 60 | int f_a(){x_a = 0;} 61 | virtual int f_b(){this->f_a();} 62 | }; 63 | 64 | class Z { 65 | public: 66 | virtual int f_z1(){cout << "f_z1";} 67 | virtual int f_z2(){cout << "f_z2";} 68 | }; 69 | 70 | class C: public B, public Z{ 71 | public: 72 | int f_a(){x_a = 5;} 73 | int x_c; 74 | int f_c(){x_c = 0;} 75 | virtual int f_z1(){cout << "f_z3";} 76 | }; 77 | 78 | 79 | int main() 80 | { 81 | C *c = new C(); 82 | c->f_a(); 83 | c->f_b(); 84 | c->f_z1(); 85 | c->f_z2(); 86 | 87 | return 0; 88 | } 89 | ``` 90 | 91 | The binary is stripped but contains RTTI. 92 | 93 | ## RTTI Classes Hierarchy Rebuilding 94 | 95 | When we just load the binary, the `main` function (`sub_84D` in the 32 bit version) looks like: 96 | 97 | ![](images/main_before.png) 98 | 99 | Initiate the g++ RTTI parser and run it, using: 100 | 101 | `from ida_medigate.rtti_parser import GccRTTIParser` 102 | 103 | `GccRTTIParser.init_parser()` 104 | 105 | `GccRTTIParser.build_all()` 106 | 107 | Now refresh struct C (see Remarks section), cast `v0` to be `C *`, decompile again: 108 | 109 | ![](images/main_after.png) 110 | 111 | ## Manual Classes Hierarchy Rebuilding 112 | 113 | For cases that there are no RTTI, our infrastructure still enables to manually define c++ class. For the same example (examples/a32_stripped) you can create manually struct B, then select it's virtual table and type 114 | 115 | 116 | 117 | ![](images/f_b_choose_vtable.png) 118 | 119 | `from ida_medigate import cpp_utils` 120 | 121 | `cpp_utils.make_vtable("B")` 122 | 123 | `make_vtable` can also get `vtable_ea` and `vtable_ea_stop` instead of the selected area. 124 | 125 | Then create struct C, and apply the inheritance: 126 | 127 | `cpp_utils.add_baseclass("C", "B")` 128 | 129 | Now you can rebuild class C vtable by selecting it and typing: 130 | 131 | `cpp_utils.make_vtable("C")` 132 | 133 | Add structure Z, rebuild its vtable too, and now is the cool part: 134 | 135 | `cpp_utils.add_baseclass("C", "Z", 0x0c, to_update=True)` which apply C inheritance of Z at offset 0x0c and refresh the struct too (see remarks). 136 | 137 | The last thing remained is too update the second vtable of C, the one that implements the interface of Z. Mark this vtable and type: 138 | 139 | `cpp_utils.make_vtable("C", offset_in_class=0x0c)` 140 | 141 | ida_medigate knows that this vtable is the vtable of class Z and the result will be: 142 | 143 | ![](images/vtable_c_z.png) 144 | 145 | The final result is the same like in the RTTI case: 146 | 147 | ![](images/main_after.png) 148 | 149 | ## Synchronization between functions and vtable members 150 | 151 | When new a vtable struct is created (by the RTTI parser of manually by the user) each function which hasn't changed yet is renamed, decompiled, and set its first argument to `this`. 152 | 153 | Double click on a structure's member which corresponds to such a function will navigate to the function, so the user can read the C++ code flow in a convenient way! 154 | 155 | Every name or type changing of a function or its corresponding function pointer member in vtables is hooked and synchronized among all of them. This means for example, the user could change the vtable member type through the decompiler window, and this new type (prototype) will be applied on the target function too. 156 | 157 | ## Convenient reading of polymorphic code 158 | 159 | In line 15 at the previous image, there is a call to B::sub_9A8 (B::f_b in the source code). This function argument is `B *`: 160 | 161 | ![](images/f_b_before.png) 162 | 163 | But, this function also might be called by a `C` instance (up-casting). we want to see the virtual function it's instance would call. Assume there are many potential derived classes so casting `this` to `C *` not always possible. For that reason, we implement a union for each baseclass that has sons that have a different virtual table. One can choose to show a different derived virtual table of `B `'s derivatives by click alt+y (the shortcut for choosing different union member): 164 | 165 | ![](images/f_b_union_choose.png) 166 | 167 | so ultimately we can "cast" only specific calls to different virtual function: 168 | 169 | ![](images/f_b_after.png) 170 | 171 | ## **Virtual Functions xref** 172 | 173 | The holy-grail of frustrated C++ reverse engineers. We maintain xrefs from virtual functions to the vtable struct's members which represents them! 174 | 175 | Combining this with `ida-referee` enables us to track all the xrefs of virtual functions calls! 176 | 177 | ![](images/f_b_xrefs.png) 178 | 179 | *A limitation: we can only track virtual calls which already been decompiled. Fortunately, the auto-analysis knows to populate an argument type between functions, so with the iterative process of casting more arguments->decompiling all the relevant functions -> reading the code again and casting more arguments (...) this ability becomes really **powerful!*** 180 | 181 | # Remarks 182 | 183 | * The way we mark structure members as subclasses in IDAPython isn't synchronized right away to the IDB. The hack we do is to edit the structure so a synchronization will be triggered. You also may use 184 | 185 | `utils.refresh_struct(struct_ptr)` 186 | 187 | which adds a dummy field at the end of the struct and then undefined it. -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = ["utils", "rtti_parser", "cpp_utils", "decompiler_utils"] 2 | -------------------------------------------------------------------------------- /cpp_plugin/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/medigateio/ida_medigate/63c7318a272640c306bb88912027fef987261f15/cpp_plugin/__init__.py -------------------------------------------------------------------------------- /cpp_plugin/hooks.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import ida_frame 3 | import ida_funcs 4 | import ida_hexrays 5 | import ida_idp 6 | import ida_kernwin 7 | import ida_nalt 8 | import ida_name 9 | import ida_struct 10 | import idaapi 11 | import idc 12 | from idc import BADADDR 13 | from .. import cpp_utils, utils 14 | 15 | 16 | logging.basicConfig( 17 | filename="/tmp/cpp_plugin.log", 18 | filemode="a", 19 | level=logging.DEBUG, 20 | format="%(asctime)s - %(levelname)s - %(message)s", 21 | ) 22 | 23 | 24 | class CPPHooks(ida_idp.IDB_Hooks): 25 | def __init__(self, is_decompiler_on): 26 | super(CPPHooks, self).__init__() 27 | self.is_decompiler_on = is_decompiler_on 28 | 29 | def renamed(self, ea, new_name, local_name): 30 | if utils.is_func(ea): 31 | func, args_list = cpp_utils.post_func_name_change(new_name, ea) 32 | self.unhook() 33 | for args in args_list: 34 | func(*args) 35 | self.hook() 36 | return 0 37 | 38 | def func_updated(self, pfn): 39 | func, args_list = cpp_utils.post_func_type_change(pfn) 40 | self.unhook() 41 | for args in args_list: 42 | func(*args) 43 | self.hook() 44 | return 0 45 | 46 | def renaming_struc_member(self, sptr, mptr, newname): 47 | if sptr.is_frame(): 48 | return 0 49 | cpp_utils.post_struct_member_name_change(mptr, newname) 50 | return 0 51 | 52 | def struc_member_changed(self, sptr, mptr): 53 | cpp_utils.post_struct_member_type_change(mptr) 54 | return 0 55 | 56 | def ti_changed(self, ea, typeinf, fnames): 57 | if self.is_decompiler_on: 58 | res = ida_struct.get_member_by_id(ea) 59 | if res is not None: 60 | m, name, sptr = res 61 | if sptr.is_frame(): 62 | func = ida_funcs.get_func(ida_frame.get_func_by_frame(sptr.id)) 63 | if func is not None: 64 | return self.func_updated(func) 65 | elif utils.is_func(ea): 66 | return self.func_updated(ida_funcs.get_func(ea)) 67 | return 0 68 | 69 | 70 | class CPPUIHooks(ida_kernwin.View_Hooks): 71 | def view_dblclick(self, viewer, point): 72 | widget_type = ida_kernwin.get_widget_type(viewer) 73 | if not (widget_type == 48 or widget_type == 28): 74 | return 75 | # Decompiler or Structures window 76 | func_cand_name = None 77 | place, x, y = ida_kernwin.get_custom_viewer_place(viewer, False) 78 | if place.name() == "structplace_t": # Structure window: 79 | structplace = ida_kernwin.place_t_as_structplace_t(place) 80 | if structplace is not None: 81 | s = ida_struct.get_struc(ida_struct.get_struc_by_idx(structplace.idx)) 82 | if s: 83 | member = ida_struct.get_member(s, structplace.offset) 84 | if member: 85 | func_cand_name = ida_struct.get_member_name(member.id) 86 | if func_cand_name is None: 87 | line = utils.get_curline_striped_from_viewer(viewer) 88 | func_cand_name = cpp_utils.find_valid_cppname_in_line(line, x) 89 | if func_cand_name is not None: 90 | func_cand_ea = ida_name.get_name_ea(BADADDR, func_cand_name) 91 | if func_cand_ea is not None and utils.is_func(func_cand_ea): 92 | idc.jumpto(func_cand_ea) 93 | 94 | 95 | class Polymorphism_fixer_visitor_t(ida_hexrays.ctree_visitor_t): 96 | def __init__(self, cfunc): 97 | ida_hexrays.ctree_visitor_t.__init__(self, ida_hexrays.CV_PARENTS) 98 | self.cfunc = cfunc 99 | self.counter = 0 100 | self.selections = [] 101 | 102 | def get_vtables_union_name(self, expr): 103 | if expr.op != ida_hexrays.cot_memref: 104 | return None 105 | typeinf = expr.type 106 | if typeinf is None: 107 | return None 108 | if not typeinf.is_union(): 109 | return None 110 | union_name = typeinf.get_type_name() 111 | if not cpp_utils.is_vtables_union_name(union_name): 112 | return None 113 | return union_name 114 | 115 | def build_classes_chain(self, expr): 116 | chain = [] 117 | n_expr = expr.x 118 | while n_expr.op == ida_hexrays.cot_memref: 119 | chain.insert(0, n_expr.type.get_type_name()) 120 | n_expr = n_expr.x 121 | chain.insert(0, n_expr.type.get_type_name()) 122 | if n_expr.op == ida_hexrays.cot_memptr: 123 | chain.insert(0, n_expr.x.type.get_pointed_object().get_type_name()) 124 | elif n_expr.op == ida_hexrays.cot_idx: 125 | logging.debug("encountered idx, skipping") 126 | return None 127 | return chain 128 | 129 | def find_best_member(self, chain, union_name): 130 | for cand in chain: 131 | result = ida_struct.get_member_by_fullname(union_name + "." + cand) 132 | if result: 133 | m, s = result 134 | logging.debug("Found class: %s, offset=%d", cand, m.soff) 135 | return m 136 | return None 137 | 138 | def get_vtable_sptr(self, m): 139 | vtable_type = utils.get_member_tinfo(m) 140 | if not (vtable_type and vtable_type.is_ptr()): 141 | logging.debug("vtable_type isn't ptr %s", vtable_type) 142 | return None 143 | 144 | vtable_struc_typeinf = vtable_type.get_pointed_object() 145 | if not (vtable_struc_typeinf and vtable_struc_typeinf.is_struct()): 146 | logging.debug("vtable isn't struct (%s)", vtable_struc_typeinf.dstr()) 147 | return None 148 | 149 | vtable_struct_name = vtable_struc_typeinf.get_type_name() 150 | vtable_sptr = utils.get_sptr_by_name(vtable_struct_name) 151 | if vtable_sptr is None: 152 | logging.debug( 153 | "0x%x: Oh no %s is not a valid struct", 154 | self.cfunc.entry_ea, 155 | vtable_struct_name, 156 | ) 157 | return None 158 | 159 | return vtable_sptr 160 | 161 | def get_ancestors(self): 162 | vtable_expr = self.parents.back().cexpr 163 | if vtable_expr.op not in (ida_hexrays.cot_memptr, ida_hexrays.cot_memref): 164 | return None 165 | 166 | if self.parents.size() < 2: 167 | logging.debug("parents size less than 2 (%d)", self.parents.size()) 168 | return None 169 | 170 | idx_cexpr = None 171 | funcptr_parent = None 172 | funcptr_item = self.parents.at(self.parents.size() - 2) 173 | if not funcptr_item.is_expr(): 174 | logging.debug( 175 | "funcptr_item is not expr!: %s %s %d", 176 | type(funcptr_item), 177 | funcptr_item.is_expr(), 178 | funcptr_item.op, 179 | ) 180 | return None 181 | funcptr_expr = funcptr_item.cexpr 182 | if funcptr_expr.op == ida_hexrays.cot_idx: 183 | idx_cexpr = funcptr_expr 184 | if self.parents.size() < 4: 185 | logging.debug( 186 | "there is idx but parents size less than 3 (%d)", 187 | self.parents.size(), 188 | ) 189 | return None 190 | 191 | funcptr_expr = self.parents.at(self.parents.size() - 3) 192 | if not funcptr_expr.is_expr(): 193 | logging.debug("funcptr isn't expr") 194 | return None 195 | funcptr_expr = funcptr_expr.cexpr 196 | funcptr_parent = self.parents.at(self.parents.size() - 4) 197 | if not funcptr_parent.is_expr(): 198 | logging.debug("funcptr_parent isn't expr") 199 | return None 200 | funcptr_parent = funcptr_parent.cexpr 201 | if funcptr_expr.op not in (ida_hexrays.cot_memptr, ida_hexrays.cot_memref): 202 | 203 | logging.debug("funcptr_expr isn't -> (%s)", funcptr_expr.opname) 204 | return None 205 | 206 | return funcptr_parent, funcptr_expr, idx_cexpr, vtable_expr 207 | 208 | def fix_member_idx(self, idx_cexpr): 209 | num = 0 210 | if idx_cexpr: 211 | # wrong vtable*, so it might be too short struct, like: 212 | # .vtable.PdmAcqServiceIf[1].___cxa_pure_virtual_2 213 | if idx_cexpr.y.op != ida_hexrays.cot_num: 214 | logging.debug( 215 | "0x%x: idx doesn't contains a num but %s", 216 | self.cfunc.entry_ea, 217 | idx_cexpr.y.opname, 218 | ) 219 | return -1 220 | num = idx_cexpr.y.get_const_value() 221 | if not (idx_cexpr.type and idx_cexpr.type.is_struct()): 222 | logging.debug( 223 | "0x%x idx type isn't struct %s", self.cfunc.entry_ea, idx_cexpr.type 224 | ) 225 | return -1 226 | idx_struct = utils.get_struc_from_tinfo(idx_cexpr.type) 227 | if idx_struct is None: 228 | logging.debug( 229 | "0x%x idx type isn't pointing to struct %s", 230 | self.cfunc.entry_ea, 231 | idx_cexpr.type, 232 | ) 233 | return -1 234 | struct_size = ida_struct.get_struc_size(idx_struct) 235 | num *= struct_size 236 | return num 237 | 238 | def get_vtable_member_type(self, vtable_sptr, offset): 239 | vtable_struct_name = ida_struct.get_struc_name(vtable_sptr.id) 240 | try: 241 | funcptr_member = ida_struct.get_member(vtable_sptr, offset) 242 | except TypeError as e: 243 | logging.exception("0x%x: bad offset: 0x%x", self.cfunc.entry_ea, offset) 244 | return None 245 | 246 | if funcptr_member is None: 247 | logging.debug( 248 | "0x%x: %s.%d is not a valid struct member", 249 | self.cfunc.entry_ea, 250 | vtable_struct_name, 251 | offset, 252 | ) 253 | return None 254 | 255 | funcptr_member_type = utils.get_member_tinfo(funcptr_member) 256 | if not funcptr_member_type.is_funcptr(): 257 | logging.debug( 258 | "0x%x: member type (%s) isn't funcptr!", 259 | self.cfunc.entry_ea, 260 | funcptr_member_type.dstr(), 261 | ) 262 | return None 263 | 264 | return funcptr_member_type 265 | 266 | def find_funcptr(self, m): 267 | ancestors = self.get_ancestors() 268 | if ancestors is None: 269 | return None 270 | funcptr_parent, funcptr_expr, idx_cexpr, vtable_expr = ancestors 271 | 272 | vtable_sptr = self.get_vtable_sptr(m) 273 | if vtable_sptr is None: 274 | return None 275 | offset = self.fix_member_idx(idx_cexpr) 276 | if offset == -1: 277 | return None 278 | funcptr_member_type = self.get_vtable_member_type( 279 | vtable_sptr, funcptr_expr.m + offset 280 | ) 281 | return funcptr_member_type 282 | 283 | def dump_expr(self, e): 284 | logging.debug("dump: %s", e.opname) 285 | while e.op in [ 286 | ida_hexrays.cot_memref, 287 | ida_hexrays.cot_memptr, 288 | ida_hexrays.cot_cast, 289 | ida_hexrays.cot_call, 290 | ]: 291 | if e.op in [ida_hexrays.cot_memref, ida_hexrays.cot_memptr]: 292 | logging.debug("(%s, %d, %s", e.opname, e.m, e.type.dstr()) 293 | else: 294 | logging.debug("(%s, %s", e.opname, e.type.dstr()) 295 | e = e.x 296 | 297 | def find_ea(self): 298 | i = self.parents.size() - 1 299 | parent = self.parents.at(i) 300 | ea = BADADDR 301 | while i >= 0 and (parent.is_expr() or parent.op == ida_hexrays.cit_expr): 302 | if parent.cexpr.ea != BADADDR: 303 | ea = parent.cexpr.ea 304 | break 305 | i -= 1 306 | parent = self.parents.at(i) 307 | return ea 308 | 309 | def visit_expr(self, expr): 310 | union_name = self.get_vtables_union_name(expr) 311 | if union_name is None: 312 | return 0 313 | logging.debug("Found union -%s", union_name) 314 | 315 | chain = self.build_classes_chain(expr) 316 | if chain is None: 317 | return 0 318 | 319 | m = self.find_best_member(chain, union_name) 320 | if m is None: 321 | return 0 322 | 323 | ea = self.find_ea() 324 | 325 | funcptr_member_type = self.find_funcptr(m) 326 | 327 | if ea == BADADDR: 328 | logging.debug("BADADDR") 329 | return 0 330 | logging.debug("Found VTABLES, ea: 0x%x", ea) 331 | self.selections.append((ea, m.soff, funcptr_member_type)) 332 | return 0 333 | 334 | 335 | class HexRaysHooks(idaapi.Hexrays_Hooks): 336 | def __init__(self, *args): 337 | idaapi.Hexrays_Hooks.__init__(self, *args) 338 | self.another_decompile_ea = False 339 | 340 | def maturity(self, cfunc, maturity): 341 | if maturity in [idaapi.CMAT_FINAL]: 342 | if self.another_decompile_ea: 343 | self.another_decompile_ea = None 344 | return 0 345 | # if maturity in [idaapi. CMAT_CPA]: 346 | # if maturity in [idaapi.CPA]: 347 | pfv = Polymorphism_fixer_visitor_t(cfunc) 348 | pfv.apply_to_exprs(cfunc.body, None) 349 | logging.debug("results: %s", pfv.selections) 350 | if pfv.selections != []: 351 | for ea, offset, funcptr_member_type in pfv.selections: 352 | intvec = idaapi.intvec_t() 353 | # TODO: Think if needed to distinguished between user 354 | # union members chooses and plugin chooses 355 | if not cfunc.get_user_union_selection(ea, intvec): 356 | intvec.push_back(offset) 357 | cfunc.set_user_union_selection(ea, intvec) 358 | if funcptr_member_type is not None: 359 | ida_nalt.set_op_tinfo(ea, 0, funcptr_member_type) 360 | cfunc.save_user_unions() 361 | self.another_decompile_ea = cfunc.entry_ea 362 | 363 | return 0 364 | 365 | def refresh_pseudocode(self, vu): 366 | if self.another_decompile_ea: 367 | logging.debug("decompile again") 368 | ea = self.another_decompile_ea 369 | ida_hexrays.mark_cfunc_dirty(ea, False) 370 | cfunc = ida_hexrays.decompile(ea) 371 | self.another_decompile_ea = None 372 | vu.switch_to(cfunc, True) 373 | return 0 374 | -------------------------------------------------------------------------------- /cpp_plugin/plugin.py: -------------------------------------------------------------------------------- 1 | import ida_idaapi 2 | import ida_kernwin 3 | import idaapi 4 | from .hooks import CPPHooks, CPPUIHooks, HexRaysHooks 5 | 6 | 7 | class CPPPlugin(ida_idaapi.plugin_t): 8 | """ 9 | This is the main class of the plugin. It subclasses plugin_t as required 10 | by IDA. It holds the modules of plugin, which themselves provides the 11 | functionality of the plugin (hooking/events, interface, networking, etc.). 12 | """ 13 | 14 | # Mandatory definitions 15 | PLUGIN_NAME = "ida_cpp" 16 | PLUGIN_VERSION = "0.0.1" 17 | PLUGIN_AUTHORS = "Medigate" 18 | TOGGLE_HOTKEY = "CTRL+ALT+C" 19 | 20 | # These flags specify that the plugin should persist between databases 21 | # loading and saving, and should not have a menu entry. 22 | flags = ida_idaapi.PLUGIN_FIX | ida_idaapi.PLUGIN_HIDE 23 | comment = "CPP support plugin" 24 | help = "" 25 | wanted_name = PLUGIN_NAME 26 | wanted_hotkey = "" 27 | 28 | def __init__(self): 29 | print("Im up") 30 | self.core_hook = None 31 | self.gui_hook = None 32 | self.hexrays_hooks = None 33 | self.hooking = False 34 | self.is_decompiler_on = False 35 | 36 | def init(self): 37 | """ 38 | This method is called when IDA is loading the plugin. It will first 39 | load the configuration file, then initialize all the modules. 40 | """ 41 | if idaapi.init_hexrays_plugin(): 42 | self.hexrays_hooks = HexRaysHooks() 43 | self.is_decompiler_on = True 44 | self.core_hook = CPPHooks(self.is_decompiler_on) 45 | self.gui_hook = CPPUIHooks() 46 | self.hook() 47 | self.install_hotkey() 48 | keep = ida_idaapi.PLUGIN_KEEP 49 | return keep 50 | 51 | def toggle_hooks(self): 52 | if self.hooking: 53 | self.unhook() 54 | else: 55 | self.hook() 56 | print("C++ plugin is now: %s" % ("On" if self.hooking else "Off")) 57 | 58 | def hook(self): 59 | if self.hexrays_hooks: 60 | self.hexrays_hooks.hook() 61 | self.core_hook.hook() 62 | self.gui_hook.hook() 63 | self.hooking = True 64 | 65 | def unhook(self): 66 | if self.hexrays_hooks: 67 | self.hexrays_hooks.unhook() 68 | self.core_hook.unhook() 69 | self.gui_hook.unhook() 70 | self.hooking = False 71 | 72 | def install_hotkey(self): 73 | ida_kernwin.add_hotkey(self.TOGGLE_HOTKEY, self.toggle_hooks) 74 | 75 | @classmethod 76 | def description(cls): 77 | """Return the description displayed in the console.""" 78 | return "{} v{}".format(cls.PLUGIN_NAME, cls.PLUGIN_VERSION) 79 | 80 | def run(self, _): 81 | """ 82 | This method is called when IDA is running the plugin as a script. 83 | """ 84 | ida_kernwin.warning("IDACpp cannot be run as a script") 85 | return False 86 | 87 | def term(self): 88 | """ 89 | This method is called when IDA is unloading the plugin. It will 90 | terminated all the modules, then save the configuration file. 91 | """ 92 | self.unhook() 93 | idaapi.term_hexrays_plugin() 94 | -------------------------------------------------------------------------------- /cpp_utils.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from functools import partial 3 | 4 | import ida_bytes 5 | import ida_hexrays 6 | import ida_name 7 | import ida_struct 8 | import ida_typeinf 9 | import ida_xref 10 | import idaapi 11 | import idautils 12 | import idc 13 | from idaapi import BADADDR 14 | from . import utils 15 | from .utils import batchmode 16 | 17 | VTABLE_KEYWORD = "vtbl" 18 | VTABLE_UNION_KEYWORD = "VTABLES" 19 | # VTABLES_UNION_VTABLE_FIELD_POSTFIX = "_vtable" 20 | VTABLES_UNION_VTABLE_FIELD_POSTFIX = "" 21 | VTABLE_DELIMITER = "__" 22 | VTABLE_POSTFIX = "_vtbl" 23 | VTABLE_FIELD_NAME = "__vftable" # Name For vtable * field 24 | VTABLE_INSTANCE_DELIMITER = VTABLE_DELIMITER 25 | VTABLE_INSTANCE_KEYWORD = "vtable" 26 | VTABLE_INSTANCE_POSTFIX = VTABLE_INSTANCE_DELIMITER + VTABLE_INSTANCE_KEYWORD 27 | 28 | 29 | def get_vtable_instance_name(class_name, parent_name=None): 30 | name = class_name + VTABLE_INSTANCE_POSTFIX 31 | if parent_name is not None: 32 | name += VTABLE_INSTANCE_DELIMITER + parent_name 33 | return name 34 | 35 | 36 | def get_base_member_name(parent_name, offset): 37 | return "baseclass_%x" % offset 38 | 39 | 40 | def get_vtable_line(ea, stop_ea=None, ignore_list=None, pure_virtual_name=None): 41 | if ignore_list is None: 42 | ignore_list = [] 43 | func_ea = utils.get_ptr(ea) 44 | if ( 45 | utils.is_func(func_ea) 46 | and (stop_ea is None or ea < stop_ea) 47 | and ( 48 | func_ea not in ignore_list 49 | or ( 50 | pure_virtual_name is not None 51 | and idc.GetDisasm(ea).endswith(pure_virtual_name) 52 | ) 53 | ) 54 | ): 55 | return func_ea, ea + utils.WORD_LEN 56 | return None, 0 57 | 58 | 59 | def is_valid_vtable_name(member_name): 60 | return VTABLE_FIELD_NAME in member_name 61 | 62 | 63 | def is_valid_vtable_type(member, member_type): 64 | if member_type.is_ptr(): 65 | struct = utils.deref_struct_from_tinfo(member_type) 66 | return is_struct_vtable(struct) 67 | return False 68 | 69 | 70 | def is_member_vtable(member): 71 | member_type = utils.get_member_tinfo(member) 72 | member_name = ida_struct.get_member_name(member.id) 73 | if not is_valid_vtable_name(member_name): 74 | return False 75 | if not is_valid_vtable_type(member, member_type): 76 | return False 77 | return True 78 | 79 | 80 | def is_struct_vtable(struct): 81 | if struct is None: 82 | return False 83 | struct_name = ida_struct.get_struc_name(struct.id) 84 | return VTABLE_POSTFIX in struct_name 85 | 86 | 87 | def is_vtables_union(union): 88 | if union is None: 89 | return False 90 | if not union.is_union(): 91 | return False 92 | union_name = ida_struct.get_struc_name(union.id) 93 | return is_vtables_union_name(union_name) 94 | 95 | 96 | def is_vtables_union_name(union_name): 97 | return union_name.endswith(VTABLE_UNION_KEYWORD) 98 | 99 | 100 | def find_vtable_at_offset(struct_ptr, vtable_offset): 101 | current_struct = struct_ptr 102 | current_offset = 0 103 | member = ida_struct.get_member(current_struct, vtable_offset) 104 | if member is None: 105 | return None 106 | parents_vtables_classes = [] 107 | current_offset += member.get_soff() 108 | while current_offset < vtable_offset and member is not None: 109 | current_struct = utils.get_member_substruct(member) 110 | if current_struct is None: 111 | return 112 | parents_vtables_classes.append( 113 | [ 114 | ida_struct.get_struc_name(current_struct.id), 115 | vtable_offset - current_offset, 116 | ] 117 | ) 118 | member = ida_struct.get_member(current_struct, vtable_offset - current_offset) 119 | if member is None: 120 | logging.exception( 121 | "Couldn't find vtable at offset %d for %d", 122 | vtable_offset - current_offset, 123 | struct_ptr.id, 124 | ) 125 | current_offset += member.get_soff() 126 | 127 | if current_offset != vtable_offset: 128 | return None 129 | 130 | while member is not None: 131 | if is_member_vtable(member): 132 | return member, current_struct, parents_vtables_classes 133 | current_struct = utils.get_member_substruct(member) 134 | if current_struct is None: 135 | return None 136 | parents_vtables_classes.append( 137 | [ida_struct.get_struc_name(current_struct.id), 0] 138 | ) 139 | member = ida_struct.get_member(current_struct, 0) 140 | 141 | return None 142 | 143 | 144 | def get_class_vtable_struct_name(class_name, vtable_offset_in_class): 145 | if vtable_offset_in_class == 0: 146 | return class_name + "_vtbl" 147 | return "%s_%04X_vtbl" % (class_name, vtable_offset_in_class) 148 | 149 | 150 | def get_class_vtable_field_name(class_name): 151 | return VTABLE_FIELD_NAME 152 | 153 | 154 | def get_class_vtables_union_name(class_name): 155 | return class_name + VTABLE_DELIMITER + VTABLE_UNION_KEYWORD 156 | 157 | 158 | def get_class_vtables_field_name(child_name): 159 | return child_name + VTABLES_UNION_VTABLE_FIELD_POSTFIX 160 | 161 | 162 | def get_interface_empty_vtable_name(): 163 | return "INTERFACE" 164 | 165 | 166 | def install_vtables_union( 167 | class_name, class_vtable_member=None, vtable_member_tinfo=None, offset=0 168 | ): 169 | logging.debug( 170 | "install_vtables_union(%s, %s, %s)", 171 | class_name, 172 | class_vtable_member, 173 | str(vtable_member_tinfo), 174 | ) 175 | if class_vtable_member and vtable_member_tinfo: 176 | old_vtable_sptr = utils.extract_struct_from_tinfo(vtable_member_tinfo) 177 | old_vtable_class_name = ida_struct.get_struc_name(old_vtable_sptr.id) 178 | else: 179 | old_vtable_class_name = get_class_vtable_struct_name(class_name, offset) 180 | old_vtable_sptr = utils.get_sptr_by_name(old_vtable_class_name) 181 | vtables_union_name = old_vtable_class_name 182 | if old_vtable_sptr and not ida_struct.set_struc_name( 183 | old_vtable_sptr.id, old_vtable_class_name + "_orig" 184 | ): 185 | logging.exception( 186 | f"Failed changing {old_vtable_class_name}->" 187 | f"{old_vtable_class_name+'orig'}" 188 | ) 189 | return -1 190 | vtables_union_id = utils.get_or_create_struct_id(vtables_union_name, True) 191 | vtable_member_tinfo = utils.get_typeinf(old_vtable_class_name + "_orig") 192 | if vtables_union_id == BADADDR: 193 | logging.exception( 194 | f"Cannot create union vtable for {class_name}(){vtables_union_name}" 195 | ) 196 | return -1 197 | 198 | vtables_union = ida_struct.get_struc(vtables_union_id) 199 | if not vtables_union: 200 | logging.exception(f"Could retrieve vtables union for {class_name}") 201 | if vtable_member_tinfo is not None: 202 | vtables_union_vtable_field_name = get_class_vtables_field_name(class_name) 203 | else: 204 | vtables_union_vtable_field_name = get_interface_empty_vtable_name() 205 | utils.add_to_struct( 206 | vtables_union, vtables_union_vtable_field_name, vtable_member_tinfo 207 | ) 208 | parent_struct = utils.get_sptr_by_name(class_name) 209 | flag = idaapi.FF_STRUCT 210 | mt = idaapi.opinfo_t() 211 | mt.tid = vtables_union_id 212 | struct_size = ida_struct.get_struc_size(vtables_union_id) 213 | vtables_union_ptr_type = utils.get_typeinf_ptr(vtables_union_name) 214 | if class_vtable_member: 215 | member_ptr = class_vtable_member 216 | else: 217 | member_id = ida_struct.add_struc_member( 218 | parent_struct, 219 | get_class_vtable_field_name(class_name), 220 | offset, 221 | flag, 222 | mt, 223 | struct_size, 224 | ) 225 | member_ptr = ida_struct.get_member_by_id(member_id) 226 | ida_struct.set_member_tinfo( 227 | parent_struct, member_ptr, 0, vtables_union_ptr_type, idaapi.TINFO_DEFINITE 228 | ) 229 | return vtables_union 230 | 231 | 232 | def add_child_vtable(parent_name, child_name, child_vtable_id, offset): 233 | logging.debug( 234 | "add_child_vtable (%s, %s, %s)", 235 | parent_name, 236 | child_name, 237 | child_vtable_id, 238 | ) 239 | parent_vtable_member = ida_struct.get_member( 240 | utils.get_sptr_by_name(parent_name), offset 241 | ) 242 | vtable_member_tinfo = utils.get_member_tinfo(parent_vtable_member) 243 | parent_vtable_struct = utils.get_sptr_by_name( 244 | get_class_vtable_struct_name(parent_name, offset) 245 | ) 246 | if parent_vtable_struct is None: 247 | return None 248 | pointed_struct = utils.extract_struct_from_tinfo(vtable_member_tinfo) 249 | logging.debug("pointed_struct: %s", str(pointed_struct)) 250 | if ( 251 | (pointed_struct is None) 252 | or (not is_struct_vtable(pointed_struct)) 253 | or (parent_vtable_struct.id != pointed_struct.id) 254 | ): 255 | parent_vtable_member = None 256 | logging.debug("Not a struct vtable: %s", str(vtable_member_tinfo)) 257 | 258 | # TODO: Check that struct is a valid vtable by name 259 | if not parent_vtable_struct.is_union(): 260 | logging.debug("%s vtable isn't union -> unionize it!", parent_name) 261 | parent_vtable_struct = install_vtables_union( 262 | parent_name, parent_vtable_member, vtable_member_tinfo, offset 263 | ) 264 | 265 | child_vtable_name = ida_struct.get_struc_name(child_vtable_id) 266 | child_vtable = utils.get_typeinf(child_vtable_name) 267 | logging.debug( 268 | "add_to_struct %s %s", parent_vtable_struct.id, str(child_vtable) 269 | ) 270 | if ida_struct.get_struc_size(child_vtable_id) == 0: 271 | utils.add_to_struct( 272 | ida_struct.get_struc(child_vtable_id), "dummy", None 273 | ) 274 | new_member = utils.add_to_struct( 275 | parent_vtable_struct, get_class_vtables_field_name(child_name), child_vtable 276 | ) 277 | ida_xref.add_dref( 278 | new_member.id, child_vtable_id, ida_xref.XREF_USER | ida_xref.dr_O 279 | ) 280 | 281 | 282 | def update_func_name_with_class(func_ea, class_name): 283 | name = ida_name.get_ea_name(func_ea) 284 | if name.startswith("sub_"): 285 | new_name = class_name + VTABLE_DELIMITER + name 286 | return utils.set_func_name(func_ea, new_name), True 287 | return name, False 288 | 289 | 290 | def update_func_this(func_ea, this_type=None): 291 | functype = None 292 | try: 293 | func_details = utils.get_func_details(func_ea) 294 | if func_details is None: 295 | return None 296 | if this_type: 297 | if len(func_details) > 0: 298 | func_details[0].name = "this" 299 | func_details[0].type = this_type 300 | functype = utils.update_func_details(func_ea, func_details) 301 | except ida_hexrays.DecompilationFailure as e: 302 | logging.exception("Couldn't decompile 0x%x", func_ea) 303 | return functype 304 | 305 | 306 | def add_class_vtable(struct_ptr, vtable_name, offset=BADADDR, vtable_field_name=None): 307 | if vtable_field_name is None: 308 | class_name = ida_struct.get_struc_name(struct_ptr.id) 309 | vtable_field_name = get_class_vtable_field_name(class_name) 310 | vtable_id = ida_struct.get_struc_id(vtable_name) 311 | vtable_type_ptr = utils.get_typeinf_ptr(vtable_name) 312 | new_member = utils.add_to_struct( 313 | struct_ptr, vtable_field_name, vtable_type_ptr, offset, overwrite=True 314 | ) 315 | if new_member is None: 316 | logging.warning( 317 | "vtable of %s couldn't added at offset %d", str(vtable_type_ptr), offset 318 | ) 319 | else: 320 | ida_xref.add_dref(new_member.id, vtable_id, ida_xref.XREF_USER | ida_xref.dr_O) 321 | 322 | 323 | @batchmode 324 | def post_func_name_change(new_name, ea): 325 | xrefs = idautils.XrefsTo(ea, ida_xref.XREF_USER) 326 | xrefs = filter(lambda x: x.type == ida_xref.dr_I and x.user == 1, xrefs) 327 | args_list = [] 328 | for xref in xrefs: 329 | member, old_name, struct = ida_struct.get_member_by_id(xref.frm) 330 | if member is not None and struct is not None: 331 | args_list.append([struct, member.get_soff(), new_name]) 332 | 333 | return utils.set_member_name, args_list 334 | 335 | 336 | def post_struct_member_name_change(member, new_name): 337 | xrefs = idautils.XrefsFrom(member.id) 338 | xrefs = filter(lambda x: x.type == ida_xref.dr_I and x.user == 1, xrefs) 339 | for xref in xrefs: 340 | if utils.is_func(xref.to): 341 | utils.set_func_name(xref.to, new_name) 342 | 343 | 344 | def post_struct_member_type_change(member): 345 | xrefs = idautils.XrefsFrom(member.id) 346 | xrefs = filter(lambda x: x.type == ida_xref.dr_I and x.user == 1, xrefs) 347 | for xref in xrefs: 348 | if utils.is_func(xref.to): 349 | function_ptr_tinfo = idaapi.tinfo_t() 350 | ida_struct.get_member_tinfo(function_ptr_tinfo, member) 351 | if function_ptr_tinfo.is_funcptr(): 352 | function_tinfo = function_ptr_tinfo.get_pointed_object() 353 | if function_tinfo is not None: 354 | ida_typeinf.apply_tinfo( 355 | xref.to, function_tinfo, idaapi.TINFO_DEFINITE 356 | ) 357 | 358 | 359 | @batchmode 360 | def post_func_type_change(pfn): 361 | ea = pfn.start_ea 362 | xrefs = idautils.XrefsTo(ea, ida_xref.XREF_USER) 363 | xrefs = list(filter(lambda x: x.type == ida_xref.dr_I and x.user == 1, xrefs)) 364 | args_list = [] 365 | if len(xrefs) == 0: 366 | return None, [] 367 | try: 368 | xfunc = ida_hexrays.decompile(ea) 369 | func_ptr_typeinf = utils.get_typeinf_ptr(xfunc.type) 370 | for xref in xrefs: 371 | member, old_name, struct = ida_struct.get_member_by_id(xref.frm) 372 | if member is not None and struct is not None: 373 | args_list.append( 374 | [struct, member, 0, func_ptr_typeinf, idaapi.TINFO_DEFINITE] 375 | ) 376 | except Exception: 377 | pass 378 | return ida_struct.set_member_tinfo, args_list 379 | 380 | 381 | def make_funcptr_pt(func, this_type): 382 | return utils.get_typeinf(f"void (*)({str(this_type)} *)") 383 | 384 | 385 | def update_vtable_struct( 386 | functions_ea, 387 | vtable_struct, 388 | class_name, 389 | this_type=None, 390 | get_next_func_callback=get_vtable_line, 391 | vtable_head=None, 392 | ignore_list=None, 393 | add_dummy_member=False, 394 | pure_virtual_name=None, 395 | parent_name=None, 396 | add_func_this=True, 397 | ): 398 | is_first_member = True 399 | if this_type is None: 400 | this_type = utils.get_typeinf_ptr(class_name) 401 | if not add_func_this: 402 | this_type = None 403 | func, next_func = get_next_func_callback( 404 | functions_ea, ignore_list=ignore_list, pure_virtual_name=pure_virtual_name 405 | ) 406 | dummy_i = 1 407 | while func is not None: 408 | new_func_name, is_name_changed = update_func_name_with_class(func, class_name) 409 | func_ptr = None 410 | if ida_hexrays.init_hexrays_plugin(): 411 | if is_name_changed: 412 | func_type = update_func_this(func, this_type) 413 | else: 414 | func_type = update_func_this(func, None) 415 | if func_type is not None: 416 | func_ptr = utils.get_typeinf_ptr(func_type) 417 | else: 418 | func_ptr = make_funcptr_pt(func, this_type) 419 | if add_dummy_member: 420 | utils.add_to_struct(vtable_struct, f"dummy_{dummy_i}", func_ptr) 421 | dummy_i += 1 422 | if is_first_member: 423 | # We did an hack for vtables contained in union vtable with one dummy member 424 | ptr_member = utils.add_to_struct( 425 | vtable_struct, new_func_name, func_ptr, 0, overwrite=True 426 | ) 427 | is_first_member = False 428 | else: 429 | ptr_member = utils.add_to_struct( 430 | vtable_struct, new_func_name, func_ptr, is_offset=True 431 | ) 432 | if ptr_member is None: 433 | logging.exception( 434 | "Couldn't add %s(%s) to %d", 435 | new_func_name, 436 | str(func_ptr), 437 | vtable_struct.id, 438 | ) 439 | ida_xref.add_dref(ptr_member.id, func, ida_xref.XREF_USER | ida_xref.dr_I) 440 | func, next_func = get_next_func_callback( 441 | next_func, ignore_list=ignore_list, pure_virtual_name=pure_virtual_name 442 | ) 443 | 444 | vtable_size = ida_struct.get_struc_size(vtable_struct) 445 | 446 | if vtable_head is None: 447 | vtable_head = functions_ea 448 | ida_bytes.del_items(vtable_head, ida_bytes.DELIT_SIMPLE, vtable_size) 449 | ida_bytes.create_struct(vtable_head, vtable_size, vtable_struct.id) 450 | if parent_name is None and this_type: 451 | parent = utils.deref_struct_from_tinfo(this_type) 452 | parent_name = ida_struct.get_struc_name(parent.id) 453 | if parent_name == class_name: 454 | parent_name = None 455 | utils.set_name_retry(vtable_head, get_vtable_instance_name(class_name, parent_name)) 456 | 457 | 458 | def is_valid_func_char(c): 459 | ALLOWED_CHARS = [":", "_"] 460 | return c.isalnum() or c in ALLOWED_CHARS 461 | 462 | 463 | def find_valid_cppname_in_line(line, idx): 464 | end_idx = idx 465 | start_idx = idx 466 | if len(line) < idx: 467 | return None 468 | while start_idx >= 0 and is_valid_func_char(line[start_idx]): 469 | if line[start_idx] == ":": 470 | if line[start_idx - 1] == ":": 471 | start_idx -= 1 472 | else: 473 | break 474 | start_idx -= 1 475 | while end_idx < len(line) and is_valid_func_char(line[end_idx]): 476 | if line[end_idx] == ":": 477 | if line[end_idx + 1] == ":": 478 | end_idx += 1 479 | else: 480 | break 481 | end_idx += 1 482 | if end_idx > start_idx: 483 | return line[start_idx + 1 : end_idx] 484 | return None 485 | 486 | 487 | def get_overriden_func_names(union_name, offset, get_not_funcs_members=False): 488 | sptr = utils.get_sptr_by_name(union_name) 489 | res = [] 490 | if not sptr.is_union: 491 | return res 492 | 493 | for i in range(ida_struct.get_max_offset(sptr)): 494 | member = ida_struct.get_member(sptr, i) 495 | cls = ida_struct.get_member_name(member.id) 496 | tinfo = utils.get_member_tinfo(member) 497 | logging.debug("Trying %s", cls) 498 | if cls == get_interface_empty_vtable_name() or not tinfo.is_ptr(): 499 | continue 500 | pointed_obj = tinfo.get_pointed_object() 501 | if not pointed_obj.is_struct(): 502 | continue 503 | vtable_sptr = utils.get_sptr_by_name(pointed_obj.get_final_type_name()) 504 | if ida_struct.get_max_offset(vtable_sptr) <= offset: 505 | continue 506 | funcptr_member = ida_struct.get_member(vtable_sptr, offset) 507 | funcptr_type = utils.get_member_tinfo(funcptr_member) 508 | func_name = ida_struct.get_member_name(funcptr_member.id) 509 | if not funcptr_type.is_funcptr() and not get_not_funcs_members: 510 | continue 511 | res.append((cls, func_name)) 512 | return res 513 | 514 | 515 | def set_polymorhpic_func_name(union_name, offset, name, force=False): 516 | for _, func_name in get_overriden_func_names(union_name, offset): 517 | func_name_splitted = func_name.split(VTABLE_DELIMITER) 518 | local_func_name = func_name_splitted[-1] 519 | if local_func_name != name and (force or local_func_name.startswith("sub_")): 520 | ea = utils.get_func_ea_by_name(func_name) 521 | if ea != BADADDR: 522 | new_func_name = VTABLE_DELIMITER.join(func_name_splitted[:-1]) 523 | if new_func_name != "": 524 | new_func_name += VTABLE_DELIMITER 525 | new_func_name += name 526 | logging.debug("0x%x -> %s", ea, new_func_name) 527 | utils.set_func_name(ea, new_func_name) 528 | 529 | 530 | def create_class(class_name, has_vtable, parent_class=None): 531 | class_id = ida_struct.add_struc(BADADDR, class_name) 532 | class_ptr = ida_struct.get_struc(class_id) 533 | # if parent class -> 534 | # if has_vtable-> if not parent- create vtable, if parent - install vtable 535 | return class_ptr 536 | 537 | 538 | def create_vtable_struct(sptr, name, vtable_offset, parent_name=None): 539 | logging.debug("create_vtable_struct(%s, %d)", name, vtable_offset) 540 | vtable_details = find_vtable_at_offset(sptr, vtable_offset) 541 | parent_vtable_member = None 542 | parent_vtable_struct = None 543 | parent_name = None 544 | parents_chain = None 545 | if vtable_details is not None: 546 | logging.debug("Found parent vtable %s %d", name, vtable_offset) 547 | parent_vtable_member, parent_vtable_struct, parents_chain = vtable_details 548 | else: 549 | logging.debug("Couldn't found parent vtable %s %d", name, vtable_offset) 550 | if parent_vtable_member is not None: 551 | parent_name = ida_struct.get_struc_name(parent_vtable_struct.id) 552 | vtable_name = get_class_vtable_struct_name(name, vtable_offset) 553 | if vtable_offset == 0: 554 | this_type = utils.get_typeinf_ptr(name) 555 | else: 556 | this_type = utils.get_typeinf_ptr(parent_name) 557 | if vtable_name is None: 558 | logging.exception( 559 | "create_vtable_struct(%s, %d): vtable_name is" " None", name, vtable_offset 560 | ) 561 | vtable_id = ida_struct.add_struc(BADADDR, vtable_name, False) 562 | if vtable_id == BADADDR: 563 | logging.exception("Couldn't create struct %s", vtable_name) 564 | vtable_struct = ida_struct.get_struc(vtable_id) 565 | if parents_chain: 566 | for parent_name, offset in parents_chain: 567 | add_child_vtable(parent_name, name, vtable_id, offset) 568 | else: 569 | add_class_vtable(sptr, vtable_name, vtable_offset) 570 | 571 | return vtable_struct, this_type 572 | 573 | 574 | def make_vtable( 575 | class_name, 576 | vtable_ea=None, 577 | vtable_ea_stop=None, 578 | offset_in_class=0, 579 | parent_name=None, 580 | add_func_this=True, 581 | _get_vtable_line=get_vtable_line, 582 | ): 583 | if not vtable_ea and not vtable_ea_stop: 584 | vtable_ea, vtable_ea_stop = utils.get_selected_range_or_line() 585 | vtable_struct, this_type = create_vtable_struct( 586 | utils.get_or_create_struct(class_name), class_name, offset_in_class, 587 | parent_name=parent_name 588 | ) 589 | update_vtable_struct( 590 | vtable_ea, 591 | vtable_struct, 592 | class_name, 593 | this_type=this_type, 594 | get_next_func_callback=partial(_get_vtable_line, stop_ea=vtable_ea_stop), 595 | parent_name=parent_name, 596 | add_func_this=add_func_this, 597 | ) 598 | 599 | 600 | def add_baseclass(class_name, baseclass_name, baseclass_offset=0, to_refresh=False): 601 | member_name = get_base_member_name(baseclass_name, baseclass_offset) 602 | struct_ptr = utils.get_sptr_by_name(class_name) 603 | baseclass_ptr = utils.get_sptr_by_name(baseclass_name) 604 | if not struct_ptr or not baseclass_ptr: 605 | return False 606 | member = utils.add_to_struct(struct_ptr, member_name, 607 | member_type=utils.get_typeinf(baseclass_name), 608 | offset=baseclass_offset, 609 | overwrite=True) 610 | if not member: 611 | logging.debug(f"add_baseclass({class_name}. {baseclass_name}): member not found") 612 | return False 613 | member.props |= ida_struct.MF_BASECLASS 614 | if to_refresh: 615 | utils.refresh_struct(struct_ptr) 616 | return True 617 | -------------------------------------------------------------------------------- /decompiler_utils.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | import ida_hexrays 4 | import idc 5 | from . import utils 6 | from idaapi import BADADDR 7 | 8 | 9 | def get_insn(ea=None): 10 | if ea is None: 11 | ea = idc.here() 12 | xfunc = ida_hexrays.decompile(ea) 13 | return xfunc.get_eamap()[ea][0] 14 | 15 | 16 | def get_str_from_expr(expr, make_str=True): 17 | if expr is None: 18 | return None 19 | str_addr = get_obj_ea_from_expr(expr) 20 | if str_addr == BADADDR: 21 | return None 22 | ret = idc.get_strlit_contents(str_addr) 23 | if ret is not None: 24 | ret = ret.decode() 25 | return ret 26 | 27 | 28 | def extract_op_from_expr(expr, op): 29 | if expr is None: 30 | return BADADDR 31 | while expr.is_expr() and expr.op != op: 32 | expr = expr.x 33 | if expr is None: 34 | return BADADDR 35 | if expr.op == op: 36 | return expr 37 | 38 | 39 | def get_obj_ea_from_expr(expr): 40 | expr = extract_op_from_expr(expr, ida_hexrays.cot_obj) 41 | if expr is None: 42 | return BADADDR 43 | return expr.obj_ea 44 | 45 | 46 | def get_num_from_expr(expr): 47 | expr = extract_op_from_expr(expr, ida_hexrays.cot_num) 48 | if expr is None: 49 | return None 50 | return expr.get_const_value() 51 | 52 | 53 | def get_call_from_insn(insn): 54 | expr = None 55 | if type(insn) == ida_hexrays.cinsn_t and insn.op == ida_hexrays.cit_expr: 56 | expr = insn.cexpr 57 | elif type(insn) == ida_hexrays.cexpr_t: 58 | expr = insn 59 | else: 60 | return None 61 | if expr.op != ida_hexrays.cot_call: 62 | return None 63 | return expr 64 | 65 | 66 | def run_operation_on_func_xrefs(func_name, operation, exception_msg=None): 67 | if exception_msg is None: 68 | exception_msg = "exception in %s xrefs" % func_name 69 | ea = utils.get_func_ea_by_name(func_name) 70 | for xref in utils.get_code_xrefs(ea): 71 | try: 72 | insn = get_insn(xref) 73 | operation(insn, xref) 74 | except Exception as e: 75 | logging.exception("0x%x: %s", ea, exception_msg) 76 | -------------------------------------------------------------------------------- /examples/a.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | 5 | class A{ 6 | public: 7 | int x_a; 8 | virtual int f_a()=0; 9 | }; 10 | 11 | class B : public A{ 12 | public: 13 | int x_b; 14 | int f_a(){x_a = 0;} 15 | virtual int f_b(){this->f_a();} 16 | }; 17 | class Z{ 18 | public: 19 | virtual int f_z1(){cout << "f_z1";} 20 | virtual int f_z2(){cout << "f_z2";} 21 | }; 22 | 23 | class C: public B, public Z{ 24 | public: 25 | int f_a(){x_a = 5;} 26 | int x_c; 27 | int f_c(){x_c = 0;} 28 | virtual int f_z1(){cout << "f_z3";} 29 | }; 30 | 31 | 32 | int main() 33 | { 34 | C *c = new C(); 35 | c->f_a(); 36 | c->f_b(); 37 | c->f_z1(); 38 | c->f_z2(); 39 | 40 | return 0; 41 | } 42 | 43 | 44 | -------------------------------------------------------------------------------- /examples/a32_stripped: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/medigateio/ida_medigate/63c7318a272640c306bb88912027fef987261f15/examples/a32_stripped -------------------------------------------------------------------------------- /examples/a64_stripped: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/medigateio/ida_medigate/63c7318a272640c306bb88912027fef987261f15/examples/a64_stripped -------------------------------------------------------------------------------- /images/f_b_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/medigateio/ida_medigate/63c7318a272640c306bb88912027fef987261f15/images/f_b_after.png -------------------------------------------------------------------------------- /images/f_b_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/medigateio/ida_medigate/63c7318a272640c306bb88912027fef987261f15/images/f_b_before.png -------------------------------------------------------------------------------- /images/f_b_choose_vtable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/medigateio/ida_medigate/63c7318a272640c306bb88912027fef987261f15/images/f_b_choose_vtable.png -------------------------------------------------------------------------------- /images/f_b_union_choose.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/medigateio/ida_medigate/63c7318a272640c306bb88912027fef987261f15/images/f_b_union_choose.png -------------------------------------------------------------------------------- /images/f_b_xrefs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/medigateio/ida_medigate/63c7318a272640c306bb88912027fef987261f15/images/f_b_xrefs.png -------------------------------------------------------------------------------- /images/main_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/medigateio/ida_medigate/63c7318a272640c306bb88912027fef987261f15/images/main_after.png -------------------------------------------------------------------------------- /images/main_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/medigateio/ida_medigate/63c7318a272640c306bb88912027fef987261f15/images/main_before.png -------------------------------------------------------------------------------- /images/vtable_c_z.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/medigateio/ida_medigate/63c7318a272640c306bb88912027fef987261f15/images/vtable_c_z.png -------------------------------------------------------------------------------- /medigate_cpp_plugin.py: -------------------------------------------------------------------------------- 1 | try: 2 | from ida_medigate.cpp_plugin.plugin import CPPPlugin 3 | 4 | def PLUGIN_ENTRY(): 5 | return CPPPlugin() 6 | 7 | 8 | except ImportError: 9 | print( 10 | "[WARN] Couldn't load ida_medigate_cpp plugin. ida_medigate Python package doesn't seem " 11 | "to be installed" 12 | ) 13 | -------------------------------------------------------------------------------- /plugins/ida-referee/LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2011 James Koppel 190 | Modifications copyright 2015 Joseph Leong 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /plugins/ida-referee/README.md: -------------------------------------------------------------------------------- 1 | # Referee 2 | 3 | This is a python port of James Koppel's Referee IDA plugin with some updates: 4 | https://github.com/jkoppel/project-ironfist/tree/master/tools/Revitalize/Referee 5 | 6 | 7 | ## What it is 8 | 9 | It's much easier to reverse-engineer a structure when you can find every place its members are used. If you wish to reengineer the binary and modify a structure, finding every use is essential. Referee makes both of these tasks easier by marking accesses of structures in decompiled functions. 10 | 11 | ## Requirements 12 | 13 | * IDA 6.2 or higher 14 | * Hex-Rays Decompiler 1.6 or higher 15 | 16 | ## Installation 17 | 18 | Copy the plugin into the IDA "plugins" folder 19 | 20 | ## Usage 21 | 22 | Referee will automatically run whenever a function is decompiled. It is recommended that you decompile the entire binary for maximum information. You can see the cross-references that Referee adds by opening a structure in the Structures window, highlighting a field of a structure, and pressing "X." 23 | 24 | Referee does not do type inference; you will still need to give types to your functions for it to find structure uses. 25 | 26 | ## Notes 27 | 28 | * If you annotate a function to remove a struct-member usage, decompiling the function again will remove the corresponding xrefs. 29 | * Referee only tracks accesses to structure members, not pointer-passing. 30 | * Configuring debug output: `logging.getLogger('referee').setLevel(logging.DEBUG)` 31 | 32 | ## Related 33 | - http://reverseengineering.stackexchange.com/questions/2139/is-it-possible-to-create-data-xrefs-manually 34 | -------------------------------------------------------------------------------- /plugins/ida-referee/referee.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Referee creates struct xrefs for decompiled functions 4 | """ 5 | import logging 6 | import traceback 7 | 8 | import idaapi 9 | 10 | logging.basicConfig(level=logging.WARN) 11 | log = logging.getLogger("referee") 12 | 13 | 14 | NETNODE_NAME = '$ referee-xrefs' 15 | NETNODE_TAG = 'X' 16 | 17 | 18 | def is_assn(t): 19 | return ( 20 | t == idaapi.cot_asg or 21 | t == idaapi.cot_asgbor or 22 | t == idaapi.cot_asgxor or 23 | t == idaapi.cot_asgband or 24 | t == idaapi.cot_asgsub or 25 | t == idaapi.cot_asgmul or 26 | t == idaapi.cot_asgsshr or 27 | t == idaapi.cot_asgushr or 28 | t == idaapi.cot_asgsdiv or 29 | t == idaapi.cot_asgudiv or 30 | t == idaapi.cot_asgsmod or 31 | t == idaapi.cot_asgumod) 32 | 33 | 34 | def is_incdec(t): 35 | return ( 36 | t == idaapi.cot_postinc or # = 53, ///< x++ 37 | t == idaapi.cot_postdec or # = 54, ///< x-- 38 | t == idaapi.cot_preinc or # = 55, ///< ++x 39 | t == idaapi.cot_predec) # = 56, ///< --x 40 | 41 | 42 | def add_struct_xrefs(cfunc): 43 | class xref_adder_t(idaapi.ctree_visitor_t): 44 | def __init__(self, cfunc): 45 | idaapi.ctree_visitor_t.__init__(self, idaapi.CV_PARENTS) 46 | self.cfunc = cfunc 47 | self.node = idaapi.netnode() 48 | self.clear_struct_xrefs() 49 | self.xrefs = {} 50 | 51 | def load(self): 52 | try: 53 | data = self.node.getblob_ea(self.cfunc.entry_ea, NETNODE_TAG) 54 | if data: 55 | xrefs = eval(data.replace(b"L", b"")) 56 | log.debug('Loaded {} xrefs'.format(len(xrefs))) 57 | return xrefs 58 | except: 59 | log.error('Failed to load xrefs from netnode') 60 | traceback.print_exc() 61 | return {} 62 | 63 | def save(self): 64 | try: 65 | self.node.setblob_ea(repr(self.xrefs).encode(), 66 | self.cfunc.entry_ea, 67 | NETNODE_TAG) 68 | except: 69 | log.error('Failed to save xrefs to netnode') 70 | traceback.print_exc() 71 | 72 | def clear_struct_xrefs(self): 73 | if not self.node.create(NETNODE_NAME): 74 | xrefs = self.load() 75 | for (ea, struct_id, member_id) in xrefs.keys(): 76 | if member_id is None: 77 | idaapi.del_dref(ea, struct_id) 78 | else: 79 | idaapi.del_dref(ea, member_id) 80 | self.xrefs = {} 81 | self.save() 82 | log.debug('Cleared {} xrefs'.format(len(xrefs))) 83 | 84 | def find_addr(self, e): 85 | if e.ea != idaapi.BADADDR: 86 | ea = e.ea 87 | else: 88 | while True: 89 | e = self.cfunc.body.find_parent_of(e) 90 | if e is None: 91 | ea = self.cfunc.entry_ea 92 | break 93 | if e.ea != idaapi.BADADDR: 94 | ea = e.ea 95 | break 96 | return ea 97 | 98 | def add_dref(self, ea, struct_id, flags, member_id=None): 99 | if ((ea, struct_id, member_id) not in self.xrefs or 100 | flags < self.xrefs[(ea, struct_id, member_id)]): 101 | self.xrefs[(ea, struct_id, member_id)] = flags 102 | strname = idaapi.get_struc_name(struct_id) 103 | if member_id is None: 104 | idaapi.add_dref(ea, struct_id, flags) 105 | log.debug((" 0x{:X} \t" 106 | "struct {} \t" 107 | "{}").format( 108 | ea, strname, flags_to_str(flags))) 109 | else: 110 | idaapi.add_dref(ea, member_id, flags) 111 | log.debug((" 0x{:X} \t" 112 | "member {}.{} \t" 113 | "{}").format( 114 | ea, strname, 115 | idaapi.get_member_name(member_id), 116 | flags_to_str(flags))) 117 | self.save() 118 | 119 | def visit_expr(self, e): 120 | dr = idaapi.dr_R | idaapi.XREF_USER 121 | ea = self.find_addr(e) 122 | 123 | # We wish to know what context a struct usage occurs in 124 | # so we can determine what kind of xref to create. Unfortunately, 125 | # a post-order traversal makes this difficult. 126 | 127 | # For assignments, we visit the left, instead 128 | # Note that immediate lvalues will be visited twice, 129 | # and will be eronneously marked with a read dref. 130 | # However, it is safer to overapproximate than underapproximate 131 | if is_assn(e.op) or is_incdec(e.op): 132 | e = e.x 133 | dr = idaapi.dr_W | idaapi.XREF_USER 134 | 135 | # &x 136 | if e.op == idaapi.cot_ref: 137 | e = e.x 138 | dr = idaapi.dr_O | idaapi.XREF_USER 139 | 140 | # x.m, x->m 141 | if (e.op == idaapi.cot_memref or e.op == idaapi.cot_memptr): 142 | moff = e.m 143 | 144 | # The only way I could figure out how 145 | # to get the structure/member associated with its use 146 | typ = e.x.type 147 | 148 | if e.op == idaapi.cot_memptr: 149 | typ.remove_ptr_or_array() 150 | 151 | strname = typ.dstr() 152 | if strname.startswith("struct "): 153 | strname = strname[len("struct "):] 154 | 155 | stid = idaapi.get_struc_id(strname) 156 | struc = idaapi.get_struc(stid) 157 | mem = idaapi.get_member(struc, moff) 158 | 159 | if struc is not None: 160 | self.add_dref(ea, stid, dr) 161 | if mem is not None: 162 | self.add_dref(ea, stid, dr, mem.id) 163 | 164 | else: 165 | log.error(("failure from 0x{:X} " 166 | "on struct {} (id: 0x{:X}) {}").format( 167 | ea, strname, stid, flags_to_str(dr))) 168 | 169 | elif idaapi.is_lvalue(e.op) and e.type.is_struct(): 170 | strname = e.type.dstr() 171 | if strname.startswith("struct "): 172 | strname = strname[len("struct "):] 173 | 174 | stid = idaapi.get_struc_id(strname) 175 | struc = idaapi.get_struc(stid) 176 | 177 | if struc is not None: 178 | self.add_dref(ea, stid, dr) 179 | 180 | return 0 181 | 182 | adder = xref_adder_t(cfunc) 183 | adder.apply_to_exprs(cfunc.body, None) 184 | 185 | 186 | def callback(*args): 187 | if args[0] == idaapi.hxe_maturity: 188 | cfunc = args[1] 189 | mat = args[2] 190 | if mat == idaapi.CMAT_FINAL: 191 | log.debug("analyzing function at 0x{:X}".format( 192 | cfunc.entry_ea)) 193 | add_struct_xrefs(cfunc) 194 | return 0 195 | 196 | 197 | class Referee(idaapi.plugin_t): 198 | flags = idaapi.PLUGIN_HIDE 199 | comment = "Adds struct xref info from decompilation" 200 | help = "" 201 | 202 | wanted_name = "Referee" 203 | wanted_hotkey = "" 204 | 205 | def __init__(self): 206 | self.inited = False 207 | 208 | def init(self): 209 | if not idaapi.init_hexrays_plugin(): 210 | return idaapi.PLUGIN_SKIP 211 | 212 | idaapi.install_hexrays_callback(callback) 213 | log.info(("Hex-Rays version {} has been detected; " 214 | "{} is ready to use").format( 215 | idaapi.get_hexrays_version(), self.wanted_name)) 216 | self.inited = True 217 | return idaapi.PLUGIN_KEEP 218 | 219 | def run(self, arg): 220 | # never called 221 | pass 222 | 223 | def term(self): 224 | if self.inited: 225 | idaapi.remove_hexrays_callback(callback) 226 | idaapi.term_hexrays_plugin() 227 | 228 | 229 | def PLUGIN_ENTRY(): 230 | return Referee() 231 | 232 | 233 | def flags_to_str(num): 234 | match = [] 235 | if num & idaapi.dr_R == idaapi.dr_R: 236 | match.append('dr_R') 237 | num ^= idaapi.dr_R 238 | if num & idaapi.dr_O == idaapi.dr_O: 239 | match.append('dr_O') 240 | num ^= idaapi.dr_O 241 | if num & idaapi.dr_W == idaapi.dr_W: 242 | match.append('dr_W') 243 | num ^= idaapi.dr_W 244 | if num & idaapi.dr_I == idaapi.dr_I: 245 | match.append('dr_I') 246 | num ^= idaapi.dr_I 247 | if num & idaapi.dr_T == idaapi.dr_T: 248 | match.append('dr_T') 249 | num ^= idaapi.dr_T 250 | if num & idaapi.XREF_USER == idaapi.XREF_USER: 251 | match.append('XREF_USER') 252 | num ^= idaapi.XREF_USER 253 | if num & idaapi.XREF_DATA == idaapi.XREF_DATA: 254 | match.append('XREF_DATA') 255 | num ^= idaapi.XREF_DATA 256 | res = ' | '.join(match) 257 | if num: 258 | res += ' unknown: 0x{:X}'.format(num) 259 | return res 260 | 261 | 262 | def clear_output_window(): 263 | idaapi.process_ui_action('msglist:Clear') 264 | -------------------------------------------------------------------------------- /rtti_parser.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | import ida_name 4 | import ida_struct 5 | import idaapi 6 | import idautils 7 | import idc 8 | from idaapi import BADADDR 9 | 10 | from . import cpp_utils 11 | from . import utils 12 | 13 | 14 | class RTTIParser(object): 15 | RTTI_OBJ_STRUC_NAME = "rtti_obj" 16 | 17 | @classmethod 18 | def init_parser(cls): 19 | logging.basicConfig( 20 | filename="/tmp/cpp.log", 21 | filemode="a", 22 | level=logging.DEBUG, 23 | format="%(asctime)s - %(levelname)s - %(message)s", 24 | ) 25 | cls.found_classes = set() 26 | 27 | @classmethod 28 | def extract_rtti_info_from_data(cls, ea=None): 29 | if ea is None: 30 | ea = idc.here() 31 | typeinfo = cls.parse_rtti_header(ea) 32 | return cls.extract_rtti_info_from_typeinfo(typeinfo) 33 | 34 | @classmethod 35 | def extract_rtti_info_from_typeinfo(cls, typeinfo): 36 | if typeinfo in cls.found_classes: 37 | return 38 | rtti_obj = cls.parse_typeinfo(typeinfo) 39 | if rtti_obj is None: 40 | return 41 | logging.info("%s: Parsed typeinfo", rtti_obj.name) 42 | cls.found_classes.add(rtti_obj.typeinfo) 43 | for parent_typeinfo, _, offset in rtti_obj.raw_parents: 44 | parent_updated_name = None 45 | parent_rtti_obj = cls.extract_rtti_info_from_typeinfo(parent_typeinfo) 46 | if parent_rtti_obj: 47 | parent_updated_name = parent_rtti_obj.name 48 | else: 49 | built_rtti_obj_name = ida_name.get_ea_name(parent_typeinfo) 50 | if built_rtti_obj_name.endswith(cls.RTTI_OBJ_STRUC_NAME): 51 | parent_updated_name = built_rtti_obj_name.rstrip( 52 | "_" + cls.RTTI_OBJ_STRUC_NAME 53 | ) 54 | if parent_updated_name is not None: 55 | rtti_obj.updated_parents.append((parent_updated_name, offset)) 56 | 57 | logging.debug("%s: Finish setup parents", rtti_obj.name) 58 | if not rtti_obj.create_structs(): 59 | return False 60 | rtti_obj.make_rtti_obj_pretty() 61 | rtti_obj.find_vtables() 62 | return rtti_obj 63 | 64 | def __init__(self, parents, typeinfo): 65 | self.raw_parents = [] 66 | self.updated_parents = [] 67 | self.typeinfo = typeinfo 68 | self.orig_name = self.name = self.get_typeinfo_name(self.typeinfo) 69 | for parent_typeinf, parent_offset in parents: 70 | parent_name = self.get_typeinfo_name(parent_typeinf) 71 | if parent_name is not None: 72 | self.raw_parents.append((parent_typeinf, parent_name, parent_offset)) 73 | self.struct_id = None 74 | self.struct_ptr = None 75 | 76 | def create_structs(self): 77 | self.name, self.struct_id = utils.add_struc_retry(self.name) 78 | if self.struct_id == BADADDR or self.name is None: 79 | return False 80 | self.struct_ptr = ida_struct.get_struc(self.struct_id) 81 | if self.struct_ptr is None: 82 | logging.exception("self.struct_ptr is None at %s", self.name) 83 | previous_parent_offset = 0 84 | previous_parent_size = 0 85 | previous_parent_struct_id = BADADDR 86 | for parent_name, parent_offset in self.updated_parents: 87 | if ( 88 | parent_offset - previous_parent_offset > previous_parent_size 89 | and previous_parent_struct_id != BADADDR 90 | ): 91 | utils.expand_struct( 92 | previous_parent_struct_id, parent_offset - previous_parent_offset 93 | ) 94 | baseclass_id = ida_struct.get_struc_id(parent_name) 95 | baseclass_size = ida_struct.get_struc_size(baseclass_id) 96 | if baseclass_id == BADADDR or baseclass_size == 0: 97 | logging.warning( 98 | "bad struct id or size: %s(0x%x:%s) - %s, %d", 99 | self.name, 100 | parent_offset, 101 | parent_name, 102 | baseclass_id, 103 | baseclass_size, 104 | ) 105 | 106 | cpp_utils.add_baseclass(self.name, parent_name, parent_offset) 107 | previous_parent_offset = parent_offset 108 | previous_parent_size = baseclass_size 109 | previous_parent_struct_id = baseclass_id 110 | if self.updated_parents: 111 | utils.refresh_struct(self.struct_ptr) 112 | 113 | return True 114 | 115 | def find_vtables(self): 116 | is_vtable_found = False 117 | for xref in utils.get_drefs(self.typeinfo): 118 | if self.try_parse_vtable(xref) is not None: 119 | is_vtable_found = True 120 | if not is_vtable_found: 121 | logging.debug( 122 | "find_vtable(%s): Couldn't find any vtable ->" " Interface!", self.name 123 | ) 124 | if len(self.updated_parents) == 0: 125 | cpp_utils.install_vtables_union(self.name) 126 | pass 127 | 128 | def try_parse_vtable(self, ea): 129 | pass 130 | 131 | def create_vtable_struct(self, vtable_offset): 132 | return cpp_utils.create_vtable_struct(self.struct_ptr, self.name, vtable_offset) 133 | 134 | def make_rtti_obj_pretty(self): 135 | pass 136 | 137 | @classmethod 138 | def parse_rtti_header(cls, ea): 139 | pass 140 | 141 | @classmethod 142 | def parse_typeinfo(cls, typeinfo): 143 | pass 144 | 145 | def get_typeinfo_name(self, typeinfo): 146 | pass 147 | 148 | 149 | class GccRTTIParser(RTTIParser): 150 | VMI = "_ZTVN10__cxxabiv121__vmi_class_type_infoE" 151 | SI = "_ZTVN10__cxxabiv120__si_class_type_infoE" 152 | NONE = "_ZTVN10__cxxabiv117__class_type_infoE" 153 | OFFSET_FROM_TYPEINF_SYM = 2 * utils.WORD_LEN 154 | 155 | RECORD_TYPEINFO_OFFSET = utils.WORD_LEN 156 | # class_type_info consts 157 | CLASS_TYPE_TYPEINFO_OFFSET = 0 158 | CLASS_TYPE_NAME_OFFSET = utils.WORD_LEN 159 | CLASS_TYPE_SIZE = 2 * utils.WORD_LEN 160 | 161 | # si_class_type_info consts 162 | SI_TYPEINFO_BASE_OFFSET = CLASS_TYPE_SIZE 163 | 164 | # vmi_class_type_info consts 165 | VMI_TYPEINFO_BASE_CLASSES_NUM_OFFSET = CLASS_TYPE_SIZE + 4 166 | VMI_TYPEINFO_BASE_CLASSES_OFFSET = VMI_TYPEINFO_BASE_CLASSES_NUM_OFFSET + 4 167 | 168 | # base_class vmi helper 169 | BASE_CLASS_TYPEINFO_OFFSET = 0 170 | BASE_CLASS_ATTRS_OFFSET = BASE_CLASS_TYPEINFO_OFFSET + utils.WORD_LEN 171 | BASE_CLASS_SIZE = utils.WORD_LEN * 2 172 | 173 | pure_virtual_name = "__cxa_pure_virtual" 174 | 175 | @classmethod 176 | def init_parser(cls): 177 | super(GccRTTIParser, cls).init_parser() 178 | cls.type_vmi = ( 179 | ida_name.get_name_ea(idaapi.BADADDR, cls.VMI) + cls.OFFSET_FROM_TYPEINF_SYM 180 | ) 181 | cls.type_si = ( 182 | ida_name.get_name_ea(idaapi.BADADDR, cls.SI) + cls.OFFSET_FROM_TYPEINF_SYM 183 | ) 184 | cls.type_none = ( 185 | ida_name.get_name_ea(idaapi.BADADDR, cls.NONE) + cls.OFFSET_FROM_TYPEINF_SYM 186 | ) 187 | cls.types = (cls.type_vmi, cls.type_si, cls.type_none) 188 | 189 | @classmethod 190 | def build_all(cls): 191 | for class_type in cls.types: 192 | logging.debug("Starting :%s %s" % (class_type, hex(class_type))) 193 | cls.build_class_type(class_type) 194 | logging.info("Done %s", class_type) 195 | 196 | @classmethod 197 | @utils.batchmode 198 | def build_class_type(cls, class_type): 199 | idx = 0 200 | for xref in idautils.XrefsTo(class_type - cls.OFFSET_FROM_TYPEINF_SYM): 201 | if (idx + 1) % 200 == 0: 202 | # idc.batch(0) 203 | logging.info("\t Done %s", idx) 204 | # ida_loader.save_database(None, 0) 205 | # idc.batch(1) 206 | if utils.get_ptr(xref.frm) != class_type: 207 | continue 208 | try: 209 | cls.extract_rtti_info_from_typeinfo(xref.frm) 210 | except Exception as e: 211 | logging.exception("Exception at 0x%x:", xref.frm) 212 | idx += 1 213 | 214 | @classmethod 215 | def parse_rtti_header(cls, ea): 216 | # offset = cls.read_offset(ea) 217 | typeinfo = cls.get_typeinfo_ea(ea) 218 | return typeinfo 219 | 220 | @classmethod 221 | def parse_typeinfo(cls, typeinfo): 222 | typeinfo_type = utils.get_ptr(typeinfo + cls.CLASS_TYPE_TYPEINFO_OFFSET) 223 | if typeinfo_type == cls.type_none: 224 | parents = [] 225 | elif typeinfo_type == cls.type_si: 226 | parents = cls.parse_si_typeinfo(typeinfo) 227 | elif typeinfo_type == cls.type_vmi: 228 | parents = cls.parse_vmi_typeinfo(typeinfo) 229 | else: 230 | return None 231 | return GccRTTIParser(parents, typeinfo) 232 | 233 | @classmethod 234 | def parse_si_typeinfo(cls, typeinfo_ea): 235 | parent_typinfo_ea = utils.get_ptr(typeinfo_ea + cls.SI_TYPEINFO_BASE_OFFSET) 236 | return [(parent_typinfo_ea, 0)] 237 | 238 | @classmethod 239 | def parse_vmi_typeinfo(cls, typeinfo_ea): 240 | base_classes_num = idaapi.get_32bit( 241 | typeinfo_ea + cls.VMI_TYPEINFO_BASE_CLASSES_NUM_OFFSET 242 | ) 243 | parents = [] 244 | for i in range(base_classes_num): 245 | base_class_desc_ea = ( 246 | typeinfo_ea 247 | + cls.VMI_TYPEINFO_BASE_CLASSES_OFFSET 248 | + i * cls.BASE_CLASS_SIZE 249 | ) 250 | parent_typeinfo_ea = utils.get_ptr( 251 | base_class_desc_ea + cls.BASE_CLASS_TYPEINFO_OFFSET 252 | ) 253 | parent_attrs = utils.get_word( 254 | base_class_desc_ea + cls.BASE_CLASS_ATTRS_OFFSET 255 | ) 256 | parent_offset_in_cls = parent_attrs >> 8 257 | parents.append((parent_typeinfo_ea, parent_offset_in_cls)) 258 | return parents 259 | 260 | @classmethod 261 | def get_typeinfo_ea(cls, ea): 262 | return utils.get_ptr(ea + cls.RECORD_TYPEINFO_OFFSET) 263 | 264 | @classmethod 265 | def get_typeinfo_name(cls, typeinfo_ea): 266 | name_ea = utils.get_ptr(typeinfo_ea + cls.CLASS_TYPE_NAME_OFFSET) 267 | if name_ea is None or name_ea == BADADDR: 268 | mangled_class_name = ida_name.get_ea_name(typeinfo_ea) 269 | else: 270 | mangled_class_name = "_Z" + idc.get_strlit_contents(name_ea).decode() 271 | class_name = ida_name.demangle_name(mangled_class_name, idc.INF_LONG_DN) 272 | return cls.strip_class_name(class_name) 273 | 274 | @classmethod 275 | def strip_class_name(cls, cls_name): 276 | pre_dict = {"`typeinfo for": ":"} 277 | words_dict = { 278 | "`anonymous namespace'": "ANONYMOUS", 279 | "`anonymous_namespace'": "ANONYMOUS", 280 | "`typeinfo for'": "", 281 | } 282 | chars_dict = { 283 | "<": "X", 284 | ">": "Z", 285 | "&": "A", 286 | "*": "P", 287 | " ": "_", 288 | ",": "C", 289 | "'": "U", 290 | "`": "T", 291 | "[": "O", 292 | "]": "P", 293 | } 294 | for target, strip in words_dict.items(): 295 | cls_name = cls_name.replace(target, strip) 296 | for target, strip in chars_dict.items(): 297 | cls_name = cls_name.replace(target, strip) 298 | return cls_name 299 | 300 | def try_parse_vtable(self, ea): 301 | functions_ea = ea + utils.WORD_LEN 302 | func_ea, _ = cpp_utils.get_vtable_line( 303 | functions_ea, 304 | ignore_list=self.types, 305 | pure_virtual_name=self.pure_virtual_name, 306 | ) 307 | if func_ea is None: 308 | return 309 | vtable_offset = utils.get_signed_int(ea - utils.WORD_LEN) * (-1) 310 | vtable_struct, this_type = self.create_vtable_struct(vtable_offset) 311 | cpp_utils.update_vtable_struct( 312 | functions_ea, 313 | vtable_struct, 314 | self.name, 315 | this_type, 316 | ignore_list=self.types, 317 | pure_virtual_name=self.pure_virtual_name, 318 | ) 319 | return vtable_struct 320 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import random 3 | 4 | import ida_bytes 5 | import ida_enum 6 | import ida_funcs 7 | import ida_hexrays 8 | import ida_kernwin 9 | import ida_lines 10 | import ida_nalt 11 | import ida_name 12 | import ida_search 13 | import ida_struct 14 | import ida_typeinf 15 | import ida_xref 16 | import idaapi 17 | import idautils 18 | import idc 19 | 20 | from idc import BADADDR 21 | 22 | # WORD length in bytes 23 | WORD_LEN = None 24 | 25 | 26 | def update_word_len(code, old=0): 27 | global WORD_LEN 28 | info = idaapi.get_inf_structure() 29 | if info.is_64bit(): 30 | logging.debug("is 32 bit") 31 | WORD_LEN = 8 32 | elif info.is_32bit(): 33 | logging.debug("is 32 bit") 34 | WORD_LEN = 4 35 | 36 | 37 | idaapi.notify_when(idaapi.NW_OPENIDB, update_word_len) 38 | 39 | 40 | def get_word(ea): 41 | if WORD_LEN == 4: 42 | return idaapi.get_32bit(ea) 43 | elif WORD_LEN == 8: 44 | return idaapi.get_64bit(ea) 45 | return None 46 | 47 | 48 | def get_ptr(ea): 49 | return get_word(ea) 50 | 51 | 52 | def make_word(ea): 53 | if WORD_LEN == 4: 54 | return ida_bytes.create_dword(ea, 4) 55 | elif WORD_LEN == 8: 56 | return ida_bytes.create_qword(ea, 8) 57 | return None 58 | 59 | 60 | def make_ptr(ea): 61 | return make_word(ea) 62 | 63 | 64 | def is_func(ea): 65 | func = ida_funcs.get_func(ea) 66 | if func is not None and func.start_ea == ea: 67 | return True 68 | return None 69 | 70 | 71 | def get_funcs_list(): 72 | pass 73 | 74 | 75 | def get_drefs(ea): 76 | xref = ida_xref.get_first_dref_to(ea) 77 | while xref != BADADDR: 78 | yield xref 79 | xref = ida_xref.get_next_dref_to(ea, xref) 80 | 81 | 82 | def get_typeinf(typestr): 83 | tif = idaapi.tinfo_t() 84 | tif.get_named_type(idaapi.get_idati(), typestr) 85 | return tif 86 | 87 | 88 | def get_typeinf_ptr(typeinf): 89 | old_typeinf = typeinf 90 | if isinstance(typeinf, str): 91 | typeinf = get_typeinf(typeinf) 92 | if typeinf is None: 93 | logging.warning("Couldn't find typeinf %s", old_typeinf or typeinf) 94 | return None 95 | tif = idaapi.tinfo_t() 96 | tif.create_ptr(typeinf) 97 | return tif 98 | 99 | 100 | def get_func_details(func_ea): 101 | xfunc = ida_hexrays.decompile(func_ea) 102 | if xfunc is None: 103 | return None 104 | func_details = idaapi.func_type_data_t() 105 | xfunc.type.get_func_details(func_details) 106 | return func_details 107 | 108 | 109 | def update_func_details(func_ea, func_details): 110 | function_tinfo = idaapi.tinfo_t() 111 | function_tinfo.create_func(func_details) 112 | if not ida_typeinf.apply_tinfo(func_ea, function_tinfo, idaapi.TINFO_DEFINITE): 113 | return None 114 | return function_tinfo 115 | 116 | 117 | def add_to_struct( 118 | struct, 119 | member_name, 120 | member_type=None, 121 | offset=BADADDR, 122 | is_offset=False, 123 | overwrite=False, 124 | ): 125 | mt = None 126 | flag = idaapi.FF_DWORD 127 | member_size = WORD_LEN 128 | if member_type is not None and (member_type.is_struct() or member_type.is_union()): 129 | logging.debug("Is struct!") 130 | substruct = extract_struct_from_tinfo(member_type) 131 | if substruct is not None: 132 | flag = idaapi.FF_STRUCT 133 | mt = ida_nalt.opinfo_t() 134 | mt.tid = substruct.id 135 | logging.debug( 136 | f"Is struct: {ida_struct.get_struc_name(substruct.id)}/{substruct.id}" 137 | ) 138 | member_size = ida_struct.get_struc_size(substruct.id) 139 | elif WORD_LEN == 4: 140 | flag = idaapi.FF_DWORD 141 | elif WORD_LEN == 8: 142 | flag = idaapi.FF_QWORD 143 | if is_offset: 144 | flag |= idaapi.FF_0OFF 145 | mt = ida_nalt.opinfo_t() 146 | r = ida_nalt.refinfo_t() 147 | r.init(ida_nalt.get_reftype_by_size(WORD_LEN) | ida_nalt.REFINFO_NOBASE) 148 | mt.ri = r 149 | 150 | new_member_name = member_name 151 | member_ptr = ida_struct.get_member(struct, offset) 152 | if overwrite and member_ptr: 153 | if ida_struct.get_member_name(member_ptr.id) != member_name: 154 | logging.debug("Overwriting!") 155 | ret_val = ida_struct.set_member_name(struct, offset, member_name) 156 | i = 0 157 | while ret_val == ida_struct.STRUC_ERROR_MEMBER_NAME: 158 | new_member_name = "%s_%d" % (member_name, i) 159 | i += 1 160 | if i > 250: 161 | logging.debug("failed change name") 162 | return 163 | ret_val = ida_struct.set_member_name(struct, offset, new_member_name) 164 | 165 | else: 166 | ret_val = ida_struct.add_struc_member( 167 | struct, new_member_name, offset, flag, mt, member_size 168 | ) 169 | i = 0 170 | while ret_val == ida_struct.STRUC_ERROR_MEMBER_NAME: 171 | new_member_name = "%s_%d" % (member_name, i) 172 | i += 1 173 | if i > 250: 174 | return 175 | ret_val = ida_struct.add_struc_member( 176 | struct, new_member_name, offset, flag, mt, member_size 177 | ) 178 | if ret_val != 0: 179 | logging.debug(f"ret_val: {ret_val}") 180 | member_ptr = ida_struct.get_member_by_name(struct, new_member_name) 181 | if member_type is not None and member_ptr is not None: 182 | ida_struct.set_member_tinfo( 183 | struct, member_ptr, 0, member_type, idaapi.TINFO_DEFINITE 184 | ) 185 | return member_ptr 186 | 187 | 188 | def set_func_name(func_ea, func_name): 189 | counter = 0 190 | new_name = func_name 191 | while not ida_name.set_name(func_ea, new_name): 192 | new_name = func_name + "_%d" % counter 193 | counter += 1 194 | return new_name 195 | 196 | 197 | def deref_tinfo(tinfo): 198 | pointed_obj = None 199 | if tinfo.is_ptr(): 200 | pointed_obj = tinfo.get_pointed_object() 201 | return pointed_obj 202 | 203 | 204 | def get_struc_from_tinfo(struct_tinfo): 205 | 206 | if ida_hexrays.init_hexrays_plugin() and ( 207 | not (struct_tinfo.is_struct() or struct_tinfo.is_union()) 208 | ): 209 | return None 210 | struct_id = ida_struct.get_struc_id(struct_tinfo.get_type_name()) 211 | if struct_id == BADADDR: 212 | return None 213 | struct = ida_struct.get_struc(struct_id) 214 | return struct 215 | 216 | 217 | def deref_struct_from_tinfo(tinfo): 218 | struct_tinfo = deref_tinfo(tinfo) 219 | if struct_tinfo is None: 220 | return None 221 | return get_struc_from_tinfo(struct_tinfo) 222 | 223 | 224 | def extract_struct_from_tinfo(tinfo): 225 | struct = get_struc_from_tinfo(tinfo) 226 | if struct is None: 227 | struct = deref_struct_from_tinfo(tinfo) 228 | return struct 229 | 230 | 231 | def get_member_tinfo(member, member_typeinf=None): 232 | if member_typeinf is None: 233 | member_typeinf = idaapi.tinfo_t() 234 | ida_struct.get_member_tinfo(member_typeinf, member) 235 | return member_typeinf 236 | 237 | 238 | def get_sptr_by_name(struct_name): 239 | s_id = ida_struct.get_struc_id(struct_name) 240 | return ida_struct.get_struc(s_id) 241 | 242 | 243 | def get_member_substruct(member): 244 | member_type = get_member_tinfo(member) 245 | if member_type is not None and member_type.is_struct(): 246 | current_struct_id = ida_struct.get_struc_id(member_type.get_type_name()) 247 | return ida_struct.get_struc(current_struct_id) 248 | elif member.flag & idaapi.FF_STRUCT == idaapi.FF_STRUCT: 249 | return ida_struct.get_sptr(member) 250 | return None 251 | 252 | 253 | def set_member_name(struct, offset, new_name): 254 | i = 0 255 | ret_val = ida_struct.set_member_name(struct, offset, new_name) 256 | while not ret_val: 257 | formatted_new_name = "%s_%d" % (new_name, i) 258 | i += 1 259 | if i > 250: 260 | return False 261 | ret_val = ida_struct.set_member_name(struct, offset, formatted_new_name) 262 | return True 263 | 264 | 265 | def get_or_create_struct_id(struct_name, is_union=False): 266 | struct_id = ida_struct.get_struc_id(struct_name) 267 | if struct_id != BADADDR: 268 | return struct_id 269 | struct_id = ida_struct.add_struc(BADADDR, struct_name, is_union) 270 | return struct_id 271 | 272 | 273 | def get_or_create_struct(struct_name): 274 | struct_id = get_or_create_struct_id(struct_name) 275 | return ida_struct.get_struc(struct_id) 276 | 277 | 278 | def get_signed_int(ea): 279 | x = idaapi.get_dword(ea) 280 | if x & (1 << 31): 281 | return ((1 << 32) - x) * (-1) 282 | return x 283 | 284 | 285 | def expand_struct(struct_id, new_size): 286 | struct = ida_struct.get_struc(struct_id) 287 | if struct is None: 288 | logging.warning("Struct id 0x%x wasn't found", struct_id) 289 | return 290 | logging.debug( 291 | "Expanding struc %s 0x%x -> 0x%x", 292 | ida_struct.get_struc_name(struct_id), 293 | ida_struct.get_struc_size(struct_id), 294 | new_size, 295 | ) 296 | if ida_struct.get_struc_size(struct_id) > new_size - WORD_LEN: 297 | return 298 | fix_list = [] 299 | xrefs = idautils.XrefsTo(struct.id) 300 | for xref in xrefs: 301 | if xref.type == ida_xref.dr_R and xref.user == 0 and xref.iscode == 0: 302 | member, full_name, x_struct = ida_struct.get_member_by_id(xref.frm) 303 | if x_struct is not None: 304 | old_name = ida_struct.get_member_name(member.id) 305 | offset = member.soff 306 | marker_name = "marker_%d" % random.randint(0, 0xFFFFFF) 307 | idc.add_struc_member( 308 | x_struct.id, 309 | marker_name, 310 | member.soff + new_size, 311 | idaapi.FF_DATA | idaapi.FF_BYTE, 312 | -1, 313 | 0, 314 | ) 315 | logging.debug( 316 | "Delete member (0x%x-0x%x)", member.soff, member.soff + new_size - 1 317 | ) 318 | ida_struct.del_struc_members( 319 | x_struct, member.soff, member.soff + new_size - 1 320 | ) 321 | fix_list.append( 322 | [ 323 | x_struct.id, 324 | old_name, 325 | offset, 326 | idaapi.FF_STRUCT | idaapi.FF_DATA, 327 | struct_id, 328 | new_size, 329 | ] 330 | ) 331 | else: 332 | logging.warning("Xref wasn't struct_member 0x%x", xref.frm) 333 | 334 | ret = add_to_struct( 335 | ida_struct.get_struc(struct_id), None, None, new_size - WORD_LEN 336 | ) 337 | logging.debug("Now fix args:") 338 | for fix_args in fix_list: 339 | ret = idc.add_struc_member(*fix_args) 340 | logging.debug("%s = %d", fix_args, ret) 341 | x_struct_id = fix_args[0] 342 | idc.del_struc_member(x_struct_id, ida_struct.get_struc_size(x_struct_id)) 343 | 344 | 345 | def get_curline_striped_from_viewer(viewer): 346 | line = ida_kernwin.get_custom_viewer_curline(viewer, False) 347 | line = ida_lines.tag_remove(line) 348 | return line 349 | 350 | 351 | strings = None 352 | 353 | 354 | def refresh_strings(): 355 | global strings 356 | strings = idautils.Strings() 357 | 358 | 359 | def get_strings(): 360 | if strings is None: 361 | refresh_strings() 362 | return strings 363 | 364 | 365 | def get_xrefs_for_string(s, filter_func=None): 366 | """filter_func(x,s) choose x if True for magic str (s)""" 367 | if filter_func is None: 368 | 369 | def filter_func(x, string): 370 | return str(x) == string 371 | 372 | filtered_strings = filter(lambda x: filter_func(x, s), get_strings()) 373 | strings_xrefs = [] 374 | for s in filtered_strings: 375 | xrefs = [] 376 | xref = ida_xref.get_first_dref_to(s.ea) 377 | while xref != BADADDR: 378 | xrefs.append(xref) 379 | xref = ida_xref.get_next_dref_to(s.ea, xref) 380 | strings_xrefs.append([str(s), xrefs]) 381 | return strings_xrefs 382 | 383 | 384 | def get_func_ea_by_name(name): 385 | loc = idc.get_name_ea_simple(name) 386 | func = ida_funcs.get_func(loc) 387 | if func is None: 388 | return BADADDR 389 | return func.start_ea 390 | 391 | 392 | def get_funcs_contains_string(s): 393 | def filter_func(x, string): 394 | return string in str(x) 395 | 396 | strings_xrefs = get_xrefs_for_string(s, filter_func) 397 | strings_funcs = [] 398 | for found_str, xrefs in strings_xrefs: 399 | funcs = set() 400 | for xref in xrefs: 401 | contained_func = ida_funcs.get_func(xref) 402 | if contained_func is not None: 403 | funcs.add(contained_func) 404 | strings_funcs.append([found_str, funcs]) 405 | return strings_funcs 406 | 407 | 408 | def batchmode(func): 409 | def wrapper(*args, **kwargs): 410 | old_batch = idc.batch(1) 411 | try: 412 | val = func(*args, **kwargs) 413 | except Exception: 414 | raise 415 | finally: 416 | idc.batch(old_batch) 417 | return val 418 | 419 | return wrapper 420 | 421 | 422 | def get_code_xrefs(ea): 423 | xref = ida_xref.get_first_cref_to(ea) 424 | while xref != BADADDR: 425 | yield xref 426 | xref = ida_xref.get_next_cref_to(ea, xref) 427 | 428 | 429 | def get_enum_const_name(enum_name, const_val): 430 | enum = ida_enum.get_enum(enum_name) 431 | if enum != BADADDR: 432 | const = ida_enum.get_const(enum, const_val, 0, BADADDR) 433 | if const != BADADDR: 434 | return ida_enum.get_const_name(const) 435 | return None 436 | 437 | 438 | def find_hex_string(start_ea, stop_ea, hex_string): 439 | curr_ea = ida_search.find_binary( 440 | start_ea, stop_ea, hex_string, 16, ida_search.SEARCH_DOWN 441 | ) 442 | while curr_ea != BADADDR: 443 | yield curr_ea 444 | curr_ea = ida_search.find_binary( 445 | curr_ea + len(hex_string), stop_ea, hex_string, 16, ida_search.SEARCH_DOWN 446 | ) 447 | 448 | 449 | def force_make_struct(ea, struct_name): 450 | sptr = get_sptr_by_name(struct_name) 451 | if sptr == BADADDR: 452 | return False 453 | s_size = ida_struct.get_struc_size(sptr) 454 | ida_bytes.del_items(ea, ida_bytes.DELIT_SIMPLE, s_size) 455 | return ida_bytes.create_struct(ea, s_size, sptr.id) 456 | 457 | 458 | @batchmode 459 | def set_name_retry(ea, name, name_func=ida_name.set_name, max_attempts=100): 460 | i = 0 461 | suggested_name = name 462 | while not name_func(ea, suggested_name): 463 | suggested_name = name + "_" + str(i) 464 | i += 1 465 | if i == max_attempts: 466 | return None 467 | return suggested_name 468 | 469 | 470 | def add_struc_retry(name, max_attempts=100): 471 | i = 0 472 | suggested_name = name 473 | sid = ida_struct.add_struc(BADADDR, suggested_name) 474 | while sid == BADADDR: 475 | suggested_name = name + "_" + str(i) 476 | sid = ida_struct.add_struc(BADADDR, suggested_name) 477 | i += 1 478 | if i == max_attempts: 479 | return None, sid 480 | return suggested_name, sid 481 | 482 | 483 | def get_selected_range_or_line(): 484 | selection, startaddr, endaddr = ida_kernwin.read_range_selection(None) 485 | if selection: 486 | return startaddr, endaddr 487 | else: 488 | return ida_kernwin.get_screen_ea(), None 489 | 490 | 491 | def refresh_struct(sptr): 492 | # Hack: need to refresh structure so MF_BASECLASS will be updated 493 | member_ptr = add_to_struct(sptr, "dummy") 494 | ida_struct.del_struc_member(sptr, member_ptr.soff) 495 | 496 | --------------------------------------------------------------------------------