Angr_0

Angr学习1,一些简单的用法:

符号执行

理解:输入的不是特定的字符,而是一个符号(可以理解为:x),每执行一步,就记录下执行的条件(此为执行)。遇到分支语句,就分为两个方程,分别对应不同的路线,这样程序执行完之后,就得到了一群方程,想要那一条路径,就分析哪一串方程。

相当于就是用方程的形式,历遍了所有的路径,最后根据需要去解出对应路径的方程,得到答案。

Angr使用

基本结构

创建项目

1
project = angr.Project("./00_angr_find", auto_load_libs=False)
  • angr的二进制装载组件是CLE,它负责装载二进制对象(以及它依赖的任何库),angr将这些包含在Project类中。
  • uto_load_libs:是否载入依赖库,默认是true,一般设置为false,好处是不会去分析lib中的函数,减少时间,缺点是无法分析外部函数

设置状态(state)

1
initial_state = project.factory.entry_state()#执行引擎从程序的入口点开始符号执行
  • entry_state():做一些初始化工作,然后在 程序的 入口停下

state代表程序的一个实例镜像,模拟执行某个时刻的状态,就类似于快照。保存运行状态的上下文信息。

保留每一步的条件。

设置 Simulation Managers(模拟管理器)

1
simulation = project.factory.simgr(initial_state)

Project 对象表示程序一开始的样子,而在执行时,我们实际上是对SimState对象进行操作,她模拟执行某个时刻的状态

探索路径

1
2
print_good_address = 成功地址  
simulation.explore(find=print_good_address)
  • .explore()方法:找到某条特定的路径,丢弃其他路径

  • find:参数的内容可以是想要执行到的某个地址、想要执行到的地址列表、或者一个获取state作为参数并判断这个state是否满足某些条件的函数。(地址,地址列表,函数)

  • 当状态和find中的条件匹配的时候,它们就会被放到found stash中,执行随即停止。

获取执行结果

1
2
3
4
if simulation.found:
solution_state = simulation.found[0] # 获取通过 explore 找到符合条件的状态
solution = solution_state.posix.dumps(sys.stdin.fileno())
print("[+] Success! Solution is: {}".format(solution.decode("utf-8")))
  • 通过simgr.found来访问所有符合条件的分支,这里我们为了解题,就选择第一个符合条件的分支即可(found[0])
  • sys.stdin.fileno()

在UNIX中,按照惯例,三个文件描述符分别表示标准输入、标准输出和标准错误

1
2
3
4
5
6
7
>>> import sys
>>> sys.stdin.fileno() #标准输入
0
>>> sys.stdout.fileno()#标准输出
1
>>> sys.stderr.fileno()#标准错误
2

所以可以写为

1
solution = solution_state.posix.dumps(0)

题目:00_angr_find

image-20220224114208823

这里find的目标地址可以是

image-20220224114251592

这里的所有地址,但不能是跳转到这个代码块的地址,因为有可能不会跳

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from angr import *
import sys

p = Project("00_angr_find", auto_load_libs = False)

init_state = p.factory.entry_state()
simulation = p.factory.simgr(init_state)

simulation.explore(find=0x8048675)#这就是跳转的情况0x8048661->OUNYWZDT

if simulation.found:#判定是否找到合适结果
solution_state = simulation.found[0]#要第一个
print(solution_state.posix.dumps(0))

#print(solution_state),这样的话就会给出simstate走到的地址
#<SimState @ 0x8048675>
#print(solution_state.posix)
#<angr.state_plugins.posix.SimSystemPosix object at 0x7fe76cb5dc70>
  • angr 重写了一些 libc 的函数,比如获取 stdin 数据,会返回符号量,用于符号执行

  • 在某个状态下可以使用state1.posix.dumps(0) 获取进入该状态时 stdin 需要输入的数据

  • posix 表示终点状态中存储的系统接口信息,然后通过dumps函数来导出系统输入

题目:01_angr_avoid

注意:不要直接在ida的字符串界面找地址,这时的地址不一定是text段的地址,angr跑不出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from angr import *
import sys

p = Project("./01_angr_avoid")#, auto_load_libs = False)

init_state = p.factory.entry_state()
simulation = p.factory.simgr(init_state)

simulation.explore(find=0x80485E0, avoid=0x80485F2)
#avoid参数可以

if simulation.found:
solution_init = simulation.found[0]
print(solution_init.posix.dumps(0))#显示当时的输入
print(solution_init.posix.dumps(1))#当时的输出(input the passwd:)

explore函数的原型,这里只用到find avoid

1
def explore(self, stash='active', n=None, find=None, avoid=None, find_stash='found', avoid_stash='avoid', cfg=None,um_find=1, **kwargs):
  • 当一个状态和avoid中的条件匹配时,它就会被放进avoided stash中,之后继续执行。

题目:02_angr_find_condition

ida分析可以看到有多处的成功和提示标语

一般手段没有得到结果,这里采用新的方法,根据回显进行判断

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
from angr import *
import sys

p = Project("./02_angr_find_condition", auto_load_libs = False)

init_state = p.factory.entry_state()
simulation = p.factory.simgr(init_state)

def IsSuccess(state):
std_out = state.posix.dumps(1)
if b'Good Job.' in std_out:
return True
else:
return False
def IsFalse(state):
std_out = state.posix.dumps(1)
if b'Try again.' in std_out:
return True
else:
return False

simulation.explore(find=IsSuccess, avoid=IsFalse)

if simulation.found:
init_solution = simulation.found[0]
print(init_solution.posix.dumps(0))

动态state

在原来是判断地址的地方,改成了两个函数的回显

1
2
3
4
5
6
7
8
9
10
11
12
def IsSuccess(state):
std_out = state.posix.dumps(1)#将打印到标准输出的字节对象赋值进行判断
if b'Good Job.' in std_out:
return True
else:
return False
def IsFalse(state):
std_out = state.posix.dumps(1)
if b'Try again.' in std_out:
return True
else:
return False

引入一个函数来对状态进行检测,是为了实现动态的选择想获取的state。

题目:03_angr_symbolic_registers

这题在于,ida中看到

输入三个十六进制数,然后有三个复杂的函数进行数学运算,最后进行比较得到答案

原来解法:完全可以行得通

1
2
3
4
5
6
7
8
9
from angr import *

p = Project("./03_angr_symbolic_registers", auto_load_libs = False)

simulation = p.factory.simgr(p.factory.entry_state()).explore(find=0x80489E6, avoid=0x80489D4)

if simulation.found:
print(simulation.found[0].posix.dumps(0))
#b9ffd04e ccf63fe8 8fd4d959

但是这里因为angr在处理复杂格式的字符串scanf()输入的时候不是很好,我们可以直接将符号之注入寄存器,也就是主要学会符号化寄存器,先分析函数

image-20220224161855471

看到输入进了这三个寄存器,所以这里直接对他下手

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
from angr import *
import sys
import claripy

p = Project("./03_angr_symbolic_registers", auto_load_libs = False)

start_addr = 0x8048980
init_state = p.factory.blank_state(addr = start_addr )
#通过blank_entry函数,让程序直接从指定的地址开始运行

pawd_size = 32
pawd_0 = claripy.BVS('pawd_0', pawd_size)
pawd_1 = claripy.BVS('pawd_1', pawd_size)
pawd_2 = claripy.BVS('pawd_2', pawd_size)

init_state.regs.eax = pawd_0
init_state.regs.ebx = pawd_1
init_state.regs.ecx = pawd_2

def issu(state):
if b"Good Job." in state.posix.dumps(1):
return True
else:
return False
def isfa(state):
if b'Try again.' in state.posix.dumps(1):
return True
else:
return False

simulation = p.factory.simgr(init_state).explore(find = issu, avoid = isfa)

if simulation.found:
#print(simulation.found[0].posix.dumps(0))#没有回显

位向量(bitvector)

1
2
3
4
pawd_size = 32#32位的寄存器,所以时是32位
pawd_0 = claripy.BVS('pawd_0', pawd_size)
pawd_1 = claripy.BVS('pawd_1', pawd_size)
pawd_2 = claripy.BVS('pawd_2', pawd_size)
  • 使用claripy通过BVS()方法生成三个位向量,此方法有两个参数:第一个是angr用来引用位向量的名称,第二个是位向量本身的大小(以位为单位,好像字节也可以)。

1.符号位向量是angr用于将符号值注入程序的数据类型

2.通过 BVV(value,size)BVS( name, size) 接口创建位向量,也可以用 FPV 和 FPS 来创建浮点值和符号

访问寄存器

1.通过 state.regs 对象的属性访问以及修改寄存器的数据

1
2
3
init_state.regs.eax = pawd_0
init_state.regs.ebx = pawd_1
init_state.regs.ecx = pawd_2

既修改了initial_state之前创建的内容,并更新寄存器的内容

接下来就是定义explore的参数了

约束求解

通过使用state.solver.eval(symbol)对各个断言进行评测来求出一个合法的符号值(若有多个合法值,返回其中的一个)【就是对所选路径的方程进行求解】

1
2
3
4
5
solution0 = format(solution_state.solver.eval(passwd0), 'x')
solution1 = format(solution_state.solver.eval(passwd1), 'x')
solution2 = format(solution_state.solver.eval(passwd2), 'x')
solution = solution0 + " " + solution1 + " " + solution2
print("[+] Success! Solution is: {}".format(solution))

根据给出的方式,可以解出前两个数据,但是解不出第三个数;发现是网上教程的寄存器最后一个设置错误

改正后得到答案

题目:04_angr_symbolic_stack

先跑简单脚本

1
2
3
4
5
6
7
8
from angr import *

p = Project("./04_angr_symbolic_stack", auto_load_libs = False)

simulation = p.factory.simgr(p.factory.entry_state()).explore(find=0x80486e9, avoid=0x80486d7)

if simulation.found:
print(simulation.found[0].posix.dumps(0))

成功得到输入

符号化栈上的值

看到汇编

image-20220224172716947

这里显然通过栈来把输入传进函数

思路还是

  • 跳过输入,直接操作栈
  • 自动求解输出
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
from angr import *
import sys
from claripy import *

p = Project("./04_angr_symbolic_stack", auto_load_libs = False)

start_addr=0x8048697

init_state = p.factory.blank_state(addr = start_addr)

init_state.regs.ebp = init_state.regs.esp

size = 32
pawd0 = BVS('pawd1', size)
pawd1 = BVS('pawd2', size)

para_size = 8
init_state.regs.esp -= para_size
init_state.stack_push(pawd0)
init_state.stack_push(pawd1)

simulation = p.factory.simgr(init_state)

def issu(state):
if b'Good Job.' in state.posix.dumps(1):
return True
else:
return False
def isfa(state):
if b'Try' in state.posix.dumps(1):
return True
else:
return False

simulation.explore(find=issu, avoid=isfa)

if simulation.found:
solution_state = simulation.found[0]
s1 = solution_state.solver.eval(pawd0)
s2 = solution_state.solver.eval(pawd1)
print(s1,s2)

模拟栈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
init_state.regs.ebp = init_state.regs.esp
#模拟的是 mov esp, ebp

size = 32
pawd0 = BVS('pawd1', size)
pawd1 = BVS('pawd2', size)

para_size = 8
init_state.regs.esp -= para_size
#sub eap, 8

init_state.stack_push(pawd0)
init_state.stack_push(pawd1)
#push para1
#push para2

需要注意的就是要保证堆栈平衡,模仿的要像才行

关于eval

  • solver.eval(expression) 将会解出一个可行解
  • solver.eval_one(expression)将会给出一个表达式的可行解,若有多个可行解,则抛出异常。
  • solver.eval_upto(expression, n)将会给出最多n个可行解,如果不足n个就给出所有的可行解。
  • solver.eval_exact(expression, n)将会给出n个可行解,如果解的个数不等于n个,将会抛出异常。
  • solver.min(expression)将会给出最小可行解
  • solver.max(expression)将会给出最大可行解

题目:05_angr_symbolic_memory

看到题目,找到find和avoid,简单方法:

1
2
3
4
5
6
7
8
9
from angr import *

p = Project("05_angr_symbolic_memory", auto_load_libs = False)

simulation = p.factory.simgr(p.factory.entry_state()).explore(find=0x8048672, avoid=0x8048660)

if simulation.found:
print(simulation.found[0].posix.dumps(0))
#b'NAXTHGNR JVSFTPWE LMGAUHWC XMDCPALU'

符号化内存实际上就是利用angr接口,把值直接写道内存里

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
from angr import *
import sys
from claripy import *

p = Project("05_angr_symbolic_memory", auto_load_libs = False)

start_addr = 0x8048601
init_state = p.factory.blank_state(addr=start_addr)
#p.factory.simgr(init_state)

size = 64

in0 = BVS('in0', size)
in1 = BVS('in1', size)
in2 = BVS('in2', size)
in3 = BVS('in3', size)

in0_addr = 0xA1BA1C0

init_state.memory.store(in0_addr, in0)
init_state.memory.store(in0_addr+8, in1)
init_state.memory.store(in0_addr+8+8, in2)
init_state.memory.store(in0_addr+8+8+8, in3)

def issu(sta):
if b'Good Job.\n' in sta.posix.dumps(1):
return True
else:
return False
def isfa(sta):
if b'Try' in sta.posix.dumps(1):
return True
else:
return False


simulation = p.factory.simgr(init_state).explore(find=issu, avoid=isfa)
#这就相当于是开始运行程序了,所以之前一定要配置好环境

if simulation.found:
solution_init = simulation.found[0]
s0 = solution_init.solver.eval(in0, cast_to=bytes )
s1 = solution_init.solver.eval(in1, cast_to=bytes )
s2 = solution_init.solver.eval(in2, cast_to=bytes )
s3 = solution_init.solver.eval(in3, cast_to=bytes )
print (s0,s1,s2,s3)

根据ida分析,这次的输入是存在了64位大小的内存中,所以

1
2
3
4
5
6
in0_addr = 0xA1BA1C0
#内存连续,直接+8
init_state.memory.store(in0_addr, in0)
init_state.memory.store(in0_addr+8, in1)
init_state.memory.store(in0_addr+8+8, in2)
init_state.memory.store(in0_addr+8+8+8, in3)
  • state.memoryload(addr, size) / store(addr, val) 接口读写内存, size 以 bytes 为单位

题目:06_angr_symbolic_dynamic_memory

简单方法得到flag

1
2
3
4
5
6
7
from angr import *

p = Project('./06_angr_symbolic_dynamic_memory', auto_load_libs = False)
simulation = p.factory.simgr(p.factory.entry_state()).explore(find=0x804875e, avoid=0x804874c)
if simulation.found:
print(simulation.found[0].posix.dumps(0))
#b'UBDKLMBV UNOERNYS'

模拟动态内存

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
from angr import *
from claripy import *
import sys

p = Project('./06_angr_symbolic_dynamic_memory', auto_load_libs = False)

start_addr = 0x8048699
init_state = p.factory.blank_state( addr=start_addr )

size = 64
in1 = BVS('in1', size)
in2 = BVS('in2', size)

fake_heap1 = 0xffffc93c#0x805A039
malloc_ptr1 = 0xabcc8a4

fake_heap2 = 0xffffc94c#0x805A049
malloc_ptr2 = 0xabcc8ac

init_state.memory.store(malloc_ptr1, fake_heap1, endness=p.arch.memory_endness)
init_state.memory.store(malloc_ptr2, fake_heap2, endness=p.arch.memory_endness)

init_state.memory.store(fake_heap1, in1)
init_state.memory.store(fake_heap2, in2)

def issu(state):
if b'Good' in state.posix.dumps(1):#因为这里的数字写错了,debug了一晚上,ctmd
return True
else:
return False
def isfa(state):
if b'Try' in state.posix.dumps(1):
return True
else:
return False

simulation = p.factory.simgr(init_state).explore(find=issu, avoid=isfa)

if simulation.found:
solution_init = simulation.found[0]
s1 = solution_init.solver.eval(in1, cast_to=bytes)
s2 = solution_init.solver.eval(in2, cast_to=bytes)
print(s1,s2)

分析程序,输入存放在一块malloc出来的空间里,所以这块空间的地址是不一定的,但是angr并没有真正“运行”二进制文件(至少到目前为止),它只是在模拟运行状态,因此它实际上不需要将内存分配到堆中,实际上可以伪造任何地址。

思路:分析汇编,看一下malloc的地址的指针是什么,然后改写这个指针,让她指向指定的空间,因为没有实际运行,所以空间的地址随便,不是会用到的就行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
size = 64
in1 = BVS('in1', size)
in2 = BVS('in2', size)

fake_heap1 = 0xffffc93c
malloc_ptr1 = 0xabcc8a4

fake_heap2 = 0xffffc94c
malloc_ptr2 = 0xabcc8ac

#这里先改掉指针,注意大小端序,angr默认大端序,最后一个参数默认本机端序
init_state.memory.store(malloc_ptr1, fake_heap1, endness=p.arch.memory_endness)
init_state.memory.store(malloc_ptr2, fake_heap2, endness=p.arch.memory_endness)

#往指针指向的内存中写入自己的符号
init_state.memory.store(fake_heap1, in1)
init_state.memory.store(fake_heap2, in2)

大小端序

参数 endness 用于设置端序,angr默认为大端序,总共可选的值如下:

1
2
3
LE – 小端序(little endian, least significant byte is stored at lowest address)
BE – 大端序(big endian, most significant byte is stored at lowest address)
ME – 中间序(Middle-endian. Yep.)

题目:07_angr_symbolic_file

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
from angr import *
from claripy import *
import sys

p = Project("./07_angr_symbolic_file", auto_load_libs = False)

start_addr = 0x80488EA
init_state = p.factory.blank_state(addr = start_addr)

filename = "OJKSQYDP.txt"
in_size = 64

pawd = BVS('pawd', in_size)
pawd_file = storage.SimFile(filename, content = pawd, size = in_size, )

init_state.fs.insert(filename, pawd_file)


def issu(state):
if b'Good' in state.posix.dumps(1):
return True
else:
return False
def isfa(state):
if b'Try' in state.posix.dumps(1):
return True
else:
return False

simulation = p.factory.simgr(init_state).explore(find=issu, avoid=isfa)

if simulation.found:
solution_init = simulation.found[0]
print(solution_init.solver.eval(pawd, cast_to = bytes))

分析ida得知,本题是由从文件中读取密码,然后进行判断,这里主要是用angr来模拟文件输入

1
2
3
4
5
6
7
filename = "OJKSQYDP.txt"
in_size = 64

pawd = BVS('pawd', in_size)
pawd_file = storage.SimFile(filename, content = pawd, size = in_size, )

init_state.fs.insert(filename, pawd_file)

状态插件(state plugin)

1.所有存储在SimState中的东西实际上都存储在附加在state上的“插件”中。几乎所有state的属性都是一个插件——memoryregistersmemregssolver等等。

2.例如:memory插件模拟平坦的地址空间。

3.state.memorystate.registers实际上是同一个插件的不同实例,因为寄存器也是用一块地址空间模拟的。

说白了就是在地址模拟的基础上开发出了不同的插件,来简化angr语法

仿真文件系统-The Emulated Filesystem

1.angr中与文件系统,套接字,管道或终端的任何交互的根源都是SimFile对象。

SimFile

1.SimFile是一种存储抽象,定义符号或其他形式的字节序列。

2.可以从某个位置读取文件;在某个位置写入文件;询问文件中当前存储了多少字节;具体化文件,并为其生成测试用例。

1
simgr_file = angr.storage.SimFile(filename, content=xxxxxx, size=file_size)

之后就需要将其传递给init_state,利用fs.insert方法进行插入

1
initial_state.fs.insert(filename, simgr_file)