OneShell

I fight for a brighter tomorrow

0%

IDAPython-基本块粒度

指令粒度介绍完毕之后,该从基本块粒度进行说明了。所涉及到的API速查表格如下:

模块 API 功能
idaapi get_func(ea) 获取地址ea所在函数
idaapi FlowChart(f=None, bounds=None, flags=0) 获取函数f或者地址范围bounds的所有基本块集合

基本块简介

先简单说一下基本块的概念,基本块(Basic Block)是一段可以被顺序执行的指令集,一般被视为程序的基本组成单元。一个程序的控制流就是由基本块组成的有向图。在一个基本块的执行过程中,它通常是由前驱基本块(predecessor)执行到当前基本块,然后当前基本块执行完毕后到后继基本块(successor)。
因此,大概也可以知道基本块的重要组成了:

  1. 起始地址:基本块的第一条指令
  2. 结束地址:基本块的最后一条指令
  3. 指令集:基本块中所有的指令,也是要执行的指令集
  4. 转移指令:用于跳转到下一个基本块

基本块具有一个特性,那就是如果传递进行的寄存器值、使用到的内存值是一样的,那么在基本块执行完毕的寄存器集的值、内存值也是一样的。这个特性可以用于对基本块级别的仿真模拟,例如判断最后的跳转是否会被采纳。基本块在模糊测试中也被用于记录执行路径,例如AFL就是在基本块的起始地址进行了插桩,如果测试值使得基本块执行到了新的路径,那么就说明这个测试值潜力更大。
基本块的概念说完了,在IDA反汇编的Graph disassemble view中(空格切换,233)可以看到函数的基本块。如下就是在IDA中显示的两个基本块:
undifined
这两个基本块的作用是通过strcmp函数判断传入到cgibin的环境变量,选择使用哪一个接口函数来处理数据。
基本块1:调用phpcgi

1
2
3
4
5
6
7
8
9
10
11
.text:00402E1C
.text:00402E1C loc_402E1C:
.text:00402E1C la $t9, strcmp
.text:00402E20 move $a0, $s0 # s1
.text:00402E24 sw $a2, 0x20+var_8($sp)
.text:00402E28 jalr $t9 ; strcmp
.text:00402E2C li $a1, aPhpcgi # "phpcgi"
.text:00402E30 lw $gp, 0x20+var_10($sp)
.text:00402E34 lw $a2, 0x20+var_8($sp)
.text:00402E38 bnez $v0, loc_402E4C
.text:00402E3C lui $a1, 0x42 # 'B'

基本块2:调用dlapn.cgi

1
2
3
4
5
6
7
8
9
10
11
.text:00402E4C
.text:00402E4C loc_402E4C:
.text:00402E4C la $t9, strcmp
.text:00402E50 move $a0, $s0 # s1
.text:00402E54 sw $a2, 0x20+var_8($sp)
.text:00402E58 jalr $t9 ; strcmp
.text:00402E5C li $a1, aDlapnCgi # "dlapn.cgi"
.text:00402E60 lw $gp, 0x20+var_10($sp)
.text:00402E64 lw $a2, 0x20+var_8($sp)
.text:00402E68 bnez $v0, loc_402E7C
.text:00402E6C lui $a1, 0x42 # 'B'

基本块操作

在IDAPython中,对基本块的操作通常是先根据一个指令地址得到指令所在的函数,然后获取到函数由基本块组成的控制流图,再遍历图中的所有基本块。如果要知道目标指令是在函数的哪一个基本块,遍历基本块然后判断。
如下的代码是遍历地址ea所在函数的所有基本块,然后判断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
import idaapi
import ida_gdl

ea = 0x00402E4C
func = idaapi.get_func(ea)
func_control_flow = idaapi.FlowChart(func, flags=idaapi.FC_PREDS)

for basic_block in func_control_flow:
print("ID: %i, Start: 0x%x, End: 0x%x" % (basic_block.id, basic_block.start_ea, basic_block.end_ea))
if basic_block.start_ea <= ea < basic_block.end_ea:
print(" ea at Here")
break
succs_blocks = basic_block.succs()
for addr in succs_blocks:
print(" Successor: 0x%x" % addr.start_ea)
pre_sblocks = basic_block.preds()
for addr in pre_sblocks:
print(" Predecessor: 0x%x" % addr.end_ea)
if ida_gdl.is_ret_block(basic_block.type):
print(" Return Block")
'''
ID: 0, Start: 0x402da0, End: 0x402e10
Successor: 0x402e10
Successor: 0x402e1c
ID: 1, Start: 0x402e10, End: 0x402e1c
Successor: 0x4032e0
Predecessor: 0x402e10
ID: 2, Start: 0x402e1c, End: 0x402e40
Successor: 0x402e40
Successor: 0x402e4c
Predecessor: 0x402e10
ID: 3, Start: 0x402e40, End: 0x402e4c
Successor: 0x4032e0
Predecessor: 0x402e40
ID: 4, Start: 0x402e4c, End: 0x402e70
ea at Here
'''

我之前写过一个判断MIPS命令注入的代码,大概就是,先定位对system代码作为ea,然后获取到ea所在的基本块,再通过广度优先搜索得到基本块的所有前驱,直到在前驱基本块中得到所有的参数(MIPS是通过a0a1a2a3寄存器进行传参),后续看能不能把代码整理出来。
调用idaapi.FlowChart()实际上是调用的ida_gdl.FlowChart(f=None, bounds=None, flags=0),传参如下:

  • f:通过get_func(ea)获取
  • bounds:如果f参数不指定则在bounds tuple类型(start, end)范围获取基本块集合
  • flagsFC_XXX标准位,具体可以在此处查询
    对于每一个基本块,都有如下的一些属性:
  • id:基本块的ID
  • start_ea:基本块的起始地址
  • end_ea:基本块的结束地址
  • type:基本块类型,如下依次数值从0~7
    • fcb_normal:正常的基本块
    • fcb_indjump:基本块以间接跳转结束
    • fcb_ret:返回基本块,估计是函数ret结束时的基本块
    • fcb_cndret:条件返回的基本块
    • fcb_noret:基本块不返回
    • fcb_enoret:不属于函数的基本块,且不返回
    • fcb_extern:外部的基本块(没有理解到含义)
    • fcb_error:跳过不执行的基本块(不知道是否这样)
  • preds():获取当前基本块的所有前驱基本块集合
  • succs():获取当前基本块的所有后继基本块集合