class VMVisitor(BNILVisitor): def __init__(self, view): super(VMVisitor, self).__init__() self.view = view self.bw = BinaryWriter(view) self.br = BinaryReader(view) self.regs = {r: 0 for r in Architecture['VMArch'].regs} def visit_LLIL_STORE(self, expr): dest = self.visit(expr.dest) src = self.visit(expr.src) if None not in (dest, src): self.bw.seek(dest) self.bw.write8(src) def visit_LLIL_CONST(self, expr): return expr.constant def visit_LLIL_CONST_PTR(self, expr): return expr.constant def visit_LLIL_SET_REG(self, expr): dest = expr.dest.name src = self.visit(expr.src) if src is not None: self.regs[dest] = src def visit_LLIL_LOAD(self, expr): src = self.visit(expr.src) if src is not None: self.br.seek(src) return self.br.read8() def visit_LLIL_XOR(self, expr): left = self.visit(expr.left) right = self.visit(expr.right) if None not in (left, right): return left ^ right def visit_LLIL_REG(self, expr): src = expr.src return self.regs[src.name] def visit_LLIL_NORET(self, expr): log_alert("VM Halted.")
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)
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
def get_instruction_low_level_il(self, data, addr, il): opcode, length = self.parse_instruction(data, addr) op = opcodes[opcode] if addr == 0x10000: il.append( il.set_reg(4, 'ptr', il.const(1, 0)) ) if op == "Right": il.append( il.set_reg(4, 'ptr', il.add( 4, il.reg(4, 'ptr'), il.const(1, 1)), None) ) elif op == "Left": il.append( il.set_reg(4, 'ptr', il.sub( 4, il.reg(4, 'ptr'), il.const(1, 1)), None) ) elif op == "Add": il.append( il.store(1, il.reg(4, 'ptr'), il.add( 1, il.load(1, il.reg(4, 'ptr')), il.const(1, 1)), None) ) elif op == "Subtract": il.append( il.store(1, il.reg(4, 'ptr'), il.sub( 1, il.load(1, il.reg(4, 'ptr')), il.const(1, 1)), None) ) elif op == "In": il.append( il.unimplemented() ) elif op == "Out": il.append( il.unimplemented() ) elif op == "Open": true_label = il.get_label_for_address( Architecture['Brainfuck'], addr + 1) br = BinaryReader(il._source_function._view) br.seek(addr+1) # print("Found Open at : ", br.offset-1) counter = 1 while counter != 0: instr = opcodes[br.read8()] if instr == "Open": counter += 1 elif instr == "Close": counter -= 1 if counter == 0: false_label = il.get_label_for_address( Architecture['Brainfuck'], br.offset) # print("Found loop close at offset : ", br.offset-1) break elif br.offset == il._source_function._view.end: print("Unfinished loop! This should never happen!") return il.append( il.if_expr(il.compare_not_equal(1, il.load( 1, il.reg(4, 'ptr')), il.const(1, 0)), true_label, false_label) ) elif op == "Close": false_label = il.get_label_for_address( Architecture['Brainfuck'], addr + 1) br = BinaryReader(il._source_function._view) br.seek(addr) # print("Found Close at : ", br.offset) counter = 1 while counter != 0: br.seek_relative(-2) instr = opcodes[br.read8()] if instr == "Close": counter += 1 elif instr == "Open": counter -= 1 if counter == 0: true_label = il.get_label_for_address( Architecture['Brainfuck'], br.offset) # print("Found loop Open at offset : ", br.offset-1) break elif br.offset == il._source_function._view.end: print("Unfinished loop! This should never happen!") return il.append( il.if_expr(il.compare_not_equal(1, il.load( 1, il.reg(4, 'ptr')), il.const(1, 0)), true_label, false_label) ) else: il.append( il.nop() ) return length