8

I am trying to write an IDAPython script that will return a list of references to a local stack-frame variable. However, I couldn't find any API that does so.

What I am trying to achieve is a code like: xrefs = get_variable_references('arg_4') that will return the results corresponding with the GUI's results:

GUI's results

Thanks in advance.

2 Answers 2

7
+50

There is one function that does this: build_stkvar_xrefs, defined in C++ but exposed via the Python SWIG bindings. IDA builds stack xrefs dynamically when you ask for it. In order to use the function, it requires a little bit of setup.

You'll need to use a few functions to get what you need:

  • get_func(ea): retrieves the func_t structure for the function at ea
  • get_frame(func_t foo): returns the struct_t structure for the function frame specified by foo
  • DecodeInstruction(ea): returns the inst_t representing instruction at ea
  • get_stkvar(op_t op, sval_t v): op is a reference to an instruction, v is the immediate value in the operand. Usually you just use op.addr. It returns a tuple, (member_t, val). member_t is a pointer to the stack variable, which is what we need. val is the same value as the soff field in the member_t for the stack var. More on this later.
  • xreflist_t(): creates a new xreflist of xreflist_entry_t
  • build_stkvar_xrefs(xreflist_t xrefs, func_t func, member_t member): fills xrefs with xreflist_entry_t's that represent the stack var xrefs given by member in func.
  • struct_t.get_member(x): You can use this method to iterate all stack variables in a frame to retrieve all member_t's. If you want to build xrefs for all stack variables, this is usually easier.

Here's an example of how this all ties together:

# 0x4012d0 is the function address
# 0x4012dc is an instruction address referencing
# a stack variable. It looks like:
# mov [ebp - 4], ecx

pFunc = get_func(0x4012d0)
pFrame = get_frame(pFunc)
inst = DecodeInstruction(0x4012dc)
op = inst[0] #first operand references stack var
pMember, val = get_stkvar(op, op.addr)
xrefs = xreflist_t()
build_stkvar_xrefs(xrefs, pFunc, pMember)
for xref in xrefs:
    print hex(xref.ea) #print xref address

# Contrived member dictionary example.
dictMem = dict()
x = 0
while(x < pFrame.memqty):
    dictMem[GetMemberName(pFrame.id, pFrame.get_member(x).soff)] = pFrame.get_member(x)
    x = x+1
# given var name you can now use the
# dictionary to grab the member_t to pass
# to build_stkvar_xrefs
pMem = dictMem["var_4"]
xrefs = xreflist_t()
build_stkvar_xrefs(xrefs, pFunc, pMem)
for xref in xrefs:
    print hex(xref.ea) #print xrefs to var_4

soff isn't a stack offset. I think it means "structure offset", and it's an offset into the frame structure so you can retrieve other bits of information. You'll need this field to use other stack variable related functions such as: SetMemberType, SetMemberName, GetMemberName, DelStrucMember, etc.

So, for a simple on the fly variable name to xref lookup, you can do something like:

def get_stack_xrefs(func_ea, var_name):
    pFunc = get_func(func_ea)
    pFrame = get_frame(pFunc)
    pMember = None
    result = []
    while(x < pFrame.memqty):
        if GetMemberName(pFrame.id, pFrame.get_member(x).soff) == var_name:
           pMember = pFrame.get_member(x)
           break; 
        x = x+1
    if pMember: 
        xrefs = xreflist_t()
        build_stkvar_xrefs(xrefs, pFunc, pMember)
        for each in xrefs:
            result.append(each.ea)
    return result

If you want more information on these functions, I recommend taking a look at the following modules from the IDA SDK documentation (in no particular order):

  • funcs.hpp
  • frame.hpp
  • struct.hpp

Reference: https://www.hex-rays.com/products/ida/support/sdkdoc/files.html

6

TL;DR: There's no simple API to achieve this, code is at the end of the answer or here.

As far as I know, there is no easy way to get the references to stack structure. It seems like calling idautils.XrefsTo(sid) where sid is the frame id (retrieved using idc.GetFrame) should work, however I couldn't get it to yield any result in my attempts.

Instead, however, you could walk over the function's instructions and calculate the offsets into the stack manually whenever a stack reference operand is encountered.

I created a gist snippet to show just that, and although I think it's quite self-explanatory I'll go over it briefly here.

First, we'll need a mapping of stack offsets to arguments, this is taken care of by the find_stack_members function, which uses the idc.GetFrame API function mentioned above to get the structure ID for the stack of a specific function. Then, it uses idautils.StructMembers API to iterate over the stack variables.

One interesting piece of logic in the find_stack_members function is that it uses the <space>r member name as the base of the stack (where the stack is once the function is entered), this is used later in find_stack_xrefs to calculate the stack offset of a variable based on the current stack delta and the operand immediate value.

The find_stack_xrefs function iterates over the instructions in the given function, and skips any instruction but those with a operand defined to reference the stack (note that arguments that reference the stack but aren't defined as such by either IDA's auto-analysis or manually will not be treated and hence will not count as a cross-reference).

If a stack offset operand exists in the instrcution, the find_stack_xrefs function will proceed to calculate it's offset using the stack's base offset (previously retrieved by find_stack_members), the current stack delta (calculated by IDA and available using the API idc.GetSpd) and the immediate taken from the Operand structure.

For convenience, I'm also including the code here:

import idc, idaapi, idautils, ida_xref

def find_stack_members(func_ea):
    members = {}
    base = None
    frame = idc.GetFrame(func_ea)
    for frame_member in idautils.StructMembers(frame):
        member_offset, member_name, _ = frame_member
        members[member_offset] = member_name
        if member_name == ' r':
            base = member_offset
    if not base:
        raise ValueError("Failed identifying the stack's base address using the return address hidden stack member")
    return members, base

def find_stack_xrefs(func_offset):
    func_ea = ida_funcs.get_func(func_offset).startEA
    members, stack_base = find_stack_members(func_ea)
    for func_item in FuncItems(func_ea):
        flags = idc.GetFlags(ea)
        stkvar = 0 if idc.isStkvar0(flags) else 1 if idc.isStkvar1(flags) else None
        if not stkvar:
            continue
        ida_ua.decode_insn(func_item)
        op = ida_ua.cmd.Operands[stkvar]
        stack_offset = op.addr + idc.GetSpd(func_item) + stack_base
        member = members[stack_offset]
        print("At offset {:x} stack member {} is referenced by operand number {}".format(func_item, member, stkvar))

if __name__ == "__main__":
    find_stack_xrefs(idc.ScreenEA())
4
  • This code doesn't work: it's outdated, it has multiple bugs and the algorithm is simply wrong. Don't anyone use it. I'll give you just a few examples: flags = idc.GetFlags(ea) is just wrong, there is no ea variable in this script. Meaning this code never actually worked. The idc.GetSpd(func_item) call is again wrong: GetSpd takes past-the-end address, not the address of the current instruction.
    – KulaGGin
    Jan 26, 2022 at 8:46
  • And even after you supply a proper argument for GetSpd, the formula op.addr + idc.GetSpd(func_item) + stack_base is still wrong. The GetSpd gets the difference between the initial and current values of ESP and doesn't help to determine which stack variable is referenced and on what offset. For example, I have instruction mov [rbp+0F0h+var_18], rax at address 0x14001242A. var_18 is at offset 0x18 respectively. The past-the-end address is 0x140012431, so I do idc.GetSpd(0x140012431), and get -0x118.
    – KulaGGin
    Jan 26, 2022 at 8:54
  • 1
    Then formula: op.addr + idc.GetSpd(func_item) + stack_base turns into: 0x14001242A + (-0x118) + 0x118, which equals to 0x14001242A, which is the instruction address, not the stack offset. Which makes this answer completely wrong. If you want a working solution, check out the accepted answer by @mayahustle.
    – KulaGGin
    Jan 26, 2022 at 8:54
  • @KulaGGin Thanks for catching my errors! It's been a while and I'm not sure I tested the code. It was meant as a thought process snippet more than as a working solution. Thanks for pointing it out!
    – NirIzr
    Jan 26, 2022 at 9:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.