IDApython&IDC

IDApython 官方文档:IDAPython | Hex-Rays Docs

IDC 官方文档:IDC | Hex-Rays Docs

常用模块

1.IDC

与 IDA 命令行进行交互的模块,提供了很多 IDA 中指令相关的函数。

  • idc.get_name(ea):获取地址 ea 处的名称。
  • idc.get_func_name(ea):获取函数地址处的函数名称。
  • idc.set_name(ea, name):为地址 ea 设置一个新的名称。
  • idc.add_entry(ea, name, start, end):在 ea 处添加一个入口点。
  • idc.get_func(ea):获取 ea 处的函数结构。

2.IDAAPI

与 IDA 的核心 API 交互的主要模块。包括了对 IDA 核心对象的访问,涵盖了地址、函数、节等许多底层操作。

  • idaapi.get_func(ea):返回地址 ea 处的函数对象。
  • idaapi.get_flags(ea):返回地址 ea 的标志,表示它是代码、数据还是其他类型。
  • idaapi.create_data(ea, size, type):在地址 ea 处创建数据。
  • idaapi.get_strlit_contents(ea):获取 ea 处的字符串内容。
  • idaapi.add_func(ea):将地址 ea 添加为一个函数。

3.IDAUTILS

工具函数,帮助在反汇编中自动化和简化分析过程。

  • idautils.Functions(start, end):遍历在 startend 地址范围内的所有函数。
  • idautils.DecodeInstruction(ea):解码地址 ea 处的指令。
  • idautils.FindBinary(start, end, pattern, flags):在指定范围内查找与模式匹配的二进制数据。
  • idautils.Heads(start, end):遍历地址范围内的所有指令地址。

4.ida_bytes

ida_bytes 提供了对内存区域字节级别操作的函数。

  • ida_bytes.get_byte(ea):获取 ea 处的一个字节
  • ida_bytes.get_word(ea):获取 ea 处的一个字.
  • ida_bytes.get_dword(ea):获取 ea 处的一个双字。

5.ida_struct

ida_struct 处理 IDA 中的结构体。你可以使用它来读取、创建、修改和设置结构体。

  • ida_struct.get_struc_name(struc):获取结构体的名称。
  • ida_struct.add_struc():添加一个新的结构体。
  • ida_struct.set_struc_member():设置结构体成员的属性。

6.ida_diskio

ida_diskio 提供了与磁盘 IO 操作相关的函数,例如读取或写入文件。

  • ida_diskio.get_input_file_path():获取当前输入文件的路径。
  • ida_diskio.save_input_file():保存当前输入文件。

常用技巧

获取基本块 ida_get_bb

ida_get_bb 函数的功能是获取指定地址 ea 所在的基本块(Basic Block)。基本块是指在程序中连续的一组指令序列,这些指令在程序的控制流图上不会被中断,只有从头到尾的顺序执行,并且只有在块的结束处才能跳出这个块。

1
2
3
4
5
6
def ida_get_bb(ea):
f_blocks = idaapi.FlowChart(idaapi.get_func(ea), flags=idaapi.FC_PREDS)
for block in f_blocks:
if block.start_ea <= ea and ea < block.end_ea:
return block
return None

函数功能概述

  • 输入:虚拟地址 (ea),即目标指令的地址。
  • 输出:包含该地址的基本块 (block) 或者 None(如果未找到)。

函数逻辑

  1. 利用 IDA Pro 的 idaapi.FlowChart 类生成当前函数的控制流图(Flow Chart),即函数内的所有基本块。
  2. 遍历每个基本块,检查该基本块的起始地址 (start_ea) 和结束地址 (end_ea),判断目标地址 (ea) 是否在该基本块范围内。
  3. 如果找到符合条件的基本块,则返回该基本块对象。
  4. 如果遍历完所有基本块都没有找到符合条件的,返回 None

创建 / 删除函数

1
2
idc.add_func(0x401000, 0x401050)  # Create a function starting at 0x401000 and ending at 0x401050
idc.del_func(0x401000) # Delete the function at 0x401000

获取函数的名称

1
get_func_name(ea)

遍历所有函数并打印其有效地址和名称

1
2
3
for func_ea in idautils.Functions(): 
func_name = idc.get_func_name(func_ea)
print(hex(func_ea), func_name)

列出地址的交叉引用:

1
2
for xref in idautils.XrefsFrom(0x401000):
print(f"Xref from 0x401000 to {hex(xref.to)}")

遍历所有交叉引用并打印

1
2
for ref in idautils.XrefsTo(ea):
print(hex(ref.frm))

获取段地址

idautils.Segments() 返回 所有段的起始地址

1
2
3
4
5
6
7
8
9
10
for seg in idautils.Segments():
seg_name = idc.get_segm_name(seg)
if seg_name != ".text" and seg_name != "UPX0":
# print(f"跳过段: {seg_name} (0x{seg:X})")
continue

print(f"正在处理段: {seg_name} (0x{seg:X})")
start_ea = seg
end_ea = idc.get_segm_end(seg) #返回段的结束地址。

文本 / 指令搜索

1
2
3
4
5
6
7
8
9
10
11
import idautils
import idc

# 搜索整个内存区域的字符串
def search_string(pattern):
for ea in idautils.Strings():
if pattern in ea:
print(f"Found string at {hex(ea)}: {ea}")

# 搜索 "Hello" 字符串
search_string("Hello")
1
2
3
4
5
6
7
8
9
10
11
12
13
#搜索汇编指令
import idautils
import idc

def search_asm_instruction(pattern):
# 遍历整个程序的所有指令
for ea in idautils.Heads(idc.get_segm_by_name(".text"), idc.get_segm_end(idc.get_segm_by_name(".text"))):
disasm = idc.GetDisasm(ea) # 获取地址处的反汇编指令
if pattern in disasm:
print(f"Found instruction at {hex(ea)}: {disasm}")

# 搜索 "mov eax, ebx" 指令
search_asm_instruction("mov eax, ebx")

循环左移 / 右移

1
2
3
4
5
6
7
8
9
10
11
def ROR(i,index):	#循环又移
tmp = bin(i)[2:].rjust(8,"0")
for _ in range(index):
tmp = tmp[-1] + tmp[:-1]
return int(tmp, 2)

def ROL(i,index): #循环左移
tmp = bin(i)[2:].rjust(8, "0")
for _ in range(index):
tmp = tmp[1:] + tmp[0]
return int(tmp, 2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

实例

```python
def ROR(i,index):
tmp = bin(i)[2:].rjust(8,"0")
for _ in range(index):
tmp = tmp[-1] + tmp[:-1]
return int(tmp, 2)

addr1=0x140001085
addr2=0x140001d00
for i in range(addr2-addr1):
PatchByte(addr1+i,ROR(Byte(addr1+i),3)^90)
print('successful')

列出 segment 函数(以及它们的交叉引用)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import ida_kernwin
import ida_segment
import ida_funcs
import ida_xref
import ida_auto

def main():
ida_auto.auto_wait() # 确保 IDA 完成自动分析
ea = ida_kernwin.get_screen_ea()
seg = ida_segment.getseg(ea)

if not seg:
print("No segment found at 0x%x" % ea)
return

func = ida_funcs.get_func(seg.start_ea)
if not func:
func = ida_funcs.get_next_func(seg.start_ea)

while func and func.start_ea < seg.end_ea:
funcea = func.start_ea
print("Function %s at 0x%x" % (ida_funcs.get_func_name(funcea), funcea))

# 查找对 funcea 的交叉引用
xb = ida_xref.xrefblk_t()
if xb.first_to(funcea, ida_xref.XREF_ALL):
while xb.iscode:
print(" called from %s(0x%x)" % (ida_funcs.get_func_name(xb.frm), xb.frm))
if not xb.next_to():
break

func = ida_funcs.get_next_func(funcea)

main()

列出 segment 中的所有函数 (和交叉引用)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import ida_kernwin
import ida_idaapi
import ida_segment
import ida_funcs

import idautils

def main():
# Get current ea
ea = ida_kernwin.get_screen_ea()
if ea == ida_idaapi.BADADDR:
print("Could not get get_screen_ea()")
return

seg = ida_segment.getseg(ea)
if seg:
# Loop from start to end in the current segment
for funcea in idautils.Functions(seg.start_ea, seg.end_ea):
print("Function %s at 0x%x" % (ida_funcs.get_func_name(funcea), funcea))

# Find all code references to funcea
for ref in idautils.CodeRefsTo(funcea, 1):
print(" called from %s(0x%x)" % (ida_funcs.get_func_name(ref), ref))
else:
print("Please position the cursor within a segment")

if __name__=="__main__":
main()

获取并打印当前光标所在函数(或代码块)的详细信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
import binascii
import ida_kernwin
import ida_funcs

def dump_flags(fn):
"""打印函数的标志信息"""
print("函数标志: %08X" % fn.flags)
if fn.is_far():
print(" 远函数")
if not fn.does_return():
print(" 该函数不会返回")
if fn.flags & ida_funcs.FUNC_FRAME:
print(" 该函数使用帧指针")
if fn.flags & ida_funcs.FUNC_THUNK:
print(" 该函数是 Thunk 函数")
if fn.flags & ida_funcs.FUNC_LUMINA:
print(" 该函数信息由 Lumina 提供")
if fn.flags & ida_funcs.FUNC_OUTLINE:
print(" 该函数是伪代码函数(非真实函数)")

def dump_regvars(pfn):
"""打印函数中的重命名寄存器信息"""
assert ida_funcs.is_func_entry(pfn)
print("该函数有 %d 个重命名寄存器" % pfn.regvarqty)
for rv in pfn.regvars:
print("%08X..%08X '%s'->'%s'" % (rv.start_ea, rv.end_ea, rv.canon, rv.user))

def dump_regargs(pfn):
"""打印寄存器传递的参数信息"""
assert ida_funcs.is_func_entry(pfn)
print("该函数有 %d 个寄存器参数" % pfn.regargqty)
for ra in pfn.regargs:
print(" 寄存器编号=%d, 参数名称=\"%s\", (序列化类型)=\"%s\"" % (
ra.reg,
ra.name,
binascii.hexlify(ra.type)))

def dump_tails(pfn):
"""打印函数的尾块信息"""
assert ida_funcs.is_func_entry(pfn)
print("该函数有 %d 个尾块" % pfn.tailqty)
for i in range(pfn.tailqty):
ft = pfn.tails[i]
print(" 尾块 %i: %08X..%08X" % (i, ft.start_ea, ft.end_ea))

def dump_stkpnts(pfn):
"""打印函数的栈调整点信息"""
print("该函数有 %d 个栈调整点" % pfn.pntqty)
for i in range(pfn.pntqty):
pnt = pfn.points[i]
print(" 栈调整点 %i @%08X: %d" % (i, pnt.ea, pnt.spd))

def dump_frame(fn):
"""打印函数的栈帧信息"""
assert ida_funcs.is_func_entry(fn)
print("帧结构 ID: %08X" % fn.frame)
print("局部变量区大小: %8X" % fn.frsize)
print("保存寄存器区大小: %8X" % fn.frregs)
print("返回时清理的字节数: %8X" % fn.argsize)
print("帧指针偏移量: %8X" % fn.fpd)

def dump_parents(fn):
"""打印函数尾块的所有者和引用信息"""
assert ida_funcs.is_func_tail(fn)
print("所属函数: %08X" % fn.owner)
print("该尾块被 %d 个函数引用" % fn.refqty)
for i in range(fn.refqty):
print(" 引用者 %i: %08X" % (i, fn.referers[i]))

def dump_func_info(ea):
"""打印指定地址所在的函数信息"""
pfn = ida_funcs.get_fchunk(ea)
if pfn is None:
print("该地址 %08X 处没有函数!" % ea)
return
print("当前代码块范围: %08X..%08X" % (pfn.start_ea, pfn.end_ea))
dump_flags(pfn)
if ida_funcs.is_func_entry(pfn):
print("这是一个入口代码块")
dump_tails(pfn)
dump_frame(pfn)
dump_regvars(pfn)
dump_regargs(pfn)
dump_stkpnts(pfn)
elif ida_funcs.is_func_tail(pfn):
print("这是一个尾代码块")
dump_parents(pfn)

ea = ida_kernwin.get_screen_ea()
dump_func_info(ea)

向现有函数帧添加新成员

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import ida_funcs
import ida_frame
import ida_typeinf
import ida_range
import idc

def add_frame_member(func_ea):
name = "my_stkvar"

tif = ida_typeinf.tinfo_t(ida_typeinf.BTF_UINT64)
#创建了一个 64 位整数 (uint64_t) 的 tinfo_t 类型对象。
tif.create_ptr(tif)
# 将其转换为指针类型,即 uint64_t*。

func = ida_funcs.get_func(func_ea)
if not func:
print('Failed to get function!')
return
print(f"Function @ {func.start_ea:x}")

frame_tif = ida_typeinf.tinfo_t()
if not ida_frame.get_func_frame(frame_tif, func):
print('Failed to get frame!')
return
print(f"{frame_tif._print()}")

rs = ida_range.rangeset_t()
sp_offset = 0
if frame_tif.calc_gaps(rs):
for range in rs:
if range.start_ea <= 0:
continue
elif (range.end_ea - range.start_ea) >= tif.get_size():
sp_offset = range.start_ea
print(f"Range [{range.start_ea:x}, {range.end_ea:x}[ selected.")
break

if sp_offset > 0:
sval = ida_frame.calc_frame_offset(func, sp_offset, None, None)
# 计算在IDA 栈帧结构中的偏移量
if ida_frame.add_frame_member(func, name, sval, tif):
print(f"Frame member added at frame offset {sval}!")
else:
print("Failed adding frame member")
else:
print("Could not find gaps in current frame...")

add_frame_member(idc.here())

常用API

idaapi.MinEA():获取载入程序的最小的有效地址。

idaapi.MaxEA():获取载入的程序最大的有效地址。

idaapi.Assemble(head, line):从head地址开始反汇编,寻找直到遇到line这条指令时停止,返回两个变量,一个表示是否成功,另外一个是最后这条line指令的字节表示,例如:ret就会是'\xc3'这一个字节来表示,有些指令可能由多个字节表示。

idaapi.GetMnem(addr):获取addr地址处的指令。

idaapi.MakeCode(addr):从addr地址开始尝试将数据转换为汇编代码。

idaapi.next_not_tail(addr):往下走一个指令,如果不是尾部,则返回下一条指令的起始地址。

idaapi.GetDisasm(addr):获取addr地址开始的一条汇编指令。

idaapi.GetFlags(addr):获取addr地址处的一系列标志位,可用来判断属于code还是data

idaapi.isCode(Flags):通过Flags判断是否是汇编代码。

idaapi.MakeUnkn(addr, size):取消对addr地址处的size大小的定义,暂不清楚该地址是代码还是数据时可以使用。

idaapi.GetOpnd(addr, index):取addr地址处的指令的第index个操作数,从零开始,从左开始,依次为intel汇编语法中的目的操作数、源操作数。

idaapi.get_name_ea(min_ea, name):从min_ea地址开始,寻找名为name的有效地址,该name可以为函数名、label名。

idaapi.get_dword(addr):从addr地址处获取一个dword数据。

idaapi.MakeDword(addr):将addr开始的一个DWORD大小的数据定义为双字形式,举一反三,Q代表四字节数据,API形式一致。

idaapi.Segname(addr):得到addr地址所处的区段名。

idaapi.MakeFunction(addr):将addr地址处定义为一个函数,相当于快捷键P。

BADADDR: 常量,代表错误的地址。

idaapi.GetSpd(addr):获取addr地址处的栈指针SP的值,而在IDA中显示的值则是SP到BP基址针的差值,例如获取到的值为-4,在IDA中显示栈指针的情况时则为4。

idaapi.SetSpDiff(addr, diff):设置addr地址处的Sp指针与Bp指针的差值,在平衡堆栈时需要用到。

idaapi.next_head(head, BADADDR):遍历下一条指令,除非遇到BADADDR,返回下一条指令的地址。

idaapi.ua_mnem(addr):返回addr地址处的指令类型。

idaapi.MakeName(addr, ''):给addr地址处一个标记label。