Skip to content

Instantly share code, notes, and snippets.

@pierrehpezier
Created March 19, 2026 14:31
Show Gist options
  • Select an option

  • Save pierrehpezier/a37984b91c2054032e856faf2700d278 to your computer and use it in GitHub Desktop.

Select an option

Save pierrehpezier/a37984b91c2054032e856faf2700d278 to your computer and use it in GitHub Desktop.
# Copyright (c) 2026 Nextron Systems
# Author: Pierre-Henri Pezier
import idaapi
import idautils
import idc
def _touches_rax(ea):
"""Check if instruction at ea writes to rax/eax/ax/al/ah."""
if idc.get_operand_type(ea, 0) == idc.o_reg and idc.get_operand_value(ea, 0) == 0:
mnem = idc.print_insn_mnem(ea)
if mnem not in ("cmp", "test", "push"):
return True
return False
def detect_indirect_calls_in_func(func):
"""Scan a single function for the mov rax,imm / add rax,mem / call rax pattern."""
insns = list(idautils.FuncItems(func.start_ea))
results = []
for i, head in enumerate(insns):
mnem = idc.print_insn_mnem(head)
# Step 1: mov rax, <immediate>
if mnem != "mov":
continue
if idc.get_operand_type(head, 0) != idc.o_reg:
continue
if idc.get_operand_value(head, 0) != 0:
continue
if idc.get_operand_type(head, 1) != idc.o_imm:
continue
imm_val = idc.get_operand_value(head, 1) & 0xFFFFFFFFFFFFFFFF
mov_ea = head
# Step 2: add rax, <mem>
if i + 1 >= len(insns):
continue
add_ea = insns[i + 1]
if idc.print_insn_mnem(add_ea) != "add":
continue
if idc.get_operand_type(add_ea, 0) != idc.o_reg:
continue
if idc.get_operand_value(add_ea, 0) != 0:
continue
add_op1_type = idc.get_operand_type(add_ea, 1)
if add_op1_type not in (idc.o_mem, idc.o_displ):
continue
offset_addr = idc.get_operand_value(add_ea, 1)
offset_val = idaapi.get_qword(offset_addr)
# Step 3: scan up to 10 instructions for call rax, bail if rax is written
found_call = False
call_ea = None
for j in range(i + 2, min(i + 2 + 10, len(insns))):
scan_ea = insns[j]
scan_mnem = idc.print_insn_mnem(scan_ea)
scan_disasm = idc.GetDisasm(scan_ea)
if scan_mnem == "call" and "rax" in scan_disasm:
found_call = True
call_ea = scan_ea
break
if _touches_rax(scan_ea):
break
if not found_call:
continue
resolved = (imm_val + offset_val) & 0xFFFFFFFFFFFFFFFF
resolved_name = idc.get_func_name(resolved) or idc.get_name(resolved) or f"sub_{resolved:x}"
results.append({
"mov_ea": mov_ea,
"call_ea": call_ea,
"resolved": resolved,
"resolved_name": resolved_name,
"imm": imm_val,
"offset_addr": offset_addr,
"offset_val": offset_val,
})
return results
def crawl_all():
"""Crawl every function in the binary, detect patterns, and add comments."""
total = 0
for func_ea in idautils.Functions():
func = idaapi.get_func(func_ea)
if not func:
continue
hits = detect_indirect_calls_in_func(func)
if not hits:
continue
func_name = idc.get_func_name(func.start_ea)
for hit in hits:
resolved = hit["resolved"]
resolved_name = hit["resolved_name"]
call_ea = hit["call_ea"]
imm = hit["imm"]
offset_val = hit["offset_val"]
offset_addr = hit["offset_addr"]
offset_name = idc.get_name(offset_addr) or f"{offset_addr:#x}"
comment = resolved_name
idc.set_cmt(call_ea, comment, 0)
total += 1
print(f" {func_name} | {call_ea:#x}: call rax ; {comment}")
print(f"\n{'='*50}")
print(f"Done. Commented {total} indirect call(s) across all functions.")
print(f"{'='*50}")
if __name__ == "__main__":
crawl_all()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment