4

I would like to determine if an instruction a) reads from or b) writes to a memory address. I can currently think of two approaches in IDA:

  1. Looking for a list of mnemonics (better: particular opcodes?), e.g. for x86 see here and manually map each and every one of them to read, write.

    This does not seem to be very elegant to me.

  2. Work with the idc.GetOpType(ea, n) function and check for the relevant o_* constants (see here), i.e:

    o_mem      = idaapi.o_mem       # Direct Memory Reference  (DATA)      addr
    o_phrase   = idaapi.o_phrase    # Memory Ref [Base Reg + Index Reg]    phrase
    o_displ    = idaapi.o_displ     # Memory Reg [Base Reg + Index Reg + Displacement] phrase+addr
    

    Yet, these do not tell me if the instruction reads from or writes to memory.

Is there a better way to find out what I want? Ideally a way that works for multiple CPU architectures?

4
  • Is it required to use IDA? Do you want to do static or dynamic analysis? Sep 15, 2016 at 16:47
  • @TaThanhDinh : static analysis. IDA would be nice but if there is another way, that is fine too. Sep 16, 2016 at 7:36
  • 1
    In this case, you may find Xed useful. It gives high-level API to determine whether an instruction may read or write memory. I dont know whether Capstone has similar API. Sep 16, 2016 at 8:32
  • Sounds like a good plan. Sep 16, 2016 at 8:35

4 Answers 4

6

While the API may be cumbersome, most of the information you are looking for is "readily" available from IDA during static analysis. Some of it is somewhat generic, while other pieces require different handling per processor type.

To check if an operand performs read or write access to memory, you can use the following:

import idaapi
import idautils
import idc

OPND_WRITE_FLAGS = {
    0: idaapi.CF_CHG1,
    1: idaapi.CF_CHG2,
    2: idaapi.CF_CHG3,
    3: idaapi.CF_CHG4,
    4: idaapi.CF_CHG5,
    5: idaapi.CF_CHG6,
}

OPND_READ_FLAGS = {
    0: idaapi.CF_USE1,
    1: idaapi.CF_USE2,
    2: idaapi.CF_USE3,
    3: idaapi.CF_USE4,
    4: idaapi.CF_USE5,
    5: idaapi.CF_USE6,
}

def print_insn_mem_interaction(ea):
    insn = idautils.DecodeInstruction(ea)

    # The features are needed for operand flags.
    feature = insn.get_canon_feature()

    for op in insn.ops:
        # You always get 6 operands. Some of them are set to `o_void` to indicate
        # that they are not used.
        if op.type == idaapi.o_void:
            break

        # There are 3 types of memory references in IDA. We want all 3.
        is_mem = op.type in (idaapi.o_mem, idaapi.o_phrase, idaapi.o_displ)

        # Extract per-operand read/write status from the feature.
        is_write = feature & OPND_WRITE_FLAGS[op.n]
        is_read = feature & OPND_READ_FLAGS[op.n]

        if not is_mem:
            # Operand does not access memory.
            continue

        # Ugly line for the display. Sorry.
        action = 'memory {}'.format('/'.join(filter(bool, ('read' if is_read else None, 'write' if is_write else None))))

        print(f"Operand[{op.n}]<{idc.print_operand(ea, op.n)}> : {action}")

Telling the addresses accessed, though, is a different story.

The parsing of o_displ and o_phrase operands is processor-type specific, and is completely undocumented (actually, the docs say "do as you like"). If you are interested in a working parser for x86 and x64, you can check my code here.

If you need to work with any other architecture, I'd recommend using a more script-friendly disassembler. Capstone is probably a good choice on that matter.

And finally, if you need the actual addresses, you will probably have to turn to dynamic analysis.

3
  • I believe there's potentially a bug in this code. When you check if operand type is o_void in if op.type == idaapi.o_void: break, you break out of the loop that iterates through the sequence of all operands in the instruction. So if first operand is o_void, it break and not check all other operands after it, which could potentially be valid operands. I noticed it after I saw that when you check if operand is_mem or not, you continue and don't break. I understand that on practice on x86 pretty much all the time third+ operands will always be null. But what about all other architectures?
    – KulaGGin
    May 11, 2022 at 16:15
  • Thx for the answer, btw. Would've taken me 10 hours, or more like days of work and parsing through the IDA documentation to be able to decipher it and manage to write a plugin to log all instructions in a function that write to memory.
    – KulaGGin
    May 11, 2022 at 16:16
  • @KulaGGin it's been some years since... But I think my assumption was that if an operand is empty, the ones after it will be empty as well. Might have been a mistake.
    – tmr232
    May 12, 2022 at 11:46
0

Updated @tmr232's answer for IDA 7.4+ using a better syntax for printing strings(f-strings):

import idaapi
import idautils
import idc

OPND_WRITE_FLAGS = {
    0: idaapi.CF_CHG1,
    1: idaapi.CF_CHG2,
    2: idaapi.CF_CHG3,
    3: idaapi.CF_CHG4,
    4: idaapi.CF_CHG5,
    5: idaapi.CF_CHG6,
}

OPND_READ_FLAGS = {
    0: idaapi.CF_USE1,
    1: idaapi.CF_USE2,
    2: idaapi.CF_USE3,
    3: idaapi.CF_USE4,
    4: idaapi.CF_USE5,
    5: idaapi.CF_USE6,
}

def print_insn_mem_interaction(ea):
    insn = idautils.DecodeInstruction(ea)

    # The features are needed for operand flags.
    feature = insn.get_canon_feature()

    for op in insn.ops:
        # You always get 6 operands. Some of them are set to `o_void` to indicate
        # that they are not used.
        if op.type == idaapi.o_void:
            break

        # There are 3 types of memory references in IDA. We want all 3.
        is_mem = op.type in (idaapi.o_mem, idaapi.o_phrase, idaapi.o_displ)

        # Extract per-operand read/write status from the feature.
        is_write = feature & OPND_WRITE_FLAGS[op.n]
        is_read = feature & OPND_READ_FLAGS[op.n]

        if not is_mem:
            # Operand does not access memory.
            continue

        # Ugly line for the display. Sorry.
        action = 'memory {}'.format('/'.join(filter(bool, ('read' if is_read else None, 'write' if is_write else None))))

        stringToPrint = f"Operand[{op.n}]<{idc.print_operand(ea, op.n)}> : {action}"
        print(stringToPrint)
-1

If you have IDA Pro 6.x, then solution is pretty straightforward: https://www.hex-rays.com/products/ida/support/tutorials/tracing.pdf

2
  • Sorry, I do not understand what you mean. Could you be a bit more specific ? Feb 17, 2016 at 5:44
  • @stackoverflowwww place the cursor inside the disassembly view, right-click menu or main "Debugger" menu, and you will see "Add read/write trace". Hope that helps
    – assp1r1n3
    Feb 21, 2016 at 9:27
-1

Other than dynamic analysis (like suggested in the comments) there's no way to get that information from IDA.

IDA does not maintain a complete Intermediate Representation (also called intermediate language) and that sort of information is unavailable to IDA, mostly because it's unneeded for implementation of disassemblers. It is however needed for decompilers, so hex-rays decompiler probably implements a more complete IR but doesn't make it accessible to the user.

Usually using IL each assembly instruction is translated into multiple intermediate language instructions, which together achieve the same goal as the original instruction. This turns instructions into operations that are less complex and are easier to manipulate/process.

There are open source tools that provide or implement an IL such as OpenREIL, which you could use outside of IDA (Specifically it has the STM and LDM instructions).

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.