angr理论笔记(二)solver engine

solver engine

简介

angr的威力不在于模拟执行,而在于将输入抽象为符号,通过将符号(也就是静态或者全局变量的名称,局部变量在编译过程中不具有符号)以及符号的运行过程抽象为语法树,判断输出结果。就像z3的工作一样。我们在这一章中需要解决的问题是:

我知道输出XXX所需要的操作序列是ABCD,那么我的输入应该是多少

bit vector

bit vector可以说是模拟了c语言中对于整数的解析方式。例如将int转换为32bit的储存形式。称作bit-vector

当然32位不是必须的,任意位数都可以

>>> import angr, monkeyhex
>>> proj = angr.Project('/bin/true')
>>> state = proj.factory.entry_state()
# 64-bit bitvectors with concrete values 1 and 100
# 注意这里bit vector是针对state.solver对象的,也就是说针对state才有vector
>>> one = state.solver.BVV(1, 64)
>>> one
 <BV64 0x1>
>>> one_hundred = state.solver.BVV(100, 64)
>>> one_hundred
 <BV64 0x64>

# create a 27-bit bitvector with concrete value 9
>>> weird_nine = state.solver.BVV(9, 27)
>>> weird_nine
<BV27 0x9>

bit vector内部可以进行加减操作。但是注意需要相同长度的vector才可以运算。

可以使用zero_extend或者sign_extend进行补齐操作

# 零补齐
>>> weird_nine.zero_extend(64 - 27)
<BV64 0x9>
>>> one + weird_nine.zero_extend(64 - 27)
<BV64 0xa>

bitvector symbol

bitvector symbol(BVS)和bitvector的区别在于:所有的运算都不会修改vector本身的值,并且这里的值只是一个未知数,就像代数中的x和y。每一次对于BVS的运算会转换为一系列运算方式(AST): abstract syntax tree

# Create a bitvector symbol named "x" of length 64 bits
>>> x = state.solver.BVS("x", 64)
>>> x
<BV64 x_9_64>
>>> y = state.solver.BVS("y", 64)
>>> y
<BV64 y_10_64>

# compute and get AST
>>> x + one
<BV64 x_9_64 + 0x1>
>>> (x + one) / 2
<BV64 (x_9_64 + 0x1) / 0x2>
>>> x - y
<BV64 x_9_64 - y_10_64>

接下来介绍AST是什么,其实AST就是一种树状结构,每一个节点都有两个属性:op(operation)和args

下图为一个AST实例

>>> tree = (x + 1) / (y + 2)
>>> tree
<BV64 (x_9_64 + 0x1) / (y_10_64 + 0x2)>
>>> tree.op
'__floordiv__'
>>> tree.args
(<BV64 x_9_64 + 0x1>, <BV64 y_10_64 + 0x2>)
>>> tree.args[0].op
'__add__'
>>> tree.args[0].args
(<BV64 x_9_64>, <BV64 0x1>)
>>> tree.args[0].args[1].op
'BVV'
>>> tree.args[0].args[1].args
(1, 64)

下图为上述算式的AST表示。每一个最终的阶段最后都归结于BVV或者BVS

在这里插入图片描述

符号限制

符号限制出现在两个类型相同的AST中,当两个AST比较时,会产生一个新的数据类型:符号布尔类型

下图为比较的一些例子

# 注意这里x是BVS,但是BVS和BVV类型类似,可以构成相同类型的AST
>>> x == 1
<Bool x_9_64 == 0x1>
>>> x == one
<Bool x_9_64 == 0x1>
>>> x > 2
<Bool x_9_64 > 0x2>
>>> x + y == one_hundred + 5
<Bool (x_9_64 + y_10_64) == 0x69>
>>> one_hundred > 5
<Bool True>
# 默认作为无符号比较
>>> one_hundred > -5
<Bool False>
# 如果想要有符号比较
>>> one_hundred.SGT(-5)
<Bool True>

注意:大于小于号比较时默认作为unsigned类型(即类似c语言,降低表示级别)

如果想有符号比较,需要使用SGT类型,正如上面所展示的一样

注意,如果想要比较布尔类型,需要使用solver.is_true,这个api只对确定的值进行比较,并不执行约束求解。比如最下面maybe的true和false都是false,表示solver并没有进行约束求解

>>> yes = one == 1
>>> no = one == 2
>>> maybe = x == y
>>> state.solver.is_true(yes)
True
>>> state.solver.is_false(yes)
False
>>> state.solver.is_true(no)
False
>>> state.solver.is_false(no)
True
>>> state.solver.is_true(maybe)
False
>>> state.solver.is_false(maybe)
False

约束求解

对于每一个符号,都可以对他添加一个符号布尔类型作为比较依据,以此进行求解

下图为对于二元一次方程组的求解。和z3-solver很相似。这些约束和c语言中assert非常相似,属于必须满足的内容。

>>> state.solver.add(x > y)
>>> state.solver.add(y > 2)
>>> state.solver.add(10 > x)
>>> state.solver.eval(x)
4

一下是一个很小的综合演示,表现了怎样使用约束求解工具。注意这里必须是BVS类型,否则无法求解

注:eval常用于将bvv类型转换为int类型

# get a fresh state without constraints
>>> state = proj.factory.entry_state()
# 创建bit vector symbol:64位数字
>>> input = state.solver.BVS('input', 64)
# operation为AST类型
>>> operation = (((input + 4) * 3) >> 1) + input
>>> output = 200
# 添加约束
>>> state.solver.add(operation == output)
>>> state.solver.eval(input)
0x3333333333333381

约束求解不满足的情况,使用satinfiable标识

>>> state.solver.add(input < 2**32)
>>> state.satisfiable()
False

注意,创建的符号和state无关。虽然目前没有涉及和state相关的计算,但是两者是分开的

# fresh state
>>> state = proj.factory.entry_state()
>>> state.solver.add(x - y >= 4)
>>> state.solver.add(y > 0)
>>> state.solver.eval(x)
5
>>> state.solver.eval(y)
1
>>> state.solver.eval(x + y)
6

上图为更加复杂一点的计算。注意在清空state之后,对于x和y并没有重新定义。symbol和state是分开的。

浮点数据

angr加入了IEEE浮点类型表示方法。和整数类型很相似

下图为创建FPV和FPS的方式,以及和BVV类似的运算结构(AST)

# fresh state
>>> state = proj.factory.entry_state()
# 注意这里的FSORT_DOUBLE是指数据宽度
>>> a = state.solver.FPV(3.2, state.solver.fp.FSORT_DOUBLE)
>>> a
<FP64 FPV(3.2, DOUBLE)>

>>> b = state.solver.FPS('b', state.solver.fp.FSORT_DOUBLE)
>>> b
<FP64 FPS('FP_b_0_64', DOUBLE)>

>>> a + b
<FP64 fpAdd('RNE', FPV(3.2, DOUBLE), FPS('FP_b_0_64', DOUBLE))>

>>> a + 4.4
<FP64 FPV(7.6000000000000005, DOUBLE)>

>>> b + 2 < 0
<Bool fpLT(fpAdd('RNE', FPS('FP_b_0_64', DOUBLE), FPV(2.0, DOUBLE)), FPV(0.0, DOUBLE))>

但是对于AST结构体,存在着第三种op,也就是rounding type(对齐格式)有向上,向下,最近整数等等

使用solver.fp.RM_*来添加对齐格式

尽管eval可以直接根据对齐方式输出浮点数,有的时候我们也需要精确表示浮点数。此时可以将FP转换为BVV类型。反之亦然。

# a=3.2
# 将a转换为bit vector格式
>>> a.raw_to_bv()
<BV64 0x400999999999999a>
# b是一个fps(floating point symbol),将其转换为bit vector类型,保留了符号名称
>>> b.raw_to_bv()
<BV64 fpToIEEEBV(FPS('FP_b_0_64', DOUBLE))>

# 将bvv和bvs转换为浮点类型
>>> state.solver.BVV(0, 64).raw_to_fp()
<FP64 FPV(0.0, DOUBLE)>
>>> state.solver.BVS('x', 64).raw_to_fp()
<FP64 fpToFP(x_1_64, DOUBLE)>

上面为raw表示,即完全表示,不丢失数据。如果想要尽可能保留数据的value(想要理解这一点,必须先要对float的表示类型有基本的理解:int和float的解析方式是完全不同的。直接转换为bit表示可能会和实际数据的值有很大的差距)

如果想要尽可能保持value,需要使用val_to_fp表示。

这种方法需要一个bit vector的大小作为转换参数。此方法也可以作用于有符号类型

>>> a
<FP64 FPV(3.2, DOUBLE)>
>>> a.val_to_bv(12)
<BV12 0x3>
>>> a.val_to_bv(12).val_to_fp(state.solver.fp.FSORT_FLOAT)
<FP32 FPV(3.0, FLOAT)>

更多求解输出

之前的eval只是输出一种可能的结果,如果想要添加更多约束,也有相应的输出方式。

表达式说明
solver.eval(expression)输出表达式的一种可能结果
solver.eval_one(expression)输出唯一结果,否则抛出异常
solver.eval_upto(expression, n)输出至多n种结果,不足不报错
solver.eval_atleast(expression, n)输出至少n种结果,不足报错
solver.eval_exact(expression, n)输出正好n个结果,多了少了都报错
solver.min/max(expression)输出某个表达式的最小、最大结果

更多的表达式可见附录

https://docs.angr.io/appendix/ops

总结

我们来想一下一开始提到的问题

我知道输出XXX所需要的操作序列是ABCD,那么我的输入应该是多少

这个问题现在的理解就是:操作序列和输出XXX就是加入的约束,我们需要知道的就是一开始写入的BVV,BVS,FPV,FPS数据内容。

在solver_engine中,主要学习的是求解器原理和数据结构表示方法。数据类型基于BVV,BVS,FPV,FPS代表了未知数和数据,类型是整数和浮点类型。要注意一般的大于小于比较方式是基于unsigned的。求解器基于AST数据结构(我的理解就是树状结构),每一个AST可以作为solver的一个约束条件用来求解,最后输出时我们可以指定输出格式。angr的solver原理和z3的十分相似,也可以作为z3的知识补充学习。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值