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
pull/2934/head
peace-maker 8 months ago committed by GitHub
parent 6204a19b34
commit 3a783d0637
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -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}")

@ -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

@ -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()

Loading…
Cancel
Save