def find(self, bv, start, end): """ :type bv: BinaryView :type start: int :type end: int """ br = BinaryReader(bv, Endianness.LittleEndian) br.offset = start ends = [] offset = bv.start while br.offset < end: start_address = br.read32() end_address = br.read32() unwind_information = br.read32() if start_address == 0 and end_address == 0 and unwind_information == 0: break start_address += offset end_address += offset ends.append(end_address) current = bv.get_function_at(start_address) # type: Function if current is None or current.start != start_address: # if not bv.get_basic_blocks_at(start_address): bv.create_user_function(start_address) f = bv.get_function_at(start_address) # type: Function if f.name[0:4] == 'sub_': f.name += '_pdata' self.found += 1 bv.update_analysis_and_wait() for end_address in ends: if not bv.get_functions_containing(end_address - 1): print "Expected pdata end_address to be in function " + hex( end_address)
def is_valid_for_data(self, data): minimum_header_size = len(INTERRUPT_TABLE) * 4 + 4 if len(data) < minimum_header_size: return False reader = BinaryReader(data) stack_pointer = reader.read32() if stack_pointer < 0x20000000 or stack_pointer >= 0x20004000: return False reset = reader.read32() if reset > 0xFFFF: return False return True
def _add_interrupt_symbols(self, data): reader = BinaryReader(data) # Skip stack pointer reader.seek(4) for interrupt in INTERRUPT_TABLE: address = reader.read32() & ~1 if address != 0: symbol = Symbol(SymbolType.FunctionSymbol, address, interrupt) self.define_auto_symbol(symbol) self.add_function(address)
def evaluate_llil(self, state, llil): # Interpreter for LLIL instructions, using data from state if llil.operation == LowLevelILOperation.LLIL_CONST: return llil.operands[0] elif llil.operation == LowLevelILOperation.LLIL_CONST_PTR: return llil.operands[0] elif llil.operation == LowLevelILOperation.LLIL_REG: reg = llil.operands[0].name return state.registers[reg] elif llil.operation == LowLevelILOperation.LLIL_LOAD: addr = self.evaluate_llil(state, llil.operands[0]) # Have to read from addr llil.size bytes reader = BinaryReader(state.memory_view) reader.seek(addr) if llil.size == 1: deref = reader.read8() elif llil.size == 2: deref = reader.read16() elif llil.size == 4: deref = reader.read32() else: deref = reader.read64() # Unimplemented: 128-bit, etc return deref elif llil.operation == LowLevelILOperation.LLIL_ADD: return self.evaluate_llil(state, llil.operands[0]) + self.evaluate_llil( state, llil.operands[1]) elif llil.operation == LowLevelILOperation.LLIL_SUB: return self.evaluate_llil(state, llil.operands[0]) - self.evaluate_llil( state, llil.operands[1]) elif llil.operation == LowLevelILOperation.LLIL_MUL: return self.evaluate_llil(state, llil.operands[0]) * self.evaluate_llil( state, llil.operands[1]) elif llil.operation == LowLevelILOperation.LLIL_LSL: return self.evaluate_llil(state, llil.operands[0]) << self.evaluate_llil( state, llil.operands[1]) elif llil.operation == LowLevelILOperation.LLIL_LSR: return self.evaluate_llil(state, llil.operands[0]) >> self.evaluate_llil( state, llil.operands[1]) else: raise NotImplementedError('todo: evaluate llil for %s' % llil.operation)
def parse_format(self, data): """ This is a helper function to parse our BS format :param data: :return: """ reader = BinaryReader(data) reader.seek(4) loading_addr = reader.read32() flags = SegmentFlag.SegmentReadable | SegmentFlag.SegmentExecutable self.add_auto_segment(loading_addr, len(data) - 8, 8, len(data) - 8, flags) self.add_auto_section("text", loading_addr, len(data) - 8, SectionSemantics.ReadOnlyCodeSectionSemantics) self.add_entry_point(loading_addr)
def _parse_format(self, data): self.load_address = self.get_address_input("Base Address", "Base Address") reader = BinaryReader(data) reader.seek(4) entry_point = reader.read32() & ~1 self.add_entry_point(entry_point) # SRAM self.add_auto_segment( 0x20000000, 0x4000, 0, 0, SegmentFlag.SegmentReadable | SegmentFlag.SegmentWritable | SegmentFlag.SegmentExecutable) # Flash self.add_auto_segment( self.load_address, len(data), 0, len(data), SegmentFlag.SegmentReadable | SegmentFlag.SegmentExecutable) self._add_hardware_registers() self._add_interrupt_symbols(data) return True
class DelphiVMT(object): ''' TODO: Doc ''' def __init__(self, bv: BinaryView, delphi_version: int, address: int, analyzer: DelphiAnalyzer, offset_ptr_size=-1): self._offset_ptr_size = offset_ptr_size if offset_ptr_size > 0 else bv.address_size self._vmt_address = address self._analyzer = analyzer self._is_valid = False self._bv = bv self._br = BinaryReader(bv) self._vmt_offsets = analyzer.vmt_offsets self._class_name = '' self._instance_size = 0 self._parent_vmt = 0 self._table_list: Mapping[int, str] = {} self._virtual_methods: Mapping[int, str] = {} if not self._check_self_ptr(): return if not self._resolve_name(): return if not self._resolve_instance_size(): return if not self._resolve_parent_vmt(): return if not self._resolve_table_list(): return if not self._resolve_virtual_methods(): return self._is_valid = True def __repr__(self): return str(self) def __str__(self): if not self._is_valid: return f'<InvalidVmt address=0x{self._vmt_address:08X}>' return (f'<{self._class_name} start=0x{self.start:08X} ' f'instance_size=0x{self._instance_size:X}>') ## Properties @property def vmt_address(self) -> int: return self._vmt_address @property def is_valid(self) -> bool: return self._is_valid @property def class_name(self) -> str: return self._class_name @property def instance_size(self) -> int: return self._instance_size @property def parent_vmt(self) -> int: return self._parent_vmt @property def table_list(self) -> Mapping[int, str]: return self._table_list @property def virtual_methods(self) -> Mapping[int, str]: return self._virtual_methods @property def vmt_offsets(self) -> VMTOffsets: return copy.copy(self._vmt_offsets) @property def start(self) -> int: return self._vmt_address + self._vmt_offsets.cVmtSelfPtr @property def size(self) -> int: end = 0 # ???? return end - self.start @property def offset_ptr_size(self) -> int: return self._offset_ptr_size @property def br_offset(self) -> int: return self._br.offset ## Public API def seek_to_code(self, address: int) -> bool: if not self._is_valid_addr(address): return False self._br.seek(address) return True def seek_to_code_offset(self, offset: int) -> bool: if not self._is_valid_addr(self._analyzer.start + offset): return False self._br.seek(self._analyzer.start + offset) return True def seek_to_vmt_offset(self, offset: int) -> bool: if not self._is_valid_addr(self._vmt_address + offset): return False self._br.seek(self._vmt_address + offset) return True def read8(self) -> Union[None, int]: return self._br.read8() def read_ptr(self) -> Union[None, int]: if self._offset_ptr_size == 4: return self._br.read32() elif self._offset_ptr_size == 8: return self._br.read64() ## Protected methods def _check_self_ptr(self) -> bool: if not self.seek_to_vmt_offset(self._vmt_offsets.cVmtSelfPtr): return False self_ptr = self.read_ptr() return self_ptr == self._vmt_address def _resolve_name(self) -> bool: class_name_addr = self._get_class_name_addr() if class_name_addr is None: return False self._br.seek(class_name_addr) name_len = self._br.read8() if name_len == 0: BNLogger.log( f'Care, VMT without name (len: 0) detected at 0x{self._vmt_address:08X}', LogLevel.WarningLog) class_name = self._br.read(name_len) if MATCH_CLASS_NAME.match(class_name) is None: return False self._class_name = class_name.decode() return True def _resolve_instance_size(self) -> bool: if not self.seek_to_vmt_offset(self._vmt_offsets.cVmtInstanceSize): return False self._instance_size = self.read_ptr() return True def _resolve_parent_vmt(self) -> bool: if not self.seek_to_vmt_offset(self._vmt_offsets.cVmtParent): return False self._parent_vmt = self.read_ptr() return True def _resolve_virtual_methods(self) -> bool: class_name_addr = self._get_class_name_addr() if class_name_addr is None: return False offsets = self.vmt_offsets.__dict__.items() offset_map = {y: x for x, y in offsets} tables_addr = self._table_list.keys() if not self.seek_to_vmt_offset(self._vmt_offsets.cVmtParent + self._offset_ptr_size): return False while self._br.offset < class_name_addr and self._br.offset not in tables_addr: field_value = self.read_ptr() if field_value == 0: continue if not self._is_valid_addr(field_value): prev_offset = self._br.offset - self._offset_ptr_size raise RuntimeError( f'Invalid code address deteted at 0x{prev_offset:08X} ' '({self.class_name})\n If you think it\'s a bug, please open an issue on ' 'Github with the used binary or the full VMT (fields + VMT) as an attachment' ) field_offset = self._br.offset - self._vmt_address - self._offset_ptr_size if field_offset in offset_map: # Remove `cVmt` prefix method_name = f'{self.class_name}.{offset_map[field_offset][4:]}' else: method_name = f'{self.class_name}.sub_{field_value:x}' self._virtual_methods[field_value] = method_name return True def _resolve_table_list(self) -> bool: if not self.seek_to_vmt_offset(self.vmt_offsets.cVmtIntfTable): return False offsets = self._vmt_offsets.__dict__.items() offset_map = {y: x[4:] for x, y in offsets} stop_at = self._vmt_address + self._vmt_offsets.cVmtClassName while self._br.offset != stop_at: prev_br_offset = self._br.offset address = self.read_ptr() if address < 1: continue if not self._is_valid_addr(address): raise RuntimeError('Invalid table address detected') self._table_list[address] = offset_map[prev_br_offset - self._vmt_address] return True def _is_valid_addr(self, addy: int, allow_null=False) -> bool: if addy == 0: return allow_null return addy >= self._analyzer.start and addy < self._analyzer.end def _get_class_name_addr(self) -> Union[None, int]: if not self.seek_to_vmt_offset(self._vmt_offsets.cVmtClassName): return None class_name_addr = self.read_ptr() if not self._is_valid_addr(class_name_addr): return None return class_name_addr
class DelphiAnalyzer(object): ''' TODO: Doc ''' def __init__(self, bv: BinaryView, delphi_version: int, offset_ptr_size=-1, start=-1, end=-1): self._offset_ptr_size = offset_ptr_size if offset_ptr_size > 0 else bv.address_size self._start = start if start >= 0 else bv.start self._end = end if end > 0 else bv.end self._vmt_list: List[DelphiVMT] = [] self._bv = bv self._br = BinaryReader(bv) self._delphi_version = delphi_version self._vmt_offsets = VMTOffsets(delphi_version, self._offset_ptr_size) # Not really sure about this but it's working on my test binary (Win64 Delphi v10.3) # Need to check offsets for macOS # I'll clean that later if self._bv.view_type == 'PE' and self._offset_ptr_size == 8: for x, y in self._vmt_offsets.__dict__.items(): setattr(self._vmt_offsets, x, y + -24) ## Properties @property def start(self) -> int: return self._start @property def end(self) -> int: return self._end @property def delphi_version(self) -> int: return self._delphi_version @property def vmt_list(self) -> List[DelphiVMT]: return self._vmt_list @property def vmt_offsets(self) -> VMTOffsets: return copy.copy(self._vmt_offsets) ## Public API def update_analysis_and_wait(self, callback: Callable[[DelphiVMT], None] = None): self._vmt_list = [] self._seek_to_offset(0) while True: addy = self._get_possible_vmt() if addy is None: break delphi_vmt = DelphiVMT(self._bv, self._delphi_version, addy, self, self._offset_ptr_size) if not delphi_vmt.is_valid: continue self._vmt_list.append(delphi_vmt) if callback is not None: callback(delphi_vmt) ## Protected methods def _seek_to_offset(self, offset: int): self._br.seek(self._start + offset) def _read_ptr(self) -> Union[None, int]: if self._offset_ptr_size == 4: return self._br.read32() elif self._offset_ptr_size == 8: return self._br.read64() def _get_possible_vmt(self) -> int: while self._br.offset <= self._end - self._offset_ptr_size - 1: begin = self._br.offset if not self._bv.is_valid_offset(begin): self._br.seek_relative(self._offset_ptr_size) continue class_vmt = self._read_ptr() if class_vmt is None: # If BinaryReader can't read, it will not update the offset self._br.seek_relative(self._offset_ptr_size) continue if begin == class_vmt + self._vmt_offsets.cVmtSelfPtr: return class_vmt