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.