From 3a783d0637027829fb9c0e13b60c7ed55ab49539 Mon Sep 17 00:00:00 2001 From: peace-maker Date: Mon, 28 Apr 2025 00:02:24 +0200 Subject: [PATCH] IDA: Support lookup of stack variables using `$ida("name")` (#2926) Instead of only allowing to lookup global symbols, allow reference local variables too. This is done by iterating the local stack frame struct in IDA and comparing the name. Depending on whether IDA created an entry for the saved frame pointer in the stack frame, the stack variable offset is returned relative to the current stack or frame pointer. This also fixes the error for invalid names in #2903 Based on https://gist.github.com/syndrowm/2968620 --- ida_script.py | 6 +++--- pwndbg/commands/ida.py | 44 ++++++++++++++++++++++++++++++++++++++- pwndbg/integration/ida.py | 37 +++++++++++++++++++++++++------- 3 files changed, 76 insertions(+), 11 deletions(-) diff --git a/ida_script.py b/ida_script.py index 1c3263647..ce5abaa47 100644 --- a/ida_script.py +++ b/ida_script.py @@ -162,10 +162,10 @@ register_module( idc ) # prioritize idc functions over above (e.g. idc.get_next_seg/ida_segment.get_next_seg) -server.register_function(lambda a: eval(a, globals(), locals()), "eval") -server.register_function(wrap(decompile)) # overwrites idaapi/ida_hexrays.decompile +server.register_function(wrap(lambda a: eval(a, globals(), locals())), "eval") +server.register_function(wrap(decompile), "decompile") # overwrites idaapi/ida_hexrays.decompile server.register_function(wrap(decompile_context), "decompile_context") # support context decompile -server.register_function(wrap(versions)) +server.register_function(wrap(versions), "versions") server.register_introspection_functions() print(f"IDA Pro xmlrpc hosted on http://{host}:{port}") diff --git a/pwndbg/commands/ida.py b/pwndbg/commands/ida.py index f81da2103..b49526d1c 100644 --- a/pwndbg/commands/ida.py +++ b/pwndbg/commands/ida.py @@ -137,6 +137,39 @@ def save_ida() -> None: save_ida() +def _ida_local(name: str) -> int | None: + if not pwndbg.aglib.proc.alive: + return None + + pc = int(pwndbg.dbg.selected_frame().pc()) + frame_id = pwndbg.integration.ida.GetFuncAttr(pc, pwndbg.integration.ida.idc.FUNCATTR_FRAME) # type: ignore[attr-defined] + if frame_id == -1: + return None + + stack_size = pwndbg.integration.ida.GetStrucSize(frame_id) + + # workaround for bug in IDA 9 when looking up the " s" member offset raises + # AttributeError: module 'ida_typeinf' has no attribute 'FRAME_UDM_NAME_S' + saved_baseptr = pwndbg.integration.ida.GetMemberOffset(frame_id, "__saved_registers") + if saved_baseptr == -1: + saved_baseptr = pwndbg.integration.ida.GetMemberOffset(frame_id, " s") + + for i in range(stack_size): + local_name = pwndbg.integration.ida.GetMemberName(frame_id, i) + if local_name != name: + continue + + # Heuristic: Offset is relative to the base pointer or stack pointer + # depending on if IDA is detecting a saved frame pointer or not. + offset = pwndbg.integration.ida.GetMemberOffset(frame_id, local_name) + if offset == -1: + raise ValueError("ida.GetMemberOffset(%r) == -1" % local_name) + if saved_baseptr != -1: + return pwndbg.aglib.regs[pwndbg.aglib.regs.frame] + offset - saved_baseptr + return pwndbg.aglib.regs[pwndbg.aglib.regs.stack] + offset + return None + + @GdbFunction() def ida(name: gdb.Value) -> int: """ @@ -159,9 +192,18 @@ def ida(name: gdb.Value) -> int: ``` """ name = name.string() + + # Lookup local variables first + result = _ida_local(name) + if result is not None: + return result + result = pwndbg.integration.ida.LocByName(name) + if result is None: + raise ValueError("ida.LocByName(%r) == None" % name) - if 0xFFFFE000 <= result <= 0xFFFFFFFF or 0xFFFFFFFFFFFFE000 <= result <= 0xFFFFFFFFFFFFFFFF: + result_r = pwndbg.integration.ida.l2r(result) + if 0xFFFFE000 <= result_r <= 0xFFFFFFFF or 0xFFFFFFFFFFFFE000 <= result_r <= 0xFFFFFFFFFFFFFFFF: raise ValueError("ida.LocByName(%r) == BADADDR" % name) return result diff --git a/pwndbg/integration/ida.py b/pwndbg/integration/ida.py index 22ab8435a..e7599333e 100644 --- a/pwndbg/integration/ida.py +++ b/pwndbg/integration/ida.py @@ -46,7 +46,7 @@ ida_timeout = pwndbg.config.add_param("ida-timeout", 2, "time to wait for ida xm _ida: xmlrpc.client.ServerProxy | None = None # to avoid printing the same exception multiple times, we store the last exception here -_ida_last_exception = None +_ida_last_exception: BaseException | None = None # to avoid checking the connection multiple times with no delay, we store the last time we checked it _ida_last_connection_check = 0 @@ -79,6 +79,7 @@ def init_ida_rpc_client() -> None: try: _ida.here() print(message.success(f"Pwndbg successfully connected to Ida Pro xmlrpc: {addr}")) + idc._update() except TimeoutError: exception = sys.exc_info() _ida = None @@ -119,7 +120,7 @@ def init_ida_rpc_client() -> None: ) print( message.notice("To disable IDA Pro integration invoke `") - + message.hint("set ida-enabled off") + + message.hint("set integration-provider none") + message.notice("`") ) @@ -230,6 +231,13 @@ def GetFuncOffset(addr: int): return rv +@withIDA +@takes_address +def GetFuncAttr(addr: int, attr: int): + rv = _ida.get_func_attr(addr, attr) + return rv + + @withIDA @takes_address @pwndbg.lib.cache.cache_until("objfile") @@ -446,6 +454,13 @@ def GetStrucSize(sid): return _ida.get_struc_size(sid) +@withIDA +@takes_address +@pwndbg.lib.cache.cache_until("stop") +def GetFrameId(addr): + return _ida.get_frame_id(addr) + + @withIDA @pwndbg.lib.cache.cache_until("stop") def GetMemberQty(sid): @@ -470,6 +485,12 @@ def GetMemberName(sid, offset): return _ida.get_member_name(sid, offset) +@withIDA +@pwndbg.lib.cache.cache_until("stop") +def GetMemberOffset(sid, member_name): + return _ida.get_member_offset(sid, member_name) + + @withIDA @pwndbg.lib.cache.cache_until("stop") def GetMemberFlag(sid, offset): @@ -483,12 +504,15 @@ def GetStrucNextOff(sid, offset): class IDC: - query = "{k:v for k,v in globals()['idc'].__dict__.items() if type(v) in (int,long)}" + query = "{k:v for k,v in globals()['idc'].__dict__.items() if isinstance(v, int)}" def __init__(self) -> None: if available(): - data: Dict[Any, Any] = _ida.eval(self.query) - self.__dict__.update(data) + self._update() + + def _update(self) -> None: + data: Dict[Any, Any] = _ida.eval(self.query) + self.__dict__.update(data) idc = IDC() @@ -554,8 +578,7 @@ class IdaProvider(pwndbg.integration.IntegrationProvider): return Name(addr) or GetFuncOffset(addr) or None return None - @pwndbg.decorators.suppress_errors() - @withIDA + @pwndbg.decorators.suppress_errors(fallback=()) def get_versions(self) -> Tuple[str, ...]: ida_versions = get_ida_versions()