From 348f15242b370e6fefb1408280f515034a949c26 Mon Sep 17 00:00:00 2001 From: mahaloz Date: Sat, 7 Sep 2024 12:04:15 -0700 Subject: [PATCH 01/11] IDA 9 support --- libbs/decompilers/ida/compat.py | 116 ++++++++++++++++++-------------- libbs/decompilers/ida/hooks.py | 28 ++++---- 2 files changed, 77 insertions(+), 67 deletions(-) diff --git a/libbs/decompilers/ida/compat.py b/libbs/decompilers/ida/compat.py index c1a64d2..a43c27e 100644 --- a/libbs/decompilers/ida/compat.py +++ b/libbs/decompilers/ida/compat.py @@ -17,7 +17,7 @@ from packaging.version import Version import idc, idaapi, ida_kernwin, ida_hexrays, ida_funcs, \ - ida_bytes, ida_struct, ida_idaapi, ida_typeinf, idautils, ida_enum, ida_kernwin, ida_segment + ida_bytes, ida_idaapi, ida_typeinf, idautils, ida_kernwin, ida_segment import libbs from libbs.artifacts import ( @@ -34,8 +34,10 @@ idaapi.BWN_PSEUDOCODE: "decompilation", idaapi.BWN_DISASM: "disassembly", idaapi.BWN_FUNCS: "functions", - idaapi.BWN_STRUCTS: "structs", - idaapi.BWN_ENUMS: "enums", + # idaapi.BWN_STRINGS + 0x1c: "structs", + # idaapi.BWN_ENUMS + 0x1b: "enums", # this is idaapi.BWN_TILIST, but made into a constant for 8.3 compatibility 0x3c: "types", } @@ -155,12 +157,20 @@ def set_func_ret_type(ea, return_type_str): # +def get_ordinal_count(): + dec_version = get_decompiler_version() + if dec_version < Version("9.0"): + return ida_typeinf.get_ordinal_qty(idaapi.get_idati()) + else: + return ida_typeinf.get_ordinal_count(idaapi.get_idati()) + + @execute_write def get_types(structs=True, enums=True, typedefs=True) -> typing.Dict[str, Artifact]: types = {} idati = idaapi.get_idati() - for ord_num in range(ida_typeinf.get_ordinal_qty(idati)): + for ord_num in range(get_ordinal_count()): tif = ida_typeinf.tinfo_t() success = tif.get_numbered_type(idati, ord_num) if not success: @@ -177,8 +187,8 @@ def get_types(structs=True, enums=True, typedefs=True) -> typing.Dict[str, Artif bs_struct = bs_struct_from_tif(tif) types[bs_struct.name] = bs_struct elif enums and tif.is_enum(): - # TODO: implement this for 9.0 - _l.warning("Enums are not supported in this API until IDA 9.0") + bs_enum = enum_from_tif(tif) + types[bs_enum.name] = bs_enum return types @@ -187,7 +197,7 @@ def get_types(structs=True, enums=True, typedefs=True) -> typing.Dict[str, Artif def get_ord_to_type_names() -> typing.Dict[int, typing.Tuple[str, typing.Type[Artifact]]]: idati = idaapi.get_idati() ord_to_name = {} - for ord_num in range(ida_typeinf.get_ordinal_qty(idati)): + for ord_num in range(get_ordinal_count()): tif = ida_typeinf.tinfo_t() success = tif.get_numbered_type(idati, ord_num) if not success: @@ -314,7 +324,8 @@ def get_func_size(ea): def set_ida_func_name(func_addr, new_name): idaapi.set_name(func_addr, new_name, idaapi.SN_FORCE) ida_kernwin.request_refresh(ida_kernwin.IWID_DISASMS) - ida_kernwin.request_refresh(ida_kernwin.IWID_STRUCTS) + # XXX: why was this here?!?!? + #ida_kernwin.request_refresh(ida_kernwin.IWID_STRUCTS) ida_kernwin.request_refresh(ida_kernwin.IWID_STKVIEW) def get_segment_range(segment_name) -> typing.Tuple[bool, int, int]: @@ -653,7 +664,7 @@ def set_stack_variable(svar: StackVariable, decompiler_available=True, **kwargs) ida_code_view.cfunc.refresh_func_ctext() frame = idaapi.get_frame(svar.addr) - if svar.name and ida_struct.set_member_name(frame, svar.offset, svar.name): + if svar.name and idc.set_member_name(frame, svar.offset, svar.name): changes |= True return changes @@ -859,8 +870,8 @@ def structs(): _structs = {} for struct_item in idautils.Structs(): idx, sid, name = struct_item[:] - sptr = ida_struct.get_struc(sid) - size = ida_struct.get_struc_size(sptr) + sptr = idc.get_struc(sid) + size = idc.get_struc_size(sptr) _structs[name] = Struct(name, size, {}) else: _structs = get_types(structs=True, enums=False, typedefs=False) @@ -870,52 +881,52 @@ def structs(): @execute_write def struct(name): - sid = ida_struct.get_struc_id(name) + sid = idc.get_struc_id(name) if sid == idaapi.BADADDR: return None - sptr = ida_struct.get_struc(sid) - size = ida_struct.get_struc_size(sptr) + sptr = idc.get_struc(sid) + size = idc.get_struc_size(sptr) _struct = Struct(name, size, {}, last_change=datetime.datetime.now(tz=datetime.timezone.utc)) for mptr in sptr.members: mid = mptr.id - m_name = ida_struct.get_member_name(mid) + m_name = idc.get_member_name(mid) m_off = mptr.soff m_type = ida_typeinf.idc_get_type(mptr.id) if mptr.has_ti() else "" - m_size = ida_struct.get_member_size(mptr) + m_size = idc.get_member_size(mptr) _struct.add_struct_member(m_name, m_off, m_type, m_size) return _struct @execute_write def del_ida_struct(name) -> bool: - sid = ida_struct.get_struc_id(name) + sid = idc.get_struc_id(name) if sid == idaapi.BADADDR: return False - sptr = ida_struct.get_struc(sid) - return ida_struct.del_struc(sptr) + sptr = idc.get_struc(sid) + return idc.del_struc(sptr) @execute_write def set_struct_member_name(ida_struct, frame, offset, name): - ida_struct.set_member_name(frame, offset, name) + idc.set_member_name(frame, offset, name) @execute_write def set_ida_struct(struct: Struct) -> bool: # first, delete any struct by the same name if it exists - sid = ida_struct.get_struc_id(struct.name) + sid = idc.get_struc_id(struct.name) if sid != idaapi.BADADDR: - sptr = ida_struct.get_struc(sid) - ida_struct.del_struc(sptr) + sptr = idc.get_struc(sid) + idc.del_struc(sptr) # now make a struct header - ida_struct.add_struc(ida_idaapi.BADADDR, struct.name, False) - sid = ida_struct.get_struc_id(struct.name) - sptr = ida_struct.get_struc(sid) + idc.add_struc(ida_idaapi.BADADDR, struct.name, False) + sid = idc.get_struc_id(struct.name) + sptr = idc.get_struc(sid) # expand the struct to the desired size # XXX: do not increment API here, why? Not sure, but you cant do it here. - ida_struct.expand_struc(sptr, 0, struct.size) + idc.expand_struc(sptr, 0, struct.size) # add every member of the struct for off, member in struct.members.items(): @@ -923,7 +934,7 @@ def set_ida_struct(struct: Struct) -> bool: mflag = convert_size_to_flag(member.size) # create the new member - ida_struct.add_struc_member( + idc.add_struc_member( sptr, member.name, member.offset, @@ -937,8 +948,8 @@ def set_ida_struct(struct: Struct) -> bool: @execute_write def set_ida_struct_member_types(struct: Struct) -> bool: # find the specific struct - sid = ida_struct.get_struc_id(struct.name) - sptr = ida_struct.get_struc(sid) + sid = idc.get_struc_id(struct.name) + sptr = idc.get_struc(sid) data_changed = False for idx, member in enumerate(struct.members.values()): @@ -953,7 +964,7 @@ def set_ida_struct_member_types(struct: Struct) -> bool: # set the type mptr = sptr.get_member(idx) - was_set = ida_struct.set_member_tinfo( + was_set = idc.set_member_tinfo( sptr, mptr, 0, @@ -1023,68 +1034,69 @@ def ida_type_from_serialized(typ: bytes, fields: bytes): def get_enum_members(_enum) -> typing.Dict[str, int]: enum_members = {} - member = ida_enum.get_first_enum_member(_enum) - member_addr = ida_enum.get_enum_member(_enum, member, 0, 0) - member_name = ida_enum.get_enum_member_name(member_addr) + member = idc.get_first_enum_member(_enum) + member_addr = idc.get_enum_member(_enum, member, 0, 0) + member_name = idc.get_enum_member_name(member_addr) if member_name is None: return enum_members enum_members[member_name] = member - member = ida_enum.get_next_enum_member(_enum, member, 0) + member = idc.get_next_enum_member(_enum, member, 0) max_iters = 100 for _ in range(max_iters): if member == idaapi.BADADDR: break - member_addr = ida_enum.get_enum_member(_enum, member, 0, 0) - member_name = ida_enum.get_enum_member_name(member_addr) + member_addr = idc.get_enum_member(_enum, member, 0, 0) + member_name = idc.get_enum_member_name(member_addr) if member_name: enum_members[member_name] = member - member = ida_enum.get_next_enum_member(_enum, member, 0) + member = idc.get_next_enum_member(_enum, member, 0) else: _l.critical(f"IDA failed to iterate all enum members for enum %s", _enum) return enum_members +def enum_from_tif(tif): + enum_name = tif.get_type_name() + enum_members = get_enum_members(tif) + return Enum(enum_name, enum_members) + + @execute_write def enums() -> typing.Dict[str, Enum]: - _enums: typing.Dict[str, Enum] = {} - for i in range(ida_enum.get_enum_qty()): - _enum = ida_enum.getn_enum(i) - enum_name = ida_enum.get_enum_name(_enum) - enum_members = get_enum_members(_enum) - _enums[enum_name] = Enum(enum_name, enum_members) - return _enums + return get_types(structs=False, enums=True, typedefs=False) @execute_write def enum(name) -> typing.Optional[Enum]: - _enum = ida_enum.get_enum(name) + _enum = idc.get_enum(name) if not _enum: return None - enum_name = ida_enum.get_enum_name(_enum) + enum_name = idc.get_enum_name(_enum) enum_members = get_enum_members(_enum) return Enum(enum_name, enum_members) @execute_write def set_enum(bs_enum: Enum): - _enum = ida_enum.get_enum(bs_enum.name) + _enum = idc.get_enum(bs_enum.name) if not _enum: return False - ida_enum.del_enum(_enum) - enum_id = ida_enum.add_enum(ida_enum.get_enum_qty(), bs_enum.name, 0) + idc.del_enum(_enum) + ords = get_ordinal_count() + enum_id = idc.add_enum(ords, bs_enum.name, 0) if enum_id is None: _l.warning(f"IDA failed to create a new enum with {bs_enum.name}") return False for member_name, value in bs_enum.members.items(): - ida_enum.add_enum_member(enum_id, member_name, value) + idc.add_enum_member(enum_id, member_name, value) return True @@ -1130,7 +1142,7 @@ def typedefs() -> typing.Dict[str, Typedef]: typedefs = {} idati = idaapi.get_idati() use_new_check = use_new_typedef_check() - for ord_num in range(ida_typeinf.get_ordinal_qty(idati)): + for ord_num in range(get_ordinal_count()): tif = ida_typeinf.tinfo_t() success = tif.get_numbered_type(idati, ord_num) if not success: diff --git a/libbs/decompilers/ida/hooks.py b/libbs/decompilers/ida/hooks.py index 0ac728b..746c425 100644 --- a/libbs/decompilers/ida/hooks.py +++ b/libbs/decompilers/ida/hooks.py @@ -33,9 +33,7 @@ import ida_hexrays import ida_idp import ida_kernwin -import ida_struct import ida_typeinf -import ida_enum import idaapi import idc @@ -207,7 +205,7 @@ def ti_changed(self, ea, type_, fields): @while_should_watch def ida_enum_changed(self, enum_id, new_name=None, deleted=False, member_deleted=False): - name = ida_enum.get_enum_name(enum_id) + name = idc.get_enum_name(enum_id) _enum = compat.enum(name) if not deleted else Enum(name, {}) if name in self.interface._deleted_artifacts[Enum]: if member_deleted: @@ -240,7 +238,7 @@ def deleting_enum(self, id): def renaming_enum(self, id, is_enum, newname): enum_id = id if not is_enum: - enum_id = ida_enum.get_enum_member_enum(id) + enum_id = idc.get_enum_member_enum(id) # delete it self.ida_enum_changed(enum_id, deleted=True) @@ -273,7 +271,7 @@ def deleting_enum_member(self, id, cid): def ida_struct_changed(self, sid: int, new_name=None, deleted=False, member_deleted=False): # parse the info of the current struct - struct_name = new_name if new_name else ida_struct.get_struc_name(sid) + struct_name = new_name if new_name else idc.get_struc_name(sid) if struct_name in self.interface._deleted_artifacts[Struct]: if member_deleted: @@ -293,18 +291,18 @@ def ida_struct_changed(self, sid: int, new_name=None, deleted=False, member_dele self.interface.struct_changed(Struct(struct_name, -1, {}), deleted=True) return 0 - struct_ptr = ida_struct.get_struc(sid) + struct_ptr = idc.get_struc(sid) bs_struct = Struct( struct_name, - ida_struct.get_struc_size(struct_ptr), + idc.get_struc_size(struct_ptr), {}, ) for mptr in struct_ptr.members: - m_name = ida_struct.get_member_name(mptr.id) + m_name = idc.get_member_name(mptr.id) m_off = mptr.soff m_type = ida_typeinf.idc_get_type(mptr.id) if mptr.has_ti() else "" - m_size = ida_struct.get_member_size(mptr) + m_size = idc.get_member_size(mptr) bs_struct.add_struct_member(m_name, m_off, m_type, m_size) self.interface.struct_changed(bs_struct, deleted=False) @@ -326,14 +324,14 @@ def ida_stack_var_changed(self, sptr, mptr): type_str = stack_var_info.type # TODO: correct this fix in the get_func_stack_var_info - new_name = ida_struct.get_member_name(mptr.id) + new_name = idc.get_member_name(mptr.id) self.interface.stack_variable_changed( StackVariable(bs_offset, new_name, type_str, size, func_addr) ) @while_should_watch def struc_created(self, tid): - sptr = ida_struct.get_struc(tid) + sptr = idc.get_struc(tid) if not sptr.is_frame(): self.ida_struct_changed(tid) @@ -357,7 +355,7 @@ def struc_align_changed(self, sptr): # XXX - use struc_renamed(self, sptr) instead? @while_should_watch def renaming_struc(self, id, oldname, newname): - sptr = ida_struct.get_struc(id) + sptr = idc.get_struc(id) if not sptr.is_frame(): # delete it self.ida_struct_changed(id, deleted=True) @@ -407,7 +405,7 @@ def struc_member_changed(self, sptr, mptr): @while_should_watch def renamed(self, ea, new_name, local_name): # ignore any changes landing here for structs and stack vars - if ida_struct.is_member_id(ea) or ida_struct.get_struc(ea) or ida_enum.get_enum_name(ea): + if idc.is_member_id(ea) or idc.get_struc(ea) or idc.get_enum_name(ea): return 0 ida_func = idaapi.get_func(ea) @@ -473,13 +471,13 @@ def extra_cmt_changed(self, ea, line_idx, cmt): @while_should_watch def struc_cmt_changed(self, id, repeatable_cmt): """ - fullname = ida_struct.get_struc_name(id) + fullname = idc.get_struc_name(id) if "." in fullname: sname, smname = fullname.split(".", 1) else: sname = fullname smname = "" - cmt = ida_struct.get_struc_cmt(id, repeatable_cmt) + cmt = idc.get_struc_cmt(id, repeatable_cmt) """ return 0 From 886b4231f3ac55e3b8ead209c6e34eaaac2b912d Mon Sep 17 00:00:00 2001 From: mahaloz Date: Sat, 14 Sep 2024 14:15:17 -0700 Subject: [PATCH 02/11] Fix stack variables --- libbs/artifacts/artifact.py | 2 +- libbs/decompilers/ida/artifact_lifter.py | 4 +- libbs/decompilers/ida/compat.py | 361 ++++++++++++++--------- libbs/decompilers/ida/hooks.py | 100 ++++--- 4 files changed, 293 insertions(+), 174 deletions(-) diff --git a/libbs/artifacts/artifact.py b/libbs/artifacts/artifact.py index 5930da8..c964c50 100644 --- a/libbs/artifacts/artifact.py +++ b/libbs/artifacts/artifact.py @@ -21,7 +21,7 @@ class Artifact: ATTR_ATTR_IGNORE_SET ) - def __init__(self, last_change: Optional[datetime.datetime] = None, **kwargs): + def __init__(self, last_change: Optional[datetime.datetime] = None): self.last_change = last_change self._attr_ignore_set = set() diff --git a/libbs/decompilers/ida/artifact_lifter.py b/libbs/decompilers/ida/artifact_lifter.py index 30f2849..4c1dbc4 100644 --- a/libbs/decompilers/ida/artifact_lifter.py +++ b/libbs/decompilers/ida/artifact_lifter.py @@ -3,6 +3,7 @@ from libbs.api import ArtifactLifter l = logging.getLogger(name=__name__) +from . import compat class IDAArtifactLifter(ArtifactLifter): @@ -23,10 +24,11 @@ def lift_type(self, type_str: str) -> str: return type_str def lift_stack_offset(self, offset: int, func_addr: int) -> int: - return offset + return compat.ida_to_bs_stack_offset(func_addr, offset) def lower_type(self, type_str: str) -> str: return type_str def lower_stack_offset(self, offset: int, func_addr: int) -> int: + # TODO: this needs to be updated return abs(offset) #compat.ida_to_angr_stack_offset(func_addr, offset) diff --git a/libbs/decompilers/ida/compat.py b/libbs/decompilers/ida/compat.py index a43c27e..79d0b1d 100644 --- a/libbs/decompilers/ida/compat.py +++ b/libbs/decompilers/ida/compat.py @@ -29,6 +29,7 @@ from .interface import IDAInterface _l = logging.getLogger(__name__) +_IDA_VERSION = None FORM_TYPE_TO_NAME = { idaapi.BWN_PSEUDOCODE: "decompilation", @@ -157,12 +158,23 @@ def set_func_ret_type(ea, return_type_str): # +def get_ida_version(): + global _IDA_VERSION + if _IDA_VERSION is None: + _IDA_VERSION = Version(idaapi.get_kernel_version()) + + return _IDA_VERSION + + +def new_ida_typing_system(): + return get_ida_version() >= Version("9.0") + + def get_ordinal_count(): - dec_version = get_decompiler_version() - if dec_version < Version("9.0"): - return ida_typeinf.get_ordinal_qty(idaapi.get_idati()) - else: + if new_ida_typing_system(): return ida_typeinf.get_ordinal_count(idaapi.get_idati()) + else: + return ida_typeinf.get_ordinal_qty(idaapi.get_idati()) @execute_write @@ -253,18 +265,6 @@ def convert_type_str_to_ida_type(type_str) -> typing.Optional['ida_typeinf']: return tif if valid_parse is not None else None -@execute_write -def ida_to_bs_stack_offset(func_addr, ida_stack_off): - frame = idaapi.get_frame(func_addr) - if not frame: - return ida_stack_off - - frame_size = idc.get_struc_size(frame) - last_member_size = idaapi.get_member_size(frame.get_member(frame.memqty - 1)) - bs_soff = ida_stack_off - frame_size + last_member_size - return bs_soff - - @execute_write def convert_size_to_flag(size): """ @@ -599,23 +599,57 @@ def bs_header_from_tif(tif, name=None, addr=None): # -def lvar_to_bs_var(lvar, vdui=None, var_name=None) -> typing.Optional[FunctionArgument]: - # only func args are supported right now - if lvar is None or vdui is None or not lvar.is_arg_var or vdui.cfunc is None or not vdui.cfunc.lvars: - return None +def lvars_to_bs(lvars: list, vdui=None, var_names: list = None, recover_offset=False) -> list[typing.Union[FunctionArgument, StackVariable]]: + bs_vars = [] + arg_name_to_off = {} + if var_names and len(var_names) == len(lvars): + if recover_offset: + for offset, _lvar in enumerate(vdui.cfunc.lvars): + if _lvar.is_arg_var: + arg_name_to_off[_lvar.name] = offset - # find the offset - var_name = var_name or lvar.name - for offset, _lvar in enumerate(vdui.cfunc.lvars): - if _lvar.name == var_name: - break - else: - return None + for lvar_off, lvar in enumerate(lvars): + if lvar is None: + # this should really never happen + continue + + if vdui is None: + _l.warning("Cannot gather local variables from decompilation that does not exist!") + return bs_vars + + if lvar.is_arg_var: + if recover_offset: + offset = arg_name_to_off.get(lvar.name, None) + if offset is None: + continue + else: + offset = lvar_off + bs_cls = FunctionArgument + elif lvar.is_stk_var(): + offset = lvar.location.stkoff() + bs_cls = StackVariable + elif lvar.is_reg_var(): + # TODO: implement register variables + continue + else: + continue + + name = None + if var_names: + name = var_names[lvar_off] + if not name: + name = lvar.name + type_ = str(lvar.type()) + size = lvar.width + + var = bs_cls(name=name, type_=type_, size=size) + var.offset = offset + if isinstance(var, StackVariable): + var.addr = vdui.cfunc.entry_ea + + bs_vars.append(var) - # construct the type - type_ = str(_lvar.type()) - size = _lvar.width - return FunctionArgument(offset, var_name, type_, size) + return bs_vars @execute_write @@ -643,32 +677,175 @@ def rename_local_variables_by_names(func: Function, name_map: typing.Dict[str, s # Stack Vars # +def _deprecated_ida_to_bs_offset(func_addr, ida_stack_off): + frame = idaapi.get_frame(func_addr) + if not frame: + return ida_stack_off + + frame_size = idc.get_struc_size(frame) + last_member_size = idaapi.get_member_size(frame.get_member(frame.memqty - 1)) + bs_soff = ida_stack_off - frame_size + last_member_size + return bs_soff + + +def get_func_stack_tif(func): + if isinstance(func, int): + func = idaapi.get_func(func) + + if func is None: + return None + + tif = ida_typeinf.tinfo_t() + if not tif.get_func_frame(func): + return None + + return tif + + +def ida_to_bs_stack_offset(func_addr: int, ida_stack_off: int): + if not new_ida_typing_system(): + return _deprecated_ida_to_bs_offset(func_addr, ida_stack_off) + + func = idaapi.get_func(func_addr) + if not func: + raise ValueError(f"Function {hex(func_addr)} does not exist.") + + stack_tif = get_func_stack_tif(func) + if stack_tif is None: + _l.warning(f"Function {hex(func_addr)} does not have a stack frame.") + return ida_stack_off + + frame_size = stack_tif.get_size() + if frame_size == 0: + _l.warning(f"Function {hex(func_addr)} has a stack frame size of 0.") + return ida_stack_off + + # get the last member size + udt_data = ida_typeinf.udt_type_data_t() + stack_tif.get_udt_details(udt_data) + membs = [m for m in udt_data] + if not membs: + _l.warning(f"Function {hex(func_addr)} has a stack frame with no members.") + return ida_stack_off + + last_member_type = membs[-1].type + if not last_member_type: + _l.warning(f"Function {hex(func_addr)} has a stack frame with a member with no type.") + return ida_stack_off + + last_member_size = last_member_type.get_size() + bs_soff = ida_stack_off - frame_size + last_member_size + return bs_soff + + @execute_write @requires_decompilation def set_stack_variable(svar: StackVariable, decompiler_available=True, **kwargs): ida_code_view = kwargs.get('ida_code_view', None) - frame = idaapi.get_frame(svar.addr) changes = False - if frame is None or frame.memqty <= 0: - _l.warning(f"Function {svar.addr:x} does not have an associated function frame. Stopping sync here!") - return False + # if we have a decompiler, we can set the type and name ind decompilation + if ida_code_view is not None: + if svar.type: + ida_type = convert_type_str_to_ida_type(svar.type) + if ida_type is None: + _l.warning(f"IDA Failed to parse type for stack var {svar}") + return changes + + changes |= set_stack_vars_types({svar.offset: ida_type}, ida_code_view) + if changes: + ida_code_view.cfunc.refresh_func_ctext() - if svar.type: - ida_type = convert_type_str_to_ida_type(svar.type) - if ida_type is None: - _l.warning(f"IDA Failed to parse type for stack var {svar}") + lvars = [v for v in ida_code_view.cfunc.lvars if v.is_stk_var()] + if not lvars: return changes - changes |= set_stack_vars_types({svar.offset: ida_type}, ida_code_view) - if changes: - ida_code_view.cfunc.refresh_func_ctext() + if svar.name: + for lvar in lvars: + if lvar.location.stkoff() == svar.offset: + print(f"Renaming {lvar.name} to {svar.name}") + lvar.name = svar.name + changes |= True + break - frame = idaapi.get_frame(svar.addr) - if svar.name and idc.set_member_name(frame, svar.offset, svar.name): - changes |= True + if changes: + ida_code_view.cfunc.refresh_func_ctext() + else: + # TODO: support stack vars without a decompiler available + _l.warning("Setting stack variables without a decompiler is not supported yet.") return changes + +@execute_write +def get_func_stack_var_info(func_addr) -> typing.Dict[int, StackVariable]: + try: + decompilation = ida_hexrays.decompile(func_addr) + except ida_hexrays.DecompilationFailure: + _l.debug("Decompiling too many functions too fast! Slow down and try that operation again.") + return {} + + if decompilation is None: + _l.warning("Decompiled something that gave no decompilation") + return {} + + stack_var_info = {} + + for var in decompilation.lvars: + if not var.is_stk_var(): + continue + + size = var.width + name = var.name + + ida_offset = var.location.stkoff() - decompilation.get_stkoff_delta() + bs_offset = ida_to_bs_stack_offset(func_addr, ida_offset) + type_str = str(var.type()) + stack_var_info[bs_offset] = StackVariable( + ida_offset, name, type_str, size, func_addr + ) + + return stack_var_info + + +@execute_write +def set_stack_vars_types(var_type_dict, ida_code_view) -> bool: + """ + Sets the type of a stack variable, which should be a local variable. + Take special note of the types of first two parameters used here: + var_type_dict is a dictionary of the offsets and the new proposed type info for each offset. + This typeinfo should be gotten either by manully making a new typeinfo object or using the + parse_decl function. code_view is a _instance of vdui_t, which should be gotten through + open_pseudocode() from ida_hexrays. + + This function also is special since it needs to iterate all of the stack variables an unknown amount + of times until a fixed point of variables types not changing is met. + + + @param var_type_dict: Dict[stack_offset, ida_typeinf_t] + @param ida_code_view: A pointer to a vdui_t screen + @param deci: The libbs deci to do operations on + @return: + """ + + data_changed = False + fixed_point = False + func_addr = ida_code_view.cfunc.entry_ea + while not fixed_point: + fixed_point = True + for lvar in ida_code_view.cfunc.lvars: + if lvar.is_stk_var(): + # TODO: this algorithm may need be corrected for programs with func args on the stack + cur_off = abs(ida_to_bs_stack_offset(func_addr, lvar.location.stkoff())) + if cur_off in var_type_dict: + if str(lvar.type()) != str(var_type_dict[cur_off]): + data_changed |= ida_code_view.set_lvar_type(lvar, var_type_dict.pop(cur_off)) + fixed_point = False + # make sure to break, in case the size of lvars array has now changed + break + + return data_changed + + # # IDA Comment r/w # @@ -757,84 +934,6 @@ def set_decomp_comments(func_addr, cmt_dict: typing.Dict[int, str]): ida_hexrays.save_user_cmts(func_addr, ida_cmts) -# -# IDA Stack Var r/w -# - -@execute_write -def get_func_stack_var_info(func_addr) -> typing.Dict[int, StackVariable]: - try: - decompilation = ida_hexrays.decompile(func_addr) - except ida_hexrays.DecompilationFailure: - _l.debug("Decompiling too many functions too fast! Slow down and try that operation again.") - return {} - - if decompilation is None: - _l.warning("Decompiled something that gave no decompilation") - return {} - - stack_var_info = {} - - for var in decompilation.lvars: - if not var.is_stk_var(): - continue - - size = var.width - name = var.name - - ida_offset = var.location.stkoff() - decompilation.get_stkoff_delta() - bs_offset = ida_to_bs_stack_offset(func_addr, ida_offset) - type_str = str(var.type()) - stack_var_info[bs_offset] = StackVariable( - ida_offset, name, type_str, size, func_addr - ) - - return stack_var_info - - -@execute_write -def set_stack_vars_types(var_type_dict, ida_code_view) -> bool: - """ - Sets the type of a stack variable, which should be a local variable. - Take special note of the types of first two parameters used here: - var_type_dict is a dictionary of the offsets and the new proposed type info for each offset. - This typeinfo should be gotten either by manully making a new typeinfo object or using the - parse_decl function. code_view is a _instance of vdui_t, which should be gotten through - open_pseudocode() from ida_hexrays. - - This function also is special since it needs to iterate all of the stack variables an unknown amount - of times until a fixed point of variables types not changing is met. - - - @param var_type_dict: Dict[stack_offset, ida_typeinf_t] - @param ida_code_view: A pointer to a vdui_t screen - @param deci: The libbs deci to do operations on - @return: - """ - - data_changed = False - fixed_point = False - func_addr = ida_code_view.cfunc.entry_ea - while not fixed_point: - fixed_point = True - for lvar in ida_code_view.cfunc.lvars: - if lvar.is_stk_var(): - # TODO: this algorithm may need be corrected for programs with func args on the stack - cur_off = abs(ida_to_bs_stack_offset(func_addr, lvar.location.stkoff())) - if cur_off in var_type_dict: - if str(lvar.type()) != str(var_type_dict[cur_off]): - data_changed |= ida_code_view.set_lvar_type(lvar, var_type_dict.pop(cur_off)) - fixed_point = False - # make sure to break, in case the size of lvars array has now changed - break - - return data_changed - -@execute_write -def ida_get_frame(func_addr): - return idaapi.get_frame(func_addr) - - # # IDA Struct r/w # @@ -864,17 +963,16 @@ def bs_struct_from_tif(tif): @execute_write def structs(): - dec_version = get_decompiler_version() - if dec_version < Version("8.4"): - # TODO: in IDA 9, we will deprecate this block of code + if new_ida_typing_system(): + _structs = get_types(structs=True, enums=False, typedefs=False) + else: + _l.warning("You are using an old IDA, this will be deprecated in the future!") _structs = {} for struct_item in idautils.Structs(): idx, sid, name = struct_item[:] sptr = idc.get_struc(sid) size = idc.get_struc_size(sptr) _structs[name] = Struct(name, size, {}) - else: - _structs = get_types(structs=True, enums=False, typedefs=False) return _structs @@ -907,10 +1005,6 @@ def del_ida_struct(name) -> bool: sptr = idc.get_struc(sid) return idc.del_struc(sptr) -@execute_write -def set_struct_member_name(ida_struct, frame, offset, name): - idc.set_member_name(frame, offset, name) - @execute_write def set_ida_struct(struct: Struct) -> bool: # first, delete any struct by the same name if it exists @@ -1106,8 +1200,7 @@ def set_enum(bs_enum: Enum): def use_new_typedef_check(): - dec_version = get_decompiler_version() - return dec_version >= Version("8.4") if dec_version is not None else False + return get_ida_version() >= Version("8.4") def typedef_info(tif, use_new_check=False) -> typing.Tuple[bool, typing.Optional[str], typing.Optional[str]]: diff --git a/libbs/decompilers/ida/hooks.py b/libbs/decompilers/ida/hooks.py index 746c425..55d88c8 100644 --- a/libbs/decompilers/ida/hooks.py +++ b/libbs/decompilers/ida/hooks.py @@ -265,6 +265,45 @@ def deleting_enum_member(self, id, cid): self.ida_enum_changed(id, member_deleted=True) return 0 + # + # Stack Variable + # + + @while_should_watch + def frame_udm_renamed(self, func_ea, udm, oldname): + self._ida_stack_var_changed(func_ea, udm) + return 0 + + @while_should_watch + def frame_udm_changed(self, func_ea, udm_tid, udm_old, udm_new): + self._ida_stack_var_changed(func_ea, udm_new) + return 0 + + def _ida_stack_var_changed(self, func_ea, udm): + # TODO: implement stack var support when there is no decompiler available + return + + def _deprecated_ida_stack_var_changed(self, sptr, mptr): + # XXX: This is a deprecated function that will be removed in the future + func_addr = idaapi.get_func_by_frame(sptr.id) + try: + stack_var_info = compat.get_func_stack_var_info(func_addr)[ + compat.ida_to_bs_stack_offset(func_addr, mptr.soff) + ] + except KeyError: + _l.debug(f"Failed to track an internal changing stack var: {mptr.id}.") + return 0 + + # find the properties of the changed stack var + bs_offset = compat.ida_to_bs_stack_offset(func_addr, stack_var_info.offset) + size = stack_var_info.size + type_str = stack_var_info.type + + new_name = idc.get_member_name(mptr.id) + self.interface.stack_variable_changed( + StackVariable(bs_offset, new_name, type_str, size, func_addr) + ) + # # Struct & Stack Var Hooks # @@ -308,27 +347,6 @@ def ida_struct_changed(self, sid: int, new_name=None, deleted=False, member_dele self.interface.struct_changed(bs_struct, deleted=False) return 0 - def ida_stack_var_changed(self, sptr, mptr): - func_addr = idaapi.get_func_by_frame(sptr.id) - try: - stack_var_info = compat.get_func_stack_var_info(func_addr)[ - compat.ida_to_bs_stack_offset(func_addr, mptr.soff) - ] - except KeyError: - _l.debug(f"Failed to track an internal changing stack var: {mptr.id}.") - return 0 - - # find the properties of the changed stack var - bs_offset = compat.ida_to_bs_stack_offset(func_addr, stack_var_info.offset) - size = stack_var_info.size - type_str = stack_var_info.type - - # TODO: correct this fix in the get_func_stack_var_info - new_name = idc.get_member_name(mptr.id) - self.interface.stack_variable_changed( - StackVariable(bs_offset, new_name, type_str, size, func_addr) - ) - @while_should_watch def struc_created(self, tid): sptr = idc.get_struc(tid) @@ -386,8 +404,9 @@ def struc_member_deleted(self, sptr, off1, off2): @while_should_watch def struc_member_renamed(self, sptr, mptr): - if sptr.is_frame(): - self.ida_stack_var_changed(sptr, mptr) + if sptr.is_frame() and not compat.new_ida_typing_system(): + # TODO: this will be deprecated in the future + self._deprecated_ida_stack_var_changed(sptr, mptr) else: self.ida_struct_changed(sptr.id) @@ -395,8 +414,9 @@ def struc_member_renamed(self, sptr, mptr): @while_should_watch def struc_member_changed(self, sptr, mptr): - if sptr.is_frame(): - self.ida_stack_var_changed(sptr, mptr) + if sptr.is_frame() and not compat.new_ida_typing_system(): + # TODO: this will be deprecated in the future + self._deprecated_ida_stack_var_changed(sptr, mptr) else: self.ida_struct_changed(sptr.id) @@ -404,10 +424,6 @@ def struc_member_changed(self, sptr, mptr): @while_should_watch def renamed(self, ea, new_name, local_name): - # ignore any changes landing here for structs and stack vars - if idc.is_member_id(ea) or idc.get_struc(ea) or idc.get_enum_name(ea): - return 0 - ida_func = idaapi.get_func(ea) # symbols changing without any corresponding func is assumed to be global var if ida_func is None: @@ -532,12 +548,12 @@ def __init__(self, interface, *args, **kwargs): @while_should_watch def lvar_name_changed(self, vdui, lvar, new_name, *args): - self.func_arg_changed(vdui, lvar, reset_type=True, var_name=new_name) + self.local_var_changed(vdui, lvar, reset_type=True, var_name=new_name) return 0 @while_should_watch def lvar_type_changed(self, vdui, lvar, *args): - self.func_arg_changed(vdui, lvar, reset_name=True) + self.local_var_changed(vdui, lvar, reset_name=True) return 0 @while_should_watch @@ -551,11 +567,16 @@ def cmt_changed(self, cfunc, treeloc, cmt_str, *args): # helpers # - def func_arg_changed(self, vdui, lvar, reset_type=False, reset_name=False, var_name=None): + def local_var_changed(self, vdui, lvar, reset_type=False, reset_name=False, var_name=None): func_addr = vdui.cfunc.entry_ea - bs_var = compat.lvar_to_bs_var(lvar, vdui=vdui, var_name=var_name) - if not bs_var: + is_func_arg = lvar.is_arg_var + bs_vars = compat.lvars_to_bs( + [lvar], vdui=vdui, var_names=[var_name], + recover_offset=True if is_func_arg else False + ) + if not bs_vars: return + bs_var = next(iter(bs_vars)) if reset_type: bs_var.type = None @@ -563,7 +584,10 @@ def func_arg_changed(self, vdui, lvar, reset_type=False, reset_name=False, var_n bs_var.name = None # proxy the change through the func header - self.interface.function_header_changed( - FunctionHeader(None, func_addr, args={bs_var.offset: bs_var}), - fargs={bs_var.offset: bs_var}, - ) \ No newline at end of file + if is_func_arg: + self.interface.function_header_changed( + FunctionHeader(None, func_addr, args={bs_var.offset: bs_var}), + fargs={bs_var.offset: bs_var}, + ) + else: + self.interface.stack_variable_changed(bs_var) From 1ac4a2658e5bc6f0358d8eded598fd3319a70be9 Mon Sep 17 00:00:00 2001 From: mahaloz Date: Sun, 15 Sep 2024 10:54:58 -0700 Subject: [PATCH 03/11] Fix an off-by-one --- libbs/decompilers/ida/compat.py | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/libbs/decompilers/ida/compat.py b/libbs/decompilers/ida/compat.py index 79d0b1d..0ddfc7d 100644 --- a/libbs/decompilers/ida/compat.py +++ b/libbs/decompilers/ida/compat.py @@ -182,7 +182,7 @@ def get_types(structs=True, enums=True, typedefs=True) -> typing.Dict[str, Artif types = {} idati = idaapi.get_idati() - for ord_num in range(get_ordinal_count()): + for ord_num in range(1, get_ordinal_count()+1): tif = ida_typeinf.tinfo_t() success = tif.get_numbered_type(idati, ord_num) if not success: @@ -209,7 +209,7 @@ def get_types(structs=True, enums=True, typedefs=True) -> typing.Dict[str, Artif def get_ord_to_type_names() -> typing.Dict[int, typing.Tuple[str, typing.Type[Artifact]]]: idati = idaapi.get_idati() ord_to_name = {} - for ord_num in range(get_ordinal_count()): + for ord_num in range(1, get_ordinal_count()+1): tif = ida_typeinf.tinfo_t() success = tif.get_numbered_type(idati, ord_num) if not success: @@ -1232,22 +1232,7 @@ def typedef_info(tif, use_new_check=False) -> typing.Tuple[bool, typing.Optional @execute_write def typedefs() -> typing.Dict[str, Typedef]: - typedefs = {} - idati = idaapi.get_idati() - use_new_check = use_new_typedef_check() - for ord_num in range(get_ordinal_count()): - tif = ida_typeinf.tinfo_t() - success = tif.get_numbered_type(idati, ord_num) - if not success: - continue - - is_typedef, name, type_name = typedef_info(tif, use_new_check=use_new_check) - if not is_typedef: - continue - - typedefs[name] = Typedef(name=name, type_=type_name) - - return typedefs + return get_types(structs=False, enums=False, typedefs=True) @execute_write From d8339071aeba87aeb4668cae03a00abf85591bd2 Mon Sep 17 00:00:00 2001 From: mahaloz Date: Sun, 15 Sep 2024 13:08:46 -0700 Subject: [PATCH 04/11] Fix enum listing --- libbs/decompilers/ida/compat.py | 54 +++++++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 10 deletions(-) diff --git a/libbs/decompilers/ida/compat.py b/libbs/decompilers/ida/compat.py index 0ddfc7d..9557912 100644 --- a/libbs/decompilers/ida/compat.py +++ b/libbs/decompilers/ida/compat.py @@ -1125,38 +1125,72 @@ def ida_type_from_serialized(typ: bytes, fields: bytes): # Enums # -def get_enum_members(_enum) -> typing.Dict[str, int]: + +def _deprecated_get_enum_mmebers(_enum_id, max_size=100) -> typing.Dict[str, int]: enum_members = {} - member = idc.get_first_enum_member(_enum) - member_addr = idc.get_enum_member(_enum, member, 0, 0) + member = idc.get_first_enum_member(_enum_id) + member_addr = idc.get_enum_member(_enum_id, member, 0, 0) member_name = idc.get_enum_member_name(member_addr) if member_name is None: return enum_members enum_members[member_name] = member - member = idc.get_next_enum_member(_enum, member, 0) - max_iters = 100 - for _ in range(max_iters): + member = idc.get_next_enum_member(_enum_id, member, 0) + for _ in range(max_size): if member == idaapi.BADADDR: break - member_addr = idc.get_enum_member(_enum, member, 0, 0) + member_addr = idc.get_enum_member(_enum_id, member, 0, 0) member_name = idc.get_enum_member_name(member_addr) if member_name: enum_members[member_name] = member - member = idc.get_next_enum_member(_enum, member, 0) + member = idc.get_next_enum_member(_enum_id, member, 0) else: - _l.critical(f"IDA failed to iterate all enum members for enum %s", _enum) + _l.critical(f"IDA failed to iterate all enum members for enum %s", _enum_id) + + return enum_members + + +def get_enum_members(_enum_id, max_size=100) -> typing.Dict[str, int]: + if not new_ida_typing_system(): + return _deprecated_get_enum_mmebers(_enum_id, max_size=max_size) + + member_val = None + enum_members = {} + for i in range(max_size): + if i == 0: + member_val = idc.get_first_enum_member(_enum_id) + else: + member_val = idc.get_next_enum_member(_enum_id, member_val) + + if member_val == -1: + break + + member_name = idc.get_enum_member_name(idc.get_enum_member(_enum_id, member_val, 0, 0)) + if member_name is None: + _l.warning(f"IDA failed to get enum member name for %s in %s", member_val, _enum_id) + continue + + enum_members[member_name] = member_val + else: + _l.critical(f"IDA failed to iterate all enum members for enum %s", _enum_id) return enum_members def enum_from_tif(tif): enum_name = tif.get_type_name() - enum_members = get_enum_members(tif) + if not enum_name: + return None + + _enum = idc.get_enum(enum_name) + if not _enum: + return None + + enum_members = get_enum_members(_enum) return Enum(enum_name, enum_members) From 99508578aee569587ce1040d103a56b524df0d85 Mon Sep 17 00:00:00 2001 From: mahaloz Date: Sun, 15 Sep 2024 14:09:53 -0700 Subject: [PATCH 05/11] Fix enum hooks --- libbs/decompilers/ida/compat.py | 52 ++++++++++++++++++--------------- libbs/decompilers/ida/hooks.py | 5 ++-- 2 files changed, 31 insertions(+), 26 deletions(-) diff --git a/libbs/decompilers/ida/compat.py b/libbs/decompilers/ida/compat.py index 9557912..e35bff1 100644 --- a/libbs/decompilers/ida/compat.py +++ b/libbs/decompilers/ida/compat.py @@ -1154,29 +1154,35 @@ def _deprecated_get_enum_mmebers(_enum_id, max_size=100) -> typing.Dict[str, int return enum_members -def get_enum_members(_enum_id, max_size=100) -> typing.Dict[str, int]: +def get_enum_members(_enum: typing.Union["ida_typeinf.tinfo_t", int], max_size=100) -> typing.Dict[str, int]: + """ + _enum can either be an ida_typeinf.tinfo_t or an int (the old enum id system) + + """ if not new_ida_typing_system(): + _enum_id: int = _enum return _deprecated_get_enum_mmebers(_enum_id, max_size=max_size) - member_val = None + # this is an enum tif if we are here + enum_tif: "ida_typeinf.tinfo_t" = _enum + ei = ida_typeinf.enum_type_data_t() + if not enum_tif.get_enum_details(ei): + _l.error(f"IDA failed to get enum details for %s", enum_tif) + return {} + enum_members = {} - for i in range(max_size): - if i == 0: - member_val = idc.get_first_enum_member(_enum_id) - else: - member_val = idc.get_next_enum_member(_enum_id, member_val) + for e_memb in ei: + val = e_memb.value + if val == -1: + _l.warning(f"IDA failed to get enum member value for %s", e_memb) + break - if member_val == -1: + name = e_memb.name + if name is None: + _l.warning(f"IDA failed to get enum member name for %s", e_memb) break - member_name = idc.get_enum_member_name(idc.get_enum_member(_enum_id, member_val, 0, 0)) - if member_name is None: - _l.warning(f"IDA failed to get enum member name for %s in %s", member_val, _enum_id) - continue - - enum_members[member_name] = member_val - else: - _l.critical(f"IDA failed to iterate all enum members for enum %s", _enum_id) + enum_members[name] = val return enum_members @@ -1186,11 +1192,7 @@ def enum_from_tif(tif): if not enum_name: return None - _enum = idc.get_enum(enum_name) - if not _enum: - return None - - enum_members = get_enum_members(_enum) + enum_members = get_enum_members(tif) return Enum(enum_name, enum_members) @@ -1201,10 +1203,12 @@ def enums() -> typing.Dict[str, Enum]: @execute_write def enum(name) -> typing.Optional[Enum]: - _enum = idc.get_enum(name) - if not _enum: + new_enums = new_ida_typing_system() + _enum = get_ida_type(name=name) if new_enums else idc.get_enum(name) + if _enum is None or _enum == idaapi.BADADDR: return None - enum_name = idc.get_enum_name(_enum) + + enum_name = str(_enum.get_type_name()) if new_enums else idc.get_enum_name(_enum) enum_members = get_enum_members(_enum) return Enum(enum_name, enum_members) diff --git a/libbs/decompilers/ida/hooks.py b/libbs/decompilers/ida/hooks.py index 55d88c8..b859d5f 100644 --- a/libbs/decompilers/ida/hooks.py +++ b/libbs/decompilers/ida/hooks.py @@ -175,8 +175,9 @@ def local_types_changed(self, ltc, ordinal, name): self.interface.struct_changed(bs_struct) new_type_type = Struct elif tif.is_enum(): - # TODO: handle enum changes in IDA 9 - pass + bs_enum = compat.enum_from_tif(tif) + self.interface.enum_changed(bs_enum) + new_type_type = Enum self.interface.cached_ord_to_type_names[ordinal] = (name, new_type_type) return 0 From e871589775696720761048aa8ca626d40492c077 Mon Sep 17 00:00:00 2001 From: mahaloz Date: Sun, 15 Sep 2024 14:57:35 -0700 Subject: [PATCH 06/11] Fix struct setting (95%) --- libbs/decompilers/ida/compat.py | 89 ++++++++++++++++++++++++++++++--- 1 file changed, 82 insertions(+), 7 deletions(-) diff --git a/libbs/decompilers/ida/compat.py b/libbs/decompilers/ida/compat.py index e35bff1..8eb306e 100644 --- a/libbs/decompilers/ida/compat.py +++ b/libbs/decompilers/ida/compat.py @@ -251,6 +251,13 @@ def get_ida_type(ida_ord=None, name=None): # Type Converters # +def type_str_to_size(type_str) -> typing.Optional[int]: + ida_type = convert_type_str_to_ida_type(type_str) + if ida_type is None: + return None + + return ida_type.get_size() + @execute_write def convert_type_str_to_ida_type(type_str) -> typing.Optional['ida_typeinf']: if type_str is None or not isinstance(type_str, str): @@ -762,7 +769,6 @@ def set_stack_variable(svar: StackVariable, decompiler_available=True, **kwargs) if svar.name: for lvar in lvars: if lvar.location.stkoff() == svar.offset: - print(f"Renaming {lvar.name} to {svar.name}") lvar.name = svar.name changes |= True break @@ -1005,8 +1011,28 @@ def del_ida_struct(name) -> bool: sptr = idc.get_struc(sid) return idc.del_struc(sptr) + +def expand_ida_struct(sid, new_size): + """ + Only works in IDA 9 and up + """ + tif = ida_typeinf.tinfo_t() + if tif.get_type_by_tid(sid) and tif.is_udt(): + if tif.get_size() == new_size: + return True + + udm = ida_typeinf.udm_t() + udm.offset = 0 + idx = tif.find_udm(udm, ida_typeinf.STRMEM_LOWBND|ida_typeinf.STRMEM_SKIP_GAPS) + if idx != -1: + return tif.expand_udt(idx, new_size) + + return False + + @execute_write def set_ida_struct(struct: Struct) -> bool: + new_struct_system = new_ida_typing_system() # first, delete any struct by the same name if it exists sid = idc.get_struc_id(struct.name) if sid != idaapi.BADADDR: @@ -1016,31 +1042,48 @@ def set_ida_struct(struct: Struct) -> bool: # now make a struct header idc.add_struc(ida_idaapi.BADADDR, struct.name, False) sid = idc.get_struc_id(struct.name) - sptr = idc.get_struc(sid) + + struct_identifier = sid if new_struct_system else idc.get_struc(sid) # expand the struct to the desired size # XXX: do not increment API here, why? Not sure, but you cant do it here. - idc.expand_struc(sptr, 0, struct.size) + if new_struct_system: + expand_ida_struct(sid, struct.size) + else: + idc.expand_struc(struct_identifier, 0, struct.size) # add every member of the struct for off, member in struct.members.items(): + if member.size is None: + if member.type is None: + raise ValueError("Member size and type cannot both be None when setting a struct!") + + type_size = type_str_to_size(member.type) + if type_size is None: + _l.warning(f"Failed to get size for member %s of struct %s, assuming 8!", member.name, struct.name) + type_size = 8 + + member.size = type_size + + if member.offset is None: + member.offset = off + # convert to ida's flag system mflag = convert_size_to_flag(member.size) # create the new member idc.add_struc_member( - sptr, + struct_identifier, member.name, member.offset, mflag, - None, + -1, member.size, ) return True -@execute_write -def set_ida_struct_member_types(struct: Struct) -> bool: +def _depreacated_set_ida_struct_member_types(struct: Struct) -> bool: # find the specific struct sid = idc.get_struc_id(struct.name) sptr = idc.get_struc(sid) @@ -1069,6 +1112,38 @@ def set_ida_struct_member_types(struct: Struct) -> bool: return data_changed + +@execute_write +def set_ida_struct_member_types(bs_struct: Struct): + if not new_ida_typing_system(): + return _depreacated_set_ida_struct_member_types(bs_struct) + + struct_tif = get_ida_type(name=bs_struct.name) + if struct_tif is None: + return False + + udt_data = ida_typeinf.udt_type_data_t() + if not struct_tif.get_udt_details(udt_data): + return False + + data_changed = False + for udt_memb in udt_data: + offset = udt_memb.offset + bs_member = bs_struct.members.get(offset, None) + if bs_member is None: + continue + + tif = convert_type_str_to_ida_type(bs_member.type) + if tif is None: + _l.warning(f"Failed to convert type %s for struct member %s", bs_member.type, bs_member.name) + continue + + # TODO: investigate why this does not always work + udt_memb.type = tif + data_changed |= True + + return data_changed + # # Global Vars # From 7ee81ed196d6623866d74862300b23f39e862a9a Mon Sep 17 00:00:00 2001 From: mahaloz Date: Sun, 22 Sep 2024 13:01:37 -0700 Subject: [PATCH 07/11] Fix stack var setting --- libbs/api/artifact_lifter.py | 9 +- libbs/decompilers/ida/artifact_lifter.py | 30 ++++-- libbs/decompilers/ida/compat.py | 113 +++++++++++++++-------- libbs/decompilers/ida/interface.py | 3 +- 4 files changed, 101 insertions(+), 54 deletions(-) diff --git a/libbs/api/artifact_lifter.py b/libbs/api/artifact_lifter.py index 5e2f88e..172838e 100644 --- a/libbs/api/artifact_lifter.py +++ b/libbs/api/artifact_lifter.py @@ -1,7 +1,7 @@ import logging import typing -from libbs.artifacts import StackVariable, Artifact +from libbs.artifacts import StackVariable, Artifact, FunctionArgument, StructMember from libbs.api.type_parser import CTypeParser if typing.TYPE_CHECKING: @@ -105,9 +105,10 @@ def _lift_or_lower_artifact(self, artifact, mode): setattr(lifted_art, attr, self._lift_or_lower_artifact(attr_val, mode)) # nested args, stack_vars, or struct_members elif isinstance(attr_val, dict): - nested_arts = { - k: self._lift_or_lower_artifact(v, mode) for k, v in attr_val.items() - } + nested_arts = {} + for k, v in attr_val.items(): + nested_art = self._lift_or_lower_artifact(v, mode) + nested_arts[nested_art.offset if isinstance(nested_art, (StackVariable, FunctionArgument, StructMember)) else k] = nested_art setattr(lifted_art, attr, nested_arts) return lifted_art diff --git a/libbs/decompilers/ida/artifact_lifter.py b/libbs/decompilers/ida/artifact_lifter.py index 4c1dbc4..fb94be2 100644 --- a/libbs/decompilers/ida/artifact_lifter.py +++ b/libbs/decompilers/ida/artifact_lifter.py @@ -3,8 +3,6 @@ from libbs.api import ArtifactLifter l = logging.getLogger(name=__name__) -from . import compat - class IDAArtifactLifter(ArtifactLifter): lift_map = { @@ -12,23 +10,39 @@ class IDAArtifactLifter(ArtifactLifter): "__int32": "int", "__int16": "short", "__int8": "char", + "_BOOL": "bool", + "_BOOL1": "bool", + "_BOOL2": "short", + "_BOOL4": "int", + "_BOOL8": "long long", + "_BYTE": "char", + "_WORD": "unsigned short", + "_DWORD": "unsigned int", + "_QWORD": "unsigned long long", } def __init__(self, deci): super(IDAArtifactLifter, self).__init__(deci) def lift_type(self, type_str: str) -> str: - for ida_t, bs_t in self.lift_map.items(): - type_str = type_str.replace(ida_t, bs_t) - - return type_str + return self.lift_ida_type(type_str) def lift_stack_offset(self, offset: int, func_addr: int) -> int: + from . import compat return compat.ida_to_bs_stack_offset(func_addr, offset) def lower_type(self, type_str: str) -> str: + # no need to lift type for IDA, since it parses normal C + # types by default return type_str def lower_stack_offset(self, offset: int, func_addr: int) -> int: - # TODO: this needs to be updated - return abs(offset) #compat.ida_to_angr_stack_offset(func_addr, offset) + from . import compat + return compat.bs_to_ida_stack_offset(self.lower_addr(func_addr), offset) + + @staticmethod + def lift_ida_type(type_str: str) -> str: + for ida_t, bs_t in IDAArtifactLifter.lift_map.items(): + type_str = type_str.replace(ida_t, bs_t) + + return type_str \ No newline at end of file diff --git a/libbs/decompilers/ida/compat.py b/libbs/decompilers/ida/compat.py index 8eb306e..06b01ee 100644 --- a/libbs/decompilers/ida/compat.py +++ b/libbs/decompilers/ida/compat.py @@ -25,6 +25,7 @@ StructMember ) +from .artifact_lifter import IDAArtifactLifter if typing.TYPE_CHECKING: from .interface import IDAInterface @@ -441,10 +442,7 @@ def set_function(func: Function, decompiler_available=True, **kwargs): # stack vars if func.stack_vars and ida_code_view is not None: - for svar in func.stack_vars.values(): - changes |= set_stack_variable( - svar, decompiler_available=decompiler_available, ida_code_view=ida_code_view, **kwargs - ) + changes |= set_stack_variables(func.stack_vars, ida_code_view=ida_code_view) if changes and ida_code_view is not None: ida_code_view.refresh_view(changes) @@ -708,11 +706,7 @@ def get_func_stack_tif(func): return tif - -def ida_to_bs_stack_offset(func_addr: int, ida_stack_off: int): - if not new_ida_typing_system(): - return _deprecated_ida_to_bs_offset(func_addr, ida_stack_off) - +def get_frame_info(func_addr) -> typing.Tuple[int, int]: func = idaapi.get_func(func_addr) if not func: raise ValueError(f"Function {hex(func_addr)} does not exist.") @@ -720,12 +714,12 @@ def ida_to_bs_stack_offset(func_addr: int, ida_stack_off: int): stack_tif = get_func_stack_tif(func) if stack_tif is None: _l.warning(f"Function {hex(func_addr)} does not have a stack frame.") - return ida_stack_off + return None, None frame_size = stack_tif.get_size() if frame_size == 0: _l.warning(f"Function {hex(func_addr)} has a stack frame size of 0.") - return ida_stack_off + return None, None # get the last member size udt_data = ida_typeinf.udt_type_data_t() @@ -733,51 +727,88 @@ def ida_to_bs_stack_offset(func_addr: int, ida_stack_off: int): membs = [m for m in udt_data] if not membs: _l.warning(f"Function {hex(func_addr)} has a stack frame with no members.") - return ida_stack_off + return None, None last_member_type = membs[-1].type if not last_member_type: _l.warning(f"Function {hex(func_addr)} has a stack frame with a member with no type.") - return ida_stack_off + return None, None last_member_size = last_member_type.get_size() + return frame_size, last_member_size + +def ida_to_bs_stack_offset(func_addr: int, ida_stack_off: int): + if not new_ida_typing_system(): + return _deprecated_ida_to_bs_offset(func_addr, ida_stack_off) + + frame_size, last_member_size = get_frame_info(func_addr) + if frame_size is None or last_member_size is None: + return ida_stack_off + bs_soff = ida_stack_off - frame_size + last_member_size return bs_soff +def bs_to_ida_stack_offset(func_addr: int, bs_stack_off: int): + if not new_ida_typing_system(): + # maintain backwards compatibility + return bs_stack_off -@execute_write -@requires_decompilation -def set_stack_variable(svar: StackVariable, decompiler_available=True, **kwargs): + frame_size, last_member_size = get_frame_info(func_addr) + if frame_size is None or last_member_size is None: + return ida_stack_off + + ida_soff = bs_stack_off + frame_size - last_member_size + return ida_soff + +def set_stack_variables(svars: list[StackVariable], decompiler_available=True, **kwargs) -> bool: + """ + This function should only be called in a function that is already used in main-thread. + This should also mean decompilation is passed in. + """ ida_code_view = kwargs.get('ida_code_view', None) changes = False - # if we have a decompiler, we can set the type and name ind decompilation - if ida_code_view is not None: - if svar.type: - ida_type = convert_type_str_to_ida_type(svar.type) - if ida_type is None: - _l.warning(f"IDA Failed to parse type for stack var {svar}") - return changes - - changes |= set_stack_vars_types({svar.offset: ida_type}, ida_code_view) - if changes: - ida_code_view.cfunc.refresh_func_ctext() + if ida_code_view is None: + # TODO: support decompilation-less stack var setting + _l.warning("Cannot set stack variables without a decompiler.") + return changes + + lvars = {v.location.stkoff(): v for v in ida_code_view.cfunc.lvars if v.is_stk_var()} + if not lvars: + _l.warning("No stack variables found in decompilation to set. Making new ones is not supported") + return changes + + for bs_off, bs_var in svars.items(): + if bs_off not in lvars: + _l.warning(f"Stack variable at offset {bs_off} not found in decompilation.") + continue - lvars = [v for v in ida_code_view.cfunc.lvars if v.is_stk_var()] - if not lvars: - return changes + lvar = lvars[bs_off] - if svar.name: - for lvar in lvars: - if lvar.location.stkoff() == svar.offset: - lvar.name = svar.name + # naming: + if bs_var.name and bs_var.name != lvar.name: + ida_code_view.rename_lvar(lvar, bs_var.name, 1) + changes |= True + ida_code_view.cfunc.refresh_func_ctext() + lvars = {v.location.stkoff(): v for v in ida_code_view.cfunc.lvars if v.is_stk_var()} + + # typing + if bs_var.type: + curr_ida_type_str = str(lvar.type()) if lvar.type() else None + curr_ida_type = IDAArtifactLifter.lift_ida_type(curr_ida_type_str) if curr_ida_type_str else None + if curr_ida_type and bs_var.type != curr_ida_type: + new_type = convert_type_str_to_ida_type(bs_var.type) + if new_type is None: + _l.warning(f"Failed to convert type string {bs_var.type} to ida type.") + continue + + updated_type = ida_code_view.set_lvar_type(lvar, new_type) + if updated_type: changes |= True - break + ida_code_view.cfunc.refresh_func_ctext() + lvars = {v.location.stkoff(): v for v in ida_code_view.cfunc.lvars if v.is_stk_var()} - if changes: - ida_code_view.cfunc.refresh_func_ctext() - else: - # TODO: support stack vars without a decompiler available - _l.warning("Setting stack variables without a decompiler is not supported yet.") + if changes: + ida_code_view.refresh_view(True) return changes @@ -814,7 +845,7 @@ def get_func_stack_var_info(func_addr) -> typing.Dict[int, StackVariable]: @execute_write -def set_stack_vars_types(var_type_dict, ida_code_view) -> bool: +def _deprecated_set_stack_vars_types(var_type_dict, ida_code_view) -> bool: """ Sets the type of a stack variable, which should be a local variable. Take special note of the types of first two parameters used here: diff --git a/libbs/decompilers/ida/interface.py b/libbs/decompilers/ida/interface.py index f665f09..1365cba 100755 --- a/libbs/decompilers/ida/interface.py +++ b/libbs/decompilers/ida/interface.py @@ -287,7 +287,8 @@ def _functions(self) -> Dict[int, Function]: # stack vars def _set_stack_variable(self, svar: StackVariable, **kwargs) -> bool: - return compat.set_stack_variable(svar, headless=self.headless, decompiler_available=self.decompiler_available, **kwargs) + _l.warning("Setting stack vars using this API is deprecared. Use _set_function instead.") + return False # global variables def _set_global_variable(self, gvar: GlobalVariable, **kwargs) -> bool: From 0158286f08f81086d2b1ed6b700bdb0425ae8566 Mon Sep 17 00:00:00 2001 From: mahaloz Date: Sun, 22 Sep 2024 17:15:29 -0700 Subject: [PATCH 08/11] Maintain minimal backwards compatability --- .../bs_change_watcher/__init__.py | 7 ++- libbs/decompilers/ida/compat.py | 61 +++++++++++++------ libbs/decompilers/ida/hooks.py | 17 +++++- libbs/decompilers/ida/interface.py | 6 +- 4 files changed, 67 insertions(+), 24 deletions(-) diff --git a/examples/change_watcher_plugin/bs_change_watcher/__init__.py b/examples/change_watcher_plugin/bs_change_watcher/__init__.py index adce2a6..10b3b57 100644 --- a/examples/change_watcher_plugin/bs_change_watcher/__init__.py +++ b/examples/change_watcher_plugin/bs_change_watcher/__init__.py @@ -5,7 +5,6 @@ __version__ = "0.0.1" - def create_plugin(*args, **kwargs): """ This is the entry point that all decompilers will call in various ways. To remain agnostic, @@ -33,11 +32,15 @@ def create_plugin(*args, **kwargs): ) } + def _start_watchers(*x, **y): + deci.start_artifact_watchers() + deci.info("Artifact watchers started!") + # register a menu to open when you right click on the psuedocode view deci.gui_register_ctx_menu( "StartArtifactChangeWatcher", "Start watching artifact changes", - lambda *x, **y: deci.start_artifact_watchers(), + _start_watchers, category="ArtifactChangeWatcher" ) diff --git a/libbs/decompilers/ida/compat.py b/libbs/decompilers/ida/compat.py index 06b01ee..f0a0579 100644 --- a/libbs/decompilers/ida/compat.py +++ b/libbs/decompilers/ida/compat.py @@ -32,20 +32,32 @@ _l = logging.getLogger(__name__) _IDA_VERSION = None -FORM_TYPE_TO_NAME = { - idaapi.BWN_PSEUDOCODE: "decompilation", - idaapi.BWN_DISASM: "disassembly", - idaapi.BWN_FUNCS: "functions", - # idaapi.BWN_STRINGS - 0x1c: "structs", - # idaapi.BWN_ENUMS - 0x1b: "enums", - # this is idaapi.BWN_TILIST, but made into a constant for 8.3 compatibility - 0x3c: "types", -} - +FORM_TYPE_TO_NAME = None FUNC_FORMS = {"decompilation", "disassembly"} +def get_form_to_type_name(): + global FORM_TYPE_TO_NAME + if FORM_TYPE_TO_NAME is None: + mapping = { + idaapi.BWN_PSEUDOCODE: "decompilation", + idaapi.BWN_DISASM: "disassembly", + idaapi.BWN_FUNCS: "functions", + idaapi.BWN_STRINGS: "strings" + } + if get_ida_version() >= Version("9.0"): + mapping.update({ + idaapi.BWN_TILIST: "types" + }) + else: + mapping.update({ + idaapi.BWN_STRINGS: "structs", + idaapi.BWN_ENUMS: "enums", + 0x3c: "types" + }) + FORM_TYPE_TO_NAME = mapping + + return FORM_TYPE_TO_NAME + # # Wrappers for IDA Main thread r/w operations # a special note about these functions: @@ -168,7 +180,7 @@ def get_ida_version(): def new_ida_typing_system(): - return get_ida_version() >= Version("9.0") + return get_ida_version() >= Version("8.4") def get_ordinal_count(): @@ -692,6 +704,16 @@ def _deprecated_ida_to_bs_offset(func_addr, ida_stack_off): bs_soff = ida_stack_off - frame_size + last_member_size return bs_soff +def _deprecated_bs_to_ida_offset(func_addr, bs_stack_off): + frame = idaapi.get_frame(func_addr) + if not frame: + return bs_stack_off + + frame_size = idc.get_struc_size(frame) + last_member_size = idaapi.get_member_size(frame.get_member(frame.memqty - 1)) + ida_soff = bs_stack_off + frame_size - last_member_size + return ida_soff + def get_func_stack_tif(func): if isinstance(func, int): @@ -738,7 +760,7 @@ def get_frame_info(func_addr) -> typing.Tuple[int, int]: return frame_size, last_member_size def ida_to_bs_stack_offset(func_addr: int, ida_stack_off: int): - if not new_ida_typing_system(): + if get_ida_version() < Version("9.0"): return _deprecated_ida_to_bs_offset(func_addr, ida_stack_off) frame_size, last_member_size = get_frame_info(func_addr) @@ -749,9 +771,9 @@ def ida_to_bs_stack_offset(func_addr: int, ida_stack_off: int): return bs_soff def bs_to_ida_stack_offset(func_addr: int, bs_stack_off: int): - if not new_ida_typing_system(): + if get_ida_version() < Version("9.0"): # maintain backwards compatibility - return bs_stack_off + return _deprecated_bs_to_ida_offset(func_addr, bs_stack_off) frame_size, last_member_size = get_frame_info(func_addr) if frame_size is None or last_member_size is None: @@ -1078,10 +1100,10 @@ def set_ida_struct(struct: Struct) -> bool: # expand the struct to the desired size # XXX: do not increment API here, why? Not sure, but you cant do it here. - if new_struct_system: + if get_ida_version() >= Version("9.0"): expand_ida_struct(sid, struct.size) else: - idc.expand_struc(struct_identifier, 0, struct.size) + idc.expand_struc(struct_identifier, 0, struct.size, False) # add every member of the struct for off, member in struct.members.items(): @@ -1586,7 +1608,8 @@ def view_to_bs_context(view, get_var=True) -> typing.Optional[Context]: if form_type is None: return None - view_name = FORM_TYPE_TO_NAME.get(form_type, "unknown") + form_to_type_name = get_form_to_type_name() + view_name = form_to_type_name.get(form_type, "unknown") ctx = Context(screen_name=view_name) if view_name in FUNC_FORMS: ctx.addr = idaapi.get_screen_ea() diff --git a/libbs/decompilers/ida/hooks.py b/libbs/decompilers/ida/hooks.py index b859d5f..3869952 100644 --- a/libbs/decompilers/ida/hooks.py +++ b/libbs/decompilers/ida/hooks.py @@ -130,6 +130,7 @@ def __init__(self, interface): ida_idp.IDB_Hooks.__init__(self) self.interface: "IDAInterface" = interface self._seen_function_prototypes = {} + self._ver_9_or_higher = compat.get_ida_version() >= Version("9.0") def bs_type_deleted(self, ordinal): old_name, old_type = self.interface.cached_ord_to_type_names[ordinal] @@ -423,8 +424,20 @@ def struc_member_changed(self, sptr, mptr): return 0 + def _valid_rename_event(self, ea): + if not self._ver_9_or_higher: + # ignore any changes landing here for structs and stack vars + import ida_struct, ida_enum + return not (ida_struct.is_member_id(ea) or ida_struct.get_struc(ea) or ida_enum.get_enum_name(ea)) + + # in version 9 and above, this event is not triggered by structs + return True + @while_should_watch def renamed(self, ea, new_name, local_name): + if not self._valid_rename_event(ea): + return 0 + ida_func = idaapi.get_func(ea) # symbols changing without any corresponding func is assumed to be global var if ida_func is None: @@ -553,8 +566,8 @@ def lvar_name_changed(self, vdui, lvar, new_name, *args): return 0 @while_should_watch - def lvar_type_changed(self, vdui, lvar, *args): - self.local_var_changed(vdui, lvar, reset_name=True) + def lvar_type_changed(self, vu: "vdui_t", v: "lvar_t", *args) -> int: + self.local_var_changed(vu, v, reset_name=True) return 0 @while_should_watch diff --git a/libbs/decompilers/ida/interface.py b/libbs/decompilers/ida/interface.py index 1365cba..f83f409 100755 --- a/libbs/decompilers/ida/interface.py +++ b/libbs/decompilers/ida/interface.py @@ -43,6 +43,7 @@ def __init__(self, **kwargs): self._max_patch_size = 0xff self._decompiler_available = None self._dec_version = None + self._ida_analysis_finished = False # GUI properties self._updated_ctx = None @@ -249,7 +250,10 @@ def gui_goto(self, func_addr) -> None: def should_watch_artifacts(self) -> bool: # never do hooks while IDA is in initial startup phase - return self._artifact_watchers_started and ida_auto.auto_is_ok() + if not self._ida_analysis_finished: + self._ida_analysis_finished = ida_auto.auto_is_ok() + + return self._ida_analysis_finished and self._artifact_watchers_started # # Optional API From 029037ff7a5a8821fed83ab23ccdf372711d63e2 Mon Sep 17 00:00:00 2001 From: mahaloz Date: Sun, 29 Sep 2024 15:41:21 -0700 Subject: [PATCH 09/11] More fixes to structs --- libbs/__init__.py | 2 +- libbs/api/decompiler_interface.py | 2 +- libbs/artifacts/typedef.py | 18 ++++++++++++++++++ libbs/decompilers/ida/compat.py | 19 +++++++++++++++---- 4 files changed, 35 insertions(+), 6 deletions(-) diff --git a/libbs/__init__.py b/libbs/__init__.py index eaff94c..286ac83 100644 --- a/libbs/__init__.py +++ b/libbs/__init__.py @@ -1,4 +1,4 @@ -__version__ = "1.26.0" +__version__ = "1.26.1" import logging diff --git a/libbs/api/decompiler_interface.py b/libbs/api/decompiler_interface.py index 7790dfa..36f84cb 100644 --- a/libbs/api/decompiler_interface.py +++ b/libbs/api/decompiler_interface.py @@ -792,7 +792,7 @@ def get_identifiers(artifact: Artifact) -> Tuple: elif isinstance(artifact, FunctionArgument): # TODO: add addr to function arguments return (artifact.offset,) - elif isinstance(artifact, (Struct, Enum)): + elif isinstance(artifact, (Struct, Enum, Typedef)): return (artifact.name,) def get_defined_type(self, type_str) -> Optional[Artifact]: diff --git a/libbs/artifacts/typedef.py b/libbs/artifacts/typedef.py index 739e865..484801d 100644 --- a/libbs/artifacts/typedef.py +++ b/libbs/artifacts/typedef.py @@ -38,3 +38,21 @@ def __init__( def __str__(self): return f"" + + def nonconflict_merge(self, typedef2: "Typedef", **kwargs): + typedef1: Typedef = self.copy() + if not typedef2 or typedef1 == typedef2: + return typedef1.copy() + + master_state = kwargs.get("master_state", None) + local_names = {typedef1.name} + if master_state: + for _, typedef in master_state.get_typedefs().items(): + local_names.add(typedef.name) + else: + local_names = {typedef1.name} + + if typedef2.name not in local_names: + typedef1.name = typedef2.name + typedef1.type = typedef2.type + return typedef1 diff --git a/libbs/decompilers/ida/compat.py b/libbs/decompilers/ida/compat.py index f0a0579..ddeb1c9 100644 --- a/libbs/decompilers/ida/compat.py +++ b/libbs/decompilers/ida/compat.py @@ -1035,9 +1035,8 @@ def structs(): return _structs +def _deprecated_get_struct(name): -@execute_write -def struct(name): sid = idc.get_struc_id(name) if sid == idaapi.BADADDR: return None @@ -1055,13 +1054,25 @@ def struct(name): return _struct +@execute_write +def struct(name): + if not new_ida_typing_system(): + return _deprecated_get_struct(name) + + tid = ida_typeinf.get_named_type_tid(name) + tif = ida_typeinf.tinfo_t() + if tid != idaapi.BADADDR and tif.get_type_by_tid(tid) and tif.is_udt(): + return bs_struct_from_tif(tif) + + return None + @execute_write def del_ida_struct(name) -> bool: sid = idc.get_struc_id(name) if sid == idaapi.BADADDR: return False - sptr = idc.get_struc(sid) + sptr = sid if new_ida_typing_system() else idc.get_struc(sid) return idc.del_struc(sptr) @@ -1089,7 +1100,7 @@ def set_ida_struct(struct: Struct) -> bool: # first, delete any struct by the same name if it exists sid = idc.get_struc_id(struct.name) if sid != idaapi.BADADDR: - sptr = idc.get_struc(sid) + sptr = sid if new_struct_system else idc.get_struc(sid) idc.del_struc(sptr) # now make a struct header From cce53792829d020fb6772a02868170788be14c66 Mon Sep 17 00:00:00 2001 From: mahaloz Date: Sun, 29 Sep 2024 16:07:48 -0700 Subject: [PATCH 10/11] Update the readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b6d16ef..78a1de2 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ The minimum Python version is **3.10**. **If you plan on using libbs alone (with you must do `libbs --install` after pip install**. This will copy the appropriate files to your decompiler. ## Supported Decompilers -- IDA Pro: **8.4 >= V >= 7.3** +- IDA Pro: **>= 8.4** (if you have an older version, use `v1.26.0`) - Binary Ninja: **>= 2.4** - angr-management: **>= 9.0** - Ghidra: **>= 11.1** From 2ec1fc993a9624b97292d2c9aabf2186d8fb7802 Mon Sep 17 00:00:00 2001 From: mahaloz Date: Sun, 29 Sep 2024 16:12:45 -0700 Subject: [PATCH 11/11] Major bump --- libbs/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libbs/__init__.py b/libbs/__init__.py index 286ac83..9897543 100644 --- a/libbs/__init__.py +++ b/libbs/__init__.py @@ -1,4 +1,4 @@ -__version__ = "1.26.1" +__version__ = "2.0.0" import logging