From 5ebf17bfd4116347d231fb746c9907bdb2550ce1 Mon Sep 17 00:00:00 2001 From: mahaloz Date: Sat, 27 Jul 2024 17:22:30 -0700 Subject: [PATCH] Feat: Support Typedefs --- libbs/api/artifact_dict.py | 3 +- libbs/api/decompiler_interface.py | 23 ++++++++++++- libbs/artifacts/__init__.py | 3 ++ libbs/artifacts/typedef.py | 40 ++++++++++++++++++++++ libbs/decompilers/ida/compat.py | 54 +++++++++++++++++++++++++++++- libbs/decompilers/ida/interface.py | 12 ++++++- 6 files changed, 131 insertions(+), 4 deletions(-) create mode 100644 libbs/artifacts/typedef.py diff --git a/libbs/api/artifact_dict.py b/libbs/api/artifact_dict.py index 89b1fb90..a6e77515 100644 --- a/libbs/api/artifact_dict.py +++ b/libbs/api/artifact_dict.py @@ -3,7 +3,7 @@ from libbs.artifacts import ( Artifact, Comment, Enum, FunctionHeader, Function, FunctionArgument, - GlobalVariable, Patch, StackVariable, Struct, StructMember + GlobalVariable, Patch, StackVariable, Struct, StructMember, Typedef ) if typing.TYPE_CHECKING: @@ -44,6 +44,7 @@ def __init__(self, artifact_cls, deci: "DecompilerInterface", error_on_duplicate GlobalVariable: (self._deci._set_global_variable, self._deci._get_global_var, self._deci._global_vars, self._deci._del_global_var), Struct: (self._deci._set_struct, self._deci._get_struct, self._deci._structs, self._deci._del_struct), Enum: (self._deci._set_enum, self._deci._get_enum, self._deci._enums, self._deci._del_enum), + Typedef: (self._deci._set_typedef, self._deci._get_typedef, self._deci._typedefs, self._deci._del_typedef), Comment: (self._deci._set_comment, self._deci._get_comment, self._deci._comments, self._deci._del_comment), Patch: (self._deci._set_patch, self._deci._get_patch, self._deci._patches, self._deci._del_patch) } diff --git a/libbs/api/decompiler_interface.py b/libbs/api/decompiler_interface.py index 424e8765..1d1fcd66 100644 --- a/libbs/api/decompiler_interface.py +++ b/libbs/api/decompiler_interface.py @@ -17,7 +17,7 @@ Artifact, Function, FunctionHeader, StackVariable, Comment, GlobalVariable, Patch, - Enum, Struct, FunctionArgument, Context, Decompilation + Enum, Struct, FunctionArgument, Context, Decompilation, Typedef ) from libbs.decompilers import SUPPORTED_DECOMPILERS, ANGR_DECOMPILER, \ BINJA_DECOMPILER, IDA_DECOMPILER, GHIDRA_DECOMPILER @@ -97,6 +97,7 @@ def __init__( self.structs = ArtifactDict(Struct, self, error_on_duplicate=error_on_artifact_duplicates) self.patches = ArtifactDict(Patch, self, error_on_duplicate=error_on_artifact_duplicates) self.global_vars = ArtifactDict(GlobalVariable, self, error_on_duplicate=error_on_artifact_duplicates) + self.typedefs = ArtifactDict(Typedef, self, error_on_duplicate=error_on_artifact_duplicates) self._decompiler_available = decompiler_available # override the file-saved config when one is passed in manually, otherwise @@ -545,6 +546,26 @@ def _enums(self) -> Dict[str, Enum]: """ return {} + # typedefs + def _set_typedef(self, typedef: Typedef, **kwargs) -> bool: + return False + + def _get_typedef(self, name) -> Optional[Typedef]: + return None + + def _del_typedef(self, name) -> bool: + return False + + def _typedefs(self) -> Dict[str, Typedef]: + """ + Returns a dict of libbs.Typedef that contain the name of the typedefs in the decompiler. + Note: this does not contain the live artifacts of the Artifact, only the minimum knowledge to that the Artifact + exists. To get live artifacts, use the singleton function of the same name. + + @return: + """ + return {} + # patches def _set_patch(self, patch: Patch, **kwargs) -> bool: return False diff --git a/libbs/artifacts/__init__.py b/libbs/artifacts/__init__.py index 1c043373..709233ad 100644 --- a/libbs/artifacts/__init__.py +++ b/libbs/artifacts/__init__.py @@ -9,6 +9,7 @@ from .stack_variable import StackVariable from .struct import Struct, StructMember from .context import Context +from .typedef import Typedef ART_NAME_TO_CLS = { Function.__name__: Function, @@ -22,4 +23,6 @@ StructMember.__name__: StructMember, Patch.__name__: Patch, Decompilation.__name__: Decompilation, + Context.__name__: Context, + Typedef.__name__: Typedef, } diff --git a/libbs/artifacts/typedef.py b/libbs/artifacts/typedef.py new file mode 100644 index 00000000..739e8655 --- /dev/null +++ b/libbs/artifacts/typedef.py @@ -0,0 +1,40 @@ +from typing import Optional + +from .artifact import Artifact + + +class Typedef(Artifact): + """ + Describe a typedef. As an example: + typedef struct MyStruct { + int a; + int b; + } my_struct_t; + + name="my_struct_t" + type="MyStruct" + + Another example: + typedef int my_int_t; + + name="my_int_t" + type="int" + """ + + __slots__ = Artifact.__slots__ + ( + "name", + "type", + ) + + def __init__( + self, + name: str = None, + type_: Optional[str] = None, + **kwargs, + ): + super().__init__(**kwargs) + self.name: str = name + self.type: str = type_ + + def __str__(self): + return f"" diff --git a/libbs/decompilers/ida/compat.py b/libbs/decompilers/ida/compat.py index e9f8cbdd..9886bf09 100644 --- a/libbs/decompilers/ida/compat.py +++ b/libbs/decompilers/ida/compat.py @@ -20,7 +20,7 @@ import libbs from libbs.artifacts import ( - Struct, FunctionHeader, FunctionArgument, StackVariable, Function, GlobalVariable, Enum, Artifact, Context + Struct, FunctionHeader, FunctionArgument, StackVariable, Function, GlobalVariable, Enum, Artifact, Context, Typedef ) from PyQt5.Qt import QObject @@ -941,6 +941,58 @@ def set_enum(bs_enum: Enum): return True +# +# Typedefs +# + +@execute_write +def typedefs() -> typing.Dict[str, Typedef]: + typedefs = {} + idati = idaapi.get_idati() + for ord_num in range(ida_typeinf.get_ordinal_qty(idati)): + tif = ida_typeinf.tinfo_t() + success = tif.get_numbered_type(idati, ord_num) + if not success: + continue + + # TODO: this is incorrect! + if not tif.is_typeref(): + continue + + name = tif.get_type_name() + if not name: + continue + + type_name = tif.get_next_type_name() + if not type_name: + continue + + typedefs[name] = Typedef(name=name, type_=type_name) + + return typedefs + +@execute_write +def typedef(name) -> typing.Optional[Typedef]: + idati = idaapi.get_idati() + tif = ida_typeinf.tinfo_t() + success = tif.get_named_type(idati, name) + if not success: + return None + + type_name = tif.get_final_type_name() + return Typedef(name=name, type_=type_name) + +@execute_write +def set_typedef(bs_typedef: Typedef): + base_type_tif = convert_type_str_to_ida_type(bs_typedef.type) + if base_type_tif is None: + return False + + idati = idaapi.get_idati() + typedef_tif = ida_typeinf.tinfo_t() + typedef_tif.create_typedef(idati, base_type_tif.get_final_type_name()) + typedef_tif.set_named_type(idati, bs_typedef.name, ida_typeinf.NTF_REPLACE) + return True # # IDA GUI r/w diff --git a/libbs/decompilers/ida/interface.py b/libbs/decompilers/ida/interface.py index fe602946..0d4dbb40 100755 --- a/libbs/decompilers/ida/interface.py +++ b/libbs/decompilers/ida/interface.py @@ -10,7 +10,7 @@ from libbs.api.decompiler_interface import DecompilerInterface from libbs.artifacts import ( StackVariable, Function, FunctionHeader, Struct, Comment, GlobalVariable, Enum, Patch, Artifact, Decompilation, - Context + Context, Typedef ) from libbs.api.decompiler_interface import requires_decompilation from . import compat @@ -334,6 +334,16 @@ def _enums(self) -> Dict[str, Enum]: """ return compat.enums() + # typedefs + def _set_typedef(self, typedef: Typedef, **kwargs) -> bool: + return compat.set_typedef(typedef) + + def _get_typedef(self, name) -> Optional[Typedef]: + return compat.typedef(name) + + def _typedefs(self) -> Dict[str, Typedef]: + return compat.typedefs() + # patches def _set_patch(self, patch: Patch, **kwargs) -> bool: idaapi.patch_bytes(patch.addr, patch.bytes)