官网angr(不够详细,想要用来做一些事情还是要看angr的代码,至少得看python实现的接口)

开始

Project

angr从加载二进制文件到Project中开始。从此以后的object基本上都围绕它展开。

1
proj = angr.Project('/binary_file')

项目基本属性:

1
2
3
proj.arch
proj.entry
proj.filename

arch

是一个archinfo.Arch对象的实例。它包含大量关于它所运行的CPU的文书数据。

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
__all__ = [
"RegisterOffset",
"TmpVar",
"RegisterName",
"Endness",
"Register",
"Arch",
"register_arch",
"ArchNotFound",
"arch_from_id",
"reverse_ends",
"get_host_arch",
"all_arches",
"defines",
"ArchAMD64",
"ArchX86",
"ArchARM",
"ArchARMEL",
"ArchARMHF",
"ArchARMCortexM",
"ArchAArch64",
"ArchAVR8",
"ArchPPC32",
"ArchPPC64",
"ArchMIPS32",
"ArchMIPS64",
"ArchSoot",
"ArchError",
"ArchS390X",
"ArchPcode",
]

# ...\site-packages\archinfo\arch_arm.py
bits = 32
vex_arch = "VexArchARM"
name = "ARMEL"
qemu_name = "arm"
ida_processor = "armb"
linux_name = "arm"
triplet = "arm-linux-gnueabihf"
max_inst_bytes = 4
ret_offset = 8
fp_ret_offset = 8
vex_conditional_helpers = True
syscall_num_offset = 36
call_pushes_ret = False
stack_change = -4
# 查看大小端
memory_endness = Endness.LE
register_endness = Endness.LE
sizeof = {"short": 16, "int": 32, "long": 32, "long long": 64}

'''
AMD64
X86
ARM
ARMEL
ARMHF
ARMCortexM
AArch64
AVR8
PPC32
PPC64
MIPS32
MIPS64
Soot
Error
S390X
Pcode
'''

CLE模块:用于加载到虚拟空间。

1
2
3
4
5
6
7
8
9
10
11
proj.loader
# 查看共享文件
proj.loader.shared_objects
proj.loader.min_addr
proj.loader.max_addr
# 主文件对象
proj.loader.main_object
# 有无可执行堆栈
proj.loader.main_object.execstack
# 是否是位置无关代码
proj.loader.main_object.pic

The factory

1
project.factory.block()

这个函数接收一个地址,用于提取一个基础块,返回一个Block对象。angr以基本块为单位分析代码。
如何使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> # 打印漂亮的反汇编代码
>>> block.pp()
0x401670: xor ebp, ebp
0x401672: mov r9, rdx
0x401675: pop rsi
0x401676: mov rdx, rsp
0x401679: and rsp, 0xfffffffffffffff0
0x40167d: push rax
0x40167e: push rsp
0x40167f: lea r8, [rip + 0x2e2a]
0x401686: lea rcx, [rip + 0x2db3]
0x40168d: lea rdi, [rip - 0xd4]
0x401694: call qword ptr [rip + 0x205866]
# 返回指令数
block.instructions
# 所有指令的地址
block.instruction_addrs

还可以获取block的其他形式。比如capstoneVEX IRSB

States

Project 对象只表示程序的初始化图像。当你使用angr执行时,你正在处理一个表示模拟程序状态的特定对象—— SimState:

1
state = proj.factory.entry_state()

使用 state.regs 和 state.mem 来访问该状态的寄存器和内存

1
2
3
state.regs.rip
# 获取程序入口内存的int形式向量
state.mem[proj.entry].int.resolved

注意!这里返回的都是向量,可以用.length返回位宽度。
如何从Python int转换为位向量:

1
2
3
4
bv = state.solver.BVV(0x1234, 32)

# 又转回来
state.solver.eval(bv)

mem接口功能强大,可以用.resolved获取bv值,用.concrete获取Python int类型值。

Function Manager

CFG结果生成一个名为Function Manager的对象,可通过cfg.kb.functions访问。这个对象最常见的用例是像字典一样访问它。它将地址映射到1个对象,可以告诉您关于函数的属性。

1
entry_func = cfg.kb.functions[p.entry]

函数类有几个重要的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
entry_func.block_addrs # 是属于函数的基本块开始的地址集。
entry_func.blocks # 是属于函数的基本块的集合,您可以使用capstone来探索和拆卸。

# 返回函数中任意点引用的所有常量字符串的列表。
# 它们被格式化为 (addr, string) 元组,
# 其中addr是字符串所在二进制数据部分中的地址,
# string是包含字符串值的Python字符串。
entry_func.string_references()

entry_func.returning # 是一个布尔值,表示函数是否可以返回。 False 表示所有路径不返回。

# 是一个描述函数内部控制流的NetworkX DiGraph。
# 它类似于IDA在每个function级别上显示的控制流图。
entry_func.transition_graph

entry_func.get_call_sites() # 返回以调用其他函数结束的基本块的所有地址的列表。

操作数

1
2
3
4
5
6
7
8
9
10
11
12
# 块的capstone
block_cap = block.capstone
for i in block_cap.insns:
# 获取操作数列表
operands = i.operands
ins.append(str(i))
op_types.append([])

# 遍历操作数列表,打印每个操作数的类型和值
for op in operands:
capstone.x86.X86OpValue
op_types[-1].append(op.type)

capstone是个很牛b的反汇编引擎,angr可以通过方法快速获取其对象,例如第一行。
然后可以通过capstone.insns获取所有指令的对象。然后每个指令对象可以通过insn.operands方法获取这条指令的操作数对象列表
类似IDAPythonGetOpType函数:
是IDAPython中的一个函数,它的作用是获取指令操作数的类型。它接受两个参数:ea和n。ea是指令的地址,n是操作数的索引(0表示第一个操作数,1表示第二个操作数)。它返回一个整数值,表示操作数的类型。不同的类型有不同的含义,参考:
IDA Help: get_operand_type — IDA帮助:get_operand_type

1
2
3
4
5
6
7
8
9
Get type of instruction operand
ea - linear address of instruction
n - number of operand:
0 - the first operand
1 - the second operand
returns:
-1 bad operand number passed

long get_operand_type(long ea, long n);

但是angr这个不一样,通过操作数列表里的元素,也就是操作数对象.type方法,可以获取这个操作数的类型,是一个整数,有以下含义:
1: 寄存器
2: 已知地址
3: 指向地址的指针
4: 常量值(有待考证)

段信息

使用angr的Project类的loader属性,它是一个cle.Loader对象,可以加载二进制文件和库。您可以通过loader对象的main_object属性或shared_objects属性来访问不同的内存段,它们是cle.Backend对象,包含了内存段的信息。您可以通过Backend对象的segments属性或sections属性来获取内存段的地址和空间。
使用angr的State类的memory属性,它是一个SimMemory对象,表示一个符号执行状态下的内存模型。您可以通过memory对象的load方法或store方法来读写内存地址和空间。您也可以通过memory对象的map_region方法或unmap_region方法来映射或取消映射内存区域。
.text:(代码段),可读、可执行
.data:(数据段),存放全局变量、全局常量等
.idata:(数据段),导入函数的代码段,存放外部函数地址。(当然还有 edata ,导出函数代码段,但不常用)
.rdata:(数据段),资源数据段,程序用到什么资源数据都在这里(包括自己打包的,还有开发工具打包的)

段对象的类型

cle.backends.pe.regions.PESection
elf也类似。

段的属性

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
[
'addr_to_offset',
'characteristics',
'contains_addr',
'contains_offset',
'filesize',
'is_executable',
'is_readable',
'is_writable',
'max_addr',
'max_offset',
'memsize',
'min_addr',
'min_offset',
'name',
'offset',
'offset_to_addr',
'only_contains_uninitialized_data',
'vaddr'
]
# 举例:
[
'.rdata\x00\x00',
1076887616,
<bound method Region.contains_addr of <.rdata | offset 0x3400, vaddr 0x405000, size 0x304>>,
<bound method Region.contains_offset of <.rdata | offset 0x3400, vaddr 0x405000, size 0x304>>,
1024,
False,
True,
False,
4215555,
14335,
772,
4214784,
<bound method Region.min_offset of <.rdata | offset 0x3400, vaddr 0x405000, size 0x304>>,
13312,
<bound method Region.offset_to_addr of <.rdata | offset 0x3400, vaddr 0x405000, size 0x304>>,
4214784
]

多余节点去除

思路:默认引擎会在call后切割(ida和radare2都不会在call后面切割),那么会产生这样的节点:
image.png
或者这种:
image.png
可以想象:
因为节点的定义是块开始地址和大小,同时节点的末尾会指向其他节点
那么:如果两个节点可以合并,下面那个节点一定只有一个别人指向它的边,同时上面那个节点一定只有一个指向下面节点的边,
这样它就可以将头部拼接到指向它的那个节点的末尾,从而合并成一个节点。

阅读angr-utils源代码

出现dot找不到问题的bug解决:
(65条消息) 使用angrutils生成控制流图出错的解决过程_芳芳超人爱学习的博客-CSDN博客

⬆︎TOP