import idaapi import idautils import idc from ..core import fix_addresses from .xref import Xref from .instruction import Instruction from ..ui import updates_ui from .base import get_selection, get_offset_name, demangle from .. import data class Comments(object): """IDA Line Comments Provides easy access to all types of comments for an IDA line. """ def __init__(self, ea): self._ea = ea def __bool__(self): return any((self.regular, self.repeat, self.anterior, self.posterior,)) @property def regular(self): """Regular Comment""" return idaapi.get_cmt(self._ea, 0) @regular.setter def regular(self, comment): idaapi.set_cmt(self._ea, comment, 0) @property def repeat(self): """Repeatable Comment""" return idaapi.get_cmt(self._ea, 1) @repeat.setter def repeat(self, comment): idaapi.set_cmt(self._ea, comment, 1) def _iter_extra_comments(self, start): end = idaapi.get_first_free_extra_cmtidx(self._ea, start) for idx in range(start, end): line = idaapi.get_extra_cmt(self._ea, idx) yield line or '' def _iter_anterior(self): return self._iter_extra_comments(idaapi.E_PREV) @property def anterior(self): """Anterior Comment""" return "\n".join(self._iter_anterior()) @anterior.setter @updates_ui def anterior(self, comment): if not comment: idaapi.del_extra_cmt(self._ea, idaapi.E_PREV) return index = 0 for index, line in enumerate(comment.splitlines()): idaapi.update_extra_cmt(self._ea, idaapi.E_PREV + index, line) idaapi.del_extra_cmt(self._ea, idaapi.E_PREV + (index + 1)) def _iter_posterior(self): return self._iter_extra_comments(idaapi.E_NEXT) @property def posterior(self): """Posterior Comment""" return "\n".join(self._iter_posterior()) @posterior.setter @updates_ui def posterior(self, comment): if not comment: idaapi.del_extra_cmt(self._ea, idaapi.E_NEXT) return index = 0 for index, line in enumerate(comment.splitlines()): idaapi.update_extra_cmt(self._ea, idaapi.E_NEXT + index, line) idaapi.del_extra_cmt(self._ea, idaapi.E_NEXT + (index + 1)) def __repr__(self): return ("Comments(" "ea=0x{ea:08X}," " reqular={regular}," " repeat={repeat}," " anterior={anterior}," " posterior={posterior})").format( ea=self._ea, regular=repr(self.regular), repeat=repr(self.repeat), anterior=repr(self.anterior), posterior=repr(self.posterior)) class Line(object): """ An IDA Line. This objects encapsulates many of IDA's line-handling APIs in an easy to use and object oriented way. """ class UseCurrentAddress(object): """ This is a filler object to replace `None` for the EA. In many cases, a programmer can accidentally initialize the `Line` object with `ea=None`, resulting in the current address. Usually, this is not the desired outcome. This object resolves this issue. """ pass def __init__(self, ea=UseCurrentAddress, name=None): if name is not None and ea != self.UseCurrentAddress: raise ValueError(("Either supply a name or an address (ea). " "Not both. (ea={!r}, name={!r})").format(ea, name)) elif name is not None: ea = idc.get_name_ea_simple(name) elif ea == self.UseCurrentAddress: ea = idc.here() elif ea is None: raise ValueError("`None` is not a valid address. To use the current screen ea, " "use `Line(ea=Line.UseCurrentAddress)` or supply no `ea`.") self._ea = idaapi.get_item_head(ea) self._comments = Comments(self._ea) @property def flags(self): """`FF_*` Flags. See `bytes.hpp`.""" return idaapi.get_full_flags(self.ea) @property def is_code(self): """Is the line code.""" return idaapi.is_code(self.flags) @property def is_data(self): """Is the line data.""" return idaapi.is_data(self.flags) @property def is_unknown(self): """Is the line unknown.""" return idaapi.is_unknown(self.flags) @property def is_tail(self): """Is the line a tail.""" return idaapi.is_tail(self.flags) @property def is_string(self): """Is the line a string.""" return data.is_string(self.ea) @property def comments(self): """Comments""" return self._comments @property def ea(self): """Line EA""" return self._ea start_ea = ea @property def end_ea(self): """End address of line (first byte after the line)""" return self.ea + self.size @property def disasm(self): """Line Disassembly""" return idc.GetDisasm(self.ea) @property def type(self): """return the type of the Line """ properties = {self.is_code: "code", self.is_data: "data", self.is_string: "string", self.is_tail: "tail", self.is_unknown: "unknown"} for k, v in properties.items(): if k: return v def __repr__(self): return "[{:08X}] {}".format(self.ea, self.disasm) @property def xrefs_from(self): """Xrefs from this line. :return: Xrefs as `sark.code.xref.Xref` objects. """ return list(map(Xref, idautils.XrefsFrom(self.ea))) @property def calls_from(self): return (xref for xref in self.xrefs_from if xref.type.is_call) @property def drefs_from(self): """Destination addresses of data references from this line.""" return idautils.DataRefsFrom(self.ea) @property def crefs_from(self): """Destination addresses of code references from this line.""" return idautils.CodeRefsFrom(self.ea, 1) @property def xrefs_to(self): """Xrefs to this line. Returns: Xrefs as `sark.code.xref.Xref` objects. """ return list(map(Xref, idautils.XrefsTo(self.ea))) @property def drefs_to(self): """Source addresses of data references from this line.""" return idautils.DataRefsTo(self.ea) @property def crefs_to(self): """Source addresses of data references to this line.""" return idautils.CodeRefsTo(self.ea, 1) @property def size(self): """Size (in bytes) of the line.""" return idaapi.get_item_size(self.ea) @property def name(self): """Name of the line (the label shown in IDA).""" return idaapi.get_ea_name(self.ea) @name.setter def name(self, value): idc.set_name(self.ea, value) @property def demangled(self): """Return the demangled name of the line. If none exists, return `.name`""" return demangle(self.name) @property def insn(self): """Instruction""" return Instruction(self.ea) @property def color(self): """Line color in IDA View""" color = idc.get_color(self.ea, idc.CIC_ITEM) if color == 0xFFFFFFFF: return None return color @color.setter @updates_ui def color(self, color): """Line Color in IDA View. Set color to `None` to clear the color. """ if color is None: color = 0xFFFFFFFF idc.set_color(self.ea, idc.CIC_ITEM, color) @property def next(self): """The next line.""" return Line(self.end_ea) @property def prev(self): """The previous line.""" return Line(self.ea - 1) @property def has_name(self): """Does the current line have a non-trivial (non-dummy) name?""" return idaapi.has_name(self.flags) @property def offset_name(self): return get_offset_name(self.ea) @property def bytes(self): return idaapi.get_bytes(self.ea, self.size) def __eq__(self, other): if not isinstance(other, Line): return False return self.ea == other.ea def __ne__(self, other): return not self.__eq__(other) def lines(start=None, end=None, reverse=False, selection=False): """Iterate lines in range. Args: start: Starting address, start of IDB if `None`. end: End address, end of IDB if `None`. reverse: Set to true to iterate in reverse order. selection: If set to True, replaces start and end with current selection. Returns: iterator of `Line` objects. """ if selection: start, end = get_selection() else: start, end = fix_addresses(start, end) if not reverse: item = idaapi.get_item_head(start) while item < end: yield Line(item) item += idaapi.get_item_size(item) else: # if reverse: item = idaapi.get_item_head(end - 1) while item >= start: yield Line(item) item = idaapi.get_item_head(item - 1)