def __init__(self, bv): BackgroundTaskThread.__init__(self, "", True) self.progress = 'genesis: Fixing up ROM checksum...' self.rom_start = 0x200 self.checksum_off = 0x18e self.bv = bv self.br = BinaryReader(self.bv)
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}
class GenesisCallTableEnum(BackgroundTaskThread): def __init__(self, bv): BackgroundTaskThread.__init__(self, "", True) self.progress = 'genesis: Enumerating call tables...' self.bv = bv self.br = BinaryReader(self.bv) def find_call_tables(self): base_addrs = [] for func in self.bv: if not func.medium_level_il.ssa_form: continue for block in func.medium_level_il.ssa_form: for instr in block: branch_operations = [ MediumLevelILOperation.MLIL_CALL_UNTYPED_SSA, MediumLevelILOperation.MLIL_JUMP, MediumLevelILOperation.MLIL_GOTO ] if instr.operation not in branch_operations: continue if type(instr.dest) == int: continue if instr.dest.operation == MediumLevelILOperation.MLIL_ADD: if instr.dest.operands[ 0].operation == MediumLevelILOperation.MLIL_CONST: base_addrs.append(instr.dest.operands[0].constant) return base_addrs def disas_call_tables(self, base_addrs): count = 0 for addr in base_addrs: i = addr while True: self.br.seek(i) opcode = self.br.read32be() if (opcode >> 24) == 0x60: if not self.bv.get_function_at(i): self.bv.add_function(i) count += 1 else: break i += 4 return count def run(self): self.bv.platform = Platform['M68000'] call_table_addrs = self.find_call_tables() if not call_table_addrs: return count = self.disas_call_tables(call_table_addrs) show_message_box( 'genesis', 'Disassembled {} call table instructions'.format(count))
def __init__(self, bnc): self.bnc = bnc self.br = BinaryReader(bnc.bv) self.hexScreen = curses.newpad(curses.LINES-1, curses.COLS) self.hexOffset = 0 self.hexCursor = bnc.pos self.loadHexLines()
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 _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 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 __init__(self, bv, filepath=None, directories=None): self.progress_banner = "Running YARA scan" BackgroundTaskThread.__init__(self, self.progress_banner, True) self.bv = bv self.reader = BinaryReader(self.bv) self.rules = [] self.results = [] # Ensure that the tag types exist before using it if "YARA Matches" not in bv.tag_types: bv.create_tag_type("YARA Matches", "🔎") if filepath: self.load_signature(filepath) if directories: self.load_signatures(directories)
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 evaluate_llil(self, state, llil): 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]) reader = BinaryReader(state.memory_view) reader.seek(addr) # TODO: 32-bit deref = reader.read64() return deref elif llil.operation == LowLevelILOperation.LLIL_ADD: return sum(self.evaluate_llil(state, op) for op in llil.operands) else: raise NotImplementedError('todo: evaluate llil for %s' % llil.operation)
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
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)
def goto_var(il: MediumLevelILInstruction): function = il.function.source_function view = function.view jump_value = JumpVisitor().visit(il) if jump_value is None: print("Jump target not constant") function.reanalyze() return br = BinaryReader(view) br.seek(jump_value) if view.arch.address_size == 4: target = br.read32le() elif view.arch.address_size == 8: target = br.read64le() else: # wtf???? function.reanalyze() return function.set_user_indirect_branches(il.address, [(view.arch, target)]) return target
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 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 __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
class GenesisChecksum(BackgroundTaskThread): def __init__(self, bv): BackgroundTaskThread.__init__(self, "", True) self.progress = 'genesis: Fixing up ROM checksum...' self.rom_start = 0x200 self.checksum_off = 0x18e self.bv = bv self.br = BinaryReader(self.bv) def _calculate_checksum(self): self.br.seek(self.rom_start) checksum = self.br.read16be() while True: checksum = (checksum + self.br.read16be()) & 0x0000ffff if self.br.eof: break return checksum def run(self): checksum = self._calculate_checksum() self.bv.write(self.checksum_off, struct.pack('>H', checksum)) show_message_box('genesis', 'ROM checksum has been updated')
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
class UEFIHelper(BackgroundTaskThread): """Class for analyzing UEFI firmware to automate GUID annotation, segment fixup, type imports, and more """ def __init__(self, bv: BinaryView): BackgroundTaskThread.__init__(self, '', False) self.bv = bv self.br = BinaryReader(self.bv) self.dirname = os.path.dirname(os.path.abspath(__file__)) self.guids = self._load_guids() def _fix_segments(self): """UEFI modules run during boot, without page protections. Everything is RWX despite that the PE is built with the segments not being writable. It needs to be RWX so calls through global function pointers are displayed properly. """ for seg in self.bv.segments: # Make segment RWX self.bv.add_user_segment( seg.start, seg.data_length, seg.data_offset, seg.data_length, SegmentFlag.SegmentWritable | SegmentFlag.SegmentReadable | SegmentFlag.SegmentExecutable) # Make section semantics ReadWriteDataSectionSemantics for section in self.bv.get_sections_at(seg.start): self.bv.add_user_section( section.name, section.end - section.start, SectionSemantics.ReadWriteDataSectionSemantics) def _import_types_from_headers(self): """Parse EDKII types from header files """ hdrs_path = os.path.join(self.dirname, 'headers') headers = glob.glob(os.path.join(hdrs_path, '*.h')) for hdr in headers: _types = self.bv.platform.parse_types_from_source_file(hdr) for name, _type in _types.types.items(): self.bv.define_user_type(name, _type) def _set_entry_point_prototype(self): """Apply correct prototype to the module entry point """ _start = self.bv.get_function_at(self.bv.entry_point) if self.bv.view_type != 'TE': _start.function_type = "EFI_STATUS ModuleEntryPoint(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable)" def _load_guids(self): """Read known GUIDs from CSV and convert string GUIDs to bytes :return: Dictionary containing GUID bytes and associated names """ guids_path = os.path.join(self.dirname, 'guids.csv') with open(guids_path) as f: reader = csv.reader(f, skipinitialspace=True) guids = dict(reader) # Convert to bytes for faster lookup guid_bytes = dict() for guid, name in guids.items(): guid_bytes[name] = uuid.UUID(guid).bytes_le return guid_bytes def _apply_guid_name_if_data(self, name: str, address: int): """Check if there is a function at the address. If not, then apply the EFI_GUID type and name it :param name: Name/symbol to apply to the GUID :param address: Address of the GUID """ print( f'Found {name} at 0x{hex(address)} ({uuid.UUID(bytes_le=self.guids[name])})' ) # Just to avoid a unlikely false positive and screwing up disassembly if self.bv.get_functions_at(address) != []: print( f'There is code at {address}, not applying GUID type and name') return self.bv.define_user_symbol( Symbol(SymbolType.DataSymbol, address, 'g' + name)) t = self.bv.parse_type_string("EFI_GUID") self.bv.define_user_data_var(address, t[0]) def _find_known_guids(self): """Search for known GUIDs and apply names to matches not within a function """ names_list = list(self.guids.keys()) guids_list = list(self.guids.values()) def _check_guid_and_get_name(guid): try: return names_list[guids_list.index(guid)] except ValueError: return None for seg in self.bv.segments: for i in range(seg.start, seg.end): self.br.seek(i) data = self.br.read(16) if not data or len(data) != 16: continue found_name = _check_guid_and_get_name(data) if found_name: self._apply_guid_name_if_data(found_name, i) def _set_if_uefi_core_type(self, instr: HighLevelILInstruction): """Using HLIL, scrutinize the instruction to determine if it's a move of a local variable to a global variable. If it is, check if the source operand type is a UEFI core type and apply the type to the destination global variable. :param instr: High level IL instruction object """ if instr.operation != HighLevelILOperation.HLIL_ASSIGN: return if instr.dest.operation != HighLevelILOperation.HLIL_DEREF: return if instr.dest.src.operation != HighLevelILOperation.HLIL_CONST_PTR: return if instr.src.operation != HighLevelILOperation.HLIL_VAR: return _type = instr.src.var.type if len(_type.tokens) == 1 and str(_type.tokens[0]) == 'EFI_HANDLE': self.bv.define_user_symbol( Symbol(SymbolType.DataSymbol, instr.dest.src.constant, 'gHandle')) elif len(_type.tokens) > 2 and str( _type.tokens[2]) == 'EFI_BOOT_SERVICES': self.bv.define_user_symbol( Symbol(SymbolType.DataSymbol, instr.dest.src.constant, 'gBS')) elif len(_type.tokens) > 2 and str( _type.tokens[2]) == 'EFI_RUNTIME_SERVICES': self.bv.define_user_symbol( Symbol(SymbolType.DataSymbol, instr.dest.src.constant, 'gRS')) elif len(_type.tokens) > 2 and str( _type.tokens[2]) == 'EFI_SYSTEM_TABLE': self.bv.define_user_symbol( Symbol(SymbolType.DataSymbol, instr.dest.src.constant, 'gST')) elif len(_type.tokens) == 1 and str( _type.tokens[0]) == 'EFI_PEI_FILE_HANDLE': self.bv.define_user_symbol( Symbol(SymbolType.DataSymbol, instr.dest.src.constant, 'gHandle')) elif len(_type.tokens) > 2 and str( _type.tokens[2]) == 'EFI_PEI_SERVICES': self.bv.define_user_symbol( Symbol(SymbolType.DataSymbol, instr.dest.src.constant, 'gPeiServices')) else: return self.bv.define_user_data_var(instr.dest.src.constant, instr.src.var.type) print( f'Found global assignment - offset:0x{hex(instr.dest.src.constant)} type:{instr.src.var.type}' ) def _check_and_prop_types_on_call(self, instr: HighLevelILInstruction): """Most UEFI modules don't assign globals in the entry function and instead call a initialization routine and pass the system table to it where global assignments are made. This function ensures that the types are applied to the initialization function params so that we can catch global assignments outside of the module entry :param instr: High level IL instruction object """ if instr.operation not in [ HighLevelILOperation.HLIL_TAILCALL, HighLevelILOperation.HLIL_CALL ]: return if instr.dest.operation != HighLevelILOperation.HLIL_CONST_PTR: return argv_is_passed = False for arg in instr.params: if 'ImageHandle' in str( arg ) or 'SystemTable' or 'FileHandle' or 'PeiServices' in str(arg): argv_is_passed = True break if not argv_is_passed: return func = self.bv.get_function_at(instr.dest.constant) old = func.function_type call_args = instr.params new_params = [] for arg, param in zip(call_args, old.parameters): if hasattr(arg, 'var'): new_type = arg.var.type else: new_type = param.type new_type.confidence = 256 new_params.append(FunctionParameter(new_type, param.name)) # TODO: this is a hack to account for odd behavior. func.function_type should be able to set directly to # Type.Function(...). However, during testing this isn't the case. I am only able to get it to work if I # set function_type to a string and update analysis. gross_hack = str( Type.function(old.return_value, new_params, old.calling_convention, old.has_variable_arguments, old.stack_adjustment)).replace( '(', '{}('.format(func.name)) try: func.function_type = gross_hack self.bv.update_analysis_and_wait() except SyntaxError: pass # BN can't parse int48_t and other types despite that it uses it. Ran into this from a sidt instruction def _set_global_variables(self): """On entry, UEFI modules usually set global variables for EFI_BOOT_SERVICES, EFI_RUNTIME_SERIVCES, and EFI_SYSTEM_TABLE. This function attempts to identify these assignments and apply types. """ func = self.bv.get_function_at(self.bv.entry_point) for block in func.high_level_il: for instr in block: self._check_and_prop_types_on_call(instr) for func in self.bv.functions: for block in func.high_level_il: for instr in block: self._set_if_uefi_core_type(instr) def run(self): """Run the task in the background """ self.progress = "UEFI Helper: Fixing up segments, applying types, and searching for known GUIDs ..." self._fix_segments() self._import_types_from_headers() self._set_entry_point_prototype() self._find_known_guids() self.progress = "UEFI Helper: searching for global assignments for UEFI core services ..." self._set_global_variables() print('UEFI Helper completed successfully!')
def __init__(self, bv: BinaryView): BackgroundTaskThread.__init__(self, '', False) self.bv = bv self.br = BinaryReader(self.bv) self.dirname = os.path.dirname(os.path.abspath(__file__)) self.guids = self._load_guids()
def __init__(self, bv): BackgroundTaskThread.__init__(self, "", True) self.progress = 'genesis: Enumerating call tables...' self.bv = bv self.br = BinaryReader(self.bv)
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
class YaraScan(BackgroundTaskThread): def __init__(self, bv, filepath=None, directories=None): self.progress_banner = "Running YARA scan" BackgroundTaskThread.__init__(self, self.progress_banner, True) self.bv = bv self.reader = BinaryReader(self.bv) self.rules = [] self.results = [] # Ensure that the tag types exist before using it if "YARA Matches" not in bv.tag_types: bv.create_tag_type("YARA Matches", "🔎") if filepath: self.load_signature(filepath) if directories: self.load_signatures(directories) def run(self): log_info("Scanning binary view for matching YARA signatures") # TODO: Scan the raw binary data from the Raw view instead of by segment. # This would require mapping the addresses from the Raw view to the PE/ELF views. # raw = self.bv.get_view_of_type("Raw") # reader = BinaryReader(raw) # data = reader.read(raw.end) try: for idx, rule in enumerate(self.rules): if len(self.bv.segments) == 0: # Scan binary without segments self.scan(self.bv.start, self.bv.end, rule) else: # Scan by segment for segment in self.bv.segments: if self.cancelled: return self.scan(segment.start, segment.data_length, rule) self.progress = f"{self.progress_banner} matching on rules ({round((idx / len(self.rules)) * 100)}%)" except yara.TimeoutError: log_warn( "YARA scan exceeded timeout limit. Consider changing the timeout in settings." ) except yara.Error as err: log_error("Error matching on YARA rules: {}".format(str(err))) show_message_box("Error", "Check logs for details", icon=MessageBoxIcon.ErrorIcon) if 0 < len(self.results): if Settings().get_bool("yara.displayReport"): self.display_report() else: log_info("YARA scan finished with no matches.") def scan(self, start, length, rule): self.reader.seek(start) data = self.reader.read(length) matches = rule.match(data=data, timeout=Settings().get_integer("yara.timeout")) for match in matches: name = match.rule # Include the description field if its present in the metadata try: description = f"{name}: {match.meta['description']}" except KeyError: description = f"{name}" tag = self.bv.create_tag(self.bv.tag_types["YARA Matches"], description, True) for address, var, value in match.strings: address += start # Display data values correctly in the report if value.isascii(): value = value.decode("ascii") elif self.bv.endianness == Endianness.BigEndian: value = hex(int.from_bytes(value, "big")) else: value = hex(int.from_bytes(value, "little")) self.results.append({ "address": address, "name": name, "string": var, "value": value }) # Add either an address or data tag to the location funcs = self.bv.get_functions_containing(address) if 0 < len(funcs): # Ensure the tag is not placed in the middle of an instruction address = get_instruction_containing(funcs[0], address) funcs[0].add_user_address_tag(address, tag) else: self.bv.add_user_data_tag(address, tag) def display_report(self): contents = """# YARA Results | Address | Name | String | Value | |---------|------|--------|-------| """ for result in self.results: contents += "| [0x{:x}](binaryninja://?expr=0x{:x}) | {} | {} | {} |\n".format( result["address"], result["address"], result["name"], result["string"], result["value"]) self.bv.show_markdown_report("YARA Results", contents) def load_signature(self, filepath): if os.path.isfile(filepath): try: self.rules.append(yara.compile(filepath)) log_info("Loaded YARA rule: {}".format(filepath)) except yara.SyntaxError: log_error( "Syntax error compiling YARA rule: {}".format(filepath)) else: log_error("YARA rule filepath is invalid: {}".format(filepath)) def load_signatures(self, directories): rule_files = [] for directory in directories: if not os.path.isdir(directory) and directory != "": log_error( "YARA rule directory is invalid: {}".format(directory)) else: for f in os.listdir(directory): if f.lower().endswith((".yar", ".yara")): rule_files.append(directory + os.sep + f) for f in rule_files: try: self.rules.append(yara.compile(f)) log_info("Loaded YARA rule: {}".format(f)) except yara.SyntaxError: log_error("Syntax error compiling YARA rule: {}".format(f))
class SymbolicExecutor(object): def __init__(self, view, addr): self.view = view self.bw = BinaryWriter(view) self.br = BinaryReader(view) self.visitor = SymbolicVisitor(self) self.bncache = BNCache(view) self.vars = set() self.fringe = Fringe() self.ip = addr self.llil_ip = None self.arch = None self.user_hooks = dict() self.user_loggers = dict() self.imported_functions, self.imported_addresses = \ get_imported_functions_and_addresses(view) self._last_colored_ip = None self._last_error = None self.init_with_zero = self.bncache.get_setting( "init_reg_mem_with_zero") == "true" self._wasjmp = False self.arch = find_arch(self.view) page_size = int(self.bncache.get_setting("memory.page_size")) self.state = State(self, arch=self.arch, os=find_os(view), page_size=page_size) # load memory print("loading segments...") for segment in self.view.segments: start = segment.start end = segment.end size = segment.data_length print(segment, hex(start), "->", hex(size)) if size == 0 and end - start != 0: size = end - start data = b"\x00" * size elif size == 0: continue else: self.br.seek(start) data = self.br.read(end - start) self.state.mem.mmap( self.state.address_page_aligned(start), self.state.address_page_aligned(end + self.state.mem.page_size - 1) - self.state.address_page_aligned(start), InitData(data, start - self.state.address_page_aligned(start))) print("loading finished!") current_function = self.bncache.get_function(addr) # initialize stack stack_page_size = int(self.bncache.get_setting("stack_size")) unmapped_page_init = self.state.mem.get_unmapped( stack_page_size, start_from=(0x80 << (self.arch.bits() - 8))) self.state.mem.mmap(unmapped_page_init * self.state.page_size, self.state.page_size * stack_page_size) # leave one page for upper stack portion p = unmapped_page_init + stack_page_size - 1 stack_base = p * self.state.page_size - self.arch.bits() // 8 self.state.initialize_stack(stack_base) # initialize registers for reg in self.arch.regs_data(): reg_dict = self.arch.regs_data()[reg] val = current_function.get_reg_value_after(addr, reg) if val.type.value == RegisterValueType.StackFrameOffset: setattr(self.state.regs, reg, BVV(stack_base + val.offset, reg_dict['size'] * 8)) elif (val.type.value == RegisterValueType.ConstantPointerValue or val.type.value == RegisterValueType.ConstantValue): setattr(self.state.regs, reg, BVV(val.value, reg_dict['size'] * 8)) else: if not self.init_with_zero: symb = BVS(reg + "_init", reg_dict['size'] * 8) self.vars.add(symb) setattr(self.state.regs, reg, symb) else: setattr(self.state.regs, reg, BVV(0, reg_dict['size'] * 8)) # initialize known local variables stack_vars = current_function.stack_layout for var in stack_vars: offset = var.storage s_type = var.type if abs(offset) > self.state.page_size * (stack_page_size - 1): print("ERROR: not enough space in stack. Increase stack size") raise Exception( "Not enough space in stack. Increase stack size") if s_type.confidence != 255: continue width = s_type.width name = var.name val = current_function.get_stack_contents_at(addr, offset, width) if val.type.value == RegisterValueType.StackFrameOffset: assert width * 8 == self.arch.bits() # has to happen... right? self.state.mem.store(BVV(stack_base + offset, self.arch.bits()), BVV(stack_base + val.offset, width * 8), endness=self.arch.endness()) elif (val.type.value == RegisterValueType.ConstantPointerValue or val.type.value == RegisterValueType.ConstantValue): self.state.mem.store(BVV(stack_base + offset, self.arch.bits()), BVV(val.value, width * 8), endness=self.arch.endness()) elif not self.init_with_zero: symb = BVS(name + "_init", self.arch.bits()) self.vars.add(symb) self.state.mem.store(BVV(stack_base + offset, self.arch.bits()), symb, endness=self.arch.endness()) # set eip self.state.set_ip(addr) self.llil_ip = current_function.llil.get_instruction_start(addr) def __str__(self): return "<SymExecutor id: 0x%x, %d states>" % \ (id(self), self.fringe.num_states + 1 if self.state is not None else 0) def __repr__(self): return self.__str__() def put_in_deferred(self, state): self.fringe.add_deferred(state) def put_in_exited(self, state): self.fringe.add_exited(state) def put_in_unsat(self, state): save_unsat = self.bncache.get_setting("save_unsat") == 'true' if save_unsat: self.fringe.add_unsat(state) def put_in_errored(self, state, msg: str): self.fringe.add_errored((msg, state)) def set_colors(self, reset=False): # TODO write an UI manager, this does not belong to the executor old_ip = self._last_colored_ip if old_ip is not None: old_func = self.bncache.get_function(old_ip) old_func.set_auto_instr_highlight(old_ip, NO_COLOR) for ip in self.fringe._deferred: func = self.bncache.get_function(ip) func.set_auto_instr_highlight( ip, DEFERRED_STATE_COLOR if not reset else NO_COLOR) # func.set_comment_at(ip, str(len(self.fringe._deferred[ip]) if not reset else "")) for _, state in self.fringe.errored: func = self.bncache.get_function(state.get_ip()) func.set_auto_instr_highlight( state.get_ip(), ERRORED_STATE_COLOR if not reset else NO_COLOR) if self.state: func = self.bncache.get_function(self.ip) func.set_auto_instr_highlight( self.ip, CURR_STATE_COLOR if not reset else NO_COLOR) if not reset: self._last_colored_ip = self.ip def reset(self): self.set_colors(reset=True) def set_current_state(self, state): if self.state is not None: self.state.llil_ip = self.llil_ip self.put_in_deferred(self.state) self.state = None ip = state.get_ip() llil_ip = state.llil_ip self.state = state new_func = self.bncache.get_function(ip) self.ip = ip self.llil_ip = new_func.llil.get_instruction_start( ip) if llil_ip is None else llil_ip def select_from_deferred(self): if self.fringe.is_empty(): return False state = self.fringe.get_one_deferred() self.set_current_state(state) return True def update_ip(self, funcion_name, new_llil_ip): self.llil_ip = new_llil_ip self.ip = self.bncache.get_address(funcion_name, new_llil_ip) self.state.set_ip(self.ip) self.state.llil_ip = new_llil_ip def _execute_one(self): self._last_error = None func_name = self.bncache.get_function_name(self.ip) # handle user hooks and loggers if self.ip in self.user_loggers: self.user_loggers[self.ip](self.state) if self.ip in self.user_hooks: old_ip = self.ip new_state, new_deferred, new_errored = self.user_hooks[self.ip]( self.state) for s in new_deferred: self.put_in_deferred(s) for s, msg in new_errored: self.put_in_errored(s, msg) if new_state is not None: self.state = new_state if old_ip == self.state.get_ip(): new_ip = self.ip + \ self.bncache.get_instruction_len(self.ip) else: new_ip = self.state.get_ip() dest_func_name = self.bncache.get_function_name(new_ip) self.update_ip( dest_func_name, self.bncache.get_llil_address(dest_func_name, new_ip)) return self.ip else: # check if a special handler is defined dont_use_special_handlers = \ self.bncache.get_setting("dont_use_special_handlers") == 'true' disasm_str = self.bncache.get_disasm(self.ip) try: if (dont_use_special_handlers or not self.arch.execute_special_handler( disasm_str, self)): expr = self.bncache.get_llil(func_name, self.llil_ip) self.visitor.visit(expr) else: self._wasjmp = True self.ip = self.ip + \ self.view.get_instruction_length(self.ip) self.state.set_ip(self.ip) self.llil_ip = self.bncache.get_function( self.ip).llil.get_instruction_start(self.ip) except exceptions.ExitException: self.put_in_exited(self.state) self.state = None except exceptions.SENinjaError as err: print("An error occurred: %s" % err.message) self.state = None self._last_error = err if err.is_fatal(): raise err if self.state is None: if self.fringe.is_empty(): print("WARNING: no more states") return -1 else: self.select_from_deferred() self._wasjmp = True if not self._wasjmp: # go on by 1 instruction self.update_ip(func_name, self.llil_ip + 1) else: self._wasjmp = False return self.ip def execute_one(self): if not self.state: return single_llil_step = self.bncache.get_setting( "single_llil_step") == 'true' if single_llil_step: res = self._execute_one() else: old_ip = self.ip res = old_ip while res == old_ip: res = self._execute_one() return res
def __init__(self, view, addr): self.view = view self.bw = BinaryWriter(view) self.br = BinaryReader(view) self.visitor = SymbolicVisitor(self) self.bncache = BNCache(view) self.vars = set() self.fringe = Fringe() self.ip = addr self.llil_ip = None self.arch = None self.user_hooks = dict() self.user_loggers = dict() self.imported_functions, self.imported_addresses = \ get_imported_functions_and_addresses(view) self._last_colored_ip = None self._last_error = None self.init_with_zero = self.bncache.get_setting( "init_reg_mem_with_zero") == "true" self._wasjmp = False self.arch = find_arch(self.view) page_size = int(self.bncache.get_setting("memory.page_size")) self.state = State(self, arch=self.arch, os=find_os(view), page_size=page_size) # load memory print("loading segments...") for segment in self.view.segments: start = segment.start end = segment.end size = segment.data_length print(segment, hex(start), "->", hex(size)) if size == 0 and end - start != 0: size = end - start data = b"\x00" * size elif size == 0: continue else: self.br.seek(start) data = self.br.read(end - start) self.state.mem.mmap( self.state.address_page_aligned(start), self.state.address_page_aligned(end + self.state.mem.page_size - 1) - self.state.address_page_aligned(start), InitData(data, start - self.state.address_page_aligned(start))) print("loading finished!") current_function = self.bncache.get_function(addr) # initialize stack stack_page_size = int(self.bncache.get_setting("stack_size")) unmapped_page_init = self.state.mem.get_unmapped( stack_page_size, start_from=(0x80 << (self.arch.bits() - 8))) self.state.mem.mmap(unmapped_page_init * self.state.page_size, self.state.page_size * stack_page_size) # leave one page for upper stack portion p = unmapped_page_init + stack_page_size - 1 stack_base = p * self.state.page_size - self.arch.bits() // 8 self.state.initialize_stack(stack_base) # initialize registers for reg in self.arch.regs_data(): reg_dict = self.arch.regs_data()[reg] val = current_function.get_reg_value_after(addr, reg) if val.type.value == RegisterValueType.StackFrameOffset: setattr(self.state.regs, reg, BVV(stack_base + val.offset, reg_dict['size'] * 8)) elif (val.type.value == RegisterValueType.ConstantPointerValue or val.type.value == RegisterValueType.ConstantValue): setattr(self.state.regs, reg, BVV(val.value, reg_dict['size'] * 8)) else: if not self.init_with_zero: symb = BVS(reg + "_init", reg_dict['size'] * 8) self.vars.add(symb) setattr(self.state.regs, reg, symb) else: setattr(self.state.regs, reg, BVV(0, reg_dict['size'] * 8)) # initialize known local variables stack_vars = current_function.stack_layout for var in stack_vars: offset = var.storage s_type = var.type if abs(offset) > self.state.page_size * (stack_page_size - 1): print("ERROR: not enough space in stack. Increase stack size") raise Exception( "Not enough space in stack. Increase stack size") if s_type.confidence != 255: continue width = s_type.width name = var.name val = current_function.get_stack_contents_at(addr, offset, width) if val.type.value == RegisterValueType.StackFrameOffset: assert width * 8 == self.arch.bits() # has to happen... right? self.state.mem.store(BVV(stack_base + offset, self.arch.bits()), BVV(stack_base + val.offset, width * 8), endness=self.arch.endness()) elif (val.type.value == RegisterValueType.ConstantPointerValue or val.type.value == RegisterValueType.ConstantValue): self.state.mem.store(BVV(stack_base + offset, self.arch.bits()), BVV(val.value, width * 8), endness=self.arch.endness()) elif not self.init_with_zero: symb = BVS(name + "_init", self.arch.bits()) self.vars.add(symb) self.state.mem.store(BVV(stack_base + offset, self.arch.bits()), symb, endness=self.arch.endness()) # set eip self.state.set_ip(addr) self.llil_ip = current_function.llil.get_instruction_start(addr)
class HexView(): def __init__(self, bnc): self.bnc = bnc self.br = BinaryReader(bnc.bv) self.hexScreen = curses.newpad(curses.LINES-1, curses.COLS) self.hexOffset = 0 self.hexCursor = bnc.pos self.loadHexLines() def loadHexLines(self): # Get lines to render self.hexLines = [] topOfScreen = (self.bnc.pos- (self.bnc.pos% self.bnc.program.settings["hexLineLength"])) - (self.hexOffset * self.bnc.program.settings["hexLineLength"]) self.topOfScreen = topOfScreen self.br.seek(topOfScreen) # TODO...make this for loop at least reasonably efficient...it's seriously just a clusterfuck right now for _ in range(self.hexScreen.getmaxyx()[0]-2): offset = "{:08x} ".format(self.br.offset) line_bytes = self.br.read(self.bnc.program.settings["hexLineLength"]) # Sections that don't exist in the memory if line_bytes is None: line_bytes = b'' line_byte = self.br.read(1) while line_byte is not None: line_bytes += line_byte line_byte = self.br.read(1) if len(line_bytes) == self.bnc.program.settings["hexLineLength"]: byteValues = ''.join(["{:02x} ".format(b) for b in line_bytes])[:-1] asciiValues = ''.join([chr(int(b, 16)) if (int(b, 16) > 31 and int(b, 16) < 127) else '.' for b in byteValues.split(' ')]) self.hexLines.append(offset + byteValues + " " + asciiValues) else: byteValues = ''.join(["{:02x} ".format(b) for b in line_bytes])[:-1] asciiValues = ''.join([chr(int(b, 16)) if (int(b, 16) > 31 and int(b, 16) < 127) else '.' for b in byteValues.split(' ')[:-1]]) self.hexLines.append(offset + byteValues + " "*(self.bnc.program.settings["hexLineLength"]*3-len(byteValues)) + " " + asciiValues) if (len(self.hexLines) != self.hexScreen.getmaxyx()[0]-2): self.hexLines.append('-'*(self.bnc.program.settings["hexLineLength"]*4 + 13)) line_byte = None while line_byte is None and self.br.offset <= self.bnc.bv.end: self.br.seek(self.br.offset+1) line_byte = self.br.read(1) self.br.seek(self.br.offset-1) if (len(self.hexLines) == self.hexScreen.getmaxyx()[0]-2): break def parseInput(self, key): # Scroll if key == self.bnc.program.settings["hexViewRight"]: self.hexCursor += 0.5 if self.hexCursor%1.0 == 0: self.bnc.pos+= 1 if self.bnc.pos% self.bnc.program.settings["hexLineLength"] == 0: self.hexOffset += 1 elif key == self.bnc.program.settings["hexViewLeft"]: self.hexCursor -= 0.5 if self.hexCursor%1.0 == 0.5: if self.bnc.pos% self.bnc.program.settings["hexLineLength"] == 0: self.hexOffset -= 1 self.bnc.pos-= 1 elif key == self.bnc.program.settings["hexViewLineDown"]: self.hexOffset += 1 self.bnc.pos+= self.bnc.program.settings["hexLineLength"] self.hexCursor += self.bnc.program.settings["hexLineLength"] elif key == self.bnc.program.settings["hexViewLineUp"]: self.hexOffset -= 1 self.bnc.pos-= self.bnc.program.settings["hexLineLength"] self.hexCursor -= self.bnc.program.settings["hexLineLength"] elif key == self.bnc.program.settings["hexViewPageDown"]: self.hexOffset += self.hexScreen.getmaxyx()[0]-3 self.bnc.pos+= self.bnc.program.settings["hexLineLength"] * (self.hexScreen.getmaxyx()[0]-3) self.hexCursor += self.bnc.program.settings["hexLineLength"] * (self.hexScreen.getmaxyx()[0]-3) elif key == self.bnc.program.settings["hexViewPageUp"]: self.hexOffset -= self.hexScreen.getmaxyx()[0]-3 self.bnc.pos-= self.bnc.program.settings["hexLineLength"] * (self.hexScreen.getmaxyx()[0]-3) self.hexCursor -= self.bnc.program.settings["hexLineLength"] * (self.hexScreen.getmaxyx()[0]-3) elif key in ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']: if self.hexCursor % 1.0 == 0.5: self.bnc.bv.write(self.bnc.pos, (int(key, 16) | (ord(self.bnc.bv.read(self.bnc.pos, 1).decode('charmap')) & 0b11110000)).to_bytes(1, 'big')) self.hexCursor += 0.5 self.bnc.pos+= 1 if self.bnc.pos% self.bnc.program.settings["hexLineLength"] == 0: self.hexOffset += 1 else: self.bnc.bv.write(self.bnc.pos, (int(key, 16) << 4 | (ord(self.bnc.bv.read(self.bnc.pos, 1).decode('charmap')) & 0b00001111)).to_bytes(1, 'big')) self.hexCursor += 0.5 # Adjust for off screen if self.hexOffset < 0: self.hexOffset = 0 elif self.hexOffset > self.hexScreen.getmaxyx()[0]-3: self.hexOffset = self.hexScreen.getmaxyx()[0]-3 if self.bnc.pos< self.bnc.bv.start: self.bnc.pos= self.bnc.bv.start self.hexCursor = self.bnc.pos elif self.bnc.pos> self.bnc.bv.end: self.bnc.pos= self.bnc.bv.end self.hexCursor = self.bnc.pos+ 0.5 # if self.hexOffset == skippedLinesLine: # if key == self.bnc.program.settings["hexViewLineUp"] or key == self.bnc.program.settings["hexViewPageUp"]: # self.hexOffset -= 1 # if key == self.bnc.program.settings["hexViewLineDown"] or key == self.bnc.program.settings["hexViewPageDown"]: # self.hexOffset += 1 # self.bnc.pos= self.br.offset self.loadHexLines() def render(self): self.hexScreen.erase() self.hexScreen.border() for yLine, rawBytes in enumerate(self.hexLines): if yLine == self.hexOffset: for xLine, rawByte in enumerate(rawBytes): if self.hexCursor%self.bnc.program.settings["hexLineLength"] == (xLine-8-3)//3 + ((xLine-8-3)%3)/2.0 and (xLine-8-3)%3 != 2: self.hexScreen.addstr(yLine+1, 2+xLine, rawByte, curses.A_STANDOUT) else: self.hexScreen.addstr(yLine+1, 2+xLine, rawByte) else: self.hexScreen.addstr(yLine+1, 2, rawBytes) title = "Hex" drawMultiLineText(0, self.hexScreen.getmaxyx()[1]-len(title)-3, title, self.hexScreen) self.hexScreen.noutrefresh(0, 0, 0, 0, curses.LINES-2, curses.COLS-1)