OneShell

I fight for a brighter tomorrow

0%

IDAPython-函数粒度

整理到这儿,发现自己整理的顺序获取有点问题,应该是从粗粒度到细粒度这种自顶向下来进行整理,从段、函数、基本块、指令这样。
API速查如下:

模块 API 功能
idautils Functions() 获取所有函数起始地址集合
idc get_func_name(ea) 获取地址ea所在函数的函数名
idaapi get_func(ea) 获取地址ea所在的函数类
idc get_next_func(ea) 获取地址ea所在函数的下一个函数
idc get_prev_func(ea) 获取地址ea所在函数的上一个函数
idc get_func_attr(ea, attr) 获取地址ea所在函数的attr函数属性
idaapi get_arg_addrs(ea) 获取函数调用地址ea处,对传参进行操作的指令集合

函数基本操作和属性

在IDAPython中可以枚举所有的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import idautils
import idc

for func in idautils.Functions():
print("0x%x, %s" % (func, idc.get_func_name(func)))
'''
0x402b84, .init_proc
0x402c00, _ftext
0x402c60, sub_402C60
0x402d34, sub_402D34
0x402da0, main
0x4033a0, cgibin_reatwhite
0x403430, cgibin_verify_seamafile
0x403700, sub_403700
0x403894, sub_403894
0x403db0, cgibin_parse_request
0x404034, sub_404034
'''

idautils.Functions()会返回一个所有已知函数起始地址的列表,也可以通过idautils.Functions(start_addr, end_addr)来指定函数搜索的地址范围。idc.get_func_name(ea)则可以获取到函数名,ea可以是函数内的任一地址。
在IDAPython中,代表函数的数据结构可以使用idaapi.get_func(ea)来获得,ea是函数内的任一地址。此处依旧使得ea在cgibin的main函数中:

1
2
3
4
5
6
7
Python>func = idaapi.get_func(ea)
Python>type(func)
<class 'ida_funcs.func_t'>
Python>print("Start: 0x%x, End: 0x%x" % (func.start_ea, func.end_ea))
Start: 0x402da0, End: 0x403328
Python>dir(func)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get_points__', '__get_referers__', '__get_regargs__', '__get_regvars__', '__get_tails__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__swig_destroy__', '__weakref__', '_print', 'analyzed_sp', 'argsize', 'clear', 'color', 'compare', 'contains', 'does_return', 'empty', 'endEA', 'end_ea', 'extend', 'flags', 'fpd', 'frame', 'frregs', 'frsize', 'intersect', 'is_far', 'need_prolog_analysis', 'overlaps', 'owner', 'pntqty', 'points', 'referers', 'refqty', 'regargqty', 'regargs', 'regvarqty', 'regvars', 'size', 'startEA', 'start_ea', 'tailqty', 'tails', 'this', 'thisown']

可以看到func是一个ida_funcs.func_t的类,可以进一步使用dir(fucn)查类中的成员。几个比较典型的成员(x86/64下没有测试过):

  • analyzed_sp():是否已经分析了SP(堆栈?)
  • does_return():是否有返回值
  • start_eaend_ea:函数的起始和结束地址
  • argsize:栈上保存的传参数量(MIPS下测试不准)
  • flags:函数标志
  • fpd:栈帧指针?
  • frregs:栈帧中存放的寄存器数量
  • frsize:栈帧中的局部变量
  • argsize:寄存器传参数量
  • referers:调用该函数的其他函数起始地址,列表
  • refqty:referer的数量(我测试了感觉不准啊,不是我想象中的引用数量)
  • regargqty:寄存器传参数量
  • regargs:寄存器传参列表
  • regvarqty:寄存器变量数量
  • regvars:寄存器变量列表

idc模块还提供了许多API用于对函数的相关属性进行查询

  • idc.get_next_func(ea)idc.get_prev_func(ea):获取当前函数的下/上一个函数,从地址空间上而言
  • idc.get_func_attr(ea, FUNCATTR_START)idc.get_func_attr(ea, FUNCATTR_END):获取函数的起始地址和结束地址

如下是一个遍历函数所有指令的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import idc

ea = 0x0040E71C
start = idc.get_func_attr(ea, idc.FUNCATTR_START)
end = idc.get_func_attr(ea, idc.FUNCATTR_END)

cur_addr = start
while cur_addr <= end:
print("0x%x %s" % (cur_addr, idc.generate_disasm_line(cur_addr, 0)))
cur_addr = idc.next_head(cur_addr, end)
'''
0x40e71c addiu $sp, -0x60
0x40e720 sw $ra, 0x3C+var_s20($sp)
0x40e724 sw $s7, 0x3C+var_s1C($sp)
0x40e728 sw $s6, 0x3C+var_s18($sp)
0x40e72c sw $s5, 0x3C+var_s14($sp)
0x40e730 sw $s4, 0x3C+var_s10($sp)
0x40e734 sw $s3, 0x3C+var_sC($sp)
0x40e738 sw $s2, 0x3C+var_s8($sp)
0x40e73c sw $s1, 0x3C+var_s4($sp)
0x40e740 sw $s0, 0x3C+var_s0($sp)
0x40e744 li $gp, 0x43F7F0
......
'''

idc.get_func_attr(ea, attr)还可以查询到函数的需要重要属性,如下(未列举全):

  • FUNC_NORET:函数无返回
  • FUNC_LIB:函数为库函数
  • FUNC_THUNK:thunk函数(未理解)

其实在idc模块中还有许多对函数的操作,可以参考官方文档idc模块

重要!获取函数传参

这个是第一次看到,IDAPython中具有获取函数传参相关的API。在之前,分析函数传参我是采用的先获取到架构的传参方式,例如MIPS传参通过a0~a3寄存器,通过jr等调用指令来调用函数。然后定位到jr指令基本块,并进行回溯,找到所有对寄存器的最近赋值操作,分析函数传参。IDAPython中有idaapi.get_arg_addrs(ea)可以获取到函数原型并判断传参。
分析一个简单的MIPS程序,反编译结果如下:

1
2
3
4
5
6
7
{
++v14;
v13 = sobj_get_length(v7);
sprintf(v25, "CONTENT-LENGTH: %d \r\n", v13);
v15 = a1;
v16 = v25;
}

汇编代码:在0x00405748处调用函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.text:0040572C move    $t9, $v1
.text:00405730 jalr $t9 ; sobj_get_length
.text:00405734 addiu $s6, 1
.text:00405738 move $a0, $s7 # s
.text:0040573C lw $gp, 0x78+var_68($sp)
.text:00405740 move $a1, $fp # format
.text:00405744 la $t9, sprintf
.text:00405748 jalr $t9 ; sprintf
.text:0040574C move $a2, $v0
.text:00405750 move $a0, $s0
.text:00405754 lw $gp, 0x78+var_68($sp)
.text:00405758 la $t9, strcat
.text:0040575C b loc_405794
.text:00405760 move $a1, $s7

IDAPython识别传参:可以识别到最近一次对寄存器a0a1的赋值操作,但是忽略了a2寄存器,因为a2是在指令延迟槽中。对于MIPS架构还是有点小瑕疵哈,但对x86/64这种应该识别率还是挺高的,毕竟反编译都能出来结果。

1
2
3
Python>ea = 0x00405748
Python>idaapi.get_arg_addrs(ea)
[0x405738, 0x405740]