From 17406aa69d0a9e6555e09a9d05b78387f809474f Mon Sep 17 00:00:00 2001 From: CySHell Date: Wed, 14 Dec 2022 14:48:38 +0200 Subject: [PATCH 1/3] Non-RTTI constructor detection --- .../Constructors/DetectConstructor.py | 173 +++++++++++----- .../NoRttiDataAvailable/DetectVtables.py | 70 +++++++ .../NoRttiDataAvailable/__init__.py | 4 + ClassObjectRepresentation/CppClass.py | 33 +++- .../VirtualFunctionTable.py | 105 ---------- .../BaseClass.py | 0 .../BaseClassArray.py | 0 .../BaseClassDescriptor.py | 0 .../ClassContext.py | 2 +- .../ClassHierarchyDescriptor.py | 0 .../ClassHierarchyDeduction.py | 8 +- .../ClassHierarchyInference/__init__.py | 0 .../ClassMemoryLayout/ClassStructCreation.py | 0 .../ClassMemoryLayout/LayoutLoader.py | 0 .../ClassMemoryLayout/LayoutParser.py | 0 .../ClassMemoryLayout/__init__.py | 0 .../ClassMemoryLayout/class_layouts.layout | 0 .../CompleteObjectLocator.py | 0 .../TypeCreation.py | 0 .../TypeDescriptor.py | 0 .../VirtualFunctionTable.py | 186 ++++++++++++++++++ .../VirtualTableInference/__init__.py | 0 .../__init__.py | 0 .../example flow.drawio | 0 .../rtti-layout-better.png | Bin .../rtti-layout.png | Bin StartInspection.py | 19 +- __init__.py | 6 +- 28 files changed, 419 insertions(+), 187 deletions(-) create mode 100644 ClassDataStructureDetection/NoRttiDataAvailable/DetectVtables.py create mode 100644 ClassDataStructureDetection/NoRttiDataAvailable/__init__.py delete mode 100644 RttiInfomation/VirtualTableInference/VirtualFunctionTable.py rename {RttiInfomation => RttiInformation}/BaseClass.py (100%) rename {RttiInfomation => RttiInformation}/BaseClassArray.py (100%) rename {RttiInfomation => RttiInformation}/BaseClassDescriptor.py (100%) rename {RttiInfomation => RttiInformation}/ClassContext.py (99%) rename {RttiInfomation => RttiInformation}/ClassHierarchyDescriptor.py (100%) rename {RttiInfomation => RttiInformation}/ClassHierarchyInference/ClassHierarchyDeduction.py (97%) rename {RttiInfomation => RttiInformation}/ClassHierarchyInference/__init__.py (100%) rename {RttiInfomation => RttiInformation}/ClassMemoryLayout/ClassStructCreation.py (100%) rename {RttiInfomation => RttiInformation}/ClassMemoryLayout/LayoutLoader.py (100%) rename {RttiInfomation => RttiInformation}/ClassMemoryLayout/LayoutParser.py (100%) rename {RttiInfomation => RttiInformation}/ClassMemoryLayout/__init__.py (100%) rename {RttiInfomation => RttiInformation}/ClassMemoryLayout/class_layouts.layout (100%) rename {RttiInfomation => RttiInformation}/CompleteObjectLocator.py (100%) rename {RttiInfomation => RttiInformation}/TypeCreation.py (100%) rename {RttiInfomation => RttiInformation}/TypeDescriptor.py (100%) create mode 100644 RttiInformation/VirtualTableInference/VirtualFunctionTable.py rename {RttiInfomation => RttiInformation}/VirtualTableInference/__init__.py (100%) rename {RttiInfomation => RttiInformation}/__init__.py (100%) rename {RttiInfomation => RttiInformation}/example flow.drawio (100%) rename {RttiInfomation => RttiInformation}/rtti-layout-better.png (100%) rename {RttiInfomation => RttiInformation}/rtti-layout.png (100%) diff --git a/ClassDataStructureDetection/Constructors/DetectConstructor.py b/ClassDataStructureDetection/Constructors/DetectConstructor.py index cca56a7..450d751 100644 --- a/ClassDataStructureDetection/Constructors/DetectConstructor.py +++ b/ClassDataStructureDetection/Constructors/DetectConstructor.py @@ -1,33 +1,90 @@ import binaryninja as bn -from typing import List +from typing import * from ... import Config -from ...RttiInfomation.VirtualTableInference import VirtualFunctionTable -import pysnooper - - -def GetVirtualTableAssignmentInstruction(func: bn.function.Function): - current_candidate_instr = None - for instr in func.hlil.instructions: - # - if instr.operation == 17: - # Check if Arg1 is being assigned to. - func_params = func.hlil.source_function.parameter_vars.vars - if func_params and instr.vars: - if func_params[0] == instr.vars[0]: - # , - if instr.operands[1].operation == 27 or instr.operands[1].operation == 26: - # - # - # De-referencing the pointer, meaning if this - # pointer is to a struct, this is de-referencing offset 0x0. - if instr.operands[0].operation == 23 or instr.operands[0].operation == 24: - if type(instr.operands[0].operands[0]) == bn.highlevelil.HighLevelILVar: - current_candidate_instr = instr +from ...RttiInformation.VirtualTableInference import VirtualFunctionTable + +global_constructor_destructor_list: List = list() + + +def GetAllAssignmentInstructions(func: bn.function.Function) -> Dict: + """ + Search the given function for all data references assigned to an offset into the pointer given + in Arg1 of the function (the "This" pointer). + :return {offset_into_Arg1: [DataVar address]} + """ + candidate_instructions: Dict = dict() + + try: + for instr in func.hlil.instructions: + # + if instr.operation.name == "HLIL_ASSIGN": + # Check if Arg1 is being assigned to. + func_params = func.hlil.source_function.parameter_vars.vars + if func_params and instr.vars: + if func_params[0] == instr.vars[0]: + if instr.operands[1].operation.name == "HLIL_CONST_PTR" or \ + instr.operands[1].operation.name == "HLIL_CONST": + # A pointer or a constant is being assigned into an offset within Arg1 + # Example: + # + if instr.operands[0].operation.name == "HLIL_DEREF" or \ + instr.operands[0].operation.name == "HLIL_DEREF_FIELD": + if type(instr.operands[0].operands[0]) == bn.highlevelil.HighLevelILVar: + if instr.operands[0].operation.name == "HLIL_ARRAY_INDEX": + # Arg1 is treated as an array and the assignment is being done into + # an offset within the array. + # Example: + # , + offset_into_class = instr.operands[0].operands[1].operands[0] + else: + # Directly De-referencing the pointer, meaning if this pointer is to a + # struct, this is de-referencing offset 0x0. + offset_into_class = 0 + + if candidate_instructions.get(offset_into_class): + candidate_instructions[offset_into_class].append( + instr.operands[1].value.value + ) + else: + candidate_instructions.update({0: [instr.operands[1].value.value]}) + + elif type(instr.operands[0].operands[0]) == bn.highlevelil.HighLevelILAdd: + # Referencing an offset within the pointer. + # example: + # [, ] + if instr.operands[0].operands[0].operands[1].operation.name == "HLIL_CONST": + offset_into_class = instr.operands[0].operands[0].operands[1].value.value + if candidate_instructions.get(offset_into_class): + candidate_instructions[offset_into_class].append( + instr.operands[1].value.value + ) + else: + candidate_instructions.update( + { + offset_into_class: [ + instr.operands[1].value.value + ] + } + ) + else: + print(f"GetAllAssignmentInstructions: UNKNOWN assignment type! please report this." + f"\nInstruction: {instr}") + except Exception as e: + print(f"GetAllAssignmentInstructions {hex(func.start)}, Exception: {e}") + # We are only interested in the last assignment of a vfTable in a function, since the ones before it are + # base classes. + return candidate_instructions + + +def GetThisClassVirtualTableAssignmentInstruction(func: bn.function.Function) -> Optional[int]: + candidate_instructions = GetAllAssignmentInstructions(func) # We are only interested in the last assignment of a vfTable in a function, since the ones before it are # base classes. - # [ - return current_candidate_instr + if candidate_instructions.get(0): + return candidate_instructions[0][-1] + else: + return None def GetPotentialConstructors(bv: bn.binaryview, vfTable_addr: int) -> \ @@ -45,14 +102,14 @@ def GetPotentialConstructors(bv: bn.binaryview, vfTable_addr: int) -> \ return potential_constructors -def DetectConstructorForVTable(bv: bn.binaryview, vfTable_addr: int, vfTable_contained_functions: List[int]) -> bool: - found_constructors = 0 - potential_constructors: List[bn.function.Function] = GetPotentialConstructors(bv, vfTable_addr) - for potential_constructor in potential_constructors: - if VerifyConstructor(bv, potential_constructor, found_constructors): +def DetectConstructorForVTable(bv: bn.binaryview, vfTable_addr: int) -> list[bn.function.Function]: + potential_constructors: List[bn.function.Function] = list() + for potential_constructor in GetPotentialConstructors(bv, vfTable_addr): + if VerifyConstructor(bv, potential_constructor): + potential_constructors.append(potential_constructor) print(f'ClassyPP: Found constructor - {potential_constructor.name}') - found_constructors += 1 - return found_constructors != 0 + global_constructor_destructor_list.append(potential_constructor.start) + return potential_constructors def IsDestructor(bv: bn.binaryview, potential_destructor: bn.function.Function) -> bool: @@ -67,36 +124,46 @@ def IsDestructor(bv: bn.binaryview, potential_destructor: bn.function.Function) return False -def VerifyConstructor(bv: bn.binaryview, potential_constructor: bn.function.Function, found_constructors: int) -> bool: +def DefineConstructor(bv: bn.binaryview, potential_constructors: list[bn.function.Function], + vtable_addr: int) -> bool: + # Since several constructors with the same name (but different signature) may exist, we + # will attach a postfix index to each of the names. + constructor_index = 0 + class_name: str = bv.get_data_var_at(vtable_addr).name + if class_name.endswith("_vfTable"): + # Remove the _vfTable tag from the name + class_name = class_name[:-8] + for constructor in potential_constructors: + func_type = "Constructor" + if IsDestructor(bv, constructor): + func_type = "Destructor" + if Config.CONSTRUCTOR_FUNCTION_HANDLING == 0: + AddComment(bv, constructor.start, vtable_addr, + class_name, func_type) + elif Config.CONSTRUCTOR_FUNCTION_HANDLING == 1: + ChangeFuncName(bv, constructor.start, constructor_index, + class_name, func_type) + else: + # invalid choice + return False + constructor_index += 1 + return True + + +def VerifyConstructor(bv: bn.binaryview, potential_constructor: bn.function.Function) -> bool: # The heuristics used here will locate both the constructors and destructors. # It is not easy to automatically distinguish between the two. - func_type = "Constructor" + try: - if instr := GetVirtualTableAssignmentInstruction(potential_constructor): - pointer: int = instr.operands[1].operands[0] + if pointer := GetThisClassVirtualTableAssignmentInstruction(potential_constructor): data_refs = list(bv.get_data_refs_from(pointer)) if data_refs: if len(data_refs) != 1: # print(f'Error, too many data refs for {pointer}') - pass + return False else: # Check if this is a function pointer - if bv.get_function_at(data_refs[0]): - class_name: str = bv.get_data_var_at(pointer).name - if class_name.endswith("_vfTable"): - # Remove the _vfTable tag from the name - class_name = class_name[:-8] - if IsDestructor(bv, potential_constructor): - func_type = "Destructor" - if Config.CONSTRUCTOR_FUNCTION_HANDLING == 0: - AddComment(bv, potential_constructor.start, pointer, - class_name, func_type) - elif Config.CONSTRUCTOR_FUNCTION_HANDLING == 1: - ChangeFuncName(bv, potential_constructor.start, found_constructors, - class_name, func_type) - else: - # invalid choice - return False + if bv.get_function_at(data_refs[0]) is not None: return True else: # print(f'Error in instruction {instr}') diff --git a/ClassDataStructureDetection/NoRttiDataAvailable/DetectVtables.py b/ClassDataStructureDetection/NoRttiDataAvailable/DetectVtables.py new file mode 100644 index 0000000..d9db9a3 --- /dev/null +++ b/ClassDataStructureDetection/NoRttiDataAvailable/DetectVtables.py @@ -0,0 +1,70 @@ +import binaryninja as bn +from typing import List +from ... import Config +from ...RttiInformation.VirtualTableInference import VirtualFunctionTable +from ..Constructors import DetectConstructor +from ...ClassObjectRepresentation import CppClass + + +def VerifyNonRttiVtable(bv: bn.binaryview, potential_vtable_addr: int) -> bool: + if potential_vtable_addr in VirtualFunctionTable.global_vfTables: + return True + else: + verified_vtable = VirtualFunctionTable.VFTABLE(bv, + potential_vtable_addr, + f"vtable_{str(potential_vtable_addr)}_nonRtti") + if verified_vtable.verified: + return True + return False + + +def DetectPotentialCandidates(bv: bn.binaryview): + """ + The general algorithm of this function is this: + 1. Go over all recognized functions in the binaryView. + 1.1 If the function is already defined as a constructor / destructor by the RTTI information then skip it. + 1.2 If the function is verified as a possible constructor / destructor detection algorithm (but was not mentioned + by the RTTI information) then check all possible vtable assignments in the function. + 2. First, if we have an assignment of a vTable to offset 0 of Arg1 then we assume this is indeed + a constructor / destructor - vTable is defined as vtable_vTableAddress_nonRtti, and class is defined + as class_vTableAddress. + 3. Once we found a constructor / destructor then we add all non 0 offsets of Arg1 assignments found in the + function for further inspection (This is he deffered_vtable_addr list). + The logic here is that the class may contain vTable assignments of its base or multiple inherited classes, + and those vTables should also have their own constructors / destructors somewhere else in the file. + 4. Once we searched all functions in the bv, we now iterate the deffered_vtable_addr list to detect which + of these addresses was later confirmed as a vtable, and accordingly we can add this information to the + correct class. + """ + print("Searching for Non-RTTI vTables...") + deffered_vtable_addr: list = list() + for func in bv.functions: + if func.start not in DetectConstructor.global_constructor_destructor_list: + if DetectConstructor.VerifyConstructor(bv, func, -1): + print(hex(func.start)) + assignment_instructions = DetectConstructor.GetAllAssignmentInstructions(func) + # Check if the last assignment into offset 0 of Arg1 in the constructor func is a vTable. + suspected_vtable: int = assignment_instructions[0][-1] + class_name: str = CppClass.GenerateClassNameFromVtableAddr(suspected_vtable) + if VerifyNonRttiVtable(bv, suspected_vtable): + if detected_class := CppClass.global_classes.get(class_name): + # If the class was already defined then do nothing (for now) + # TODO: merge more info into the class based on this newly found constructor. + if func.start not in detected_class.constructors: + detected_class.constructors.append(func.start) + else: + detected_class: CppClass.ClassyClass = CppClass.ClassyClass(name=class_name, + vfTable_addr=suspected_vtable, + constructors=[func.start]) + + for class_offset, potential_table_addresses in assignment_instructions.items(): + + for potential_vtable_addr in potential_table_addresses: + if potential_vtable_addr in VirtualFunctionTable.global_vfTables: + pass + else: + if verified_vtable := VirtualFunctionTable.VFTABLE( + bv, + potential_vtable_addr, + f"vtable_{str(potential_vtable_addr)}_nonRtti"): + pass diff --git a/ClassDataStructureDetection/NoRttiDataAvailable/__init__.py b/ClassDataStructureDetection/NoRttiDataAvailable/__init__.py new file mode 100644 index 0000000..81d36cb --- /dev/null +++ b/ClassDataStructureDetection/NoRttiDataAvailable/__init__.py @@ -0,0 +1,4 @@ +""" +This package deals with a situation where RTTI data is non existent (possibly removed by the compiler) +but the code still includes classes. +""" \ No newline at end of file diff --git a/ClassObjectRepresentation/CppClass.py b/ClassObjectRepresentation/CppClass.py index 9f7c383..f21376c 100644 --- a/ClassObjectRepresentation/CppClass.py +++ b/ClassObjectRepresentation/CppClass.py @@ -1,13 +1,30 @@ +from dataclasses import dataclass, field +# {ClassName: ClassyClass} +global_classes: dict = dict() + + +def GenerateClassNameFromVtableAddr(vTable_addr: int) -> str: + return f"class_{str(vTable_addr)}" + + +@dataclass class ClassyClass: - def __init__(self): - self.name: str = '' - self.size: int = 0 + def __init__(self, name: str, vfTable_addr: int, constructors: list[int] = None, + inherited_classes: list = None, vfTable_functions: list[int] = None, size: int = 0, + namespace="", fields=None): + + self.name: str = name + self.vfTable_addr: int = vfTable_addr + self.constructors: list[int] = constructors if constructors else list() + self.namespace: str = namespace + self.size: int = size # fields - {offset: (, , Optional(Address in executable pointed to))} - self.fields: dict = dict() + self.fields: dict = fields if fields else dict() + # list[ClassyClass] + self.inherited_classes: list = inherited_classes if inherited_classes else list() # vfTable - A list of all function addresses in the table - self.vfTable: list[int] = list() - self.constructors: list[int] = list() - self.inherited_classes: list[ClassyClass] = list() - self.namespace: str = '' \ No newline at end of file + self.vfTable_functions: list[int] = vfTable_functions if vfTable_functions else list() + + global_classes.update({self.name: self}) diff --git a/RttiInfomation/VirtualTableInference/VirtualFunctionTable.py b/RttiInfomation/VirtualTableInference/VirtualFunctionTable.py deleted file mode 100644 index 4fcad3c..0000000 --- a/RttiInfomation/VirtualTableInference/VirtualFunctionTable.py +++ /dev/null @@ -1,105 +0,0 @@ -import binaryninja as bn - -from ...Common import Utils -from typing import * - -# {vfTable_addr: [ContainedFunctions]} -global_vfTables: Dict[int, List[int]] = dict() -global_functions_contained_in_all_vfTables: List[int] = list() - - -class VFTABLE: - - def __init__(self, bv: bn.binaryview, base_addr: int, demangled_name: str): - self.bv: bn.binaryview = bv - self.base_addr: int = base_addr - self.vfTable_length: int = 0 - # Strip the "class " from the start of the demangled name. - self.demangled_name: str = demangled_name[6:] if demangled_name.startswith("class ") else demangled_name - self.contained_functions: List[int] = list() - self.verified = self.VerifyVFT() - - def VerifyVFT(self) -> bool: - data_refs_from_base_addr = list(self.bv.get_data_refs_from(self.base_addr)) - if len(data_refs_from_base_addr) == 0: - # This is a situation that occurs due to a bug in binary ninja - if the data var at base_addr - # is defined as a symbol from a previously loaded PDB file then binja will not recognize any data refs. - # Since we are positive at this point that this location is a vTable (since we verified the COL for this - # class) then it is safe to change its type to 'void *' in order to "fix" the binja bug and allow it to - # recognize data refs. - vTable_data_var = self.bv.get_data_var_at(self.base_addr) - vTable_data_var.type = self.bv.parse_type_string("void*")[0] - # Now we should see data refs from this address - data_refs_from_base_addr = list(self.bv.get_data_refs_from(self.base_addr)) - - if len(data_refs_from_base_addr) > 0: - if self.bv.get_function_at(data_refs_from_base_addr[0]): - if self.DefineVFT(): - # Update this vfTable in the global table, this will be used later for locating constructor funcs - global_vfTables.update({self.base_addr: self.contained_functions}) - Utils.LogToFile(f'VFTABLE: verified table at address {hex(self.base_addr)}') - return True - return False - - def DefineVFT(self) -> bool: - Utils.LogToFile(f'vfTable: Attempt to define data var at {hex(self.base_addr)}') - current_data_var_addr: int = self.base_addr - while self.IsPointerToFunction(current_data_var_addr): - self.vfTable_length += 1 - current_data_var_addr += 0x8 if self.bv.arch.name == "x86_64" else 0x4 - if self.vfTable_length > 0: - try: - # Define the Complete object locator - self.bv.define_user_data_var(self.base_addr, - self.bv.parse_type_string(f'void*[{self.vfTable_length}]')[0], - self.demangled_name) - Utils.LogToFile(f'vfTable: Defined data var at {hex(self.base_addr)}') - return True - except Exception as e: - Utils.LogToFile(f'vfTable: Failed to Define data var at {hex(self.base_addr)}, \n Exception: {e}') - return False - else: - return False - - def GetBinjaVoidPointerType(self) -> bn.types.PointerType: - return bn.Type.pointer(self.bv.arch, self.bv.parse_type_string("void")[0]) - - def GetPointer(self, pointer_addr: int) -> Optional[bn.DataVariable]: - # Sometimes binja parses PDB information incorrectly, and instead of a pointer to a vTable - # it just defines the struct that the PDB says defines the vTable. - # If this happens - we dont change the PDB struct, we just say this is a pointer to the PDB struct. - pointer: bn.DataVariable - if pointer := self.bv.get_data_var_at(pointer_addr): - if type(pointer) != bn.types.PointerType: - try: - Utils.LogToFile(f"GetPointer: Overriding original type for Vtable -\n" - f"pointer_addr: {hex(pointer_addr)}\n" - f"current pointer type: {pointer.type}\n" - f"pointer name: {pointer.name}") - self.bv.define_user_data_var(pointer_addr, - self.GetBinjaVoidPointerType(), - pointer.name) - return self.bv.get_data_var_at(pointer_addr) - except Exception as e: - Utils.LogToFile(f"GetPointer: Exception while trying to define pointer at addr {pointer_addr}.\n" - f"Exception: {e}") - return None - return pointer - return None - - def IsPointerToFunction(self, pointer_addr: int) -> bool: - try: - if pointer := self.GetPointer(pointer_addr): - if self.bv.get_sections_at(pointer.value)[0].semantics is \ - bn.SectionSemantics.ReadOnlyCodeSectionSemantics: - if not self.bv.get_function_at(pointer.value): - self.bv.add_function(pointer.value) - self.contained_functions.append(pointer.value) - return True - except Exception as e: - Utils.LogToFile(f"IsPointerToFunction: Failed to determine if pointer to function at {pointer_addr}.\n" - f"Exception: {e}") - return False - - def GetLength(self): - return self.vfTable_length diff --git a/RttiInfomation/BaseClass.py b/RttiInformation/BaseClass.py similarity index 100% rename from RttiInfomation/BaseClass.py rename to RttiInformation/BaseClass.py diff --git a/RttiInfomation/BaseClassArray.py b/RttiInformation/BaseClassArray.py similarity index 100% rename from RttiInfomation/BaseClassArray.py rename to RttiInformation/BaseClassArray.py diff --git a/RttiInfomation/BaseClassDescriptor.py b/RttiInformation/BaseClassDescriptor.py similarity index 100% rename from RttiInfomation/BaseClassDescriptor.py rename to RttiInformation/BaseClassDescriptor.py diff --git a/RttiInfomation/ClassContext.py b/RttiInformation/ClassContext.py similarity index 99% rename from RttiInfomation/ClassContext.py rename to RttiInformation/ClassContext.py index 278380f..d336382 100644 --- a/RttiInfomation/ClassContext.py +++ b/RttiInformation/ClassContext.py @@ -6,7 +6,7 @@ from ..Common import Utils from .. import Config from .ClassMemoryLayout import ClassStructCreation -import pysnooper + ############################################################################################### # GLOBAL STRUCTS diff --git a/RttiInfomation/ClassHierarchyDescriptor.py b/RttiInformation/ClassHierarchyDescriptor.py similarity index 100% rename from RttiInfomation/ClassHierarchyDescriptor.py rename to RttiInformation/ClassHierarchyDescriptor.py diff --git a/RttiInfomation/ClassHierarchyInference/ClassHierarchyDeduction.py b/RttiInformation/ClassHierarchyInference/ClassHierarchyDeduction.py similarity index 97% rename from RttiInfomation/ClassHierarchyInference/ClassHierarchyDeduction.py rename to RttiInformation/ClassHierarchyInference/ClassHierarchyDeduction.py index 72097d6..8ce29f7 100644 --- a/RttiInfomation/ClassHierarchyInference/ClassHierarchyDeduction.py +++ b/RttiInformation/ClassHierarchyInference/ClassHierarchyDeduction.py @@ -2,7 +2,7 @@ import networkx as nx from networkx import DiGraph -from ...RttiInfomation import ClassContext +from ...RttiInformation import ClassContext from ...Common import Utils from ... import Config import binaryninja as bn @@ -136,17 +136,17 @@ def CreateBcdHierarchyRecursively(base_class_array: List[int], def WriteGraphToFile(graph: DiGraph, gexf=True, graphml=False): if gexf: # To read the following stored graph: read_gexf(Config.GRAPH_FILE_FULL_PATH) - nx.write_gexf(graph, Config.GRAPH_FILE_FULL_PATH + 'RttiInfomation.gexf') + nx.write_gexf(graph, Config.GRAPH_FILE_FULL_PATH + 'RttiInformation.gexf') if graphml: # Write the graph in graphml form in order to be able to upload it to other databases (such as neo4j) # In neo4j: - # CALL apoc.import.graphml('RttiInfomation.graphml', {storeNodeIds: true}) + # CALL apoc.import.graphml('RttiInformation.graphml', {storeNodeIds: true}) # MATCH (n) # CALL apoc.create.addLabels([id(n)], [n.id]) # yield node # return node - nx.write_graphml(graph, Config.GRAPH_FILE_FULL_PATH + 'RttiInfomation.graphml') + nx.write_graphml(graph, Config.GRAPH_FILE_FULL_PATH + 'RttiInformation.graphml') def CreateHierarchyGraph() -> nx.DiGraph: diff --git a/RttiInfomation/ClassHierarchyInference/__init__.py b/RttiInformation/ClassHierarchyInference/__init__.py similarity index 100% rename from RttiInfomation/ClassHierarchyInference/__init__.py rename to RttiInformation/ClassHierarchyInference/__init__.py diff --git a/RttiInfomation/ClassMemoryLayout/ClassStructCreation.py b/RttiInformation/ClassMemoryLayout/ClassStructCreation.py similarity index 100% rename from RttiInfomation/ClassMemoryLayout/ClassStructCreation.py rename to RttiInformation/ClassMemoryLayout/ClassStructCreation.py diff --git a/RttiInfomation/ClassMemoryLayout/LayoutLoader.py b/RttiInformation/ClassMemoryLayout/LayoutLoader.py similarity index 100% rename from RttiInfomation/ClassMemoryLayout/LayoutLoader.py rename to RttiInformation/ClassMemoryLayout/LayoutLoader.py diff --git a/RttiInfomation/ClassMemoryLayout/LayoutParser.py b/RttiInformation/ClassMemoryLayout/LayoutParser.py similarity index 100% rename from RttiInfomation/ClassMemoryLayout/LayoutParser.py rename to RttiInformation/ClassMemoryLayout/LayoutParser.py diff --git a/RttiInfomation/ClassMemoryLayout/__init__.py b/RttiInformation/ClassMemoryLayout/__init__.py similarity index 100% rename from RttiInfomation/ClassMemoryLayout/__init__.py rename to RttiInformation/ClassMemoryLayout/__init__.py diff --git a/RttiInfomation/ClassMemoryLayout/class_layouts.layout b/RttiInformation/ClassMemoryLayout/class_layouts.layout similarity index 100% rename from RttiInfomation/ClassMemoryLayout/class_layouts.layout rename to RttiInformation/ClassMemoryLayout/class_layouts.layout diff --git a/RttiInfomation/CompleteObjectLocator.py b/RttiInformation/CompleteObjectLocator.py similarity index 100% rename from RttiInfomation/CompleteObjectLocator.py rename to RttiInformation/CompleteObjectLocator.py diff --git a/RttiInfomation/TypeCreation.py b/RttiInformation/TypeCreation.py similarity index 100% rename from RttiInfomation/TypeCreation.py rename to RttiInformation/TypeCreation.py diff --git a/RttiInfomation/TypeDescriptor.py b/RttiInformation/TypeDescriptor.py similarity index 100% rename from RttiInfomation/TypeDescriptor.py rename to RttiInformation/TypeDescriptor.py diff --git a/RttiInformation/VirtualTableInference/VirtualFunctionTable.py b/RttiInformation/VirtualTableInference/VirtualFunctionTable.py new file mode 100644 index 0000000..f0b6731 --- /dev/null +++ b/RttiInformation/VirtualTableInference/VirtualFunctionTable.py @@ -0,0 +1,186 @@ +import binaryninja as bn +from ...ClassDataStructureDetection.Constructors import DetectConstructor +from ...ClassObjectRepresentation import CppClass +from ...Common import Utils +from typing import * + +# {vfTable_addr: [ContainedFunctions]} +global_vfTables: Dict[int, List[int]] = dict() +global_functions_contained_in_all_vfTables: List[int] = list() + + +def VerifyNonRttiVtable(bv: bn.binaryview, potential_vtable_addr: int) -> bool: + if potential_vtable_addr in global_vfTables.keys(): + return True + else: + verified_vtable = VFTABLE(bv, + potential_vtable_addr, + f"vtable_{str(potential_vtable_addr)}_nonRtti") + if verified_vtable.verified: + return True + return False + + +def DetectVTables(bv: bn.binaryview): + """ + The general algorithm of this function is this: + 1. Go over all recognized functions in the binaryView. + 1.1 If the function is already defined as a constructor / destructor by the RTTI information then skip it. + 1.2 If the function is verified as a possible constructor / destructor detection algorithm (but was not mentioned + by the RTTI information) then check all possible vtable assignments in the function. + 2. First, if we have an assignment of a vTable to offset 0 of Arg1 then we assume this is indeed + a constructor / destructor - vTable is defined as vtable_vTableAddress_nonRtti, and class is defined + as class_vTableAddress. + 3. Once we found a constructor / destructor then we add all non 0 offsets of Arg1 assignments found in the + function for further inspection (This is he deffered_vtable_addr list). + The logic here is that the class may contain vTable assignments of its base or multiple inherited classes, + and those vTables should also have their own constructors / destructors somewhere else in the file. + 4. Once we searched all functions in the bv, we now iterate the deffered_vtable_addr list to detect which + of these addresses was later confirmed as a vtable, and accordingly we can add this information to the + correct class. + """ + print("Searching for vTables...") + # First, we go over the known vfTables (As inferred from RTTI info) and locate their constructors. + for vtable_addr, contained_functions in global_vfTables.items(): + if potential_constructors := DetectConstructor.DetectConstructorForVTable(bv, vtable_addr): + DetectConstructor.DefineConstructor(bv, potential_constructors, vtable_addr) + for func in bv.functions: + if func.start not in DetectConstructor.global_constructor_destructor_list and \ + func.start not in global_functions_contained_in_all_vfTables: + if DetectConstructor.VerifyConstructor(bv, func): + # VerifyConstructor will check that there is a pointer assignment into offset 0x0 + # in the "This" pointer (Arg1). Now we need to check if this pointer is a vTable. + assignment_instructions = DetectConstructor.GetAllAssignmentInstructions(func) + # Check if the last assignment into offset 0 of Arg1 in the constructor func is a vTable. + suspected_vtable: int = assignment_instructions[0][-1] + + class_name: str = CppClass.GenerateClassNameFromVtableAddr(suspected_vtable) + print(f"Found non RTTI vtable at {suspected_vtable}") + """ + TODO : add information of base classes according to non 0x0 offset assignments. + + if VerifyNonRttiVtable(bv, suspected_vtable): + if detected_class := CppClass.global_classes.get(class_name): + # If the class was already defined then do nothing (for now) + # TODO: merge more info into the class based on this newly found constructor. + if func.start not in detected_class.constructors: + detected_class.constructors.append(func.start) + else: + detected_class: CppClass.ClassyClass = CppClass.ClassyClass(name=class_name, + vfTable_addr=suspected_vtable, + constructors=[func.start]) + + for class_offset, potential_table_addresses in assignment_instructions.items(): + + for potential_vtable_addr in potential_table_addresses: + if potential_vtable_addr in global_vfTables: + pass + else: + if verified_vtable := VFTABLE( + bv, + potential_vtable_addr, + f"vtable_{str(potential_vtable_addr)}_nonRtti"): + pass + """ + + +class VFTABLE: + + def __init__(self, bv: bn.binaryview, base_addr: int, demangled_name: str): + self.bv: bn.binaryview = bv + self.base_addr: int = base_addr + self.vfTable_length: int = 0 + # Strip the "class " from the start of the demangled name. + self.demangled_name: str = demangled_name[6:] if demangled_name.startswith("class ") else demangled_name + self.contained_functions: List[int] = list() + self.verified = self.VerifyVFT() + + def VerifyVFT(self) -> bool: + data_refs_from_base_addr = list(self.bv.get_data_refs_from(self.base_addr)) + if len(data_refs_from_base_addr) == 0: + # This is a situation that occurs due to a bug in binary ninja - if the data var at base_addr + # is defined as a symbol from a previously loaded PDB file then binja will not recognize any data refs. + # Since we are positive at this point that this location is a vTable (since we verified the COL for this + # class) then it is safe to change its type to 'void *' in order to "fix" the binja bug and allow it to + # recognize data refs. + vTable_data_var = self.bv.get_data_var_at(self.base_addr) + if vTable_data_var is not None: + vTable_data_var.type = self.bv.parse_type_string("void*")[0] + # Now we should see data refs from this address + data_refs_from_base_addr = list(self.bv.get_data_refs_from(self.base_addr)) + else: + print(f"VerifyVFT: unable to get information on data variable at addr {self.base_addr}.") + return False + + if len(data_refs_from_base_addr) > 0: + if self.bv.get_function_at(data_refs_from_base_addr[0]): + if self.DefineVFT(): + # Update this vfTable in the global table, this will be used later for locating constructor funcs + global_vfTables.update({self.base_addr: self.contained_functions}) + global_functions_contained_in_all_vfTables.extend(self.contained_functions) + Utils.LogToFile(f'VFTABLE: verified table at address {hex(self.base_addr)}') + return True + return False + + def DefineVFT(self) -> bool: + Utils.LogToFile(f'vfTable: Attempt to define data var at {hex(self.base_addr)}') + current_data_var_addr: int = self.base_addr + while self.IsPointerToFunction(current_data_var_addr): + self.vfTable_length += 1 + current_data_var_addr += 0x8 if self.bv.arch.name == "x86_64" else 0x4 + if self.vfTable_length > 0: + try: + # Define the Complete object locator + self.bv.define_user_data_var(self.base_addr, + self.bv.parse_type_string(f'void*[{self.vfTable_length}]')[0], + self.demangled_name) + Utils.LogToFile(f'vfTable: Defined data var at {hex(self.base_addr)}') + return True + except Exception as e: + Utils.LogToFile(f'vfTable: Failed to Define data var at {hex(self.base_addr)}, \n Exception: {e}') + return False + else: + return False + + def GetBinjaVoidPointerType(self) -> bn.types.PointerType: + return bn.Type.pointer(self.bv.arch, self.bv.parse_type_string("void")[0]) + + def GetPointer(self, pointer_addr: int) -> Optional[bn.DataVariable]: + # Sometimes binja parses PDB information incorrectly, and instead of a pointer to a vTable + # it just defines the struct that the PDB says defines the vTable. + # If this happens - we dont change the PDB struct, we just say this is a pointer to the PDB struct. + pointer: bn.DataVariable + if pointer := self.bv.get_data_var_at(pointer_addr): + if type(pointer) != bn.types.PointerType: + try: + Utils.LogToFile(f"GetPointer: Overriding original type for Vtable -\n" + f"pointer_addr: {hex(pointer_addr)}\n" + f"current pointer type: {pointer.type}\n" + f"pointer name: {pointer.name}") + self.bv.define_user_data_var(pointer_addr, + self.GetBinjaVoidPointerType(), + pointer.name) + return self.bv.get_data_var_at(pointer_addr) + except Exception as e: + Utils.LogToFile(f"GetPointer: Exception while trying to define pointer at addr {pointer_addr}.\n" + f"Exception: {e}") + return None + return pointer + return None + + def IsPointerToFunction(self, pointer_addr: int) -> bool: + try: + if pointer := self.GetPointer(pointer_addr): + if self.bv.get_sections_at(pointer.value)[0].semantics is \ + bn.SectionSemantics.ReadOnlyCodeSectionSemantics: + if not self.bv.get_function_at(pointer.value): + self.bv.add_function(pointer.value) + self.contained_functions.append(pointer.value) + return True + except Exception as e: + Utils.LogToFile(f"IsPointerToFunction: Failed to determine if pointer to function at {pointer_addr}.\n" + f"Exception: {e}") + return False + + def GetLength(self): + return self.vfTable_length diff --git a/RttiInfomation/VirtualTableInference/__init__.py b/RttiInformation/VirtualTableInference/__init__.py similarity index 100% rename from RttiInfomation/VirtualTableInference/__init__.py rename to RttiInformation/VirtualTableInference/__init__.py diff --git a/RttiInfomation/__init__.py b/RttiInformation/__init__.py similarity index 100% rename from RttiInfomation/__init__.py rename to RttiInformation/__init__.py diff --git a/RttiInfomation/example flow.drawio b/RttiInformation/example flow.drawio similarity index 100% rename from RttiInfomation/example flow.drawio rename to RttiInformation/example flow.drawio diff --git a/RttiInfomation/rtti-layout-better.png b/RttiInformation/rtti-layout-better.png similarity index 100% rename from RttiInfomation/rtti-layout-better.png rename to RttiInformation/rtti-layout-better.png diff --git a/RttiInfomation/rtti-layout.png b/RttiInformation/rtti-layout.png similarity index 100% rename from RttiInfomation/rtti-layout.png rename to RttiInformation/rtti-layout.png diff --git a/StartInspection.py b/StartInspection.py index e205a4b..88147b4 100644 --- a/StartInspection.py +++ b/StartInspection.py @@ -1,17 +1,11 @@ -""" -import ClassyPP -import importlib -importlib.reload(ClassyPP); -ClassyPP.StartInspection.inspect(bv) -""" - +import pprint import binaryninja as bn -from .RttiInfomation.ClassContext import GlobalClassContextManager +from .RttiInformation.ClassContext import GlobalClassContextManager from .Common import Utils from . import Config -from .RttiInfomation import TypeCreation +from .RttiInformation import TypeCreation from .ClassDataStructureDetection.Constructors import DetectConstructor -from .RttiInfomation.VirtualTableInference import VirtualFunctionTable +from .RttiInformation.VirtualTableInference import VirtualFunctionTable def is_bv_valid_for_plugin(bv: bn.binaryview) -> bool: @@ -55,9 +49,8 @@ def DetectAndVerifyConstructor(self): if Config.CONSTRUCTOR_FUNCTION_HANDLING != 2: # Iterate over all found vfTables and detect their constructors print(f'ClassyPP: Constructor Detection process started...') - for base_addr, contained_functions in VirtualFunctionTable.global_vfTables.items(): - if DetectConstructor.DetectConstructorForVTable(self.bv, base_addr, contained_functions): - pass + pprint.pprint(VirtualFunctionTable.global_vfTables) + VirtualFunctionTable.DetectVTables(self.bv) def RTTI_inspection(self) -> bool: Utils.LogToFile(f'inspect: Starting Scan.') diff --git a/__init__.py b/__init__.py index c32c1a9..2888b42 100644 --- a/__init__.py +++ b/__init__.py @@ -1,11 +1,11 @@ """ -from .RttiInfomation import TypeCreation -from .RttiInfomation import BaseClassDescriptor, ClassContext, \ +from .RttiInformation import TypeCreation +from .RttiInformation import BaseClassDescriptor, ClassContext, \ ClassHierarchyDescriptor, CompleteObjectLocator, TypeDescriptor, \ BaseClassArray, VirtualFunctionTable, ClassHierarchyDeduction from .Common import Utils from . import Config -from .RttiInfomation.ClassMemoryLayout import ClassStructCreation, LayoutParser, LayoutLoader +from .RttiInformation.ClassMemoryLayout import ClassStructCreation, LayoutParser, LayoutLoader import importlib importlib.reload(StartInspection) From 46506fa3d39afc4cc854b4860cecd1981b751bcb Mon Sep 17 00:00:00 2001 From: CySHell Date: Thu, 15 Dec 2022 17:47:51 +0200 Subject: [PATCH 2/3] Non-RTTI constructor detection --- .../Constructors/DetectConstructor.py | 50 +++++++------ .../NoRttiDataAvailable/DetectVtables.py | 70 ------------------- .../NoRttiDataAvailable/__init__.py | 4 -- RttiInformation/ClassContext.py | 4 +- .../ClassHierarchyDeduction.py | 3 +- .../VirtualFunctionTable.py | 37 +++------- StartInspection.py | 4 +- __init__.py | 27 ------- 8 files changed, 42 insertions(+), 157 deletions(-) delete mode 100644 ClassDataStructureDetection/NoRttiDataAvailable/DetectVtables.py delete mode 100644 ClassDataStructureDetection/NoRttiDataAvailable/__init__.py diff --git a/ClassDataStructureDetection/Constructors/DetectConstructor.py b/ClassDataStructureDetection/Constructors/DetectConstructor.py index 450d751..1f66c51 100644 --- a/ClassDataStructureDetection/Constructors/DetectConstructor.py +++ b/ClassDataStructureDetection/Constructors/DetectConstructor.py @@ -2,6 +2,7 @@ from typing import * from ... import Config from ...RttiInformation.VirtualTableInference import VirtualFunctionTable +from ...Common import Utils global_constructor_destructor_list: List = list() @@ -67,8 +68,9 @@ def GetAllAssignmentInstructions(func: bn.function.Function) -> Dict: } ) else: - print(f"GetAllAssignmentInstructions: UNKNOWN assignment type! please report this." - f"\nInstruction: {instr}") + Utils.LogToFile(f"GetAllAssignmentInstructions: UNKNOWN assignment type at HLIL " + f"Address {hex(instr.address)} ! please report this. " + f"\nInstruction: {instr}") except Exception as e: print(f"GetAllAssignmentInstructions {hex(func.start)}, Exception: {e}") # We are only interested in the last assignment of a vfTable in a function, since the ones before it are @@ -107,7 +109,7 @@ def DetectConstructorForVTable(bv: bn.binaryview, vfTable_addr: int) -> list[bn. for potential_constructor in GetPotentialConstructors(bv, vfTable_addr): if VerifyConstructor(bv, potential_constructor): potential_constructors.append(potential_constructor) - print(f'ClassyPP: Found constructor - {potential_constructor.name}') + print(f'Found constructor - {potential_constructor.name}') global_constructor_destructor_list.append(potential_constructor.start) return potential_constructors @@ -125,29 +127,33 @@ def IsDestructor(bv: bn.binaryview, potential_destructor: bn.function.Function) def DefineConstructor(bv: bn.binaryview, potential_constructors: list[bn.function.Function], - vtable_addr: int) -> bool: + vtable_addr: int, class_name=None) -> bool: # Since several constructors with the same name (but different signature) may exist, we # will attach a postfix index to each of the names. constructor_index = 0 - class_name: str = bv.get_data_var_at(vtable_addr).name - if class_name.endswith("_vfTable"): - # Remove the _vfTable tag from the name - class_name = class_name[:-8] - for constructor in potential_constructors: - func_type = "Constructor" - if IsDestructor(bv, constructor): - func_type = "Destructor" - if Config.CONSTRUCTOR_FUNCTION_HANDLING == 0: - AddComment(bv, constructor.start, vtable_addr, - class_name, func_type) - elif Config.CONSTRUCTOR_FUNCTION_HANDLING == 1: - ChangeFuncName(bv, constructor.start, constructor_index, + if not class_name: + class_name: str = bv.get_data_var_at(vtable_addr).name + if class_name: + if class_name.endswith("_vfTable"): + # Remove the _vfTable tag from the name + class_name = class_name[:-8] + for constructor in potential_constructors: + func_type = "Constructor" + if IsDestructor(bv, constructor): + func_type = "Destructor" + if Config.CONSTRUCTOR_FUNCTION_HANDLING == 0: + AddComment(bv, constructor.start, vtable_addr, class_name, func_type) - else: - # invalid choice - return False - constructor_index += 1 - return True + elif Config.CONSTRUCTOR_FUNCTION_HANDLING == 1: + ChangeFuncName(bv, constructor.start, constructor_index, + class_name, func_type) + else: + # invalid choice + return False + constructor_index += 1 + return True + else: + print(f"DefineConstructor: Cannot get class name for vtable at {hex(vtable_addr)}") def VerifyConstructor(bv: bn.binaryview, potential_constructor: bn.function.Function) -> bool: diff --git a/ClassDataStructureDetection/NoRttiDataAvailable/DetectVtables.py b/ClassDataStructureDetection/NoRttiDataAvailable/DetectVtables.py deleted file mode 100644 index d9db9a3..0000000 --- a/ClassDataStructureDetection/NoRttiDataAvailable/DetectVtables.py +++ /dev/null @@ -1,70 +0,0 @@ -import binaryninja as bn -from typing import List -from ... import Config -from ...RttiInformation.VirtualTableInference import VirtualFunctionTable -from ..Constructors import DetectConstructor -from ...ClassObjectRepresentation import CppClass - - -def VerifyNonRttiVtable(bv: bn.binaryview, potential_vtable_addr: int) -> bool: - if potential_vtable_addr in VirtualFunctionTable.global_vfTables: - return True - else: - verified_vtable = VirtualFunctionTable.VFTABLE(bv, - potential_vtable_addr, - f"vtable_{str(potential_vtable_addr)}_nonRtti") - if verified_vtable.verified: - return True - return False - - -def DetectPotentialCandidates(bv: bn.binaryview): - """ - The general algorithm of this function is this: - 1. Go over all recognized functions in the binaryView. - 1.1 If the function is already defined as a constructor / destructor by the RTTI information then skip it. - 1.2 If the function is verified as a possible constructor / destructor detection algorithm (but was not mentioned - by the RTTI information) then check all possible vtable assignments in the function. - 2. First, if we have an assignment of a vTable to offset 0 of Arg1 then we assume this is indeed - a constructor / destructor - vTable is defined as vtable_vTableAddress_nonRtti, and class is defined - as class_vTableAddress. - 3. Once we found a constructor / destructor then we add all non 0 offsets of Arg1 assignments found in the - function for further inspection (This is he deffered_vtable_addr list). - The logic here is that the class may contain vTable assignments of its base or multiple inherited classes, - and those vTables should also have their own constructors / destructors somewhere else in the file. - 4. Once we searched all functions in the bv, we now iterate the deffered_vtable_addr list to detect which - of these addresses was later confirmed as a vtable, and accordingly we can add this information to the - correct class. - """ - print("Searching for Non-RTTI vTables...") - deffered_vtable_addr: list = list() - for func in bv.functions: - if func.start not in DetectConstructor.global_constructor_destructor_list: - if DetectConstructor.VerifyConstructor(bv, func, -1): - print(hex(func.start)) - assignment_instructions = DetectConstructor.GetAllAssignmentInstructions(func) - # Check if the last assignment into offset 0 of Arg1 in the constructor func is a vTable. - suspected_vtable: int = assignment_instructions[0][-1] - class_name: str = CppClass.GenerateClassNameFromVtableAddr(suspected_vtable) - if VerifyNonRttiVtable(bv, suspected_vtable): - if detected_class := CppClass.global_classes.get(class_name): - # If the class was already defined then do nothing (for now) - # TODO: merge more info into the class based on this newly found constructor. - if func.start not in detected_class.constructors: - detected_class.constructors.append(func.start) - else: - detected_class: CppClass.ClassyClass = CppClass.ClassyClass(name=class_name, - vfTable_addr=suspected_vtable, - constructors=[func.start]) - - for class_offset, potential_table_addresses in assignment_instructions.items(): - - for potential_vtable_addr in potential_table_addresses: - if potential_vtable_addr in VirtualFunctionTable.global_vfTables: - pass - else: - if verified_vtable := VirtualFunctionTable.VFTABLE( - bv, - potential_vtable_addr, - f"vtable_{str(potential_vtable_addr)}_nonRtti"): - pass diff --git a/ClassDataStructureDetection/NoRttiDataAvailable/__init__.py b/ClassDataStructureDetection/NoRttiDataAvailable/__init__.py deleted file mode 100644 index 81d36cb..0000000 --- a/ClassDataStructureDetection/NoRttiDataAvailable/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -""" -This package deals with a situation where RTTI data is non existent (possibly removed by the compiler) -but the code still includes classes. -""" \ No newline at end of file diff --git a/RttiInformation/ClassContext.py b/RttiInformation/ClassContext.py index d336382..6b64e02 100644 --- a/RttiInformation/ClassContext.py +++ b/RttiInformation/ClassContext.py @@ -106,13 +106,13 @@ def DebugPrintCol(self, Col: CompleteObjectLocator, current_address): def DeduceClassHierarchies(self): ClassHierarchyDeduction.DefineClassHierarchy(self.bv) - def DefineRTTI(self) -> bool: + def DetectAndDefineAllInformation(self) -> bool: for sect in self.bv.sections.values(): if IsSectionCompatibleToRTTI(sect): current_address = sect.start while current_address < sect.end - self.rtti_complete_object_locator_size: if Col := self.GetCompleteObjectLocator(current_address): - Utils.LogToFile(f'DefineRTTI: Defined {Col.__repr__()} \n') + Utils.LogToFile(f'Defined {Col.__repr__()} \n') print(f"Defined Class: {Utils.DemangleName(Col.mangled_class_name)}") if Config.ENABLE_DEBUG_LOGGING: self.DebugPrintCol(Col, current_address) diff --git a/RttiInformation/ClassHierarchyInference/ClassHierarchyDeduction.py b/RttiInformation/ClassHierarchyInference/ClassHierarchyDeduction.py index 8ce29f7..532d2f8 100644 --- a/RttiInformation/ClassHierarchyInference/ClassHierarchyDeduction.py +++ b/RttiInformation/ClassHierarchyInference/ClassHierarchyDeduction.py @@ -76,7 +76,8 @@ def RenameFunction(bv: bn.binaryview, vtable_function: int, lca: int, function_i else: func.set_comment_at(func.start, f'{class_name}_method{function_index}') return True - except: + except Exception as e: + print(f"Unable to rename function {hex(vtable_function)}, got Exception: \n{e}") return False diff --git a/RttiInformation/VirtualTableInference/VirtualFunctionTable.py b/RttiInformation/VirtualTableInference/VirtualFunctionTable.py index f0b6731..a27363b 100644 --- a/RttiInformation/VirtualTableInference/VirtualFunctionTable.py +++ b/RttiInformation/VirtualTableInference/VirtualFunctionTable.py @@ -15,7 +15,7 @@ def VerifyNonRttiVtable(bv: bn.binaryview, potential_vtable_addr: int) -> bool: else: verified_vtable = VFTABLE(bv, potential_vtable_addr, - f"vtable_{str(potential_vtable_addr)}_nonRtti") + f"vtable_{hex(potential_vtable_addr)}_nonRtti") if verified_vtable.verified: return True return False @@ -53,35 +53,14 @@ def DetectVTables(bv: bn.binaryview): assignment_instructions = DetectConstructor.GetAllAssignmentInstructions(func) # Check if the last assignment into offset 0 of Arg1 in the constructor func is a vTable. suspected_vtable: int = assignment_instructions[0][-1] - - class_name: str = CppClass.GenerateClassNameFromVtableAddr(suspected_vtable) print(f"Found non RTTI vtable at {suspected_vtable}") - """ - TODO : add information of base classes according to non 0x0 offset assignments. - - if VerifyNonRttiVtable(bv, suspected_vtable): - if detected_class := CppClass.global_classes.get(class_name): - # If the class was already defined then do nothing (for now) - # TODO: merge more info into the class based on this newly found constructor. - if func.start not in detected_class.constructors: - detected_class.constructors.append(func.start) - else: - detected_class: CppClass.ClassyClass = CppClass.ClassyClass(name=class_name, - vfTable_addr=suspected_vtable, - constructors=[func.start]) - - for class_offset, potential_table_addresses in assignment_instructions.items(): - - for potential_vtable_addr in potential_table_addresses: - if potential_vtable_addr in global_vfTables: - pass - else: - if verified_vtable := VFTABLE( - bv, - potential_vtable_addr, - f"vtable_{str(potential_vtable_addr)}_nonRtti"): - pass - """ + if potential_constructors := DetectConstructor.DetectConstructorForVTable(bv, suspected_vtable): + class_name: str = CppClass.GenerateClassNameFromVtableAddr(suspected_vtable) + vtable = VFTABLE(bv, suspected_vtable, class_name) + if vtable.verified: + DetectConstructor.DefineConstructor(bv, potential_constructors, suspected_vtable, class_name) + + # TODO : add information of base classes according to non 0x0 offset assignments. class VFTABLE: diff --git a/StartInspection.py b/StartInspection.py index 88147b4..0dbc1c4 100644 --- a/StartInspection.py +++ b/StartInspection.py @@ -49,14 +49,14 @@ def DetectAndVerifyConstructor(self): if Config.CONSTRUCTOR_FUNCTION_HANDLING != 2: # Iterate over all found vfTables and detect their constructors print(f'ClassyPP: Constructor Detection process started...') - pprint.pprint(VirtualFunctionTable.global_vfTables) + Utils.LogToFile(str(VirtualFunctionTable.global_vfTables)) VirtualFunctionTable.DetectVTables(self.bv) def RTTI_inspection(self) -> bool: Utils.LogToFile(f'inspect: Starting Scan.') if TypeCreation.CreateTypes(self.bv): GCM: GlobalClassContextManager = GlobalClassContextManager(self.bv) - if GCM.DefineRTTI(): + if GCM.DetectAndDefineAllInformation(): Utils.LogToFile(f'ClassyPP: Successfully created types.') print(f'ClassyPP: Successfully defined RTTI Information.') return True diff --git a/__init__.py b/__init__.py index 2888b42..aa356e7 100644 --- a/__init__.py +++ b/__init__.py @@ -1,30 +1,3 @@ -""" -from .RttiInformation import TypeCreation -from .RttiInformation import BaseClassDescriptor, ClassContext, \ - ClassHierarchyDescriptor, CompleteObjectLocator, TypeDescriptor, \ - BaseClassArray, VirtualFunctionTable, ClassHierarchyDeduction -from .Common import Utils -from . import Config -from .RttiInformation.ClassMemoryLayout import ClassStructCreation, LayoutParser, LayoutLoader -import importlib - -importlib.reload(StartInspection) -importlib.reload(BaseClassDescriptor) -importlib.reload(BaseClassArray) -importlib.reload(ClassContext) -importlib.reload(ClassHierarchyDescriptor) -importlib.reload(CompleteObjectLocator) -importlib.reload(TypeDescriptor) -importlib.reload(TypeCreation) -importlib.reload(VirtualFunctionTable) -importlib.reload(Utils) -importlib.reload(Config) -importlib.reload(ClassHierarchyDeduction) -importlib.reload(LayoutLoader) -importlib.reload(LayoutParser) -importlib.reload(ClassStructCreation) -""" - import binaryninja as bn from . import StartInspection From 41976a987b7468f8abdd7113a309dafc14f0efe1 Mon Sep 17 00:00:00 2001 From: "rowr12000@gmail.com" Date: Fri, 16 Dec 2022 22:33:42 +0200 Subject: [PATCH 3/3] Version change --- ClassObjectRepresentation/CppClass.py | 2 +- RttiInformation/VirtualTableInference/VirtualFunctionTable.py | 2 +- plugin.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ClassObjectRepresentation/CppClass.py b/ClassObjectRepresentation/CppClass.py index f21376c..3105d63 100644 --- a/ClassObjectRepresentation/CppClass.py +++ b/ClassObjectRepresentation/CppClass.py @@ -5,7 +5,7 @@ def GenerateClassNameFromVtableAddr(vTable_addr: int) -> str: - return f"class_{str(vTable_addr)}" + return f"class_{hex(vTable_addr)}_vfTable" @dataclass diff --git a/RttiInformation/VirtualTableInference/VirtualFunctionTable.py b/RttiInformation/VirtualTableInference/VirtualFunctionTable.py index a27363b..f354183 100644 --- a/RttiInformation/VirtualTableInference/VirtualFunctionTable.py +++ b/RttiInformation/VirtualTableInference/VirtualFunctionTable.py @@ -53,7 +53,7 @@ def DetectVTables(bv: bn.binaryview): assignment_instructions = DetectConstructor.GetAllAssignmentInstructions(func) # Check if the last assignment into offset 0 of Arg1 in the constructor func is a vTable. suspected_vtable: int = assignment_instructions[0][-1] - print(f"Found non RTTI vtable at {suspected_vtable}") + print(f"Found non RTTI vtable at {hex(suspected_vtable)}") if potential_constructors := DetectConstructor.DetectConstructorForVTable(bv, suspected_vtable): class_name: str = CppClass.GenerateClassNameFromVtableAddr(suspected_vtable) vtable = VFTABLE(bv, suspected_vtable, class_name) diff --git a/plugin.json b/plugin.json index 0436ab5..251dfbe 100644 --- a/plugin.json +++ b/plugin.json @@ -31,7 +31,7 @@ "ClassyPP uses an external C++ demangler - Demumble - https://github.com/nico/demumble/releases." ] }, - "version": "1.4.0", + "version": "1.5.0", "author": "CyShell", "minimumbinaryninjaversion": 3233 }